dashing 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/README.md +202 -0
  2. data/bin/dashing +82 -0
  3. data/javascripts/batman.jquery.js +109 -0
  4. data/javascripts/batman.js +11811 -0
  5. data/javascripts/dashing.coffee +96 -0
  6. data/javascripts/es5-shim.js +1021 -0
  7. data/javascripts/jquery.js +4 -0
  8. data/lib/dashing.rb +109 -0
  9. data/templates/dashboard/%name%.erb.tt +7 -0
  10. data/templates/job/%name%.rb +4 -0
  11. data/templates/project/Gemfile +3 -0
  12. data/templates/project/README.md +0 -0
  13. data/templates/project/assets/fonts/fontawesome-webfont.eot +0 -0
  14. data/templates/project/assets/fonts/fontawesome-webfont.svg +255 -0
  15. data/templates/project/assets/fonts/fontawesome-webfont.ttf +0 -0
  16. data/templates/project/assets/fonts/fontawesome-webfont.woff +0 -0
  17. data/templates/project/assets/images/favicon.ico +0 -0
  18. data/templates/project/assets/javascripts/application.coffee +24 -0
  19. data/templates/project/assets/javascripts/d3.v2.min.js +4 -0
  20. data/templates/project/assets/javascripts/dashing.gridster.coffee +32 -0
  21. data/templates/project/assets/javascripts/gridster/jquery.gridster.js +2890 -0
  22. data/templates/project/assets/javascripts/gridster/jquery.leanModal.min.js +5 -0
  23. data/templates/project/assets/javascripts/jquery.knob.js +646 -0
  24. data/templates/project/assets/javascripts/rickshaw.min.js +2 -0
  25. data/templates/project/assets/stylesheets/application.scss +229 -0
  26. data/templates/project/assets/stylesheets/font-awesome.css +303 -0
  27. data/templates/project/assets/stylesheets/jquery.gridster.css +57 -0
  28. data/templates/project/config.ru +18 -0
  29. data/templates/project/dashboards/layout.erb +32 -0
  30. data/templates/project/dashboards/sample.erb +25 -0
  31. data/templates/project/jobs/buzzwords.rb +9 -0
  32. data/templates/project/jobs/convergence.rb +13 -0
  33. data/templates/project/jobs/sample.rb +9 -0
  34. data/templates/project/lib/.empty_directory +1 -0
  35. data/templates/project/public/.empty_directory +1 -0
  36. data/templates/project/widgets/graph/graph.coffee +26 -0
  37. data/templates/project/widgets/graph/graph.html +5 -0
  38. data/templates/project/widgets/graph/graph.scss +51 -0
  39. data/templates/project/widgets/list/list.coffee +13 -0
  40. data/templates/project/widgets/list/list.html +15 -0
  41. data/templates/project/widgets/list/list.scss +52 -0
  42. data/templates/project/widgets/meter/meter.coffee +14 -0
  43. data/templates/project/widgets/meter/meter.html +5 -0
  44. data/templates/project/widgets/meter/meter.scss +37 -0
  45. data/templates/project/widgets/number/number.coffee +25 -0
  46. data/templates/project/widgets/number/number.html +9 -0
  47. data/templates/project/widgets/number/number.scss +35 -0
  48. data/templates/project/widgets/text/text.coffee +1 -0
  49. data/templates/project/widgets/text/text.html +5 -0
  50. data/templates/project/widgets/text/text.scss +25 -0
  51. data/templates/widget/%name%/%name%.coffee.tt +10 -0
  52. data/templates/widget/%name%/%name%.html +1 -0
  53. data/templates/widget/%name%/%name%.scss.tt +3 -0
  54. metadata +187 -0
