dashing-rails 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +161 -0
  3. data/Rakefile +6 -0
  4. data/app/assets/fonts/dashing/fontawesome-webfont.eot +0 -0
  5. data/app/assets/fonts/dashing/fontawesome-webfont.svg +255 -0
  6. data/app/assets/fonts/dashing/fontawesome-webfont.ttf +0 -0
  7. data/app/assets/fonts/dashing/fontawesome-webfont.woff +0 -0
  8. data/app/assets/javascripts/dashing/application.js +27 -0
  9. data/app/assets/javascripts/dashing/dashing-src.coffee +110 -0
  10. data/app/assets/javascripts/dashing/dashing.coffee +18 -0
  11. data/app/assets/stylesheets/dashing/application.css +17 -0
  12. data/app/assets/stylesheets/dashing/dashing.scss +250 -0
  13. data/app/controllers/dashing/application_controller.rb +15 -0
  14. data/app/controllers/dashing/dashboards_controller.rb +31 -0
  15. data/app/controllers/dashing/events_controller.rb +25 -0
  16. data/app/controllers/dashing/widgets_controller.rb +50 -0
  17. data/app/helpers/dashing/application_helper.rb +4 -0
  18. data/app/views/dashing/default_widgets/clock/clock.coffee +18 -0
  19. data/app/views/dashing/default_widgets/clock/clock.html +2 -0
  20. data/app/views/dashing/default_widgets/clock/clock.scss +13 -0
  21. data/app/views/dashing/default_widgets/comments/comments.coffee +24 -0
  22. data/app/views/dashing/default_widgets/comments/comments.html +7 -0
  23. data/app/views/dashing/default_widgets/comments/comments.scss +33 -0
  24. data/app/views/dashing/default_widgets/graph/graph.coffee +35 -0
  25. data/app/views/dashing/default_widgets/graph/graph.html +5 -0
  26. data/app/views/dashing/default_widgets/graph/graph.scss +65 -0
  27. data/app/views/dashing/default_widgets/iframe/iframe.coffee +9 -0
  28. data/app/views/dashing/default_widgets/iframe/iframe.html +1 -0
  29. data/app/views/dashing/default_widgets/iframe/iframe.scss +8 -0
  30. data/app/views/dashing/default_widgets/image/image.coffee +9 -0
  31. data/app/views/dashing/default_widgets/image/image.html +1 -0
  32. data/app/views/dashing/default_widgets/image/image.scss +13 -0
  33. data/app/views/dashing/default_widgets/index.css +12 -0
  34. data/app/views/dashing/default_widgets/index.js +13 -0
  35. data/app/views/dashing/default_widgets/list/list.coffee +6 -0
  36. data/app/views/dashing/default_widgets/list/list.html +18 -0
  37. data/app/views/dashing/default_widgets/list/list.scss +60 -0
  38. data/app/views/dashing/default_widgets/meter/meter.coffee +14 -0
  39. data/app/views/dashing/default_widgets/meter/meter.html +7 -0
  40. data/app/views/dashing/default_widgets/meter/meter.scss +35 -0
  41. data/app/views/dashing/default_widgets/number/number.coffee +24 -0
  42. data/app/views/dashing/default_widgets/number/number.html +11 -0
  43. data/app/views/dashing/default_widgets/number/number.scss +39 -0
  44. data/app/views/dashing/default_widgets/text/text.coffee +1 -0
  45. data/app/views/dashing/default_widgets/text/text.html +7 -0
  46. data/app/views/dashing/default_widgets/text/text.scss +32 -0
  47. data/app/views/layouts/dashing/dashboard.html.erb +30 -0
  48. data/config/routes.rb +15 -0
  49. data/lib/assets/javascripts/batman.jquery.js +163 -0
  50. data/lib/assets/javascripts/batman.js +13680 -0
  51. data/lib/assets/javascripts/d3.v2.min.js +4 -0
  52. data/lib/assets/javascripts/dashing.gridster.coffee +35 -0
  53. data/lib/assets/javascripts/es5-shim.js +1021 -0
  54. data/lib/assets/javascripts/jquery.gridster.js +2890 -0
  55. data/lib/assets/javascripts/jquery.js +4 -0
  56. data/lib/assets/javascripts/jquery.knob.js +646 -0
  57. data/lib/assets/javascripts/jquery.leanModal.min.js +5 -0
  58. data/lib/assets/javascripts/jquery.timeago.js +184 -0
  59. data/lib/assets/javascripts/moment.min.js +6 -0
  60. data/lib/assets/javascripts/rickshaw.min.js +2 -0
  61. data/lib/assets/stylesheets/font-awesome.css +303 -0
  62. data/lib/assets/stylesheets/jquery.gridster.css +57 -0
  63. data/lib/dashing.rb +29 -0
  64. data/lib/dashing/configuration.rb +26 -0
  65. data/lib/dashing/engine.rb +13 -0
  66. data/lib/dashing/version.rb +3 -0
  67. data/lib/generators/dashing/install_generator.rb +32 -0
  68. data/lib/generators/dashing/job_generator.rb +15 -0
  69. data/lib/generators/templates/dashboards/sample.html.erb +28 -0
  70. data/lib/generators/templates/initializer.rb +50 -0
  71. data/lib/generators/templates/jobs/new.rb +3 -0
  72. data/lib/generators/templates/jobs/sample.rb +9 -0
  73. data/lib/generators/templates/widgets/index.css +12 -0
  74. data/lib/generators/templates/widgets/index.js +13 -0
  75. data/lib/tasks/dashing_tasks.rake +4 -0
  76. data/spec/controllers/dashing/dashboards_controller_spec.rb +42 -0
  77. data/spec/controllers/dashing/events_controller_spec.rb +11 -0
  78. data/spec/controllers/dashing/widgets_controller_spec.rb +61 -0
  79. data/spec/dummy/README.rdoc +28 -0
  80. data/spec/dummy/Rakefile +6 -0
  81. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  82. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  83. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  84. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  85. data/spec/dummy/app/views/dashing/dashboards/foo.erb +0 -0
  86. data/spec/dummy/app/views/dashing/widgets/foo/foo.coffee +0 -0
  87. data/spec/dummy/app/views/dashing/widgets/foo/foo.html +0 -0
  88. data/spec/dummy/app/views/dashing/widgets/foo/foo.scss +0 -0
  89. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  90. data/spec/dummy/app/views/layouts/dashing/dashboard.html.erb +30 -0
  91. data/spec/dummy/bin/bundle +3 -0
  92. data/spec/dummy/bin/rails +4 -0
  93. data/spec/dummy/bin/rake +4 -0
  94. data/spec/dummy/config.ru +4 -0
  95. data/spec/dummy/config/application.rb +23 -0
  96. data/spec/dummy/config/boot.rb +5 -0
  97. data/spec/dummy/config/database.yml +25 -0
  98. data/spec/dummy/config/environment.rb +5 -0
  99. data/spec/dummy/config/environments/development.rb +29 -0
  100. data/spec/dummy/config/environments/production.rb +80 -0
  101. data/spec/dummy/config/environments/test.rb +36 -0
  102. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  103. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  104. data/spec/dummy/config/initializers/inflections.rb +16 -0
  105. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  106. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  107. data/spec/dummy/config/initializers/session_store.rb +3 -0
  108. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  109. data/spec/dummy/config/locales/en.yml +23 -0
  110. data/spec/dummy/config/routes.rb +4 -0
  111. data/spec/dummy/db/test.sqlite3 +0 -0
  112. data/spec/dummy/log/test.log +27 -0
  113. data/spec/dummy/public/404.html +58 -0
  114. data/spec/dummy/public/422.html +58 -0
  115. data/spec/dummy/public/500.html +57 -0
  116. data/spec/dummy/public/favicon.ico +0 -0
  117. data/spec/lib/dashing/configuration_spec.rb +43 -0
  118. data/spec/lib/dashing_spec.rb +41 -0
  119. data/spec/spec_helper.rb +44 -0
  120. metadata +342 -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));