@@ -0,0 +1,2890 @@
1
+ /*! gridster.js - v0.1.0 - 2012-08-14
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
+ var min_area = 100;
289
+ colliders.sort(function(a, b){
290
+ if (a.area <= min_area) {
291
+ return 1;
292
+ }
293
+
294
+ /* if colliders are being overlapped by the "C" (center) region,
295
+ * we have to set a lower index in the array to which they are placed
296
+ * above in the grid. */
297
+ if (a.region === 'C' && b.region === 'C') {
298
+ if (a.coords.y1 < b.coords.y1 || a.coords.x1 < b.coords.x1) {
299
+ return - 1;
300
+ }else{
301
+ return 1;
302
+ }
303
+ }
304
+
305
+ if (a.area < b.area){
306
+ return 1;
307
+ }
308
+
309
+ return 1;
310
+ });
311
+ return colliders;
312
+ };
313
+
314
+
315
+ //jQuery adapter
316
+ $.fn.collision = function(collider, options) {
317
+ return new Collision( this, collider, options );
318
+ };
319
+
320
+
321
+ }(jQuery, window, document));
322
+
323
+ ;(function(window, undefined) {
324
+ /* Debounce and throttle functions taken from underscore.js */
325
+ window.debounce = function(func, wait, immediate) {
326
+ var timeout;
327
+ return function() {
328
+ var context = this, args = arguments;
329
+ var later = function() {
330
+ timeout = null;
331
+ if (!immediate) func.apply(context, args);
332
+ };
333
+ if (immediate && !timeout) func.apply(context, args);
334
+ clearTimeout(timeout);
335
+ timeout = setTimeout(later, wait);
336
+ };
337
+ };
338
+
339
+
340
+ window.throttle = function(func, wait) {
341
+ var context, args, timeout, throttling, more, result;
342
+ var whenDone = debounce(
343
+ function(){ more = throttling = false; }, wait);
344
+ return function() {
345
+ context = this; args = arguments;
346
+ var later = function() {
347
+ timeout = null;
348
+ if (more) func.apply(context, args);
349
+ whenDone();
350
+ };
351
+ if (!timeout) timeout = setTimeout(later, wait);
352
+ if (throttling) {
353
+ more = true;
354
+ } else {
355
+ result = func.apply(context, args);
356
+ }
357
+ whenDone();
358
+ throttling = true;
359
+ return result;
360
+ };
361
+ };
362
+
363
+ })(window);
364
+
365
+ ;(function($, window, document, undefined){
366
+
367
+ var defaults = {
368
+ items: '.gs_w',
369
+ distance: 1,
370
+ limit: true,
371
+ offset_left: 0,
372
+ autoscroll: true
373
+ // ,drag: function(e){},
374
+ // start : function(e, ui){},
375
+ // stop : function(e){}
376
+ };
377
+
378
+ var $window = $(window);
379
+ var isTouch = !!('ontouchstart' in window);
380
+ var pointer_events = {
381
+ start: isTouch ? 'touchstart' : 'mousedown.draggable',
382
+ move: isTouch ? 'touchmove' : 'mousemove.draggable',
383
+ end: isTouch ? 'touchend' : 'mouseup.draggable'
384
+ };
385
+
386
+ /**
387
+ * Basic drag implementation for DOM elements inside a container.
388
+ * Provide start/stop/drag callbacks.
389
+ *
390
+ * @class Draggable
391
+ * @param {HTMLElement} el The HTMLelement that contains all the widgets
392
+ * to be dragged.
393
+ * @param {Object} [options] An Object with all options you want to
394
+ * overwrite:
395
+ * @param {HTMLElement|String} [options.items] Define who will
396
+ * be the draggable items. Can be a CSS Selector String or a
397
+ * collection of HTMLElements.
398
+ * @param {Number} [options.distance] Distance in pixels after mousedown
399
+ * the mouse must move before dragging should start.
400
+ * @param {Boolean} [options.limit] Constrains dragging to the width of
401
+ * the container
402
+ * @param {offset_left} [options.offset_left] Offset added to the item
403
+ * that is being dragged.
404
+ * @param {Number} [options.drag] Executes a callback when the mouse is
405
+ * moved during the dragging.
406
+ * @param {Number} [options.start] Executes a callback when the drag
407
+ * starts.
408
+ * @param {Number} [options.stop] Executes a callback when the drag stops.
409
+ * @return {Object} Returns `el`.
410
+ * @constructor
411
+ */
412
+ function Draggable(el, options) {
413
+ this.options = $.extend({}, defaults, options);
414
+ this.$body = $(document.body);
415
+ this.$container = $(el);
416
+ this.$dragitems = $(this.options.items, this.$container);
417
+ this.is_dragging = false;
418
+ this.player_min_left = 0 + this.options.offset_left;
419
+ this.init();
420
+ }
421
+
422
+ var fn = Draggable.prototype;
423
+
424
+ fn.init = function() {
425
+ this.calculate_positions();
426
+ this.$container.css('position', 'relative');
427
+ this.enable();
428
+
429
+ $(window).bind('resize',
430
+ throttle($.proxy(this.calculate_positions, this), 200));
431
+ };
432
+
433
+
434
+ fn.get_actual_pos = function($el) {
435
+ var pos = $el.position();
436
+ return pos;
437
+ };
438
+
439
+
440
+ fn.get_mouse_pos = function(e) {
441
+ if (isTouch) {
442
+ var oe = e.originalEvent;
443
+ e = oe.touches.length ? oe.touches[0] : oe.changedTouches[0];
444
+ };
445
+
446
+ return {
447
+ left: e.clientX,
448
+ top: e.clientY
449
+ };
450
+ };
451
+
452
+
453
+ fn.get_offset = function(e) {
454
+ e.preventDefault();
455
+ var mouse_actual_pos = this.get_mouse_pos(e);
456
+ var diff_x = Math.round(
457
+ mouse_actual_pos.left - this.mouse_init_pos.left);
458
+ var diff_y = Math.round(mouse_actual_pos.top - this.mouse_init_pos.top);
459
+
460
+ var left = Math.round(this.el_init_offset.left + diff_x - this.baseX);
461
+ var top = Math.round(
462
+ this.el_init_offset.top + diff_y - this.baseY + this.scrollOffset);
463
+
464
+ if (this.options.limit) {
465
+ if (left > this.player_max_left) {
466
+ left = this.player_max_left;
467
+ }else if(left < this.player_min_left) {
468
+ left = this.player_min_left;
469
+ }
470
+ }
471
+
472
+ return {
473
+ left: left,
474
+ top: top,
475
+ mouse_left: mouse_actual_pos.left,
476
+ mouse_top: mouse_actual_pos.top
477
+ };
478
+ };
479
+
480
+
481
+ fn.manage_scroll = function(offset) {
482
+ /* scroll document */
483
+ var nextScrollTop;
484
+ var scrollTop = $window.scrollTop();
485
+ var min_window_y = scrollTop;
486
+ var max_window_y = min_window_y + this.window_height;
487
+
488
+ var mouse_down_zone = max_window_y - 50;
489
+ var mouse_up_zone = min_window_y + 50;
490
+
491
+ var abs_mouse_left = offset.mouse_left;
492
+ var abs_mouse_top = min_window_y + offset.mouse_top;
493
+
494
+ var max_player_y = (this.doc_height - this.window_height +
495
+ this.player_height);
496
+
497
+ if (abs_mouse_top >= mouse_down_zone) {
498
+ nextScrollTop = scrollTop + 30;
499
+ if (nextScrollTop < max_player_y) {
500
+ $window.scrollTop(nextScrollTop);
501
+ this.scrollOffset = this.scrollOffset + 30;
502
+ }
503
+ };
504
+
505
+ if (abs_mouse_top <= mouse_up_zone) {
506
+ nextScrollTop = scrollTop - 30;
507
+ if (nextScrollTop > 0) {
508
+ $window.scrollTop(nextScrollTop);
509
+ this.scrollOffset = this.scrollOffset - 30;
510
+ }
511
+ };
512
+ }
513
+
514
+
515
+ fn.calculate_positions = function(e) {
516
+ this.window_height = $window.height();
517
+ }
518
+
519
+
520
+ fn.drag_handler = function(e) {
521
+ var node = e.target.nodeName;
522
+
523
+ if (e.which !== 1 && !isTouch) {
524
+ return;
525
+ }
526
+
527
+ if (node === 'INPUT' || node === 'TEXTAREA' || node === 'SELECT' ||
528
+ node === 'BUTTON') {
529
+ return;
530
+ };
531
+
532
+ var self = this;
533
+ var first = true;
534
+ this.$player = $(e.currentTarget);
535
+
536
+ this.el_init_pos = this.get_actual_pos(this.$player);
537
+ this.mouse_init_pos = this.get_mouse_pos(e);
538
+ this.offsetY = this.mouse_init_pos.top - this.el_init_pos.top;
539
+
540
+ this.$body.on(pointer_events.move, function(mme){
541
+ var mouse_actual_pos = self.get_mouse_pos(mme);
542
+ var diff_x = Math.abs(
543
+ mouse_actual_pos.left - self.mouse_init_pos.left);
544
+ var diff_y = Math.abs(
545
+ mouse_actual_pos.top - self.mouse_init_pos.top);
546
+ if (!(diff_x > self.options.distance ||
547
+ diff_y > self.options.distance)
548
+ ) {
549
+ return false;
550
+ }
551
+
552
+ if (first) {
553
+ first = false;
554
+ self.on_dragstart.call(self, mme);
555
+ return false;
556
+ }
557
+
558
+ if (self.is_dragging == true) {
559
+ self.on_dragmove.call(self, mme);
560
+ }
561
+
562
+ return false;
563
+ });
564
+
565
+ return false;
566
+ };
567
+
568
+
569
+ fn.on_dragstart = function(e) {
570
+ e.preventDefault();
571
+ this.drag_start = true;
572
+ this.is_dragging = true;
573
+ var offset = this.$container.offset();
574
+ this.baseX = Math.round(offset.left);
575
+ this.baseY = Math.round(offset.top);
576
+ this.doc_height = $(document).height();
577
+
578
+ if (this.options.helper === 'clone') {
579
+ this.$helper = this.$player.clone()
580
+ .appendTo(this.$container).addClass('helper');
581
+ this.helper = true;
582
+ }else{
583
+ this.helper = false;
584
+ }
585
+ this.scrollOffset = 0;
586
+ this.el_init_offset = this.$player.offset();
587
+ this.player_width = this.$player.width();
588
+ this.player_height = this.$player.height();
589
+ this.player_max_left = (this.$container.width() - this.player_width +
590
+ this.options.offset_left);
591
+
592
+ if (this.options.start) {
593
+ this.options.start.call(this.$player, e, {
594
+ helper: this.helper ? this.$helper : this.$player
595
+ });
596
+ }
597
+ return false;
598
+ };
599
+
600
+
601
+ fn.on_dragmove = function(e) {
602
+ var offset = this.get_offset(e);
603
+
604
+ this.options.autoscroll && this.manage_scroll(offset);
605
+
606
+ (this.helper ? this.$helper : this.$player).css({
607
+ 'position': 'absolute',
608
+ 'left' : offset.left,
609
+ 'top' : offset.top
610
+ });
611
+
612
+ var ui = {
613
+ 'position': {
614
+ 'left': offset.left,
615
+ 'top': offset.top
616
+ }
617
+ };
618
+
619
+ if (this.options.drag) {
620
+ this.options.drag.call(this.$player, e, ui);
621
+ }
622
+ return false;
623
+ };
624
+
625
+
626
+ fn.on_dragstop = function(e) {
627
+ var offset = this.get_offset(e);
628
+ this.drag_start = false;
629
+
630
+ var ui = {
631
+ 'position': {
632
+ 'left': offset.left,
633
+ 'top': offset.top
634
+ }
635
+ };
636
+
637
+ if (this.options.stop) {
638
+ this.options.stop.call(this.$player, e, ui);
639
+ }
640
+
641
+ if (this.helper) {
642
+ this.$helper.remove();
643
+ }
644
+
645
+ return false;
646
+ };
647
+
648
+ fn.on_select_start = function(e) {
649
+ return false;
650
+ }
651
+
652
+
653
+ fn.enable = function(){
654
+ this.$container.on('selectstart', this.on_select_start);
655
+
656
+ this.$container.on(pointer_events.start, this.options.items, $.proxy(
657
+ this.drag_handler, this));
658
+
659
+ this.$body.on(pointer_events.end, $.proxy(function(e) {
660
+ this.is_dragging = false;
661
+ this.$body.off(pointer_events.move);
662
+ if (this.drag_start) {
663
+ this.on_dragstop(e);
664
+ }
665
+ }, this));
666
+ };
667
+
668
+
669
+ fn.disable = function(){
670
+ this.$container.off(pointer_events.start);
671
+ this.$body.off(pointer_events.end);
672
+ this.$container.off('selectstart', this.on_select_start);
673
+ };
674
+
675
+
676
+ fn.destroy = function(){
677
+ this.disable();
678
+ $.removeData(this.$container, 'drag');
679
+ };
680
+
681
+
682
+ //jQuery adapter
683
+ $.fn.drag = function ( options ) {
684
+ return this.each(function () {
685
+ if (!$.data(this, 'drag')) {
686
+ $.data(this, 'drag', new Draggable( this, options ));
687
+ }
688
+ });
689
+ };
690
+
691
+
692
+ }(jQuery, window, document));
693
+
694
+ ;(function($, window, document, undefined) {
695
+
696
+ var defaults = {
697
+ widget_selector: '> li',
698
+ widget_margins: [10, 10],
699
+ widget_base_dimensions: [400, 225],
700
+ extra_rows: 0,
701
+ extra_cols: 0,
702
+ min_cols: 1,
703
+ min_rows: 15,
704
+ max_size_x: 6,
705
+ max_size_y: 6,
706
+ autogenerate_stylesheet: true,
707
+ avoid_overlapped_widgets: true,
708
+ serialize_params: function($w, wgd) {
709
+ return {
710
+ col: wgd.col,
711
+ row: wgd.row
712
+ };
713
+ },
714
+ collision: {},
715
+ draggable: {
716
+ distance: 4
717
+ }
718
+ };
719
+
720
+
721
+ /**
722
+ * @class Gridster
723
+ * @uses Draggable
724
+ * @uses Collision
725
+ * @param {HTMLElement} el The HTMLelement that contains all the widgets.
726
+ * @param {Object} [options] An Object with all options you want to
727
+ * overwrite:
728
+ * @param {HTMLElement|String} [options.widget_selector] Define who will
729
+ * be the draggable widgets. Can be a CSS Selector String or a
730
+ * collection of HTMLElements
731
+ * @param {Array} [options.widget_margins] Margin between widgets.
732
+ * The first index for the horizontal margin (left, right) and
733
+ * the second for the vertical margin (top, bottom).
734
+ * @param {Array} [options.widget_base_dimensions] Base widget dimensions
735
+ * in pixels. The first index for the width and the second for the
736
+ * height.
737
+ * @param {Number} [options.extra_cols] Add more columns in addition to
738
+ * those that have been calculated.
739
+ * @param {Number} [options.extra_rows] Add more rows in addition to
740
+ * those that have been calculated.
741
+ * @param {Number} [options.min_cols] The minimum required columns.
742
+ * @param {Number} [options.min_rows] The minimum required rows.
743
+ * @param {Number} [options.max_size_x] The maximum number of columns
744
+ * that a widget can span.
745
+ * @param {Number} [options.max_size_y] The maximum number of rows
746
+ * that a widget can span.
747
+ * @param {Boolean} [options.autogenerate_stylesheet] If true, all the
748
+ * CSS required to position all widgets in their respective columns
749
+ * and rows will be generated automatically and injected to the
750
+ * `<head>` of the document. You can set this to false, and write
751
+ * your own CSS targeting rows and cols via data-attributes like so:
752
+ * `[data-col="1"] { left: 10px; }`
753
+ * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded
754
+ * from the DOM can be overlapped. It is helpful if the positions were
755
+ * bad stored in the database or if there was any conflict.
756
+ * @param {Function} [options.serialize_params] Return the data you want
757
+ * for each widget in the serialization. Two arguments are passed:
758
+ * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid
759
+ * coords object (`col`, `row`, `size_x`, `size_y`).
760
+ * @param {Object} [options.collision] An Object with all options for
761
+ * Collision class you want to overwrite. See Collision docs for
762
+ * more info.
763
+ * @param {Object} [options.draggable] An Object with all options for
764
+ * Draggable class you want to overwrite. See Draggable docs for more
765
+ * info.
766
+ *
767
+ * @constructor
768
+ */
769
+ function Gridster(el, options) {
770
+ this.options = $.extend(true, defaults, options);
771
+ this.$el = $(el);
772
+ this.$wrapper = this.$el.parent();
773
+ this.$widgets = $(this.options.widget_selector, this.$el).addClass('gs_w');
774
+ this.widgets = [];
775
+ this.$changed = $([]);
776
+ this.wrapper_width = this.$wrapper.width();
777
+ this.min_widget_width = (this.options.widget_margins[0] * 2) +
778
+ this.options.widget_base_dimensions[0];
779
+ this.min_widget_height = (this.options.widget_margins[1] * 2) +
780
+ this.options.widget_base_dimensions[1];
781
+ this.init();
782
+ }
783
+
784
+ Gridster.generated_stylesheets = [];
785
+
786
+ var fn = Gridster.prototype;
787
+
788
+ fn.init = function() {
789
+ this.generate_grid_and_stylesheet();
790
+ this.get_widgets_from_DOM();
791
+ this.set_dom_grid_height();
792
+ this.$wrapper.addClass('ready');
793
+ this.draggable();
794
+
795
+ $(window).bind(
796
+ 'resize', throttle($.proxy(this.recalculate_faux_grid, this), 200));
797
+ };
798
+
799
+
800
+ /**
801
+ * Disables dragging.
802
+ *
803
+ * @method disable
804
+ * @return {Class} Returns the instance of the Gridster Class.
805
+ */
806
+ fn.disable = function() {
807
+ this.$wrapper.find('.player-revert').removeClass('player-revert');
808
+ this.drag_api.disable();
809
+ return this;
810
+ };
811
+
812
+
813
+ /**
814
+ * Enables dragging.
815
+ *
816
+ * @method enable
817
+ * @return {Class} Returns the instance of the Gridster Class.
818
+ */
819
+ fn.enable = function() {
820
+ this.drag_api.enable();
821
+ return this;
822
+ };
823
+
824
+
825
+ /**
826
+ * Add a new widget to the grid.
827
+ *
828
+ * @method add_widget
829
+ * @param {String} html The string representing the HTML of the widget.
830
+ * @param {Number} size_x The nº of rows the widget occupies horizontally.
831
+ * @param {Number} size_y The nº of columns the widget occupies vertically.
832
+ * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing.
833
+ * the widget that was just created.
834
+ */
835
+ fn.add_widget = function(html, size_x, size_y) {
836
+ var next_pos = this.next_position(size_x, size_y);
837
+
838
+ var $w = $(html).attr({
839
+ 'data-col': next_pos.col,
840
+ 'data-row': next_pos.row,
841
+ 'data-sizex' : next_pos.size_x,
842
+ 'data-sizey' : next_pos.size_y
843
+ }).addClass('gs_w').appendTo(this.$el).hide();
844
+
845
+ this.$widgets = this.$widgets.add($w);
846
+
847
+ this.register_widget($w);
848
+
849
+ this.set_dom_grid_height();
850
+
851
+ return $w.fadeIn();
852
+ };
853
+
854
+
855
+ /**
856
+ * Get the most left column below to add a new widget.
857
+ *
858
+ * @method next_position
859
+ * @param {Number} size_x The nº of rows the widget occupies horizontally.
860
+ * @param {Number} size_y The nº of columns the widget occupies vertically.
861
+ * @return {Object} Returns a grid coords object representing the future
862
+ * widget coords.
863
+ */
864
+ fn.next_position = function(size_x, size_y) {
865
+ size_x || (size_x = 1);
866
+ size_y || (size_y = 1);
867
+ var ga = this.gridmap;
868
+ var cols_l = ga.length;
869
+ var valid_pos = [];
870
+
871
+ for (var c = 1; c < cols_l; c++) {
872
+ var rows_l = ga[c].length;
873
+ for (var r = 1; r <= rows_l; r++) {
874
+ var can_move_to = this.can_move_to({
875
+ size_x: size_x,
876
+ size_y: size_y
877
+ }, c, r);
878
+
879
+ if (can_move_to) {
880
+ valid_pos.push({
881
+ col: c,
882
+ row: r,
883
+ size_y: size_y,
884
+ size_x: size_x
885
+ });
886
+ }
887
+ }
888
+ }
889
+
890
+ if (valid_pos.length) {
891
+ return this.sort_by_row_and_col_asc(valid_pos)[0];
892
+ }
893
+ return false;
894
+ };
895
+
896
+
897
+ /**
898
+ * Remove a widget from the grid.
899
+ *
900
+ * @method remove_widget
901
+ * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove.
902
+ * @return {Class} Returns the instance of the Gridster Class.
903
+ */
904
+ fn.remove_widget = function(el, callback) {
905
+ var $el = el instanceof jQuery ? el : $(el);
906
+ var wgd = $el.coords().grid;
907
+
908
+ this.cells_occupied_by_placeholder = {};
909
+ this.$widgets = this.$widgets.not($el);
910
+
911
+ var $nexts = this.widgets_below($el);
912
+
913
+ this.remove_from_gridmap(wgd);
914
+
915
+ $el.fadeOut($.proxy(function() {
916
+ $el.remove();
917
+
918
+ $nexts.each($.proxy(function(i, widget) {
919
+ this.move_widget_up( $(widget), wgd.size_y );
920
+ }, this));
921
+
922
+ this.set_dom_grid_height();
923
+
924
+ if (callback) {
925
+ callback.call(this, el);
926
+ }
927
+ }, this));
928
+ };
929
+
930
+
931
+ /**
932
+ * Returns a serialized array of the widgets in the grid.
933
+ *
934
+ * @method serialize
935
+ * @param {HTMLElement} [$widgets] The collection of jQuery wrapped
936
+ * HTMLElements you want to serialize. If no argument is passed all widgets
937
+ * will be serialized.
938
+ * @return {Array} Returns an Array of Objects with the data specified in
939
+ * the serialize_params option.
940
+ */
941
+ fn.serialize = function($widgets) {
942
+ $widgets || ($widgets = this.$widgets);
943
+ var result = [];
944
+ $widgets.each($.proxy(function(i, widget) {
945
+ result.push(this.options.serialize_params(
946
+ $(widget), $(widget).coords().grid ) );
947
+ }, this));
948
+
949
+ return result;
950
+ };
951
+
952
+
953
+ /**
954
+ * Returns a serialized array of the widgets that have changed their
955
+ * position.
956
+ *
957
+ * @method serialize_changed
958
+ * @return {Array} Returns an Array of Objects with the data specified in
959
+ * the serialize_params option.
960
+ */
961
+ fn.serialize_changed = function() {
962
+ return this.serialize(this.$changed);
963
+ };
964
+
965
+
966
+ /**
967
+ * Creates the grid coords object representing the widget a add it to the
968
+ * mapped array of positions.
969
+ *
970
+ * @method register_widget
971
+ * @return {Array} Returns the instance of the Gridster class.
972
+ */
973
+ fn.register_widget = function($el) {
974
+
975
+ var wgd = {
976
+ 'col': parseInt($el.attr('data-col'), 10),
977
+ 'row': parseInt($el.attr('data-row'), 10),
978
+ 'size_x': parseInt($el.attr('data-sizex'), 10),
979
+ 'size_y': parseInt($el.attr('data-sizey'), 10),
980
+ 'el': $el
981
+ };
982
+
983
+ if (this.options.avoid_overlapped_widgets &&
984
+ !this.can_move_to(
985
+ {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row)
986
+ ) {
987
+ wgd = this.next_position(wgd.size_x, wgd.size_y);
988
+ wgd.el = $el;
989
+ $el.attr({
990
+ 'data-col': wgd.col,
991
+ 'data-row': wgd.row,
992
+ 'data-sizex': wgd.size_x,
993
+ 'data-sizey': wgd.size_y
994
+ });
995
+ }
996
+
997
+ // attach Coord object to player data-coord attribute
998
+ $el.data('coords', $el.coords());
999
+
1000
+ // Extend Coord object with grid position info
1001
+ $el.data('coords').grid = wgd;
1002
+
1003
+ this.add_to_gridmap(wgd, $el);
1004
+ this.widgets.push($el);
1005
+ return this;
1006
+ };
1007
+
1008
+
1009
+ /**
1010
+ * Update in the mapped array of positions the value of cells represented by
1011
+ * the grid coords object passed in the `grid_data` param.
1012
+ *
1013
+ * @param {Object} grid_data The grid coords object representing the cells
1014
+ * to update in the mapped array.
1015
+ * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped
1016
+ * HTMLElement, depends if you want to delete an existing position or add
1017
+ * a new one.
1018
+ * @method update_widget_position
1019
+ * @return {Class} Returns the instance of the Gridster Class.
1020
+ */
1021
+ fn.update_widget_position = function(grid_data, value) {
1022
+ this.for_each_cell_occupied(grid_data, function(col, row) {
1023
+ if (!this.gridmap[col]) { return this; }
1024
+ this.gridmap[col][row] = value;
1025
+ });
1026
+ return this;
1027
+ };
1028
+
1029
+
1030
+ /**
1031
+ * Remove a widget from the mapped array of positions.
1032
+ *
1033
+ * @method remove_from_gridmap
1034
+ * @param {Object} grid_data The grid coords object representing the cells
1035
+ * to update in the mapped array.
1036
+ * @return {Class} Returns the instance of the Gridster Class.
1037
+ */
1038
+ fn.remove_from_gridmap = function(grid_data) {
1039
+ return this.update_widget_position(grid_data, false);
1040
+ };
1041
+
1042
+
1043
+ /**
1044
+ * Add a widget to the mapped array of positions.
1045
+ *
1046
+ * @method add_to_gridmap
1047
+ * @param {Object} grid_data The grid coords object representing the cells
1048
+ * to update in the mapped array.
1049
+ * @param {HTMLElement|Boolean} value The value to set in the specified
1050
+ * position .
1051
+ * @return {Class} Returns the instance of the Gridster Class.
1052
+ */
1053
+ fn.add_to_gridmap = function(grid_data, value) {
1054
+ this.update_widget_position(grid_data, value || grid_data.el);
1055
+
1056
+ if (grid_data.el) {
1057
+ var $widgets = this.widgets_below(grid_data.el);
1058
+ $widgets.each($.proxy(function(i, widget) {
1059
+ this.move_widget_up( $(widget));
1060
+ }, this));
1061
+ }
1062
+ };
1063
+
1064
+
1065
+ /**
1066
+ * Make widgets draggable.
1067
+ *
1068
+ * @uses Draggable
1069
+ * @method draggable
1070
+ * @return {Class} Returns the instance of the Gridster Class.
1071
+ */
1072
+ fn.draggable = function() {
1073
+ var self = this;
1074
+ var draggable_options = $.extend(true, {}, this.options.draggable, {
1075
+ offset_left: this.options.widget_margins[0],
1076
+ items: '.gs_w',
1077
+ start: function(event, ui) {
1078
+ self.$widgets.filter('.player-revert')
1079
+ .removeClass('player-revert');
1080
+
1081
+ self.$player = $(this);
1082
+ self.$helper = self.options.draggable.helper === 'clone' ?
1083
+ $(ui.helper) : self.$player;
1084
+ self.helper = !self.$helper.is(self.$player);
1085
+
1086
+ self.on_start_drag.call(self, event, ui);
1087
+ self.$el.trigger('gridster:dragstart');
1088
+ },
1089
+ stop: function(event, ui) {
1090
+ self.on_stop_drag.call(self, event, ui);
1091
+ self.$el.trigger('gridster:dragstop');
1092
+ },
1093
+ drag: throttle(function(event, ui) {
1094
+ self.on_drag.call(self, event, ui);
1095
+ self.$el.trigger('gridster:drag');
1096
+ }, 60)
1097
+ });
1098
+
1099
+ this.drag_api = this.$el.drag(draggable_options).data('drag');
1100
+ return this;
1101
+ };
1102
+
1103
+
1104
+ /**
1105
+ * This function is executed when the player begins to be dragged.
1106
+ *
1107
+ * @method on_start_drag
1108
+ * @param {Event} The original browser event
1109
+ * @param {Object} A prepared ui object.
1110
+ */
1111
+ fn.on_start_drag = function(event, ui) {
1112
+
1113
+ this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging');
1114
+
1115
+ this.$player.addClass('player');
1116
+ this.player_grid_data = this.$player.coords().grid;
1117
+ this.placeholder_grid_data = $.extend({}, this.player_grid_data);
1118
+
1119
+ //set new grid height along the dragging period
1120
+ this.$el.css('height', this.$el.height() +
1121
+ (this.player_grid_data.size_y * this.min_widget_height));
1122
+
1123
+ var colliders = this.faux_grid;
1124
+ var coords = this.$player.data('coords').coords;
1125
+
1126
+ this.cells_occupied_by_player = this.get_cells_occupied(
1127
+ this.player_grid_data);
1128
+ this.cells_occupied_by_placeholder = this.get_cells_occupied(
1129
+ this.placeholder_grid_data);
1130
+
1131
+ this.last_cols = [];
1132
+ this.last_rows = [];
1133
+
1134
+
1135
+ // see jquery.collision.js
1136
+ this.collision_api = this.$helper.collision(
1137
+ colliders, this.options.collision);
1138
+
1139
+ this.$preview_holder = $('<li />', {
1140
+ 'class': 'preview-holder',
1141
+ 'data-row': this.$player.attr('data-row'),
1142
+ 'data-col': this.$player.attr('data-col'),
1143
+ css: {
1144
+ width: coords.width,
1145
+ height: coords.height
1146
+ }
1147
+ }).appendTo(this.$el);
1148
+
1149
+ if (this.options.draggable.start) {
1150
+ this.options.draggable.start.call(this, event, ui);
1151
+ }
1152
+ };
1153
+
1154
+
1155
+ /**
1156
+ * This function is executed when the player is being dragged.
1157
+ *
1158
+ * @method on_drag
1159
+ * @param {Event} The original browser event
1160
+ * @param {Object} A prepared ui object.
1161
+ */
1162
+ fn.on_drag = function(event, ui) {
1163
+ //break if dragstop has been fired
1164
+ if (this.$player === null) {
1165
+ return false;
1166
+ };
1167
+
1168
+ var abs_offset = {
1169
+ left: ui.position.left + this.baseX,
1170
+ top: ui.position.top + this.baseY
1171
+ };
1172
+
1173
+ this.colliders_data = this.collision_api.get_closest_colliders(
1174
+ abs_offset);
1175
+
1176
+ this.on_overlapped_column_change(
1177
+ this.on_start_overlapping_column,
1178
+ this.on_stop_overlapping_column
1179
+ );
1180
+
1181
+ this.on_overlapped_row_change(
1182
+ this.on_start_overlapping_row,
1183
+ this.on_stop_overlapping_row
1184
+ );
1185
+
1186
+ if (this.helper && this.$player) {
1187
+ this.$player.css({
1188
+ 'left': ui.position.left,
1189
+ 'top': ui.position.top
1190
+ });
1191
+ }
1192
+
1193
+ if (this.options.draggable.drag) {
1194
+ this.options.draggable.drag.call(this, event, ui);
1195
+ }
1196
+ };
1197
+
1198
+ /**
1199
+ * This function is executed when the player stops being dragged.
1200
+ *
1201
+ * @method on_stop_drag
1202
+ * @param {Event} The original browser event
1203
+ * @param {Object} A prepared ui object.
1204
+ */
1205
+ fn.on_stop_drag = function(event, ui) {
1206
+ this.$helper.add(this.$player).add(this.$wrapper)
1207
+ .removeClass('dragging');
1208
+
1209
+ ui.position.left = ui.position.left + this.baseX;
1210
+ ui.position.top = ui.position.top + this.baseY;
1211
+ this.colliders_data = this.collision_api.get_closest_colliders(ui.position);
1212
+
1213
+ this.on_overlapped_column_change(
1214
+ this.on_start_overlapping_column,
1215
+ this.on_stop_overlapping_column
1216
+ );
1217
+
1218
+ this.on_overlapped_row_change(
1219
+ this.on_start_overlapping_row,
1220
+ this.on_stop_overlapping_row
1221
+ );
1222
+
1223
+ this.$player.addClass('player-revert').removeClass('player')
1224
+ .attr({
1225
+ 'data-col': this.placeholder_grid_data.col,
1226
+ 'data-row': this.placeholder_grid_data.row
1227
+ }).css({
1228
+ 'left': '',
1229
+ 'top': ''
1230
+ });
1231
+
1232
+ this.$changed = this.$changed.add(this.$player);
1233
+
1234
+ this.cells_occupied_by_player = this.get_cells_occupied(
1235
+ this.placeholder_grid_data);
1236
+ this.set_cells_player_occupies(
1237
+ this.placeholder_grid_data.col, this.placeholder_grid_data.row);
1238
+
1239
+ this.$player.coords().grid.row = this.placeholder_grid_data.row;
1240
+ this.$player.coords().grid.col = this.placeholder_grid_data.col;
1241
+
1242
+ if (this.options.draggable.stop) {
1243
+ this.options.draggable.stop.call(this, event, ui);
1244
+ }
1245
+
1246
+ this.$preview_holder.remove();
1247
+ this.$player = null;
1248
+
1249
+ this.set_dom_grid_height();
1250
+ };
1251
+
1252
+
1253
+ /**
1254
+ * Executes the callbacks passed as arguments when a column begins to be
1255
+ * overlapped or stops being overlapped.
1256
+ *
1257
+ * @param {Function} start_callback Function executed when a new column
1258
+ * begins to be overlapped. The column is passed as first argument.
1259
+ * @param {Function} stop_callback Function executed when a column stops
1260
+ * being overlapped. The column is passed as first argument.
1261
+ * @method on_overlapped_column_change
1262
+ * @return {Class} Returns the instance of the Gridster Class.
1263
+ */
1264
+ fn.on_overlapped_column_change = function(start_callback, stop_callback) {
1265
+ if (!this.colliders_data.length) {
1266
+ return;
1267
+ }
1268
+ var cols = this.get_targeted_columns(
1269
+ this.colliders_data[0].el.data.col);
1270
+
1271
+ var last_n_cols = this.last_cols.length;
1272
+ var n_cols = cols.length;
1273
+ var i;
1274
+
1275
+ for (i = 0; i < n_cols; i++) {
1276
+ if ($.inArray(cols[i], this.last_cols) === -1) {
1277
+ (start_callback || $.noop).call(this, cols[i]);
1278
+ }
1279
+ }
1280
+
1281
+ for (i = 0; i< last_n_cols; i++) {
1282
+ if ($.inArray(this.last_cols[i], cols) === -1) {
1283
+ (stop_callback || $.noop).call(this, this.last_cols[i]);
1284
+ }
1285
+ }
1286
+
1287
+ this.last_cols = cols;
1288
+
1289
+ return this;
1290
+ };
1291
+
1292
+
1293
+ /**
1294
+ * Executes the callbacks passed as arguments when a row starts to be
1295
+ * overlapped or stops being overlapped.
1296
+ *
1297
+ * @param {Function} start_callback Function executed when a new row begins
1298
+ * to be overlapped. The row is passed as first argument.
1299
+ * @param {Function} stop_callback Function executed when a row stops being
1300
+ * overlapped. The row is passed as first argument.
1301
+ * @method on_overlapped_row_change
1302
+ * @return {Class} Returns the instance of the Gridster Class.
1303
+ */
1304
+ fn.on_overlapped_row_change = function(start_callback, end_callback) {
1305
+ if (!this.colliders_data.length) {
1306
+ return;
1307
+ }
1308
+ var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row);
1309
+ var last_n_rows = this.last_rows.length;
1310
+ var n_rows = rows.length;
1311
+ var i;
1312
+
1313
+ for (i = 0; i < n_rows; i++) {
1314
+ if ($.inArray(rows[i], this.last_rows) === -1) {
1315
+ (start_callback || $.noop).call(this, rows[i]);
1316
+ }
1317
+ }
1318
+
1319
+ for (i = 0; i < last_n_rows; i++) {
1320
+ if ($.inArray(this.last_rows[i], rows) === -1) {
1321
+ (end_callback || $.noop).call(this, this.last_rows[i]);
1322
+ }
1323
+ }
1324
+
1325
+ this.last_rows = rows;
1326
+ };
1327
+
1328
+
1329
+ /**
1330
+ * Sets the current position of the player
1331
+ *
1332
+ * @param {Function} start_callback Function executed when a new row begins
1333
+ * to be overlapped. The row is passed as first argument.
1334
+ * @param {Function} stop_callback Function executed when a row stops being
1335
+ * overlapped. The row is passed as first argument.
1336
+ * @method set_player
1337
+ * @return {Class} Returns the instance of the Gridster Class.
1338
+ */
1339
+ fn.set_player = function(col, row) {
1340
+ this.empty_cells_player_occupies();
1341
+
1342
+ var self = this;
1343
+ var cell = self.colliders_data[0].el.data;
1344
+ var to_col = cell.col;
1345
+ var to_row = row || cell.row;
1346
+
1347
+ this.player_grid_data = {
1348
+ col: to_col,
1349
+ row: to_row,
1350
+ size_y : this.player_grid_data.size_y,
1351
+ size_x : this.player_grid_data.size_x
1352
+ };
1353
+
1354
+ this.cells_occupied_by_player = this.get_cells_occupied(
1355
+ this.player_grid_data);
1356
+
1357
+ var $overlapped_widgets = this.get_widgets_overlapped(
1358
+ this.player_grid_data);
1359
+
1360
+ var constraints = this.widgets_constraints($overlapped_widgets);
1361
+
1362
+ this.manage_movements(constraints.can_go_up, to_col, to_row);
1363
+ this.manage_movements(constraints.can_not_go_up, to_col, to_row);
1364
+
1365
+ /* if there is not widgets overlapping in the new player position,
1366
+ * update the new placeholder position. */
1367
+ if (!$overlapped_widgets.length) {
1368
+ var pp = this.can_go_player_up(this.player_grid_data);
1369
+ if (pp !== false) {
1370
+ to_row = pp;
1371
+ }
1372
+ this.set_placeholder(to_col, to_row);
1373
+ }
1374
+
1375
+ return {
1376
+ col: to_col,
1377
+ row: to_row
1378
+ };
1379
+ };
1380
+
1381
+
1382
+ /**
1383
+ * See which of the widgets in the $widgets param collection can go to
1384
+ * a upper row and which not.
1385
+ *
1386
+ * @method widgets_contraints
1387
+ * @param {HTMLElements} $widgets A jQuery wrapped collection of
1388
+ * HTMLElements.
1389
+ * @return {Array} Returns a literal Object with two keys: `can_go_up` &
1390
+ * `can_not_go_up`. Each contains a set of HTMLElements.
1391
+ */
1392
+ fn.widgets_constraints = function($widgets) {
1393
+ var $widgets_can_go_up = $([]);
1394
+ var $widgets_can_not_go_up;
1395
+ var wgd_can_go_up = [];
1396
+ var wgd_can_not_go_up = [];
1397
+
1398
+ $widgets.each($.proxy(function(i, w) {
1399
+ var $w = $(w);
1400
+ var wgd = $w.coords().grid;
1401
+ if (this.can_go_widget_up(wgd)) {
1402
+ $widgets_can_go_up = $widgets_can_go_up.add($w);
1403
+ wgd_can_go_up.push(wgd);
1404
+ }else{
1405
+ wgd_can_not_go_up.push(wgd);
1406
+ }
1407
+ }, this));
1408
+
1409
+ $widgets_can_not_go_up = $widgets.not($widgets_can_go_up);
1410
+
1411
+ return {
1412
+ can_go_up: this.sort_by_row_asc(wgd_can_go_up),
1413
+ can_not_go_up: this.sort_by_row_desc(wgd_can_not_go_up)
1414
+ };
1415
+ };
1416
+
1417
+
1418
+ /**
1419
+ * Sorts an Array of grid coords objects (representing the grid coords of
1420
+ * each widget) in ascending way.
1421
+ *
1422
+ * @method sort_by_row_asc
1423
+ * @param {Array} widgets Array of grid coords objects
1424
+ * @return {Array} Returns the array sorted.
1425
+ */
1426
+ fn.sort_by_row_asc = function(widgets) {
1427
+ widgets = widgets.sort(function(a, b) {
1428
+ if (a.row > b.row) {
1429
+ return 1;
1430
+ }
1431
+ return -1;
1432
+ });
1433
+
1434
+ return widgets;
1435
+ };
1436
+
1437
+
1438
+ /**
1439
+ * Sorts an Array of grid coords objects (representing the grid coords of
1440
+ * each widget) placing first the empty cells upper left.
1441
+ *
1442
+ * @method sort_by_row_and_col_asc
1443
+ * @param {Array} widgets Array of grid coords objects
1444
+ * @return {Array} Returns the array sorted.
1445
+ */
1446
+ fn.sort_by_row_and_col_asc = function(widgets) {
1447
+ widgets = widgets.sort(function(a, b) {
1448
+ if (a.row > b.row || a.row == b.row && a.col > b.col) {
1449
+ return 1;
1450
+ }
1451
+ return -1;
1452
+ });
1453
+
1454
+ return widgets;
1455
+ };
1456
+
1457
+
1458
+ /**
1459
+ * Sorts an Array of grid coords objects by column (representing the grid
1460
+ * coords of each widget) in ascending way.
1461
+ *
1462
+ * @method sort_by_col_asc
1463
+ * @param {Array} widgets Array of grid coords objects
1464
+ * @return {Array} Returns the array sorted.
1465
+ */
1466
+ fn.sort_by_col_asc = function(widgets) {
1467
+ widgets = widgets.sort(function(a, b) {
1468
+ if (a.col > b.col) {
1469
+ return 1;
1470
+ }
1471
+ return -1;
1472
+ });
1473
+
1474
+ return widgets;
1475
+ };
1476
+
1477
+
1478
+ /**
1479
+ * Sorts an Array of grid coords objects (representing the grid coords of
1480
+ * each widget) in descending way.
1481
+ *
1482
+ * @method sort_by_row_desc
1483
+ * @param {Array} widgets Array of grid coords objects
1484
+ * @return {Array} Returns the array sorted.
1485
+ */
1486
+ fn.sort_by_row_desc = function(widgets) {
1487
+ widgets = widgets.sort(function(a, b) {
1488
+ if (a.row + a.size_y < b.row + b.size_y) {
1489
+ return 1;
1490
+ }
1491
+ return -1;
1492
+ });
1493
+ return widgets;
1494
+ };
1495
+
1496
+
1497
+ /**
1498
+ * Sorts an Array of grid coords objects (representing the grid coords of
1499
+ * each widget) in descending way.
1500
+ *
1501
+ * @method manage_movements
1502
+ * @param {HTMLElements} $widgets A jQuery collection of HTMLElements
1503
+ * representing the widgets you want to move.
1504
+ * @param {Number} to_col The column to which we want to move the widgets.
1505
+ * @param {Number} to_row The row to which we want to move the widgets.
1506
+ * @return {Class} Returns the instance of the Gridster Class.
1507
+ */
1508
+ fn.manage_movements = function($widgets, to_col, to_row) {
1509
+ $.each($widgets, $.proxy(function(i, w) {
1510
+ var wgd = w;
1511
+ var $w = wgd.el;
1512
+
1513
+ var can_go_widget_up = this.can_go_widget_up(wgd);
1514
+
1515
+ if (can_go_widget_up) {
1516
+ //target CAN go up
1517
+ //so move widget up
1518
+ this.move_widget_to($w, can_go_widget_up);
1519
+ this.set_placeholder(to_col, can_go_widget_up + wgd.size_y);
1520
+
1521
+ } else {
1522
+ //target can't go up
1523
+ var can_go_player_up = this.can_go_player_up(
1524
+ this.player_grid_data);
1525
+
1526
+ if (!can_go_player_up) {
1527
+ // target can't go up
1528
+ // player cant't go up
1529
+ // so we need to move widget down to a position that dont
1530
+ // overlaps player
1531
+ var y = (to_row + this.player_grid_data.size_y) - wgd.row;
1532
+
1533
+ this.move_widget_down($w, y);
1534
+ this.set_placeholder(to_col, to_row);
1535
+ }
1536
+ }
1537
+ }, this));
1538
+
1539
+ return this;
1540
+ };
1541
+
1542
+ /**
1543
+ * Determines if there is a widget in the row and col given. Or if the
1544
+ * HTMLElement passed as first argument is the player.
1545
+ *
1546
+ * @method is_player
1547
+ * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of
1548
+ * HTMLElements.
1549
+ * @param {Number} [row] The column to which we want to move the widgets.
1550
+ * @return {Boolean} Returns true or false.
1551
+ */
1552
+ fn.is_player = function(col_or_el, row) {
1553
+ if (row && !this.gridmap[col_or_el]) { return false; }
1554
+ var $w = row ? this.gridmap[col_or_el][row] : col_or_el;
1555
+ return $w && ($w.is(this.$player) || $w.is(this.$helper));
1556
+ };
1557
+
1558
+
1559
+ /**
1560
+ * Determines if the widget that is being dragged is currently over the row
1561
+ * and col given.
1562
+ *
1563
+ * @method is_player_in
1564
+ * @param {Number} col The column to check.
1565
+ * @param {Number} row The row to check.
1566
+ * @return {Boolean} Returns true or false.
1567
+ */
1568
+ fn.is_player_in = function(col, row) {
1569
+ var c = this.cells_occupied_by_player || {};
1570
+ return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0;
1571
+ };
1572
+
1573
+
1574
+ /**
1575
+ * Determines if the placeholder is currently over the row and col given.
1576
+ *
1577
+ * @method is_placeholder_in
1578
+ * @param {Number} col The column to check.
1579
+ * @param {Number} row The row to check.
1580
+ * @return {Boolean} Returns true or false.
1581
+ */
1582
+ fn.is_placeholder_in = function(col, row) {
1583
+ var c = this.cells_occupied_by_placeholder || {};
1584
+ return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0;
1585
+ };
1586
+
1587
+
1588
+ /**
1589
+ * Determines if the placeholder is currently over the column given.
1590
+ *
1591
+ * @method is_placeholder_in_col
1592
+ * @param {Number} col The column to check.
1593
+ * @return {Boolean} Returns true or false.
1594
+ */
1595
+ fn.is_placeholder_in_col = function(col) {
1596
+ var c = this.cells_occupied_by_placeholder || [];
1597
+ return $.inArray(col, c.cols) >= 0;
1598
+ };
1599
+
1600
+
1601
+ /**
1602
+ * Determines if the cell represented by col and row params is empty.
1603
+ *
1604
+ * @method is_empty
1605
+ * @param {Number} col The column to check.
1606
+ * @param {Number} row The row to check.
1607
+ * @return {Boolean} Returns true or false.
1608
+ */
1609
+ fn.is_empty = function(col, row) {
1610
+ if (typeof this.gridmap[col] !== 'undefined' &&
1611
+ typeof this.gridmap[col][row] !== 'undefined' &&
1612
+ this.gridmap[col][row] === false
1613
+ ) {
1614
+ return true;
1615
+ }
1616
+ return false;
1617
+ };
1618
+
1619
+
1620
+ /**
1621
+ * Determines if the cell represented by col and row params is occupied.
1622
+ *
1623
+ * @method is_occupied
1624
+ * @param {Number} col The column to check.
1625
+ * @param {Number} row The row to check.
1626
+ * @return {Boolean} Returns true or false.
1627
+ */
1628
+ fn.is_occupied = function(col, row) {
1629
+ if (!this.gridmap[col]) {
1630
+ return false;
1631
+ }
1632
+
1633
+ if (this.gridmap[col][row]) {
1634
+ return true;
1635
+ }
1636
+ return false;
1637
+ };
1638
+
1639
+
1640
+ /**
1641
+ * Determines if there is a widget in the cell represented by col/row params.
1642
+ *
1643
+ * @method is_widget
1644
+ * @param {Number} col The column to check.
1645
+ * @param {Number} row The row to check.
1646
+ * @return {Boolean|HTMLElement} Returns false if there is no widget,
1647
+ * else returns the jQuery HTMLElement
1648
+ */
1649
+ fn.is_widget = function(col, row) {
1650
+ var cell = this.gridmap[col];
1651
+ if (!cell) {
1652
+ return false;
1653
+ }
1654
+
1655
+ cell = cell[row];
1656
+
1657
+ if (cell) {
1658
+ return cell;
1659
+ }
1660
+
1661
+ return false;
1662
+ };
1663
+
1664
+
1665
+ /**
1666
+ * Determines if there is a widget in the cell represented by col/row
1667
+ * params and if this is under the widget that is being dragged.
1668
+ *
1669
+ * @method is_widget_under_player
1670
+ * @param {Number} col The column to check.
1671
+ * @param {Number} row The row to check.
1672
+ * @return {Boolean} Returns true or false.
1673
+ */
1674
+ fn.is_widget_under_player = function(col, row) {
1675
+ if (this.is_widget(col, row)) {
1676
+ return this.is_player_in(col, row);
1677
+ }
1678
+ return false;
1679
+ };
1680
+
1681
+
1682
+ /**
1683
+ * Get widgets overlapping with the player.
1684
+ *
1685
+ * @method get_widgets_under_player
1686
+ * @return {HTMLElement} Returns a jQuery collection of HTMLElements
1687
+ */
1688
+ fn.get_widgets_under_player = function() {
1689
+ var cells = this.cells_occupied_by_player;
1690
+ var $widgets = $([]);
1691
+
1692
+ $.each(cells.cols, $.proxy(function(i, col) {
1693
+ $.each(cells.rows, $.proxy(function(i, row) {
1694
+ if(this.is_widget(col, row)) {
1695
+ $widgets = $widgets.add(this.gridmap[col][row]);
1696
+ }
1697
+ }, this));
1698
+ }, this));
1699
+
1700
+ return $widgets;
1701
+ };
1702
+
1703
+
1704
+ /**
1705
+ * Put placeholder at the row and column specified.
1706
+ *
1707
+ * @method set_placeholder
1708
+ * @param {Number} col The column to which we want to move the
1709
+ * placeholder.
1710
+ * @param {Number} row The row to which we want to move the
1711
+ * placeholder.
1712
+ * @return {Class} Returns the instance of the Gridster Class.
1713
+ */
1714
+ fn.set_placeholder = function(col, row) {
1715
+ var phgd = $.extend({}, this.placeholder_grid_data);
1716
+ var $nexts = this.widgets_below({
1717
+ col: phgd.col,
1718
+ row: phgd.row,
1719
+ size_y: phgd.size_y,
1720
+ size_x: phgd.size_x
1721
+ });
1722
+
1723
+ //Prevents widgets go out of the grid
1724
+ var right_col = (col + phgd.size_x - 1);
1725
+ if (right_col > this.cols) {
1726
+ col = col - (right_col - col);
1727
+ }
1728
+
1729
+ var moved_down = this.placeholder_grid_data.row < row;
1730
+ var changed_column = this.placeholder_grid_data.col !== col;
1731
+
1732
+ this.placeholder_grid_data.col = col;
1733
+ this.placeholder_grid_data.row = row;
1734
+
1735
+ this.cells_occupied_by_placeholder = this.get_cells_occupied(
1736
+ this.placeholder_grid_data);
1737
+
1738
+ this.$preview_holder.attr({
1739
+ 'data-row' : row,
1740
+ 'data-col' : col
1741
+ });
1742
+
1743
+ if (moved_down || changed_column) {
1744
+ $nexts.each($.proxy(function(i, widget) {
1745
+ this.move_widget_up(
1746
+ $(widget), this.placeholder_grid_data.col - col + phgd.size_y);
1747
+ }, this));
1748
+ }
1749
+
1750
+ };
1751
+
1752
+
1753
+ /**
1754
+ * Determines whether the player can move to a position above.
1755
+ *
1756
+ * @method can_go_player_up
1757
+ * @param {Object} widget_grid_data The actual grid coords object of the
1758
+ * player.
1759
+ * @return {Number|Boolean} If the player can be moved to an upper row
1760
+ * returns the row number, else returns false.
1761
+ */
1762
+ fn.can_go_player_up = function(widget_grid_data) {
1763
+ var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1;
1764
+ var result = true;
1765
+ var upper_rows = [];
1766
+ var min_row = 10000;
1767
+ var $widgets_under_player = this.get_widgets_under_player();
1768
+
1769
+ /* generate an array with columns as index and array with upper rows
1770
+ * empty as value */
1771
+ this.for_each_column_occupied(widget_grid_data, function(tcol) {
1772
+ var grid_col = this.gridmap[tcol];
1773
+ var r = p_bottom_row + 1;
1774
+ upper_rows[tcol] = [];
1775
+
1776
+ while (--r > 0) {
1777
+ if (this.is_empty(tcol, r) || this.is_player(tcol, r) ||
1778
+ this.is_widget(tcol, r) &&
1779
+ grid_col[r].is($widgets_under_player)
1780
+ ) {
1781
+ upper_rows[tcol].push(r);
1782
+ min_row = r < min_row ? r : min_row;
1783
+ }else{
1784
+ break;
1785
+ }
1786
+ }
1787
+
1788
+ if (upper_rows[tcol].length === 0) {
1789
+ result = false;
1790
+ return true; //break
1791
+ }
1792
+
1793
+ upper_rows[tcol].sort();
1794
+ });
1795
+
1796
+ if (!result) { return false; }
1797
+
1798
+ return this.get_valid_rows(widget_grid_data, upper_rows, min_row);
1799
+ };
1800
+
1801
+
1802
+ /**
1803
+ * Determines whether a widget can move to a position above.
1804
+ *
1805
+ * @method can_go_widget_up
1806
+ * @param {Object} widget_grid_data The actual grid coords object of the
1807
+ * widget we want to check.
1808
+ * @return {Number|Boolean} If the widget can be moved to an upper row
1809
+ * returns the row number, else returns false.
1810
+ */
1811
+ fn.can_go_widget_up = function(widget_grid_data) {
1812
+ var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1;
1813
+ var result = true;
1814
+ var upper_rows = [];
1815
+ var min_row = 10000;
1816
+
1817
+ if (widget_grid_data.col < this.player_grid_data.col &&
1818
+ (widget_grid_data.col + widget_grid_data.size_y - 1) >
1819
+ (this.player_grid_data.col + this.player_grid_data.size_y - 1)
1820
+ ) {
1821
+ return false;
1822
+ };
1823
+
1824
+ /* generate an array with columns as index and array with upper rows
1825
+ * empty as value */
1826
+ this.for_each_column_occupied(widget_grid_data, function(tcol) {
1827
+ var grid_col = this.gridmap[tcol];
1828
+ upper_rows[tcol] = [];
1829
+
1830
+ var r = p_bottom_row + 1;
1831
+
1832
+ while (--r > 0) {
1833
+ if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) {
1834
+ if (!grid_col[r].is(widget_grid_data.el)) {
1835
+ break;
1836
+ };
1837
+ }
1838
+
1839
+ if (!this.is_player(tcol, r) &&
1840
+ !this.is_placeholder_in(tcol, r) &&
1841
+ !this.is_player_in(tcol, r)) {
1842
+ upper_rows[tcol].push(r);
1843
+ };
1844
+
1845
+ if (r < min_row) {
1846
+ min_row = r;
1847
+ }
1848
+ }
1849
+
1850
+ if (upper_rows[tcol].length === 0) {
1851
+ result = false;
1852
+ return true; //break
1853
+ }
1854
+
1855
+ upper_rows[tcol].sort();
1856
+ });
1857
+
1858
+ if (!result) { return false; }
1859
+
1860
+ return this.get_valid_rows(widget_grid_data, upper_rows, min_row);
1861
+ };
1862
+
1863
+
1864
+ /**
1865
+ * Search a valid row for the widget represented by `widget_grid_data' in
1866
+ * the `upper_rows` array. Iteration starts from row specified in `min_row`.
1867
+ *
1868
+ * @method get_valid_rows
1869
+ * @param {Object} widget_grid_data The actual grid coords object of the
1870
+ * player.
1871
+ * @param {Array} upper_rows An array with columns as index and arrays
1872
+ * of valid rows as values.
1873
+ * @param {Number} min_row The upper row from which the iteration will start.
1874
+ * @return {Number|Boolean} Returns the upper row valid from the `upper_rows`
1875
+ * for the widget in question.
1876
+ */
1877
+ fn.get_valid_rows = function(widget_grid_data, upper_rows, min_row) {
1878
+ var p_top_row = widget_grid_data.row;
1879
+ var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1;
1880
+ var size_y = widget_grid_data.size_y;
1881
+ var r = min_row - 1;
1882
+ var valid_rows = [];
1883
+
1884
+ while (++r <= p_bottom_row ) {
1885
+ var common = true;
1886
+ $.each(upper_rows, function(col, rows) {
1887
+ if ($.isArray(rows) && $.inArray(r, rows) === -1) {
1888
+ common = false;
1889
+ }
1890
+ });
1891
+
1892
+ if (common === true) {
1893
+ valid_rows.push(r);
1894
+ if (valid_rows.length === size_y) {
1895
+ break;
1896
+ }
1897
+ }
1898
+ }
1899
+
1900
+ var new_row = false;
1901
+ if (size_y === 1) {
1902
+ if (valid_rows[0] !== p_top_row) {
1903
+ new_row = valid_rows[0] || false;
1904
+ }
1905
+ }else{
1906
+ if (valid_rows[0] !== p_top_row) {
1907
+ new_row = this.get_consecutive_numbers_index(
1908
+ valid_rows, size_y);
1909
+ }
1910
+ }
1911
+
1912
+ return new_row;
1913
+ };
1914
+
1915
+
1916
+ fn.get_consecutive_numbers_index = function(arr, size_y) {
1917
+ var max = arr.length;
1918
+ var result = [];
1919
+ var first = true;
1920
+ var prev = -1; // or null?
1921
+
1922
+ for (var i=0; i < max; i++) {
1923
+ if (first || arr[i] === prev + 1) {
1924
+ result.push(i);
1925
+ if (result.length === size_y) {
1926
+ break;
1927
+ }
1928
+ first = false;
1929
+ }else{
1930
+ result = [];
1931
+ first = true;
1932
+ }
1933
+
1934
+ prev = arr[i];
1935
+ }
1936
+
1937
+ return result.length >= size_y ? arr[result[0]] : false;
1938
+ };
1939
+
1940
+
1941
+ /**
1942
+ * Get widgets overlapping with the player.
1943
+ *
1944
+ * @method get_widgets_overlapped
1945
+ * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
1946
+ */
1947
+ fn.get_widgets_overlapped = function() {
1948
+ var $w;
1949
+ var $widgets = $([]);
1950
+ var used = [];
1951
+ var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0);
1952
+ rows_from_bottom.reverse();
1953
+
1954
+ $.each(this.cells_occupied_by_player.cols, $.proxy(function(i, col) {
1955
+ $.each(rows_from_bottom, $.proxy(function(i, row) {
1956
+ // if there is a widget in the player position
1957
+ if (!this.gridmap[col]) { return true; } //next iteration
1958
+ var $w = this.gridmap[col][row];
1959
+
1960
+ if (this.is_occupied(col, row) && !this.is_player($w) &&
1961
+ $.inArray($w, used) === -1
1962
+ ) {
1963
+ $widgets = $widgets.add($w);
1964
+ used.push($w);
1965
+ }
1966
+
1967
+ }, this));
1968
+ }, this));
1969
+
1970
+ return $widgets;
1971
+ };
1972
+
1973
+
1974
+ /**
1975
+ * This callback is executed when the player begins to collide with a column.
1976
+ *
1977
+ * @method on_start_overlapping_column
1978
+ * @param {Number} col The collided column.
1979
+ * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
1980
+ */
1981
+ fn.on_start_overlapping_column = function(col) {
1982
+ this.set_player(col, false);
1983
+ };
1984
+
1985
+
1986
+ /**
1987
+ * A callback executed when the player begins to collide with a row.
1988
+ *
1989
+ * @method on_start_overlapping_row
1990
+ * @param {Number} col The collided row.
1991
+ * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
1992
+ */
1993
+ fn.on_start_overlapping_row = function(row) {
1994
+ this.set_player(false, row);
1995
+ };
1996
+
1997
+
1998
+ /**
1999
+ * A callback executed when the the player ends to collide with a column.
2000
+ *
2001
+ * @method on_stop_overlapping_column
2002
+ * @param {Number} col The collided row.
2003
+ * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
2004
+ */
2005
+ fn.on_stop_overlapping_column = function(col) {
2006
+ this.set_player(col, false);
2007
+
2008
+ var self = this;
2009
+ this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0],
2010
+ function(tcol, trow) {
2011
+ self.move_widget_up(this, self.player_grid_data.size_y);
2012
+ });
2013
+ };
2014
+
2015
+
2016
+ /**
2017
+ * This callback is executed when the player ends to collide with a row.
2018
+ *
2019
+ * @method on_stop_overlapping_row
2020
+ * @param {Number} row The collided row.
2021
+ * @return {HTMLElements} Returns a jQuery collection of HTMLElements.
2022
+ */
2023
+ fn.on_stop_overlapping_row = function(row) {
2024
+ this.set_player(false, row);
2025
+
2026
+ var self = this;
2027
+ var cols = this.cells_occupied_by_player.cols;
2028
+ for (var c = 0, cl = cols.length; c < cl; c++) {
2029
+ this.for_each_widget_below(cols[c], row, function(tcol, trow) {
2030
+ self.move_widget_up(this, self.player_grid_data.size_y);
2031
+ });
2032
+ }
2033
+ };
2034
+
2035
+
2036
+ /**
2037
+ * Move a widget to a specific row. The cell or cells must be empty.
2038
+ * If the widget has widgets below, all of these widgets will be moved also
2039
+ * if they can.
2040
+ *
2041
+ * @method move_widget_to
2042
+ * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the
2043
+ * widget is going to be moved.
2044
+ * @return {Class} Returns the instance of the Gridster Class.
2045
+ */
2046
+ fn.move_widget_to = function($widget, row) {
2047
+ var self = this;
2048
+ var widget_grid_data = $widget.coords().grid;
2049
+ var diff = row - widget_grid_data.row;
2050
+ var $next_widgets = this.widgets_below($widget);
2051
+
2052
+ var can_move_to_new_cell = this.can_move_to(
2053
+ widget_grid_data, widget_grid_data.col, row, $widget);
2054
+
2055
+ if (can_move_to_new_cell === false) {
2056
+ return false;
2057
+ }
2058
+
2059
+ this.remove_from_gridmap(widget_grid_data);
2060
+ widget_grid_data.row = row;
2061
+ this.add_to_gridmap(widget_grid_data);
2062
+ $widget.attr('data-row', row);
2063
+ this.$changed = this.$changed.add($widget);
2064
+
2065
+
2066
+ $next_widgets.each(function(i, widget) {
2067
+ var $w = $(widget);
2068
+ var wgd = $w.coords().grid;
2069
+ var can_go_up = self.can_go_widget_up(wgd);
2070
+ if (can_go_up && can_go_up !== wgd.row) {
2071
+ self.move_widget_to($w, can_go_up);
2072
+ }
2073
+ });
2074
+
2075
+ return this;
2076
+ };
2077
+
2078
+
2079
+ /**
2080
+ * Move up the specified widget and all below it.
2081
+ *
2082
+ * @method move_widget_up
2083
+ * @param {HTMLElement} $widget The widget you want to move.
2084
+ * @param {Number} [y_units] The number of cells that the widget has to move.
2085
+ * @return {Class} Returns the instance of the Gridster Class.
2086
+ */
2087
+ fn.move_widget_up = function($widget, y_units) {
2088
+ var el_grid_data = $widget.coords().grid;
2089
+ var actual_row = el_grid_data.row;
2090
+ var moved = [];
2091
+ var can_go_up = true;
2092
+ y_units || (y_units = 1);
2093
+
2094
+ if (!this.can_go_up($widget)) { return false; } //break;
2095
+
2096
+ this.for_each_column_occupied(el_grid_data, function(col) {
2097
+ // can_go_up
2098
+ if ($.inArray($widget, moved) === -1) {
2099
+ var widget_grid_data = $widget.coords().grid;
2100
+ var next_row = actual_row - y_units;
2101
+ next_row = this.can_go_up_to_row(
2102
+ widget_grid_data, col, next_row);
2103
+
2104
+ if (!next_row) {
2105
+ return true;
2106
+ }
2107
+
2108
+ var $next_widgets = this.widgets_below($widget);
2109
+
2110
+ this.remove_from_gridmap(widget_grid_data);
2111
+ widget_grid_data.row = next_row;
2112
+ this.add_to_gridmap(widget_grid_data);
2113
+ $widget.attr('data-row', widget_grid_data.row);
2114
+ this.$changed = this.$changed.add($widget);
2115
+
2116
+ moved.push($widget);
2117
+
2118
+ $next_widgets.each($.proxy(function(i, widget) {
2119
+ this.move_widget_up($(widget), y_units);
2120
+ }, this));
2121
+ }
2122
+ });
2123
+
2124
+ };
2125
+
2126
+
2127
+ /**
2128
+ * Move down the specified widget and all below it.
2129
+ *
2130
+ * @method move_widget_down
2131
+ * @param {HTMLElement} $widget The jQuery object representing the widget
2132
+ * you want to move.
2133
+ * @param {Number} The number of cells that the widget has to move.
2134
+ * @return {Class} Returns the instance of the Gridster Class.
2135
+ */
2136
+ fn.move_widget_down = function($widget, y_units) {
2137
+ var el_grid_data = $widget.coords().grid;
2138
+ var actual_row = el_grid_data.row;
2139
+ var moved = [];
2140
+ var y_diff = y_units;
2141
+
2142
+ if (!$widget) { return false; }
2143
+
2144
+ if ($.inArray($widget, moved) === -1) {
2145
+
2146
+ var widget_grid_data = $widget.coords().grid;
2147
+ var next_row = actual_row + y_units;
2148
+ var $next_widgets = this.widgets_below($widget);
2149
+
2150
+ this.remove_from_gridmap(widget_grid_data);
2151
+
2152
+ $next_widgets.each($.proxy(function(i, widget) {
2153
+ var $w = $(widget);
2154
+ var wd = $w.coords().grid;
2155
+ var tmp_y = this.displacement_diff(
2156
+ wd, widget_grid_data, y_diff);
2157
+
2158
+ if (tmp_y > 0) {
2159
+ this.move_widget_down($w, tmp_y);
2160
+ }
2161
+ }, this));
2162
+
2163
+ widget_grid_data.row = next_row;
2164
+ this.update_widget_position(widget_grid_data, $widget);
2165
+ $widget.attr('data-row', widget_grid_data.row);
2166
+ this.$changed = this.$changed.add($widget);
2167
+
2168
+ moved.push($widget);
2169
+ }
2170
+ };
2171
+
2172
+
2173
+ /**
2174
+ * Check if the widget can move to the specified row, else returns the
2175
+ * upper row possible.
2176
+ *
2177
+ * @method can_go_up_to_row
2178
+ * @param {Number} widget_grid_data The current grid coords object of the
2179
+ * widget.
2180
+ * @param {Number} col The target column.
2181
+ * @param {Number} row The target row.
2182
+ * @return {Boolean|Number} Returns the row number if the widget can move
2183
+ * to the target position, else returns false.
2184
+ */
2185
+ fn.can_go_up_to_row = function(widget_grid_data, col, row) {
2186
+ var ga = this.gridmap;
2187
+ var result = true;
2188
+ var urc = []; // upper_rows_in_columns
2189
+ var actual_row = widget_grid_data.row;
2190
+ var r;
2191
+
2192
+ /* generate an array with columns as index and array with
2193
+ * upper rows empty in the column */
2194
+ this.for_each_column_occupied(widget_grid_data, function(tcol) {
2195
+ var grid_col = ga[tcol];
2196
+ urc[tcol] = [];
2197
+
2198
+ r = actual_row;
2199
+ while (r--) {
2200
+ if (this.is_empty(tcol, r) &&
2201
+ !this.is_placeholder_in(tcol, r)
2202
+ ) {
2203
+ urc[tcol].push(r);
2204
+ }else{
2205
+ break;
2206
+ }
2207
+ }
2208
+
2209
+ if (!urc[tcol].length) {
2210
+ result = false;
2211
+ return true;
2212
+ }
2213
+
2214
+ });
2215
+
2216
+ if (!result) { return false; }
2217
+
2218
+ /* get common rows starting from upper position in all the columns
2219
+ * that widget occupies */
2220
+ r = row;
2221
+ for (r = 1; r < actual_row; r++) {
2222
+ var common = true;
2223
+
2224
+ for (var uc = 0, ucl = urc.length; uc < ucl; uc++) {
2225
+ if (urc[uc] && $.inArray(r, urc[uc]) === -1) {
2226
+ common = false;
2227
+ }
2228
+ }
2229
+
2230
+ if (common === true) {
2231
+ result = r;
2232
+ break;
2233
+ }
2234
+ }
2235
+
2236
+ return result;
2237
+ };
2238
+
2239
+
2240
+ fn.displacement_diff = function(widget_grid_data, parent_bgd, y_units) {
2241
+ var actual_row = widget_grid_data.row;
2242
+ var diffs = [];
2243
+ var parent_max_y = parent_bgd.row + parent_bgd.size_y;
2244
+
2245
+ this.for_each_column_occupied(widget_grid_data, function(col) {
2246
+ var temp_y_units = 0;
2247
+
2248
+ for (var r = parent_max_y; r < actual_row; r++) {
2249
+ if (this.is_empty(col, r)) {
2250
+ temp_y_units = temp_y_units + 1;
2251
+ }
2252
+ }
2253
+
2254
+ diffs.push(temp_y_units);
2255
+ });
2256
+
2257
+ var max_diff = Math.max.apply(Math, diffs);
2258
+ y_units = (y_units - max_diff);
2259
+
2260
+ return y_units > 0 ? y_units : 0;
2261
+ };
2262
+
2263
+
2264
+ /**
2265
+ * Get widgets below a widget.
2266
+ *
2267
+ * @method widgets_below
2268
+ * @param {HTMLElement} $el The jQuery wrapped HTMLElement.
2269
+ * @return {HTMLElements} A jQuery collection of HTMLElements.
2270
+ */
2271
+ fn.widgets_below = function($el) {
2272
+ var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid;
2273
+ var self = this;
2274
+ var ga = this.gridmap;
2275
+ var next_row = el_grid_data.row + el_grid_data.size_y - 1;
2276
+ var $nexts = $([]);
2277
+
2278
+ this.for_each_column_occupied(el_grid_data, function(col) {
2279
+ self.for_each_widget_below(col, next_row,
2280
+ function(tcol, trow) {
2281
+ if (!self.is_player(this) &&
2282
+ $.inArray(this, $nexts) === -1) {
2283
+ $nexts = $nexts.add(this);
2284
+ return true; // break
2285
+ }
2286
+ });
2287
+ });
2288
+
2289
+ return this.sort_by_row_asc($nexts);
2290
+ };
2291
+
2292
+
2293
+ /**
2294
+ * Update the array of mapped positions with the new player position.
2295
+ *
2296
+ * @method set_cells_player_occupies
2297
+ * @param {Number} col The new player col.
2298
+ * @param {Number} col The new player row.
2299
+ * @return {Class} Returns the instance of the Gridster Class.
2300
+ */
2301
+ fn.set_cells_player_occupies = function(col, row) {
2302
+ this.remove_from_gridmap(this.placeholder_grid_data);
2303
+ this.placeholder_grid_data.col = col;
2304
+ this.placeholder_grid_data.row = row;
2305
+ this.add_to_gridmap(this.placeholder_grid_data, this.$player);
2306
+ return this;
2307
+ };
2308
+
2309
+
2310
+ /**
2311
+ * Remove from the array of mapped positions the reference to the player.
2312
+ *
2313
+ * @method empty_cells_player_occupies
2314
+ * @return {Class} Returns the instance of the Gridster Class.
2315
+ */
2316
+ fn.empty_cells_player_occupies = function() {
2317
+ this.remove_from_gridmap(this.placeholder_grid_data);
2318
+ return this;
2319
+ };
2320
+
2321
+
2322
+ fn.can_go_up = function($el) {
2323
+ var el_grid_data = $el.coords().grid;
2324
+ var initial_row = el_grid_data.row;
2325
+ var prev_row = initial_row - 1;
2326
+ var ga = this.gridmap;
2327
+ var upper_rows_by_column = [];
2328
+
2329
+ var result = true;
2330
+ if (initial_row === 1) { return false; }
2331
+
2332
+ this.for_each_column_occupied(el_grid_data, function(col) {
2333
+ var $w = this.is_widget(col, prev_row);
2334
+ if (this.is_occupied(col, prev_row) ||
2335
+ this.is_player(col, prev_row) ||
2336
+ this.is_placeholder_in(col, prev_row) ||
2337
+ this.is_player_in(col, prev_row)
2338
+ ) {
2339
+ result = false;
2340
+ return true; //break
2341
+ }
2342
+ });
2343
+
2344
+ return result;
2345
+ };
2346
+
2347
+
2348
+
2349
+ /**
2350
+ * Check if it's possible to move a widget to a specific col/row. It takes
2351
+ * into account the dimensions (`size_y` and `size_x` attrs. of the grid
2352
+ * coords object) the widget occupies.
2353
+ *
2354
+ * @method can_move_to
2355
+ * @param {Object} widget_grid_data The grid coords object that represents
2356
+ * the widget.
2357
+ * @param {Object} col The col to check.
2358
+ * @param {Object} row The row to check.
2359
+ * @return {Boolean} Returns true if all cells are empty, else return false.
2360
+ */
2361
+ fn.can_move_to = function(widget_grid_data, col, row) {
2362
+ var ga = this.gridmap;
2363
+ var $w = widget_grid_data.el;
2364
+ var future_wd = {
2365
+ size_y: widget_grid_data.size_y,
2366
+ size_x: widget_grid_data.size_x,
2367
+ col: col,
2368
+ row: row
2369
+ };
2370
+ var result = true;
2371
+
2372
+ //Prevents widgets go out of the grid
2373
+ var right_col = col + widget_grid_data.size_x - 1;
2374
+ if (right_col > this.cols) {
2375
+ return false;
2376
+ };
2377
+
2378
+ this.for_each_cell_occupied(future_wd, function(tcol, trow) {
2379
+ var $tw = this.is_widget(tcol, trow);
2380
+ if ($tw && (!widget_grid_data.el || $tw.is($w))) {
2381
+ result = false;
2382
+ }
2383
+ });
2384
+
2385
+ return result;
2386
+ };
2387
+
2388
+
2389
+ /**
2390
+ * Given the leftmost column returns all columns that are overlapping
2391
+ * with the player.
2392
+ *
2393
+ * @method get_targeted_columns
2394
+ * @param {Number} [from_col] The leftmost column.
2395
+ * @return {Array} Returns an array with column numbers.
2396
+ */
2397
+ fn.get_targeted_columns = function(from_col) {
2398
+ var max = (from_col || this.player_grid_data.col) +
2399
+ (this.player_grid_data.size_x - 1);
2400
+ var cols = [];
2401
+ for (var col = from_col; col <= max; col++) {
2402
+ cols.push(col);
2403
+ }
2404
+ return cols;
2405
+ };
2406
+
2407
+
2408
+ /**
2409
+ * Given the upper row returns all rows that are overlapping with the player.
2410
+ *
2411
+ * @method get_targeted_rows
2412
+ * @param {Number} [from_row] The upper row.
2413
+ * @return {Array} Returns an array with row numbers.
2414
+ */
2415
+ fn.get_targeted_rows = function(from_row) {
2416
+ var max = (from_row || this.player_grid_data.row) +
2417
+ (this.player_grid_data.size_y - 1);
2418
+ var rows = [];
2419
+ for (var row = from_row; row <= max; row++) {
2420
+ rows.push(row);
2421
+ }
2422
+ return rows;
2423
+ };
2424
+
2425
+ /**
2426
+ * Get all columns and rows that a widget occupies.
2427
+ *
2428
+ * @method get_cells_occupied
2429
+ * @param {Object} el_grid_data The grid coords object of the widget.
2430
+ * @return {Object} Returns an object like `{ cols: [], rows: []}`.
2431
+ */
2432
+ fn.get_cells_occupied = function(el_grid_data) {
2433
+ var cells = { cols: [], rows: []};
2434
+ var i;
2435
+ if (arguments[1] instanceof jQuery) {
2436
+ el_grid_data = arguments[1].coords().grid;
2437
+ }
2438
+
2439
+ for (i = 0; i < el_grid_data.size_x; i++) {
2440
+ var col = el_grid_data.col + i;
2441
+ cells.cols.push(col);
2442
+ }
2443
+
2444
+ for (i = 0; i < el_grid_data.size_y; i++) {
2445
+ var row = el_grid_data.row + i;
2446
+ cells.rows.push(row);
2447
+ }
2448
+
2449
+ return cells;
2450
+ };
2451
+
2452
+
2453
+ /**
2454
+ * Iterate over the cells occupied by a widget executing a function for
2455
+ * each one.
2456
+ *
2457
+ * @method for_each_cell_occupied
2458
+ * @param {Object} el_grid_data The grid coords object that represents the
2459
+ * widget.
2460
+ * @param {Function} callback The function to execute on each column
2461
+ * iteration. Column and row are passed as arguments.
2462
+ * @return {Class} Returns the instance of the Gridster Class.
2463
+ */
2464
+ fn.for_each_cell_occupied = function(grid_data, callback) {
2465
+ this.for_each_column_occupied(grid_data, function(col) {
2466
+ this.for_each_row_occupied(grid_data, function(row) {
2467
+ callback.call(this, col, row);
2468
+ });
2469
+ });
2470
+ return this;
2471
+ };
2472
+
2473
+
2474
+ /**
2475
+ * Iterate over the columns occupied by a widget executing a function for
2476
+ * each one.
2477
+ *
2478
+ * @method for_each_column_occupied
2479
+ * @param {Object} el_grid_data The grid coords object that represents
2480
+ * the widget.
2481
+ * @param {Function} callback The function to execute on each column
2482
+ * iteration. The column number is passed as first argument.
2483
+ * @return {Class} Returns the instance of the Gridster Class.
2484
+ */
2485
+ fn.for_each_column_occupied = function(el_grid_data, callback) {
2486
+ for (var i = 0; i < el_grid_data.size_x; i++) {
2487
+ var col = el_grid_data.col + i;
2488
+ callback.call(this, col, el_grid_data);
2489
+ }
2490
+ };
2491
+
2492
+
2493
+ /**
2494
+ * Iterate over the rows occupied by a widget executing a function for
2495
+ * each one.
2496
+ *
2497
+ * @method for_each_row_occupied
2498
+ * @param {Object} el_grid_data The grid coords object that represents
2499
+ * the widget.
2500
+ * @param {Function} callback The function to execute on each column
2501
+ * iteration. The row number is passed as first argument.
2502
+ * @return {Class} Returns the instance of the Gridster Class.
2503
+ */
2504
+ fn.for_each_row_occupied = function(el_grid_data, callback) {
2505
+ for (var i = 0; i < el_grid_data.size_y; i++) {
2506
+ var row = el_grid_data.row + i;
2507
+ callback.call(this, row, el_grid_data);
2508
+ }
2509
+ };
2510
+
2511
+
2512
+
2513
+ fn._traversing_widgets = function(type, direction, col, row, callback) {
2514
+ var ga = this.gridmap;
2515
+ if (!ga[col]) { return; }
2516
+
2517
+ var cr, max;
2518
+ var action = type + '/' + direction;
2519
+ if (arguments[2] instanceof jQuery) {
2520
+ var el_grid_data = arguments[2].coords().grid;
2521
+ col = el_grid_data.col;
2522
+ row = el_grid_data.row;
2523
+ callback = arguments[3];
2524
+ }
2525
+ var matched = [];
2526
+ var trow = row;
2527
+
2528
+
2529
+ var methods = {
2530
+ 'for_each/above': function() {
2531
+ while (trow--) {
2532
+ if (trow > 0 && this.is_widget(col, trow) &&
2533
+ $.inArray(ga[col][trow], matched) === -1
2534
+ ) {
2535
+ cr = callback.call(ga[col][trow], col, trow);
2536
+ matched.push(ga[col][trow]);
2537
+ if (cr) { break; }
2538
+ }
2539
+ }
2540
+ },
2541
+ 'for_each/below': function() {
2542
+ for (trow = row + 1, max = ga[col].length; trow < max; trow++) {
2543
+ if (this.is_widget(col, trow) &&
2544
+ $.inArray(ga[col][trow], matched) === -1
2545
+ ) {
2546
+ cr = callback.call(ga[col][trow], col, trow);
2547
+ matched.push(ga[col][trow]);
2548
+ if (cr) { break; }
2549
+ }
2550
+ }
2551
+ }
2552
+ };
2553
+
2554
+ if (methods[action]) {
2555
+ methods[action].call(this);
2556
+ }
2557
+ };
2558
+
2559
+
2560
+ /**
2561
+ * Iterate over each widget above the column and row specified.
2562
+ *
2563
+ * @method for_each_widget_above
2564
+ * @param {Number} col The column to start iterating.
2565
+ * @param {Number} row The row to start iterating.
2566
+ * @param {Function} callback The function to execute on each widget
2567
+ * iteration. The value of `this` inside the function is the jQuery
2568
+ * wrapped HTMLElement.
2569
+ * @return {Class} Returns the instance of the Gridster Class.
2570
+ */
2571
+ fn.for_each_widget_above = function(col, row, callback) {
2572
+ this._traversing_widgets('for_each', 'above', col, row, callback);
2573
+ return this;
2574
+ };
2575
+
2576
+
2577
+ /**
2578
+ * Iterate over each widget below the column and row specified.
2579
+ *
2580
+ * @method for_each_widget_below
2581
+ * @param {Number} col The column to start iterating.
2582
+ * @param {Number} row The row to start iterating.
2583
+ * @param {Function} callback The function to execute on each widget
2584
+ * iteration. The value of `this` inside the function is the jQuery wrapped
2585
+ * HTMLElement.
2586
+ * @return {Class} Returns the instance of the Gridster Class.
2587
+ */
2588
+ fn.for_each_widget_below = function(col, row, callback) {
2589
+ this._traversing_widgets('for_each', 'below', col, row, callback);
2590
+ return this;
2591
+ };
2592
+
2593
+
2594
+ /**
2595
+ * Returns the highest occupied cell in the grid.
2596
+ *
2597
+ * @method get_highest_occupied_cell
2598
+ * @return {Object} Returns an object with `col` and `row` numbers.
2599
+ */
2600
+ fn.get_highest_occupied_cell = function() {
2601
+ var r;
2602
+ var gm = this.gridmap;
2603
+ var rows = [];
2604
+ var row_in_col = [];
2605
+ for (var c = gm.length - 1; c >= 1; c--) {
2606
+ for (r = gm[c].length - 1; r >= 1; r--) {
2607
+ if (this.is_widget(c, r)) {
2608
+ rows.push(r);
2609
+ row_in_col[r] = c;
2610
+ break;
2611
+ }
2612
+ }
2613
+ }
2614
+
2615
+ var highest_row = Math.max.apply(Math, rows);
2616
+
2617
+ this.highest_occupied_cell = {
2618
+ col: row_in_col[highest_row],
2619
+ row: highest_row
2620
+ };
2621
+
2622
+ return this.highest_occupied_cell;
2623
+ };
2624
+
2625
+
2626
+ fn.get_widgets_from = function(col, row) {
2627
+ var ga = this.gridmap;
2628
+ var $widgets = $();
2629
+
2630
+ if (col) {
2631
+ $widgets = $widgets.add(
2632
+ this.$widgets.filter(function() {
2633
+ var tcol = $(this).attr('data-col');
2634
+ return (tcol === col || tcol > col);
2635
+ })
2636
+ );
2637
+ }
2638
+
2639
+ if (row) {
2640
+ $widgets = $widgets.add(
2641
+ this.$widgets.filter(function() {
2642
+ var trow = $(this).attr('data-row');
2643
+ return (trow === row || trow > row);
2644
+ })
2645
+ );
2646
+ }
2647
+
2648
+ return $widgets;
2649
+ }
2650
+
2651
+
2652
+ /**
2653
+ * Set the current height of the parent grid.
2654
+ *
2655
+ * @method set_dom_grid_height
2656
+ * @return {Object} Returns the instance of the Gridster class.
2657
+ */
2658
+ fn.set_dom_grid_height = function() {
2659
+ var r = this.get_highest_occupied_cell().row;
2660
+ this.$el.css('height', r * this.min_widget_height);
2661
+ return this;
2662
+ };
2663
+
2664
+
2665
+ /**
2666
+ * It generates the neccessary styles to position the widgets.
2667
+ *
2668
+ * @method generate_stylesheet
2669
+ * @param {Number} rows Number of columns.
2670
+ * @param {Number} cols Number of rows.
2671
+ * @return {Object} Returns the instance of the Gridster class.
2672
+ */
2673
+ fn.generate_stylesheet = function(opts) {
2674
+ var styles = '';
2675
+ var extra_cells = 10;
2676
+ var max_size_y = this.options.max_size_y;
2677
+ var max_size_x = this.options.max_size_x;
2678
+ var i;
2679
+ var rules;
2680
+
2681
+ opts || (opts = {});
2682
+ opts.cols || (opts.cols = this.cols);
2683
+ opts.rows || (opts.rows = this.rows);
2684
+ opts.namespace || (opts.namespace = '');
2685
+ opts.widget_base_dimensions ||
2686
+ (opts.widget_base_dimensions = this.options.widget_base_dimensions);
2687
+ opts.widget_margins ||
2688
+ (opts.widget_margins = this.options.widget_margins);
2689
+ opts.min_widget_width = (opts.widget_margins[0] * 2) +
2690
+ opts.widget_base_dimensions[0];
2691
+ opts.min_widget_height = (opts.widget_margins[1] * 2) +
2692
+ opts.widget_base_dimensions[1];
2693
+
2694
+ var serialized_opts = $.param(opts);
2695
+ // don't duplicate stylesheets for the same configuration
2696
+ if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) {
2697
+ return false;
2698
+ }
2699
+
2700
+ Gridster.generated_stylesheets.push(serialized_opts);
2701
+
2702
+ /* generate CSS styles for cols */
2703
+ for (i = opts.cols + extra_cells; i >= 0; i--) {
2704
+ styles += (opts.namespace + ' [data-col="'+ (i + 1) + '"] { left:' +
2705
+ ((i * opts.widget_base_dimensions[0]) +
2706
+ (i * opts.widget_margins[0]) +
2707
+ ((i + 1) * opts.widget_margins[0])) + 'px;} ');
2708
+ }
2709
+
2710
+ /* generate CSS styles for rows */
2711
+ for (i = opts.rows + extra_cells; i >= 0; i--) {
2712
+ styles += (opts.namespace + ' [data-row="' + (i + 1) + '"] { top:' +
2713
+ ((i * opts.widget_base_dimensions[1]) +
2714
+ (i * opts.widget_margins[1]) +
2715
+ ((i + 1) * opts.widget_margins[1]) ) + 'px;} ');
2716
+ }
2717
+
2718
+ for (var y = 1; y <= max_size_y; y++) {
2719
+ styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' +
2720
+ (y * opts.widget_base_dimensions[1] +
2721
+ (y - 1) * (opts.widget_margins[1] * 2)) + 'px;}');
2722
+ }
2723
+
2724
+ for (var x = 1; x <= max_size_x; x++) {
2725
+ styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' +
2726
+ (x * opts.widget_base_dimensions[0] +
2727
+ (x - 1) * (opts.widget_margins[0] * 2)) + 'px;}');
2728
+ }
2729
+
2730
+ return this.add_style_tag(styles);
2731
+ };
2732
+
2733
+
2734
+ /**
2735
+ * Injects the given CSS as string to the head of the document.
2736
+ *
2737
+ * @method add_style_tag
2738
+ * @param {String} css The styles to apply.
2739
+ * @return {Object} Returns the instance of the Gridster class.
2740
+ */
2741
+ fn.add_style_tag = function(css) {
2742
+ var d = document;
2743
+ var tag = d.createElement('style');
2744
+
2745
+ d.getElementsByTagName('head')[0].appendChild(tag);
2746
+ tag.setAttribute('type', 'text/css');
2747
+
2748
+ if (tag.styleSheet) {
2749
+ tag.styleSheet.cssText = css;
2750
+ }else{
2751
+ tag.appendChild(document.createTextNode(css));
2752
+ }
2753
+ return this;
2754
+ };
2755
+
2756
+
2757
+ /**
2758
+ * Generates a faux grid to collide with it when a widget is dragged and
2759
+ * detect row or column that we want to go.
2760
+ *
2761
+ * @method generate_faux_grid
2762
+ * @param {Number} rows Number of columns.
2763
+ * @param {Number} cols Number of rows.
2764
+ * @return {Object} Returns the instance of the Gridster class.
2765
+ */
2766
+ fn.generate_faux_grid = function(rows, cols) {
2767
+ this.faux_grid = [];
2768
+ this.gridmap = [];
2769
+ var col;
2770
+ var row;
2771
+ for (col = cols; col > 0; col--) {
2772
+ this.gridmap[col] = [];
2773
+ for (row = rows; row > 0; row--) {
2774
+ var coords = $({
2775
+ left: this.baseX + ((col - 1) * this.min_widget_width),
2776
+ top: this.baseY + (row -1) * this.min_widget_height,
2777
+ width: this.min_widget_width,
2778
+ height: this.min_widget_height,
2779
+ col: col,
2780
+ row: row,
2781
+ original_col: col,
2782
+ original_row: row
2783
+ }).coords();
2784
+
2785
+ this.gridmap[col][row] = false;
2786
+ this.faux_grid.push(coords);
2787
+ }
2788
+ }
2789
+ return this;
2790
+ };
2791
+
2792
+
2793
+ /**
2794
+ * Recalculates the offsets for the faux grid. You need to use it when
2795
+ * the browser is resized.
2796
+ *
2797
+ * @method recalculate_faux_grid
2798
+ * @return {Object} Returns the instance of the Gridster class.
2799
+ */
2800
+ fn.recalculate_faux_grid = function() {
2801
+ var aw = this.$wrapper.width();
2802
+ this.baseX = ($(window).width() - aw) / 2;
2803
+ this.baseY = this.$wrapper.offset().top;
2804
+
2805
+ $.each(this.faux_grid, $.proxy(function(i, coords) {
2806
+ this.faux_grid[i] = coords.update({
2807
+ left: this.baseX + (coords.data.col -1) * this.min_widget_width,
2808
+ top: this.baseY + (coords.data.row -1) * this.min_widget_height
2809
+ });
2810
+
2811
+ }, this));
2812
+
2813
+ return this;
2814
+ };
2815
+
2816
+
2817
+ /**
2818
+ * Get all widgets in the DOM and register them.
2819
+ *
2820
+ * @method get_widgets_from_DOM
2821
+ * @return {Object} Returns the instance of the Gridster class.
2822
+ */
2823
+ fn.get_widgets_from_DOM = function() {
2824
+ this.$widgets.each($.proxy(function(i, widget) {
2825
+ this.register_widget($(widget));
2826
+ }, this));
2827
+ return this;
2828
+ };
2829
+
2830
+
2831
+ /**
2832
+ * Calculate columns and rows to be set based on the configuration
2833
+ * parameters, grid dimensions, etc ...
2834
+ *
2835
+ * @method generate_grid_and_stylesheet
2836
+ * @return {Object} Returns the instance of the Gridster class.
2837
+ */
2838
+ fn.generate_grid_and_stylesheet = function() {
2839
+ var aw = this.$wrapper.width();
2840
+ var ah = this.$wrapper.height();
2841
+
2842
+ var cols = Math.floor(aw / this.min_widget_width) +
2843
+ this.options.extra_cols;
2844
+ var rows = Math.floor(ah / this.min_widget_height) +
2845
+ this.options.extra_rows;
2846
+
2847
+ var actual_cols = this.$widgets.map(function() {
2848
+ return $(this).attr('data-col');
2849
+ });
2850
+ actual_cols = Array.prototype.slice.call(actual_cols, 0);
2851
+ //needed to pass tests with phantomjs
2852
+ actual_cols.length || (actual_cols = [0]);
2853
+
2854
+ var actual_rows = this.$widgets.map(function() {
2855
+ return $(this).attr('data-row');
2856
+ });
2857
+ actual_rows = Array.prototype.slice.call(actual_rows, 0);
2858
+ //needed to pass tests with phantomjs
2859
+ actual_rows.length || (actual_rows = [0]);
2860
+
2861
+ var min_cols = Math.max.apply(Math, actual_cols);
2862
+ var min_rows = Math.max.apply(Math, actual_rows);
2863
+
2864
+ this.cols = Math.max(min_cols, cols, this.options.min_cols);
2865
+ this.rows = Math.max(min_rows, rows, this.options.min_rows);
2866
+
2867
+ this.baseX = ($(window).width() - aw) / 2;
2868
+ this.baseY = this.$wrapper.offset().top;
2869
+
2870
+ if (this.options.autogenerate_stylesheet) {
2871
+ this.generate_stylesheet();
2872
+ }
2873
+
2874
+ /* more faux rows that needed are created so that there are cells
2875
+ * where drag beyond the limits */
2876
+ return this.generate_faux_grid(this.rows, this.cols);
2877
+ };
2878
+
2879
+
2880
+ //jQuery adapter
2881
+ $.fn.gridster = function(options) {
2882
+ return this.each(function() {
2883
+ if (!$(this).data('gridster')) {
2884
+ $(this).data('gridster', new Gridster( this, options ));
2885
+ }
2886
+ });
2887
+ };
2888
+
2889
+
2890
+ }(jQuery, window, document));