my_dashboard 0.4.1

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 (148) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.travis.yml +9 -0
  4. data/CHANGELOG.md +23 -0
  5. data/Gemfile +20 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +223 -0
  8. data/Rakefile +7 -0
  9. data/app/assets/javascripts/my_dashboard/application.js +17 -0
  10. data/app/assets/javascripts/my_dashboard/my_dashboard.coffee +0 -0
  11. data/app/assets/stylesheets/my_dashboard/application.css +13 -0
  12. data/app/controllers/my_dashboard/application_controller.rb +23 -0
  13. data/app/controllers/my_dashboard/dashboards_controller.rb +31 -0
  14. data/app/controllers/my_dashboard/events_controller.rb +28 -0
  15. data/app/controllers/my_dashboard/widgets_controller.rb +45 -0
  16. data/app/helpers/my_dashboard/application_helper.rb +4 -0
  17. data/app/views/layouts/my_dashboard/dashboard.html.erb +30 -0
  18. data/app/views/my_dashboard/widgets/clock.html +2 -0
  19. data/app/views/my_dashboard/widgets/comments.html +7 -0
  20. data/app/views/my_dashboard/widgets/graph.html +5 -0
  21. data/app/views/my_dashboard/widgets/iframe.html +1 -0
  22. data/app/views/my_dashboard/widgets/image.html +1 -0
  23. data/app/views/my_dashboard/widgets/list.html +18 -0
  24. data/app/views/my_dashboard/widgets/meter.html +7 -0
  25. data/app/views/my_dashboard/widgets/number.html +11 -0
  26. data/app/views/my_dashboard/widgets/text.html +7 -0
  27. data/bin/rails +8 -0
  28. data/config/routes.rb +15 -0
  29. data/lib/assets/javascripts/my_dashboard.gridster.coffee +0 -0
  30. data/lib/generators/my_dashboard/install_generator.rb +36 -0
  31. data/lib/generators/my_dashboard/job_generator.rb +15 -0
  32. data/lib/generators/my_dashboard/widget_generator.rb +17 -0
  33. data/lib/generators/templates/dashboards/sample.html.erb +28 -0
  34. data/lib/generators/templates/initializer.rb +58 -0
  35. data/lib/generators/templates/jobs/new.rb +3 -0
  36. data/lib/generators/templates/jobs/sample.rb +9 -0
  37. data/lib/generators/templates/layouts/dashboard.html.erb +30 -0
  38. data/lib/generators/templates/widgets/index.css +12 -0
  39. data/lib/generators/templates/widgets/index.js +13 -0
  40. data/lib/generators/templates/widgets/new.coffee +9 -0
  41. data/lib/generators/templates/widgets/new.html +1 -0
  42. data/lib/generators/templates/widgets/new.scss +10 -0
  43. data/lib/my_dashboard.rb +50 -0
  44. data/lib/my_dashboard/configuration.rb +63 -0
  45. data/lib/my_dashboard/engine.rb +7 -0
  46. data/lib/my_dashboard/railtie.rb +31 -0
  47. data/lib/my_dashboard/version.rb +3 -0
  48. data/lib/tasks/my_dashboard_tasks.rake +4 -0
  49. data/my_dashboard.gemspec +31 -0
  50. data/spec/controllers/my_dashboard/application_controller_spec.rb +21 -0
  51. data/spec/controllers/my_dashboard/dashboards_controller_spec.rb +46 -0
  52. data/spec/controllers/my_dashboard/events_controller_spec.rb +11 -0
  53. data/spec/controllers/my_dashboard/widgets_controller_spec.rb +72 -0
  54. data/spec/dummy/README.rdoc +28 -0
  55. data/spec/dummy/Rakefile +6 -0
  56. data/spec/dummy/app/assets/images/.keep +0 -0
  57. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  58. data/spec/dummy/app/assets/javascripts/my_dashboard/widgets/foo.coffee +0 -0
  59. data/spec/dummy/app/assets/javascripts/my_dashboard/widgets/index.js +13 -0
  60. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  61. data/spec/dummy/app/assets/stylesheets/my_dashboard/widgets/foo.scss +0 -0
  62. data/spec/dummy/app/assets/stylesheets/my_dashboard/widgets/index.css +12 -0
  63. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  64. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  65. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  66. data/spec/dummy/app/jobs/sample.rb +9 -0
  67. data/spec/dummy/app/mailers/.keep +0 -0
  68. data/spec/dummy/app/models/.keep +0 -0
  69. data/spec/dummy/app/models/concerns/.keep +0 -0
  70. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  71. data/spec/dummy/app/views/layouts/my_dashboard/dashboard.html.erb +30 -0
  72. data/spec/dummy/app/views/my_dashboard/dashboards/foo.erb +0 -0
  73. data/spec/dummy/app/views/my_dashboard/dashboards/sample.html.erb +28 -0
  74. data/spec/dummy/app/views/my_dashboard/widgets/foo.html +0 -0
  75. data/spec/dummy/bin/bundle +3 -0
  76. data/spec/dummy/bin/rails +4 -0
  77. data/spec/dummy/bin/rake +4 -0
  78. data/spec/dummy/config.ru +4 -0
  79. data/spec/dummy/config/application.rb +23 -0
  80. data/spec/dummy/config/boot.rb +5 -0
  81. data/spec/dummy/config/database.yml +25 -0
  82. data/spec/dummy/config/environment.rb +5 -0
  83. data/spec/dummy/config/environments/development.rb +29 -0
  84. data/spec/dummy/config/environments/production.rb +80 -0
  85. data/spec/dummy/config/environments/test.rb +36 -0
  86. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  87. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  88. data/spec/dummy/config/initializers/inflections.rb +16 -0
  89. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  90. data/spec/dummy/config/initializers/my_dashboard.rb +58 -0
  91. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  92. data/spec/dummy/config/initializers/session_store.rb +3 -0
  93. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  94. data/spec/dummy/config/locales/en.yml +23 -0
  95. data/spec/dummy/config/routes.rb +3 -0
  96. data/spec/dummy/lib/assets/.keep +0 -0
  97. data/spec/dummy/log/.keep +0 -0
  98. data/spec/dummy/public/404.html +58 -0
  99. data/spec/dummy/public/422.html +58 -0
  100. data/spec/dummy/public/500.html +57 -0
  101. data/spec/dummy/public/favicon.ico +0 -0
  102. data/spec/lib/generators/widget_generator_spec.rb +26 -0
  103. data/spec/lib/my_dashboard/configuration_spec.rb +66 -0
  104. data/spec/lib/my_dashboard_spec.rb +55 -0
  105. data/spec/spec_helper.rb +52 -0
  106. data/spec/support/controller_spec_helpers.rb +9 -0
  107. data/vendor/assets/fonts/my_dashboard/fontawesome-webfont.eot +0 -0
  108. data/vendor/assets/fonts/my_dashboard/fontawesome-webfont.svg +414 -0
  109. data/vendor/assets/fonts/my_dashboard/fontawesome-webfont.ttf +0 -0
  110. data/vendor/assets/fonts/my_dashboard/fontawesome-webfont.woff +0 -0
  111. data/vendor/assets/javascripts/my_dashboard/batman.jquery.js +163 -0
  112. data/vendor/assets/javascripts/my_dashboard/batman.js +13680 -0
  113. data/vendor/assets/javascripts/my_dashboard/d3-3.2.8.min.js +5 -0
  114. data/vendor/assets/javascripts/my_dashboard/default_widgets/clock.coffee +18 -0
  115. data/vendor/assets/javascripts/my_dashboard/default_widgets/comments.coffee +24 -0
  116. data/vendor/assets/javascripts/my_dashboard/default_widgets/graph.coffee +37 -0
  117. data/vendor/assets/javascripts/my_dashboard/default_widgets/iframe.coffee +9 -0
  118. data/vendor/assets/javascripts/my_dashboard/default_widgets/image.coffee +9 -0
  119. data/vendor/assets/javascripts/my_dashboard/default_widgets/index.js +13 -0
  120. data/vendor/assets/javascripts/my_dashboard/default_widgets/list.coffee +6 -0
  121. data/vendor/assets/javascripts/my_dashboard/default_widgets/meter.coffee +14 -0
  122. data/vendor/assets/javascripts/my_dashboard/default_widgets/number.coffee +25 -0
  123. data/vendor/assets/javascripts/my_dashboard/default_widgets/text.coffee +1 -0
  124. data/vendor/assets/javascripts/my_dashboard/es5-shim.js +1021 -0
  125. data/vendor/assets/javascripts/my_dashboard/index.js +21 -0
  126. data/vendor/assets/javascripts/my_dashboard/jquery.gridster.js +3987 -0
  127. data/vendor/assets/javascripts/my_dashboard/jquery.js +4 -0
  128. data/vendor/assets/javascripts/my_dashboard/jquery.knob.js +646 -0
  129. data/vendor/assets/javascripts/my_dashboard/jquery.leanModal.min.js +5 -0
  130. data/vendor/assets/javascripts/my_dashboard/jquery.timeago.js +184 -0
  131. data/vendor/assets/javascripts/my_dashboard/moment.min.js +6 -0
  132. data/vendor/assets/javascripts/my_dashboard/my_dashboard.coffee +0 -0
  133. data/vendor/assets/javascripts/my_dashboard/rickshaw-1.5.1.min.js +3 -0
  134. data/vendor/assets/stylesheets/my_dashboard/font-awesome.scss +1338 -0
  135. data/vendor/assets/stylesheets/my_dashboard/index.css +15 -0
  136. data/vendor/assets/stylesheets/my_dashboard/jquery.gridster.css +121 -0
  137. data/vendor/assets/stylesheets/my_dashboard/my_dashboard.scss +302 -0
  138. data/vendor/assets/stylesheets/my_dashboard/widgets/clock.scss +13 -0
  139. data/vendor/assets/stylesheets/my_dashboard/widgets/comments.scss +33 -0
  140. data/vendor/assets/stylesheets/my_dashboard/widgets/graph.scss +65 -0
  141. data/vendor/assets/stylesheets/my_dashboard/widgets/iframe.scss +8 -0
  142. data/vendor/assets/stylesheets/my_dashboard/widgets/image.scss +13 -0
  143. data/vendor/assets/stylesheets/my_dashboard/widgets/index.css +12 -0
  144. data/vendor/assets/stylesheets/my_dashboard/widgets/list.scss +60 -0
  145. data/vendor/assets/stylesheets/my_dashboard/widgets/meter.scss +35 -0
  146. data/vendor/assets/stylesheets/my_dashboard/widgets/number.scss +39 -0
  147. data/vendor/assets/stylesheets/my_dashboard/widgets/text.scss +32 -0
  148. metadata +373 -0
@@ -0,0 +1,21 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
13
+ //= require jquery
14
+ //= require jquery.knob
15
+ //= require jquery.leanModal.min
16
+ //= require jquery.timeago
17
+ //= require moment.min
18
+ //= require d3-3.2.8.min
19
+ //= require rickshaw-1.5.1.min
20
+ //= require es5-shim
21
+ //= require my_dashboard
@@ -0,0 +1,3987 @@
1
+ /*! gridster.js - v0.5.6 - 2014-09-25
2
+ * http://gridster.net/
3
+ * Copyright (c) 2014 ducksboard; Licensed MIT */
4
+
5
+ ;(function(root, factory) {
6
+
7
+ if (typeof define === 'function' && define.amd) {
8
+ define('gridster-coords', ['jquery'], factory);
9
+ } else {
10
+ root.GridsterCoords = factory(root.$ || root.jQuery);
11
+ }
12
+
13
+ }(this, function($) {
14
+ /**
15
+ * Creates objects with coordinates (x1, y1, x2, y2, cx, cy, width, height)
16
+ * to simulate DOM elements on the screen.
17
+ * Coords is used by Gridster to create a faux grid with any DOM element can
18
+ * collide.
19
+ *
20
+ * @class Coords
21
+ * @param {HTMLElement|Object} obj The jQuery HTMLElement or a object with: left,
22
+ * top, width and height properties.
23
+ * @return {Object} Coords instance.
24
+ * @constructor
25
+ */
26
+ function Coords(obj) {
27
+ if (obj[0] && $.isPlainObject(obj[0])) {
28
+ this.data = obj[0];
29
+ }else {
30
+ this.el = obj;
31
+ }
32
+
33
+ this.isCoords = true;
34
+ this.coords = {};
35
+ this.init();
36
+ return this;
37
+ }
38
+
39
+
40
+ var fn = Coords.prototype;
41
+
42
+
43
+ fn.init = function(){
44
+ this.set();
45
+ this.original_coords = this.get();
46
+ };
47
+
48
+
49
+ fn.set = function(update, not_update_offsets) {
50
+ var el = this.el;
51
+
52
+ if (el && !update) {
53
+ this.data = el.offset();
54
+ this.data.width = el.width();
55
+ this.data.height = el.height();
56
+ }
57
+
58
+ if (el && update && !not_update_offsets) {
59
+ var offset = el.offset();
60
+ this.data.top = offset.top;
61
+ this.data.left = offset.left;
62
+ }
63
+
64
+ var d = this.data;
65
+
66
+ typeof d.left === 'undefined' && (d.left = d.x1);
67
+ typeof d.top === 'undefined' && (d.top = d.y1);
68
+
69
+ this.coords.x1 = d.left;
70
+ this.coords.y1 = d.top;
71
+ this.coords.x2 = d.left + d.width;
72
+ this.coords.y2 = d.top + d.height;
73
+ this.coords.cx = d.left + (d.width / 2);
74
+ this.coords.cy = d.top + (d.height / 2);
75
+ this.coords.width = d.width;
76
+ this.coords.height = d.height;
77
+ this.coords.el = el || false ;
78
+
79
+ return this;
80
+ };
81
+
82
+
83
+ fn.update = function(data){
84
+ if (!data && !this.el) {
85
+ return this;
86
+ }
87
+
88
+ if (data) {
89
+ var new_data = $.extend({}, this.data, data);
90
+ this.data = new_data;
91
+ return this.set(true, true);
92
+ }
93
+
94
+ this.set(true);
95
+ return this;
96
+ };
97
+
98
+
99
+ fn.get = function(){
100
+ return this.coords;
101
+ };
102
+
103
+ fn.destroy = function() {
104
+ this.el.removeData('coords');
105
+ delete this.el;
106
+ };
107
+
108
+ //jQuery adapter
109
+ $.fn.coords = function() {
110
+ if (this.data('coords') ) {
111
+ return this.data('coords');
112
+ }
113
+
114
+ var ins = new Coords(this, arguments[0]);
115
+ this.data('coords', ins);
116
+ return ins;
117
+ };
118
+
119
+ return Coords;
120
+
121
+ }));
122
+
123
+ ;(function(root, factory) {
124
+
125
+ if (typeof define === 'function' && define.amd) {
126
+ define('gridster-collision', ['jquery', 'gridster-coords'], factory);
127
+ } else {
128
+ root.GridsterCollision = factory(root.$ || root.jQuery,
129
+ root.GridsterCoords);
130
+ }
131
+
132
+ }(this, function($, Coords) {
133
+
134
+ var defaults = {
135
+ colliders_context: document.body,
136
+ overlapping_region: 'C'
137
+ // ,on_overlap: function(collider_data){},
138
+ // on_overlap_start : function(collider_data){},
139
+ // on_overlap_stop : function(collider_data){}
140
+ };
141
+
142
+
143
+ /**
144
+ * Detects collisions between a DOM element against other DOM elements or
145
+ * Coords objects.
146
+ *
147
+ * @class Collision
148
+ * @uses Coords
149
+ * @param {HTMLElement} el The jQuery wrapped HTMLElement.
150
+ * @param {HTMLElement|Array} colliders Can be a jQuery collection
151
+ * of HTMLElements or an Array of Coords instances.
152
+ * @param {Object} [options] An Object with all options you want to
153
+ * overwrite:
154
+ * @param {String} [options.overlapping_region] Determines when collision
155
+ * is valid, depending on the overlapped area. Values can be: 'N', 'S',
156
+ * 'W', 'E', 'C' or 'all'. Default is 'C'.
157
+ * @param {Function} [options.on_overlap_start] Executes a function the first
158
+ * time each `collider ` is overlapped.
159
+ * @param {Function} [options.on_overlap_stop] Executes a function when a
160
+ * `collider` is no longer collided.
161
+ * @param {Function} [options.on_overlap] Executes a function when the
162
+ * mouse is moved during the collision.
163
+ * @return {Object} Collision instance.
164
+ * @constructor
165
+ */
166
+ function Collision(el, colliders, options) {
167
+ this.options = $.extend(defaults, options);
168
+ this.$element = el;
169
+ this.last_colliders = [];
170
+ this.last_colliders_coords = [];
171
+ this.set_colliders(colliders);
172
+
173
+ this.init();
174
+ }
175
+
176
+ Collision.defaults = defaults;
177
+
178
+ var fn = Collision.prototype;
179
+
180
+
181
+ fn.init = function() {
182
+ this.find_collisions();
183
+ };
184
+
185
+
186
+ fn.overlaps = function(a, b) {
187
+ var x = false;
188
+ var y = false;
189
+
190
+ if ((b.x1 >= a.x1 && b.x1 <= a.x2) ||
191
+ (b.x2 >= a.x1 && b.x2 <= a.x2) ||
192
+ (a.x1 >= b.x1 && a.x2 <= b.x2)
193
+ ) { x = true; }
194
+
195
+ if ((b.y1 >= a.y1 && b.y1 <= a.y2) ||
196
+ (b.y2 >= a.y1 && b.y2 <= a.y2) ||
197
+ (a.y1 >= b.y1 && a.y2 <= b.y2)
198
+ ) { y = true; }
199
+
200
+ return (x && y);
201
+ };
202
+
203
+
204
+ fn.detect_overlapping_region = function(a, b){
205
+ var regionX = '';
206
+ var regionY = '';
207
+
208
+ if (a.y1 > b.cy && a.y1 < b.y2) { regionX = 'N'; }
209
+ if (a.y2 > b.y1 && a.y2 < b.cy) { regionX = 'S'; }
210
+ if (a.x1 > b.cx && a.x1 < b.x2) { regionY = 'W'; }
211
+ if (a.x2 > b.x1 && a.x2 < b.cx) { regionY = 'E'; }
212
+
213
+ return (regionX + regionY) || 'C';
214
+ };
215
+
216
+
217
+ fn.calculate_overlapped_area_coords = function(a, b){
218
+ var x1 = Math.max(a.x1, b.x1);
219
+ var y1 = Math.max(a.y1, b.y1);
220
+ var x2 = Math.min(a.x2, b.x2);
221
+ var y2 = Math.min(a.y2, b.y2);
222
+
223
+ return $({
224
+ left: x1,
225
+ top: y1,
226
+ width : (x2 - x1),
227
+ height: (y2 - y1)
228
+ }).coords().get();
229
+ };
230
+
231
+
232
+ fn.calculate_overlapped_area = function(coords){
233
+ return (coords.width * coords.height);
234
+ };
235
+
236
+
237
+ fn.manage_colliders_start_stop = function(new_colliders_coords, start_callback, stop_callback){
238
+ var last = this.last_colliders_coords;
239
+
240
+ for (var i = 0, il = last.length; i < il; i++) {
241
+ if ($.inArray(last[i], new_colliders_coords) === -1) {
242
+ start_callback.call(this, last[i]);
243
+ }
244
+ }
245
+
246
+ for (var j = 0, jl = new_colliders_coords.length; j < jl; j++) {
247
+ if ($.inArray(new_colliders_coords[j], last) === -1) {
248
+ stop_callback.call(this, new_colliders_coords[j]);
249
+ }
250
+
251
+ }
252
+ };
253
+
254
+
255
+ fn.find_collisions = function(player_data_coords){
256
+ var self = this;
257
+ var overlapping_region = this.options.overlapping_region;
258
+ var colliders_coords = [];
259
+ var colliders_data = [];
260
+ var $colliders = (this.colliders || this.$colliders);
261
+ var count = $colliders.length;
262
+ var player_coords = self.$element.coords()
263
+ .update(player_data_coords || false).get();
264
+
265
+ while(count--){
266
+ var $collider = self.$colliders ?
267
+ $($colliders[count]) : $colliders[count];
268
+ var $collider_coords_ins = ($collider.isCoords) ?
269
+ $collider : $collider.coords();
270
+ var collider_coords = $collider_coords_ins.get();
271
+ var overlaps = self.overlaps(player_coords, collider_coords);
272
+
273
+ if (!overlaps) {
274
+ continue;
275
+ }
276
+
277
+ var region = self.detect_overlapping_region(
278
+ player_coords, collider_coords);
279
+
280
+ //todo: make this an option
281
+ if (region === overlapping_region || overlapping_region === 'all') {
282
+
283
+ var area_coords = self.calculate_overlapped_area_coords(
284
+ player_coords, collider_coords);
285
+ var area = self.calculate_overlapped_area(area_coords);
286
+ var collider_data = {
287
+ area: area,
288
+ area_coords : area_coords,
289
+ region: region,
290
+ coords: collider_coords,
291
+ player_coords: player_coords,
292
+ el: $collider
293
+ };
294
+
295
+ if (self.options.on_overlap) {
296
+ self.options.on_overlap.call(this, collider_data);
297
+ }
298
+ colliders_coords.push($collider_coords_ins);
299
+ colliders_data.push(collider_data);
300
+ }
301
+ }
302
+
303
+ if (self.options.on_overlap_stop || self.options.on_overlap_start) {
304
+ this.manage_colliders_start_stop(colliders_coords,
305
+ self.options.on_overlap_start, self.options.on_overlap_stop);
306
+ }
307
+
308
+ this.last_colliders_coords = colliders_coords;
309
+
310
+ return colliders_data;
311
+ };
312
+
313
+
314
+ fn.get_closest_colliders = function(player_data_coords){
315
+ var colliders = this.find_collisions(player_data_coords);
316
+
317
+ colliders.sort(function(a, b) {
318
+ /* if colliders are being overlapped by the "C" (center) region,
319
+ * we have to set a lower index in the array to which they are placed
320
+ * above in the grid. */
321
+ if (a.region === 'C' && b.region === 'C') {
322
+ if (a.coords.y1 < b.coords.y1 || a.coords.x1 < b.coords.x1) {
323
+ return - 1;
324
+ }else{
325
+ return 1;
326
+ }
327
+ }
328
+
329
+ if (a.area < b.area) {
330
+ return 1;
331
+ }
332
+
333
+ return 1;
334
+ });
335
+ return colliders;
336
+ };
337
+
338
+
339
+ fn.set_colliders = function(colliders) {
340
+ if (typeof colliders === 'string' || colliders instanceof $) {
341
+ this.$colliders = $(colliders,
342
+ this.options.colliders_context).not(this.$element);
343
+ }else{
344
+ this.colliders = $(colliders);
345
+ }
346
+ };
347
+
348
+
349
+ //jQuery adapter
350
+ $.fn.collision = function(collider, options) {
351
+ return new Collision( this, collider, options );
352
+ };
353
+
354
+ return Collision;
355
+
356
+ }));
357
+
358
+ ;(function(window, undefined) {
359
+
360
+ /* Delay, debounce and throttle functions taken from underscore.js
361
+ *
362
+ * Copyright (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and
363
+ * Investigative Reporters & Editors
364
+ *
365
+ * Permission is hereby granted, free of charge, to any person
366
+ * obtaining a copy of this software and associated documentation
367
+ * files (the "Software"), to deal in the Software without
368
+ * restriction, including without limitation the rights to use,
369
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
370
+ * copies of the Software, and to permit persons to whom the
371
+ * Software is furnished to do so, subject to the following
372
+ * conditions:
373
+ *
374
+ * The above copyright notice and this permission notice shall be
375
+ * included in all copies or substantial portions of the Software.
376
+ *
377
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
378
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
379
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
380
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
381
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
382
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
383
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
384
+ * OTHER DEALINGS IN THE SOFTWARE.
385
+ */
386
+
387
+ window.delay = function(func, wait) {
388
+ var args = Array.prototype.slice.call(arguments, 2);
389
+ return setTimeout(function(){ return func.apply(null, args); }, wait);
390
+ };
391
+
392
+ window.debounce = function(func, wait, immediate) {
393
+ var timeout;
394
+ return function() {
395
+ var context = this, args = arguments;
396
+ var later = function() {
397
+ timeout = null;
398
+ if (!immediate) func.apply(context, args);
399
+ };
400
+ if (immediate && !timeout) func.apply(context, args);
401
+ clearTimeout(timeout);
402
+ timeout = setTimeout(later, wait);
403
+ };
404
+ };
405
+
406
+ window.throttle = function(func, wait) {
407
+ var context, args, timeout, throttling, more, result;
408
+ var whenDone = debounce(
409
+ function(){ more = throttling = false; }, wait);
410
+ return function() {
411
+ context = this; args = arguments;
412
+ var later = function() {
413
+ timeout = null;
414
+ if (more) func.apply(context, args);
415
+ whenDone();
416
+ };
417
+ if (!timeout) timeout = setTimeout(later, wait);
418
+ if (throttling) {
419
+ more = true;
420
+ } else {
421
+ result = func.apply(context, args);
422
+ }
423
+ whenDone();
424
+ throttling = true;
425
+ return result;
426
+ };
427
+ };
428
+
429
+ })(window);
430
+
431
+ ;(function(root, factory) {
432
+
433
+ if (typeof define === 'function' && define.amd) {
434
+ define('gridster-draggable', ['jquery'], factory);
435
+ } else {
436
+ root.GridsterDraggable = factory(root.$ || root.jQuery);
437
+ }
438
+
439
+ }(this, function($) {
440
+
441
+ var defaults = {
442
+ items: 'li',
443
+ distance: 1,
444
+ limit: true,
445
+ offset_left: 0,
446
+ autoscroll: true,
447
+ ignore_dragging: ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'], // or function
448
+ handle: null,
449
+ container_width: 0, // 0 == auto
450
+ move_element: true,
451
+ helper: false, // or 'clone'
452
+ remove_helper: true
453
+ // drag: function(e) {},
454
+ // start : function(e, ui) {},
455
+ // stop : function(e) {}
456
+ };
457
+
458
+ var $window = $(window);
459
+ var dir_map = { x : 'left', y : 'top' };
460
+ var isTouch = !!('ontouchstart' in window);
461
+
462
+ var capitalize = function(str) {
463
+ return str.charAt(0).toUpperCase() + str.slice(1);
464
+ };
465
+
466
+ var idCounter = 0;
467
+ var uniqId = function() {
468
+ return ++idCounter + '';
469
+ }
470
+
471
+ /**
472
+ * Basic drag implementation for DOM elements inside a container.
473
+ * Provide start/stop/drag callbacks.
474
+ *
475
+ * @class Draggable
476
+ * @param {HTMLElement} el The HTMLelement that contains all the widgets
477
+ * to be dragged.
478
+ * @param {Object} [options] An Object with all options you want to
479
+ * overwrite:
480
+ * @param {HTMLElement|String} [options.items] Define who will
481
+ * be the draggable items. Can be a CSS Selector String or a
482
+ * collection of HTMLElements.
483
+ * @param {Number} [options.distance] Distance in pixels after mousedown
484
+ * the mouse must move before dragging should start.
485
+ * @param {Boolean} [options.limit] Constrains dragging to the width of
486
+ * the container
487
+ * @param {Object|Function} [options.ignore_dragging] Array of node names
488
+ * that sould not trigger dragging, by default is `['INPUT', 'TEXTAREA',
489
+ * 'SELECT', 'BUTTON']`. If a function is used return true to ignore dragging.
490
+ * @param {offset_left} [options.offset_left] Offset added to the item
491
+ * that is being dragged.
492
+ * @param {Number} [options.drag] Executes a callback when the mouse is
493
+ * moved during the dragging.
494
+ * @param {Number} [options.start] Executes a callback when the drag
495
+ * starts.
496
+ * @param {Number} [options.stop] Executes a callback when the drag stops.
497
+ * @return {Object} Returns `el`.
498
+ * @constructor
499
+ */
500
+ function Draggable(el, options) {
501
+ this.options = $.extend({}, defaults, options);
502
+ this.$document = $(document);
503
+ this.$container = $(el);
504
+ this.$dragitems = $(this.options.items, this.$container);
505
+ this.is_dragging = false;
506
+ this.player_min_left = 0 + this.options.offset_left;
507
+ this.id = uniqId();
508
+ this.ns = '.gridster-draggable-' + this.id;
509
+ this.init();
510
+ }
511
+
512
+ Draggable.defaults = defaults;
513
+
514
+ var fn = Draggable.prototype;
515
+
516
+ fn.init = function() {
517
+ var pos = this.$container.css('position');
518
+ this.calculate_dimensions();
519
+ this.$container.css('position', pos === 'static' ? 'relative' : pos);
520
+ this.disabled = false;
521
+ this.events();
522
+
523
+ $(window).bind(this.nsEvent('resize'),
524
+ throttle($.proxy(this.calculate_dimensions, this), 200));
525
+ };
526
+
527
+ fn.nsEvent = function(ev) {
528
+ return (ev || '') + this.ns;
529
+ };
530
+
531
+ fn.events = function() {
532
+ this.pointer_events = {
533
+ start: this.nsEvent('touchstart') + ' ' + this.nsEvent('mousedown'),
534
+ move: this.nsEvent('touchmove') + ' ' + this.nsEvent('mousemove'),
535
+ end: this.nsEvent('touchend') + ' ' + this.nsEvent('mouseup'),
536
+ };
537
+
538
+ this.$container.on(this.nsEvent('selectstart'),
539
+ $.proxy(this.on_select_start, this));
540
+
541
+ this.$container.on(this.pointer_events.start, this.options.items,
542
+ $.proxy(this.drag_handler, this));
543
+
544
+ this.$document.on(this.pointer_events.end, $.proxy(function(e) {
545
+ this.is_dragging = false;
546
+ if (this.disabled) { return; }
547
+ this.$document.off(this.pointer_events.move);
548
+ if (this.drag_start) {
549
+ this.on_dragstop(e);
550
+ }
551
+ }, this));
552
+ };
553
+
554
+ fn.get_actual_pos = function($el) {
555
+ var pos = $el.position();
556
+ return pos;
557
+ };
558
+
559
+
560
+ fn.get_mouse_pos = function(e) {
561
+ if (e.originalEvent && e.originalEvent.touches) {
562
+ var oe = e.originalEvent;
563
+ e = oe.touches.length ? oe.touches[0] : oe.changedTouches[0];
564
+ }
565
+
566
+ return {
567
+ left: e.clientX,
568
+ top: e.clientY
569
+ };
570
+ };
571
+
572
+
573
+ fn.get_offset = function(e) {
574
+ e.preventDefault();
575
+ var mouse_actual_pos = this.get_mouse_pos(e);
576
+ var diff_x = Math.round(
577
+ mouse_actual_pos.left - this.mouse_init_pos.left);
578
+ var diff_y = Math.round(mouse_actual_pos.top - this.mouse_init_pos.top);
579
+
580
+ var left = Math.round(this.el_init_offset.left +
581
+ diff_x - this.baseX + $(window).scrollLeft() - this.win_offset_x);
582
+ var top = Math.round(this.el_init_offset.top +
583
+ diff_y - this.baseY + $(window).scrollTop() - this.win_offset_y);
584
+
585
+ if (this.options.limit) {
586
+ if (left > this.player_max_left) {
587
+ left = this.player_max_left;
588
+ } else if(left < this.player_min_left) {
589
+ left = this.player_min_left;
590
+ }
591
+ }
592
+
593
+ return {
594
+ position: {
595
+ left: left,
596
+ top: top
597
+ },
598
+ pointer: {
599
+ left: mouse_actual_pos.left,
600
+ top: mouse_actual_pos.top,
601
+ diff_left: diff_x + ($(window).scrollLeft() - this.win_offset_x),
602
+ diff_top: diff_y + ($(window).scrollTop() - this.win_offset_y)
603
+ }
604
+ };
605
+ };
606
+
607
+
608
+ fn.get_drag_data = function(e) {
609
+ var offset = this.get_offset(e);
610
+ offset.$player = this.$player;
611
+ offset.$helper = this.helper ? this.$helper : this.$player;
612
+
613
+ return offset;
614
+ };
615
+
616
+
617
+ fn.set_limits = function(container_width) {
618
+ container_width || (container_width = this.$container.width());
619
+ this.player_max_left = (container_width - this.player_width +
620
+ - this.options.offset_left);
621
+
622
+ this.options.container_width = container_width;
623
+
624
+ return this;
625
+ };
626
+
627
+
628
+ fn.scroll_in = function(axis, data) {
629
+ var dir_prop = dir_map[axis];
630
+
631
+ var area_size = 50;
632
+ var scroll_inc = 30;
633
+
634
+ var is_x = axis === 'x';
635
+ var window_size = is_x ? this.window_width : this.window_height;
636
+ var doc_size = is_x ? $(document).width() : $(document).height();
637
+ var player_size = is_x ? this.$player.width() : this.$player.height();
638
+
639
+ var next_scroll;
640
+ var scroll_offset = $window['scroll' + capitalize(dir_prop)]();
641
+ var min_window_pos = scroll_offset;
642
+ var max_window_pos = min_window_pos + window_size;
643
+
644
+ var mouse_next_zone = max_window_pos - area_size; // down/right
645
+ var mouse_prev_zone = min_window_pos + area_size; // up/left
646
+
647
+ var abs_mouse_pos = min_window_pos + data.pointer[dir_prop];
648
+
649
+ var max_player_pos = (doc_size - window_size + player_size);
650
+
651
+ if (abs_mouse_pos >= mouse_next_zone) {
652
+ next_scroll = scroll_offset + scroll_inc;
653
+ if (next_scroll < max_player_pos) {
654
+ $window['scroll' + capitalize(dir_prop)](next_scroll);
655
+ this['scroll_offset_' + axis] += scroll_inc;
656
+ }
657
+ }
658
+
659
+ if (abs_mouse_pos <= mouse_prev_zone) {
660
+ next_scroll = scroll_offset - scroll_inc;
661
+ if (next_scroll > 0) {
662
+ $window['scroll' + capitalize(dir_prop)](next_scroll);
663
+ this['scroll_offset_' + axis] -= scroll_inc;
664
+ }
665
+ }
666
+
667
+ return this;
668
+ };
669
+
670
+
671
+ fn.manage_scroll = function(data) {
672
+ this.scroll_in('x', data);
673
+ this.scroll_in('y', data);
674
+ };
675
+
676
+
677
+ fn.calculate_dimensions = function(e) {
678
+ this.window_height = $window.height();
679
+ this.window_width = $window.width();
680
+ };
681
+
682
+
683
+ fn.drag_handler = function(e) {
684
+ var node = e.target.nodeName;
685
+ // skip if drag is disabled, or click was not done with the mouse primary button
686
+ if (this.disabled || e.which !== 1 && !isTouch) {
687
+ return;
688
+ }
689
+
690
+ if (this.ignore_drag(e)) {
691
+ return;
692
+ }
693
+
694
+ var self = this;
695
+ var first = true;
696
+ this.$player = $(e.currentTarget);
697
+
698
+ this.el_init_pos = this.get_actual_pos(this.$player);
699
+ this.mouse_init_pos = this.get_mouse_pos(e);
700
+ this.offsetY = this.mouse_init_pos.top - this.el_init_pos.top;
701
+
702
+ this.$document.on(this.pointer_events.move, function(mme) {
703
+ var mouse_actual_pos = self.get_mouse_pos(mme);
704
+ var diff_x = Math.abs(
705
+ mouse_actual_pos.left - self.mouse_init_pos.left);
706
+ var diff_y = Math.abs(
707
+ mouse_actual_pos.top - self.mouse_init_pos.top);
708
+ if (!(diff_x > self.options.distance ||
709
+ diff_y > self.options.distance)
710
+ ) {
711
+ return false;
712
+ }
713
+
714
+ if (first) {
715
+ first = false;
716
+ self.on_dragstart.call(self, mme);
717
+ return false;
718
+ }
719
+
720
+ if (self.is_dragging === true) {
721
+ self.on_dragmove.call(self, mme);
722
+ }
723
+
724
+ return false;
725
+ });
726
+
727
+ if (!isTouch) { return false; }
728
+ };
729
+
730
+
731
+ fn.on_dragstart = function(e) {
732
+ e.preventDefault();
733
+
734
+ if (this.is_dragging) { return this; }
735
+
736
+ this.drag_start = this.is_dragging = true;
737
+ var offset = this.$container.offset();
738
+ this.baseX = Math.round(offset.left);
739
+ this.baseY = Math.round(offset.top);
740
+ this.initial_container_width = this.options.container_width || this.$container.width();
741
+
742
+ if (this.options.helper === 'clone') {
743
+ this.$helper = this.$player.clone()
744
+ .appendTo(this.$container).addClass('helper');
745
+ this.helper = true;
746
+ } else {
747
+ this.helper = false;
748
+ }
749
+
750
+ this.win_offset_y = $(window).scrollTop();
751
+ this.win_offset_x = $(window).scrollLeft();
752
+ this.scroll_offset_y = 0;
753
+ this.scroll_offset_x = 0;
754
+ this.el_init_offset = this.$player.offset();
755
+ this.player_width = this.$player.width();
756
+ this.player_height = this.$player.height();
757
+
758
+ this.set_limits(this.options.container_width);
759
+
760
+ if (this.options.start) {
761
+ this.options.start.call(this.$player, e, this.get_drag_data(e));
762
+ }
763
+ return false;
764
+ };
765
+
766
+
767
+ fn.on_dragmove = function(e) {
768
+ var data = this.get_drag_data(e);
769
+
770
+ this.options.autoscroll && this.manage_scroll(data);
771
+
772
+ if (this.options.move_element) {
773
+ (this.helper ? this.$helper : this.$player).css({
774
+ 'position': 'absolute',
775
+ 'left' : data.position.left,
776
+ 'top' : data.position.top
777
+ });
778
+ }
779
+
780
+ var last_position = this.last_position || data.position;
781
+ data.prev_position = last_position;
782
+
783
+ if (this.options.drag) {
784
+ this.options.drag.call(this.$player, e, data);
785
+ }
786
+
787
+ this.last_position = data.position;
788
+ return false;
789
+ };
790
+
791
+
792
+ fn.on_dragstop = function(e) {
793
+ var data = this.get_drag_data(e);
794
+ this.drag_start = false;
795
+
796
+ if (this.options.stop) {
797
+ this.options.stop.call(this.$player, e, data);
798
+ }
799
+
800
+ if (this.helper && this.options.remove_helper) {
801
+ this.$helper.remove();
802
+ }
803
+
804
+ return false;
805
+ };
806
+
807
+ fn.on_select_start = function(e) {
808
+ if (this.disabled) { return; }
809
+
810
+ if (this.ignore_drag(e)) {
811
+ return;
812
+ }
813
+
814
+ return false;
815
+ };
816
+
817
+ fn.enable = function() {
818
+ this.disabled = false;
819
+ };
820
+
821
+ fn.disable = function() {
822
+ this.disabled = true;
823
+ };
824
+
825
+ fn.destroy = function() {
826
+ this.disable();
827
+
828
+ this.$container.off(this.ns);
829
+ this.$document.off(this.ns);
830
+ $(window).off(this.ns);
831
+
832
+ $.removeData(this.$container, 'drag');
833
+ };
834
+
835
+ fn.ignore_drag = function(event) {
836
+ if (this.options.handle) {
837
+ return !$(event.target).is(this.options.handle);
838
+ }
839
+
840
+ if ($.isFunction(this.options.ignore_dragging)) {
841
+ return this.options.ignore_dragging(event);
842
+ }
843
+
844
+ return $(event.target).is(this.options.ignore_dragging.join(', '));
845
+ };
846
+
847
+ //jQuery adapter
848
+ $.fn.drag = function ( options ) {
849
+ return new Draggable(this, options);
850
+ };
851
+
852
+ return Draggable;
853
+
854
+ }));
855
+
856
+ ;(function(root, factory) {
857
+
858
+ if (typeof define === 'function' && define.amd) {
859
+ define(['jquery', 'gridster-draggable', 'gridster-collision'], factory);
860
+ } else {
861
+ root.Gridster = factory(root.$ || root.jQuery, root.GridsterDraggable,
862
+ root.GridsterCollision);
863
+ }
864
+
865
+ }(this, function($, Draggable, Collision) {
866
+
867
+ var defaults = {
868
+ namespace: '',
869
+ widget_selector: 'li',
870
+ widget_margins: [10, 10],
871
+ widget_base_dimensions: [400, 225],
872
+ extra_rows: 0,
873
+ extra_cols: 0,
874
+ min_cols: 1,
875
+ max_cols: Infinity,
876
+ min_rows: 15,
877
+ max_size_x: false,
878
+ autogrow_cols: false,
879
+ autogenerate_stylesheet: true,
880
+ avoid_overlapped_widgets: true,
881
+ auto_init: true,
882
+ serialize_params: function($w, wgd) {
883
+ return {
884
+ col: wgd.col,
885
+ row: wgd.row,
886
+ size_x: wgd.size_x,
887
+ size_y: wgd.size_y
888
+ };
889
+ },
890
+ collision: {},
891
+ draggable: {
892
+ items: '.gs-w',
893
+ distance: 4,
894
+ ignore_dragging: Draggable.defaults.ignore_dragging.slice(0)
895
+ },
896
+ resize: {
897
+ enabled: false,
898
+ axes: ['both'],
899
+ handle_append_to: '',
900
+ handle_class: 'gs-resize-handle',
901
+ max_size: [Infinity, Infinity],
902
+ min_size: [1, 1]
903
+ }
904
+ };
905
+
906
+ /**
907
+ * @class Gridster
908
+ * @uses Draggable
909
+ * @uses Collision
910
+ * @param {HTMLElement} el The HTMLelement that contains all the widgets.
911
+ * @param {Object} [options] An Object with all options you want to
912
+ * overwrite:
913
+ * @param {HTMLElement|String} [options.widget_selector] Define who will
914
+ * be the draggable widgets. Can be a CSS Selector String or a
915
+ * collection of HTMLElements
916
+ * @param {Array} [options.widget_margins] Margin between widgets.
917
+ * The first index for the horizontal margin (left, right) and
918
+ * the second for the vertical margin (top, bottom).
919
+ * @param {Array} [options.widget_base_dimensions] Base widget dimensions
920
+ * in pixels. The first index for the width and the second for the
921
+ * height.
922
+ * @param {Number} [options.extra_cols] Add more columns in addition to
923
+ * those that have been calculated.
924
+ * @param {Number} [options.extra_rows] Add more rows in addition to
925
+ * those that have been calculated.
926
+ * @param {Number} [options.min_cols] The minimum required columns.
927
+ * @param {Number} [options.max_cols] The maximum columns possible (set to null
928
+ * for no maximum).
929
+ * @param {Number} [options.min_rows] The minimum required rows.
930
+ * @param {Number} [options.max_size_x] The maximum number of columns
931
+ * that a widget can span.
932
+ * @param {Boolean} [options.autogenerate_stylesheet] If true, all the
933
+ * CSS required to position all widgets in their respective columns
934
+ * and rows will be generated automatically and injected to the
935
+ * `<head>` of the document. You can set this to false, and write
936
+ * your own CSS targeting rows and cols via data-attributes like so:
937
+ * `[data-col="1"] { left: 10px; }`
938
+ * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded
939
+ * from the DOM can be overlapped. It is helpful if the positions were
940
+ * bad stored in the database or if there was any conflict.
941
+ * @param {Boolean} [options.auto_init] Automatically call gridster init
942
+ * method or not when the plugin is instantiated.
943
+ * @param {Function} [options.serialize_params] Return the data you want
944
+ * for each widget in the serialization. Two arguments are passed:
945
+ * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid
946
+ * coords object (`col`, `row`, `size_x`, `size_y`).
947
+ * @param {Object} [options.collision] An Object with all options for
948
+ * Collision class you want to overwrite. See Collision docs for
949
+ * more info.
950
+ * @param {Object} [options.draggable] An Object with all options for
951
+ * Draggable class you want to overwrite. See Draggable docs for more
952
+ * info.
953
+ * @param {Object|Function} [options.draggable.ignore_dragging] Note that
954
+ * if you use a Function, and resize is enabled, you should ignore the
955
+ * resize handlers manually (options.resize.handle_class).
956
+ * @param {Object} [options.resize] An Object with resize config options.
957
+ * @param {Boolean} [options.resize.enabled] Set to true to enable
958
+ * resizing.
959
+ * @param {Array} [options.resize.axes] Axes in which widgets can be
960
+ * resized. Possible values: ['x', 'y', 'both'].
961
+ * @param {String} [options.resize.handle_append_to] Set a valid CSS
962
+ * selector to append resize handles to.
963
+ * @param {String} [options.resize.handle_class] CSS class name used
964
+ * by resize handles.
965
+ * @param {Array} [options.resize.max_size] Limit widget dimensions
966
+ * when resizing. Array values should be integers:
967
+ * `[max_cols_occupied, max_rows_occupied]`
968
+ * @param {Array} [options.resize.min_size] Limit widget dimensions
969
+ * when resizing. Array values should be integers:
970
+ * `[min_cols_occupied, min_rows_occupied]`
971
+ * @param {Function} [options.resize.start] Function executed
972
+ * when resizing starts.
973
+ * @param {Function} [otions.resize.resize] Function executed
974
+ * during the resizing.
975
+ * @param {Function} [options.resize.stop] Function executed
976
+ * when resizing stops.
977
+ *
978
+ * @constructor
979
+ */
980
+ function Gridster(el, options) {
981
+ this.options = $.extend(true, {}, defaults, options);
982
+ this.$el = $(el);
983
+ this.$wrapper = this.$el.parent();
984
+ this.$widgets = this.$el.children(
985
+ this.options.widget_selector).addClass('gs-w');
986
+ this.widgets = [];
987
+ this.$changed = $([]);
988
+ this.wrapper_width = this.$wrapper.width();
989
+ this.min_widget_width = (this.options.widget_margins[0] * 2) +
990
+ this.options.widget_base_dimensions[0];
991
+ this.min_widget_height = (this.options.widget_margins[1] * 2) +
992
+ this.options.widget_base_dimensions[1];
993
+
994
+ this.generated_stylesheets = [];
995
+ this.$style_tags = $([]);
996
+
997
+ this.options.auto_init && this.init();
998
+ }
999
+
1000
+ Gridster.defaults = defaults;
1001
+ Gridster.generated_stylesheets = [];
1002
+
1003
+
1004
+ /**
1005
+ * Sorts an Array of grid coords objects (representing the grid coords of
1006
+ * each widget) in ascending way.
1007
+ *
1008
+ * @method sort_by_row_asc
1009
+ * @param {Array} widgets Array of grid coords objects
1010
+ * @return {Array} Returns the array sorted.
1011
+ */
1012
+ Gridster.sort_by_row_asc = function(widgets) {
1013
+ widgets = widgets.sort(function(a, b) {
1014
+ if (!a.row) {
1015
+ a = $(a).coords().grid;
1016
+ b = $(b).coords().grid;
1017
+ }
1018
+
1019
+ if (a.row > b.row) {
1020
+ return 1;
1021
+ }
1022
+ return -1;
1023
+ });
1024
+
1025
+ return widgets;
1026
+ };
1027
+
1028
+
1029
+ /**
1030
+ * Sorts an Array of grid coords objects (representing the grid coords of
1031
+ * each widget) placing first the empty cells upper left.
1032
+ *
1033
+ * @method sort_by_row_and_col_asc
1034
+ * @param {Array} widgets Array of grid coords objects
1035
+ * @return {Array} Returns the array sorted.
1036
+ */
1037
+ Gridster.sort_by_row_and_col_asc = function(widgets) {
1038
+ widgets = widgets.sort(function(a, b) {
1039
+ if (a.row > b.row || a.row === b.row && a.col > b.col) {
1040
+ return 1;
1041
+ }
1042
+ return -1;
1043
+ });
1044
+
1045
+ return widgets;
1046
+ };
1047
+
1048
+
1049
+ /**
1050
+ * Sorts an Array of grid coords objects by column (representing the grid
1051
+ * coords of each widget) in ascending way.
1052
+ *
1053
+ * @method sort_by_col_asc
1054
+ * @param {Array} widgets Array of grid coords objects
1055
+ * @return {Array} Returns the array sorted.
1056
+ */
1057
+ Gridster.sort_by_col_asc = function(widgets) {
1058
+ widgets = widgets.sort(function(a, b) {
1059
+ if (a.col > b.col) {
1060
+ return 1;
1061
+ }
1062
+ return -1;
1063
+ });
1064
+
1065
+ return widgets;
1066
+ };
1067
+
1068
+
1069
+ /**
1070
+ * Sorts an Array of grid coords objects (representing the grid coords of
1071
+ * each widget) in descending way.
1072
+ *
1073
+ * @method sort_by_row_desc
1074
+ * @param {Array} widgets Array of grid coords objects
1075
+ * @return {Array} Returns the array sorted.
1076
+ */
1077
+ Gridster.sort_by_row_desc = function(widgets) {
1078
+ widgets = widgets.sort(function(a, b) {
1079
+ if (a.row + a.size_y < b.row + b.size_y) {
1080
+ return 1;
1081
+ }
1082
+ return -1;
1083
+ });
1084
+ return widgets;
1085
+ };
1086
+
1087
+
1088
+
1089
+ /** Instance Methods **/
1090
+
1091
+ var fn = Gridster.prototype;
1092
+
1093
+ fn.init = function() {
1094
+ this.options.resize.enabled && this.setup_resize();
1095
+ this.generate_grid_and_stylesheet();
1096
+ this.get_widgets_from_DOM();
1097
+ this.set_dom_grid_height();
1098
+ this.set_dom_grid_width();
1099
+ this.$wrapper.addClass('ready');
1100
+ this.draggable();
1101
+ this.options.resize.enabled && this.resizable();
1102
+
1103
+ $(window).bind('resize.gridster', throttle(
1104
+ $.proxy(this.recalculate_faux_grid, this), 200));
1105
+ };
1106
+
1107
+
1108
+ /**
1109
+ * Disables dragging.
1110
+ *
1111
+ * @method disable
1112
+ * @return {Class} Returns the instance of the Gridster Class.
1113
+ */
1114
+ fn.disable = function() {
1115
+ this.$wrapper.find('.player-revert').removeClass('player-revert');
1116
+ this.drag_api.disable();
1117
+ return this;
1118
+ };
1119
+
1120
+
1121
+ /**
1122
+ * Enables dragging.
1123
+ *
1124
+ * @method enable
1125
+ * @return {Class} Returns the instance of the Gridster Class.
1126
+ */
1127
+ fn.enable = function() {
1128
+ this.drag_api.enable();
1129
+ return this;
1130
+ };
1131
+
1132
+
1133
+
1134
+ /**
1135
+ * Disables drag-and-drop widget resizing.
1136
+ *
1137
+ * @method disable
1138
+ * @return {Class} Returns instance of gridster Class.
1139
+ */
1140
+ fn.disable_resize = function() {
1141
+ this.$el.addClass('gs-resize-disabled');
1142
+ this.resize_api.disable();
1143
+ return this;
1144
+ };
1145
+
1146
+
1147
+ /**
1148
+ * Enables drag-and-drop widget resizing.
1149
+ *
1150
+ * @method enable
1151
+ * @return {Class} Returns instance of gridster Class.
1152
+ */
1153
+ fn.enable_resize = function() {
1154
+ this.$el.removeClass('gs-resize-disabled');
1155
+ this.resize_api.enable();
1156
+ return this;
1157
+ };
1158
+
1159
+
1160
+ /**
1161
+ * Add a new widget to the grid.
1162
+ *
1163
+ * @method add_widget
1164
+ * @param {String|HTMLElement} html The string representing the HTML of the widget
1165
+ * or the HTMLElement.
1166
+ * @param {Number} [size_x] The nº of rows the widget occupies horizontally.
1167
+ * @param {Number} [size_y] The nº of columns the widget occupies vertically.
1168
+ * @param {Number} [col] The column the widget should start in.
1169
+ * @param {Number} [row] The row the widget should start in.
1170
+ * @param {Array} [max_size] max_size Maximun size (in units) for width and height.
1171
+ * @param {Array} [min_size] min_size Minimum size (in units) for width and height.
1172
+ * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing.
1173
+ * the widget that was just created.
1174
+ */
1175
+ fn.add_widget = function(html, size_x, size_y, col, row, max_size, min_size) {
1176
+ var pos;
1177
+ size_x || (size_x = 1);
1178
+ size_y || (size_y = 1);
1179
+
1180
+ if (!col & !row) {
1181
+ pos = this.next_position(size_x, size_y);
1182
+ } else {
1183
+ pos = {
1184
+ col: col,
1185
+ row: row,
1186
+ size_x: size_x,
1187
+ size_y: size_y
1188
+ };
1189
+
1190
+ this.empty_cells(col, row, size_x, size_y);
1191
+ }
1192
+
1193
+ var $w = $(html).attr({
1194
+ 'data-col': pos.col,
1195
+ 'data-row': pos.row,
1196
+ 'data-sizex' : size_x,
1197
+ 'data-sizey' : size_y
1198
+ }).addClass('gs-w').appendTo(this.$el).hide();
1199
+
1200
+ this.$widgets = this.$widgets.add($w);
1201
+
1202
+ this.register_widget($w);
1203
+
1204
+ this.add_faux_rows(pos.size_y);
1205
+ //this.add_faux_cols(pos.size_x);
1206
+
1207
+ if (max_size) {
1208
+ this.set_widget_max_size($w, max_size);
1209
+ }
1210
+
1211
+ if (min_size) {
1212
+ this.set_widget_min_size($w, min_size);
1213
+ }
1214
+
1215
+ this.set_dom_grid_width();
1216
+ this.set_dom_grid_height();
1217
+
1218
+ this.drag_api.set_limits(this.cols * this.min_widget_width);
1219
+
1220
+ return $w.fadeIn();
1221
+ };
1222
+
1223
+
1224
+ /**
1225
+ * Change widget size limits.
1226
+ *
1227
+ * @method set_widget_min_size
1228
+ * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement
1229
+ * representing the widget or an index representing the desired widget.
1230
+ * @param {Array} min_size Minimum size (in units) for width and height.
1231
+ * @return {HTMLElement} Returns instance of gridster Class.
1232
+ */
1233
+ fn.set_widget_min_size = function($widget, min_size) {
1234
+ $widget = typeof $widget === 'number' ?
1235
+ this.$widgets.eq($widget) : $widget;
1236
+
1237
+ if (!$widget.length) { return this; }
1238
+
1239
+ var wgd = $widget.data('coords').grid;
1240
+ wgd.min_size_x = min_size[0];
1241
+ wgd.min_size_y = min_size[1];
1242
+
1243
+ return this;
1244
+ };
1245
+
1246
+
1247
+ /**
1248
+ * Change widget size limits.
1249
+ *
1250
+ * @method set_widget_max_size
1251
+ * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement
1252
+ * representing the widget or an index representing the desired widget.
1253
+ * @param {Array} max_size Maximun size (in units) for width and height.
1254
+ * @return {HTMLElement} Returns instance of gridster Class.
1255
+ */
1256
+ fn.set_widget_max_size = function($widget, max_size) {
1257
+ $widget = typeof $widget === 'number' ?
1258
+ this.$widgets.eq($widget) : $widget;
1259
+
1260
+ if (!$widget.length) { return this; }
1261
+
1262
+ var wgd = $widget.data('coords').grid;
1263
+ wgd.max_size_x = max_size[0];
1264
+ wgd.max_size_y = max_size[1];
1265
+
1266
+ return this;
1267
+ };
1268
+
1269
+
1270
+ /**
1271
+ * Append the resize handle into a widget.
1272
+ *
1273
+ * @method add_resize_handle
1274
+ * @param {HTMLElement} $widget The jQuery wrapped HTMLElement
1275
+ * representing the widget.
1276
+ * @return {HTMLElement} Returns instance of gridster Class.
1277
+ */
1278
+ fn.add_resize_handle = function($w) {
1279
+ var append_to = this.options.resize.handle_append_to;
1280
+ $(this.resize_handle_tpl).appendTo( append_to ? $(append_to, $w) : $w);
1281
+
1282
+ return this;
1283
+ };
1284
+
1285
+
1286
+ /**
1287
+ * Change the size of a widget. Width is limited to the current grid width.
1288
+ *
1289
+ * @method resize_widget
1290
+ * @param {HTMLElement} $widget The jQuery wrapped HTMLElement
1291
+ * representing the widget.
1292
+ * @param {Number} size_x The number of columns that will occupy the widget.
1293
+ * By default <code>size_x</code> is limited to the space available from
1294
+ * the column where the widget begins, until the last column to the right.
1295
+ * @param {Number} size_y The number of rows that will occupy the widget.
1296
+ * @param {Function} [callback] Function executed when the widget is removed.
1297
+ * @return {HTMLElement} Returns $widget.
1298
+ */
1299
+ fn.resize_widget = function($widget, size_x, size_y, callback) {
1300
+ var wgd = $widget.coords().grid;
1301
+ var col = wgd.col;
1302
+ var max_cols = this.options.max_cols;
1303
+ var old_size_y = wgd.size_y;
1304
+ var old_col = wgd.col;
1305
+ var new_col = old_col;
1306
+
1307
+ size_x || (size_x = wgd.size_x);
1308
+ size_y || (size_y = wgd.size_y);
1309
+
1310
+ if (max_cols !== Infinity) {
1311
+ size_x = Math.min(size_x, max_cols - col + 1);
1312
+ }
1313
+
1314
+ if (size_y > old_size_y) {
1315
+ this.add_faux_rows(Math.max(size_y - old_size_y, 0));
1316
+ }
1317
+
1318
+ var player_rcol = (col + size_x - 1);
1319
+ if (player_rcol > this.cols) {
1320
+ this.add_faux_cols(player_rcol - this.cols);
1321
+ }
1322
+
1323
+ var new_grid_data = {
1324
+ col: new_col,
1325
+ row: wgd.row,
1326
+ size_x: size_x,
1327
+ size_y: size_y
1328
+ };
1329
+
1330
+ this.mutate_widget_in_gridmap($widget, wgd, new_grid_data);
1331
+
1332
+ this.set_dom_grid_height();
1333
+ this.set_dom_grid_width();
1334
+
1335
+ if (callback) {
1336
+ callback.call(this, new_grid_data.size_x, new_grid_data.size_y);
1337
+ }
1338
+
1339
+ return $widget;
1340
+ };
1341
+
1342
+
1343
+ /**
1344
+ * Mutate widget dimensions and position in the grid map.
1345
+ *
1346
+ * @method mutate_widget_in_gridmap
1347
+ * @param {HTMLElement} $widget The jQuery wrapped HTMLElement
1348
+ * representing the widget to mutate.
1349
+ * @param {Object} wgd Current widget grid data (col, row, size_x, size_y).
1350
+ * @param {Object} new_wgd New widget grid data.
1351
+ * @return {HTMLElement} Returns instance of gridster Class.
1352
+ */
1353
+ fn.mutate_widget_in_gridmap = function($widget, wgd, new_wgd) {
1354
+ var old_size_x = wgd.size_x;
1355
+ var old_size_y = wgd.size_y;
1356
+
1357
+ var old_cells_occupied = this.get_cells_occupied(wgd);
1358
+ var new_cells_occupied = this.get_cells_occupied(new_wgd);
1359
+
1360
+ var empty_cols = [];
1361
+ $.each(old_cells_occupied.cols, function(i, col) {
1362
+ if ($.inArray(col, new_cells_occupied.cols) === -1) {
1363
+ empty_cols.push(col);
1364
+ }
1365
+ });
1366
+
1367
+ var occupied_cols = [];
1368
+ $.each(new_cells_occupied.cols, function(i, col) {
1369
+ if ($.inArray(col, old_cells_occupied.cols) === -1) {
1370
+ occupied_cols.push(col);
1371
+ }
1372
+ });
1373
+
1374
+ var empty_rows = [];
1375
+ $.each(old_cells_occupied.rows, function(i, row) {
1376
+ if ($.inArray(row, new_cells_occupied.rows) === -1) {
1377
+ empty_rows.push(row);
1378
+ }
1379
+ });
1380
+
1381
+ var occupied_rows = [];
1382
+ $.each(new_cells_occupied.rows, function(i, row) {
1383
+ if ($.inArray(row, old_cells_occupied.rows) === -1) {
1384
+ occupied_rows.push(row);
1385
+ }
1386
+ });
1387
+
1388
+ this.remove_from_gridmap(wgd);
1389
+
1390
+ if (occupied_cols.length) {
1391
+ var cols_to_empty = [
1392
+ new_wgd.col, new_wgd.row, new_wgd.size_x, Math.min(old_size_y, new_wgd.size_y), $widget
1393
+ ];
1394
+ this.empty_cells.apply(this, cols_to_empty);
1395
+ }
1396
+
1397
+ if (occupied_rows.length) {
1398
+ var rows_to_empty = [new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget];
1399
+ this.empty_cells.apply(this, rows_to_empty);
1400
+ }
1401
+
1402
+ // not the same that wgd = new_wgd;
1403
+ wgd.col = new_wgd.col;
1404
+ wgd.row = new_wgd.row;
1405
+ wgd.size_x = new_wgd.size_x;
1406
+ wgd.size_y = new_wgd.size_y;
1407
+
1408
+ this.add_to_gridmap(new_wgd, $widget);
1409
+
1410
+ $widget.removeClass('player-revert');
1411
+
1412
+ //update coords instance attributes
1413
+ $widget.data('coords').update({
1414
+ width: (new_wgd.size_x * this.options.widget_base_dimensions[0] +
1415
+ ((new_wgd.size_x - 1) * this.options.widget_margins[0]) * 2),
1416
+ height: (new_wgd.size_y * this.options.widget_base_dimensions[1] +
1417
+ ((new_wgd.size_y - 1) * this.options.widget_margins[1]) * 2)
1418
+ });
1419
+
1420
+ $widget.attr({
1421
+ 'data-col': new_wgd.col,
1422
+ 'data-row': new_wgd.row,
1423
+ 'data-sizex': new_wgd.size_x,
1424
+ 'data-sizey': new_wgd.size_y
1425
+ });
1426
+
1427
+ if (empty_cols.length) {
1428
+ var cols_to_remove_holes = [
1429
+ empty_cols[0], new_wgd.row,
1430
+ empty_cols.length,
1431
+ Math.min(old_size_y, new_wgd.size_y),
1432
+ $widget
1433
+ ];
1434
+
1435
+ this.remove_empty_cells.apply(this, cols_to_remove_holes);
1436
+ }
1437
+
1438
+ if (empty_rows.length) {
1439
+ var rows_to_remove_holes = [
1440
+ new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget
1441
+ ];
1442
+ this.remove_empty_cells.apply(this, rows_to_remove_holes);
1443
+ }
1444
+
1445
+ this.move_widget_up($widget);
1446
+
1447
+ return this;
1448
+ };
1449
+
1450
+
1451
+ /**
1452
+ * Move down widgets in cells represented by the arguments col, row, size_x,
1453
+ * size_y
1454
+ *
1455
+ * @method empty_cells
1456
+ * @param {Number} col The column where the group of cells begin.
1457
+ * @param {Number} row The row where the group of cells begin.
1458
+ * @param {Number} size_x The number of columns that the group of cells
1459
+ * occupy.
1460
+ * @param {Number} size_y The number of rows that the group of cells
1461
+ * occupy.
1462
+ * @param {HTMLElement} $exclude Exclude widgets from being moved.
1463
+ * @return {Class} Returns the instance of the Gridster Class.
1464
+ */
1465
+ fn.empty_cells = function(col, row, size_x, size_y, $exclude) {
1466
+ var $nexts = this.widgets_below({
1467
+ col: col,
1468
+ row: row - size_y,
1469
+ size_x: size_x,
1470
+ size_y: size_y
1471
+ });
1472
+
1473
+ $nexts.not($exclude).each($.proxy(function(i, w) {
1474
+ var wgd = $(w).coords().grid;
1475
+ if ( !(wgd.row <= (row + size_y - 1))) { return; }
1476
+ var diff = (row + size_y) - wgd.row;
1477
+ this.move_widget_down($(w), diff);
1478
+ }, this));
1479
+
1480
+ this.set_dom_grid_height();
1481
+
1482
+ return this;
1483
+ };
1484
+
1485
+
1486
+ /**
1487
+ * Move up widgets below cells represented by the arguments col, row, size_x,
1488
+ * size_y.
1489
+ *
1490
+ * @method remove_empty_cells
1491
+ * @param {Number} col The column where the group of cells begin.
1492
+ * @param {Number} row The row where the group of cells begin.
1493
+ * @param {Number} size_x The number of columns that the group of cells
1494
+ * occupy.
1495
+ * @param {Number} size_y The number of rows that the group of cells
1496
+ * occupy.
1497
+ * @param {HTMLElement} exclude Exclude widgets from being moved.
1498
+ * @return {Class} Returns the instance of the Gridster Class.
1499
+ */
1500
+ fn.remove_empty_cells = function(col, row, size_x, size_y, exclude) {
1501
+ var $nexts = this.widgets_below({
1502
+ col: col,
1503
+ row: row,
1504
+ size_x: size_x,
1505
+ size_y: size_y
1506
+ });
1507
+
1508
+ $nexts.not(exclude).each($.proxy(function(i, widget) {
1509
+ this.move_widget_up( $(widget), size_y );
1510
+ }, this));
1511
+
1512
+ this.set_dom_grid_height();
1513
+
1514
+ return this;
1515
+ };
1516
+
1517
+
1518
+ /**
1519
+ * Get the most left column below to add a new widget.
1520
+ *
1521
+ * @method next_position
1522
+ * @param {Number} size_x The nº of rows the widget occupies horizontally.
1523
+ * @param {Number} size_y The nº of columns the widget occupies vertically.
1524
+ * @return {Object} Returns a grid coords object representing the future
1525
+ * widget coords.
1526
+ */
1527
+ fn.next_position = function(size_x, size_y) {
1528
+ size_x || (size_x = 1);
1529
+ size_y || (size_y = 1);
1530
+ var ga = this.gridmap;
1531
+ var cols_l = ga.length;
1532
+ var valid_pos = [];
1533
+ var rows_l;
1534
+
1535
+ for (var c = 1; c < cols_l; c++) {
1536
+ rows_l = ga[c].length;
1537
+ for (var r = 1; r <= rows_l; r++) {
1538
+ var can_move_to = this.can_move_to({
1539
+ size_x: size_x,
1540
+ size_y: size_y
1541
+ }, c, r);
1542
+
1543
+ if (can_move_to) {
1544
+ valid_pos.push({
1545
+ col: c,
1546
+ row: r,
1547
+ size_y: size_y,
1548
+ size_x: size_x
1549
+ });
1550
+ }
1551
+ }
1552
+ }
1553
+
1554
+ if (valid_pos.length) {
1555
+ return Gridster.sort_by_row_and_col_asc(valid_pos)[0];
1556
+ }
1557
+ return false;
1558
+ };
1559
+
1560
+
1561
+ /**
1562
+ * Remove a widget from the grid.
1563
+ *
1564
+ * @method remove_widget
1565
+ * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove.
1566
+ * @param {Boolean|Function} silent If true, widgets below the removed one
1567
+ * will not move up. If a Function is passed it will be used as callback.
1568
+ * @param {Function} callback Function executed when the widget is removed.
1569
+ * @return {Class} Returns the instance of the Gridster Class.
1570
+ */
1571
+ fn.remove_widget = function(el, silent, callback) {
1572
+ var $el = el instanceof $ ? el : $(el);
1573
+ var wgd = $el.coords().grid;
1574
+
1575
+ // if silent is a function assume it's a callback
1576
+ if ($.isFunction(silent)) {
1577
+ callback = silent;
1578
+ silent = false;
1579
+ }
1580
+
1581
+ this.cells_occupied_by_placeholder = {};
1582
+ this.$widgets = this.$widgets.not($el);
1583
+
1584
+ var $nexts = this.widgets_below($el);
1585
+
1586
+ this.remove_from_gridmap(wgd);
1587
+
1588
+ $el.fadeOut($.proxy(function() {
1589
+ $el.remove();
1590
+
1591
+ if (!silent) {
1592
+ $nexts.each($.proxy(function(i, widget) {
1593
+ this.move_widget_up( $(widget), wgd.size_y );
1594
+ }, this));
1595
+ }
1596
+
1597
+ this.set_dom_grid_height();
1598
+
1599
+ if (callback) {
1600
+ callback.call(this, el);
1601
+ }
1602
+ }, this));
1603
+
1604
+ return this;
1605
+ };
1606
+
1607
+
1608
+ /**
1609
+ * Remove all widgets from the grid.
1610
+ *
1611
+ * @method remove_all_widgets
1612
+ * @param {Function} callback Function executed for each widget removed.
1613
+ * @return {Class} Returns the instance of the Gridster Class.
1614
+ */
1615
+ fn.remove_all_widgets = function(callback) {
1616
+ this.$widgets.each($.proxy(function(i, el){
1617
+ this.remove_widget(el, true, callback);
1618
+ }, this));
1619
+
1620
+ return this;
1621
+ };
1622
+
1623
+
1624
+ /**
1625
+ * Returns a serialized array of the widgets in the grid.
1626
+ *
1627
+ * @method serialize
1628
+ * @param {HTMLElement} [$widgets] The collection of jQuery wrapped
1629
+ * HTMLElements you want to serialize. If no argument is passed all widgets
1630
+ * will be serialized.
1631
+ * @return {Array} Returns an Array of Objects with the data specified in
1632
+ * the serialize_params option.
1633
+ */
1634
+ fn.serialize = function($widgets) {
1635
+ $widgets || ($widgets = this.$widgets);
1636
+
1637
+ return $widgets.map($.proxy(function(i, widget) {
1638
+ var $w = $(widget);
1639
+ return this.options.serialize_params($w, $w.coords().grid);
1640
+ }, this)).get();
1641
+ };
1642
+
1643
+
1644
+ /**
1645
+ * Returns a serialized array of the widgets that have changed their
1646
+ * position.
1647
+ *
1648
+ * @method serialize_changed
1649
+ * @return {Array} Returns an Array of Objects with the data specified in
1650
+ * the serialize_params option.
1651
+ */
1652
+ fn.serialize_changed = function() {
1653
+ return this.serialize(this.$changed);
1654
+ };
1655
+
1656
+
1657
+ /**
1658
+ * Convert widgets from DOM elements to "widget grid data" Objects.
1659
+ *
1660
+ * @method dom_to_coords
1661
+ * @param {HTMLElement} $widget The widget to be converted.
1662
+ */
1663
+ fn.dom_to_coords = function($widget) {
1664
+ return {
1665
+ 'col': parseInt($widget.attr('data-col'), 10),
1666
+ 'row': parseInt($widget.attr('data-row'), 10),
1667
+ 'size_x': parseInt($widget.attr('data-sizex'), 10) || 1,
1668
+ 'size_y': parseInt($widget.attr('data-sizey'), 10) || 1,
1669
+ 'max_size_x': parseInt($widget.attr('data-max-sizex'), 10) || false,
1670
+ 'max_size_y': parseInt($widget.attr('data-max-sizey'), 10) || false,
1671
+ 'min_size_x': parseInt($widget.attr('data-min-sizex'), 10) || false,
1672
+ 'min_size_y': parseInt($widget.attr('data-min-sizey'), 10) || false,
1673
+ 'el': $widget
1674
+ };
1675
+ };
1676
+
1677
+
1678
+ /**
1679
+ * Creates the grid coords object representing the widget an add it to the
1680
+ * mapped array of positions.
1681
+ *
1682
+ * @method register_widget
1683
+ * @param {HTMLElement|Object} $el jQuery wrapped HTMLElement representing
1684
+ * the widget, or an "widget grid data" Object with (col, row, el ...).
1685
+ * @return {Boolean} Returns true if the widget final position is different
1686
+ * than the original.
1687
+ */
1688
+ fn.register_widget = function($el) {
1689
+ var isDOM = $el instanceof jQuery;
1690
+ var wgd = isDOM ? this.dom_to_coords($el) : $el;
1691
+ var posChanged = false;
1692
+ isDOM || ($el = wgd.el);
1693
+
1694
+ var empty_upper_row = this.can_go_widget_up(wgd);
1695
+ if (empty_upper_row) {
1696
+ wgd.row = empty_upper_row;
1697
+ $el.attr('data-row', empty_upper_row);
1698
+ this.$el.trigger('gridster:positionchanged', [wgd]);
1699
+ posChanged = true;
1700
+ }
1701
+
1702
+ if (this.options.avoid_overlapped_widgets &&
1703
+ !this.can_move_to(
1704
+ {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row)
1705
+ ) {
1706
+ $.extend(wgd, this.next_position(wgd.size_x, wgd.size_y));
1707
+ $el.attr({
1708
+ 'data-col': wgd.col,
1709
+ 'data-row': wgd.row,
1710
+ 'data-sizex': wgd.size_x,
1711
+ 'data-sizey': wgd.size_y
1712
+ });
1713
+ posChanged = true;
1714
+ }
1715
+
1716
+ // attach Coord object to player data-coord attribute
1717
+ $el.data('coords', $el.coords());
1718
+ // Extend Coord object with grid position info
1719
+ $el.data('coords').grid = wgd;
1720
+
1721
+ this.add_to_gridmap(wgd, $el);
1722
+
1723
+ this.options.resize.enabled && this.add_resize_handle($el);
1724
+
1725
+ return posChanged;
1726
+ };
1727
+
1728
+
1729
+ /**
1730
+ * Update in the mapped array of positions the value of cells represented by
1731
+ * the grid coords object passed in the `grid_data` param.
1732
+ *
1733
+ * @param {Object} grid_data The grid coords object representing the cells
1734
+ * to update in the mapped array.
1735
+ * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped
1736
+ * HTMLElement, depends if you want to delete an existing position or add
1737
+ * a new one.
1738
+ * @method update_widget_position
1739
+ * @return {Class} Returns the instance of the Gridster Class.
1740
+ */
1741
+ fn.update_widget_position = function(grid_data, value) {
1742
+ this.for_each_cell_occupied(grid_data, function(col, row) {
1743
+ if (!this.gridmap[col]) { return this; }
1744
+ this.gridmap[col][row] = value;
1745
+ });
1746
+ return this;
1747
+ };
1748
+
1749
+
1750
+ /**
1751
+ * Remove a widget from the mapped array of positions.
1752
+ *
1753
+ * @method remove_from_gridmap
1754
+ * @param {Object} grid_data The grid coords object representing the cells
1755
+ * to update in the mapped array.
1756
+ * @return {Class} Returns the instance of the Gridster Class.
1757
+ */
1758
+ fn.remove_from_gridmap = function(grid_data) {
1759
+ return this.update_widget_position(grid_data, false);
1760
+ };
1761
+
1762
+
1763
+ /**
1764
+ * Add a widget to the mapped array of positions.
1765
+ *
1766
+ * @method add_to_gridmap
1767
+ * @param {Object} grid_data The grid coords object representing the cells
1768
+ * to update in the mapped array.
1769
+ * @param {HTMLElement|Boolean} value The value to set in the specified
1770
+ * position .
1771
+ * @return {Class} Returns the instance of the Gridster Class.
1772
+ */
1773
+ fn.add_to_gridmap = function(grid_data, value) {
1774
+ this.update_widget_position(grid_data, value || grid_data.el);
1775
+
1776
+ if (grid_data.el) {
1777
+ var $widgets = this.widgets_below(grid_data.el);
1778
+ $widgets.each($.proxy(function(i, widget) {
1779
+ this.move_widget_up( $(widget));
1780
+ }, this));
1781
+ }
1782
+ };
1783
+
1784
+
1785
+ /**
1786
+ * Make widgets draggable.
1787
+ *
1788
+ * @uses Draggable
1789
+ * @method draggable
1790
+ * @return {Class} Returns the instance of the Gridster Class.
1791
+ */
1792
+ fn.draggable = function() {
1793
+ var self = this;
1794
+ var draggable_options = $.extend(true, {}, this.options.draggable, {
1795
+ offset_left: this.options.widget_margins[0],
1796
+ offset_top: this.options.widget_margins[1],
1797
+ container_width: this.cols * this.min_widget_width,
1798
+ limit: true,
1799
+ start: function(event, ui) {
1800
+ self.$widgets.filter('.player-revert')
1801
+ .removeClass('player-revert');
1802
+
1803
+ self.$player = $(this);
1804
+ self.$helper = $(ui.$helper);
1805
+
1806
+ self.helper = !self.$helper.is(self.$player);
1807
+
1808
+ self.on_start_drag.call(self, event, ui);
1809
+ self.$el.trigger('gridster:dragstart');
1810
+ },
1811
+ stop: function(event, ui) {
1812
+ self.on_stop_drag.call(self, event, ui);
1813
+ self.$el.trigger('gridster:dragstop');
1814
+ },
1815
+ drag: throttle(function(event, ui) {
1816
+ self.on_drag.call(self, event, ui);
1817
+ self.$el.trigger('gridster:drag');
1818
+ }, 60)
1819
+ });
1820
+
1821
+ this.drag_api = this.$el.drag(draggable_options);
1822
+ return this;
1823
+ };
1824
+
1825
+
1826
+ /**
1827
+ * Bind resize events to get resize working.
1828
+ *
1829
+ * @method resizable
1830
+ * @return {Class} Returns instance of gridster Class.
1831
+ */
1832
+ fn.resizable = function() {
1833
+ this.resize_api = this.$el.drag({
1834
+ items: '.' + this.options.resize.handle_class,
1835
+ offset_left: this.options.widget_margins[0],
1836
+ container_width: this.container_width,
1837
+ move_element: false,
1838
+ resize: true,
1839
+ limit: this.options.autogrow_cols ? false : true,
1840
+ start: $.proxy(this.on_start_resize, this),
1841
+ stop: $.proxy(function(event, ui) {
1842
+ delay($.proxy(function() {
1843
+ this.on_stop_resize(event, ui);
1844
+ }, this), 120);
1845
+ }, this),
1846
+ drag: throttle($.proxy(this.on_resize, this), 60)
1847
+ });
1848
+
1849
+ return this;
1850
+ };
1851
+
1852
+
1853
+ /**
1854
+ * Setup things required for resizing. Like build templates for drag handles.
1855
+ *
1856
+ * @method setup_resize
1857
+ * @return {Class} Returns instance of gridster Class.
1858
+ */
1859
+ fn.setup_resize = function() {
1860
+ this.resize_handle_class = this.options.resize.handle_class;
1861
+ var axes = this.options.resize.axes;
1862
+ var handle_tpl = '<span class="' + this.resize_handle_class + ' ' +
1863
+ this.resize_handle_class + '-{type}" />';
1864
+
1865
+ this.resize_handle_tpl = $.map(axes, function(type) {
1866
+ return handle_tpl.replace('{type}', type);
1867
+ }).join('');
1868
+
1869
+ if ($.isArray(this.options.draggable.ignore_dragging)) {
1870
+ this.options.draggable.ignore_dragging.push(
1871
+ '.' + this.resize_handle_class);
1872
+ }
1873
+
1874
+ return this;
1875
+ };
1876
+
1877
+
1878
+ /**
1879
+ * This function is executed when the player begins to be dragged.
1880
+ *
1881
+ * @method on_start_drag
1882
+ * @param {Event} event The original browser event
1883
+ * @param {Object} ui A prepared ui object with useful drag-related data
1884
+ */
1885
+ fn.on_start_drag = function(event, ui) {
1886
+ this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging');
1887
+
1888
+ this.highest_col = this.get_highest_occupied_cell().col;
1889
+
1890
+ this.$player.addClass('player');
1891
+ this.player_grid_data = this.$player.coords().grid;
1892
+ this.placeholder_grid_data = $.extend({}, this.player_grid_data);
1893
+
1894
+ this.set_dom_grid_height(this.$el.height() +
1895
+ (this.player_grid_data.size_y * this.min_widget_height));
1896
+
1897
+ this.set_dom_grid_width(this.cols);
1898
+
1899
+ var pgd_sizex = this.player_grid_data.size_x;
1900
+ var cols_diff = this.cols - this.highest_col;
1901
+
1902
+ if (this.options.autogrow_cols && cols_diff <= pgd_sizex) {
1903
+ this.add_faux_cols(Math.min(pgd_sizex - cols_diff, 1));
1904
+ }
1905
+
1906
+ var colliders = this.faux_grid;
1907
+ var coords = this.$player.data('coords').coords;
1908
+
1909
+ this.cells_occupied_by_player = this.get_cells_occupied(
1910
+ this.player_grid_data);
1911
+ this.cells_occupied_by_placeholder = this.get_cells_occupied(
1912
+ this.placeholder_grid_data);
1913
+
1914
+ this.last_cols = [];
1915
+ this.last_rows = [];
1916
+
1917
+ // see jquery.collision.js
1918
+ this.collision_api = this.$helper.collision(
1919
+ colliders, this.options.collision);
1920
+
1921
+ this.$preview_holder = $('<' + this.$player.get(0).tagName + ' />', {
1922
+ 'class': 'preview-holder',
1923
+ 'data-row': this.$player.attr('data-row'),
1924
+ 'data-col': this.$player.attr('data-col'),
1925
+ css: {
1926
+ width: coords.width,
1927
+ height: coords.height
1928
+ }
1929
+ }).appendTo(this.$el);
1930
+
1931
+ if (this.options.draggable.start) {
1932
+ this.options.draggable.start.call(this, event, ui);
1933
+ }
1934
+ };
1935
+
1936
+
1937
+ /**
1938
+ * This function is executed when the player is being dragged.
1939
+ *
1940
+ * @method on_drag
1941
+ * @param {Event} event The original browser event
1942
+ * @param {Object} ui A prepared ui object with useful drag-related data
1943
+ */
1944
+ fn.on_drag = function(event, ui) {
1945
+ //break if dragstop has been fired
1946
+ if (this.$player === null) {
1947
+ return false;
1948
+ }
1949
+
1950
+ var abs_offset = {
1951
+ left: ui.position.left + this.baseX,
1952
+ top: ui.position.top + this.baseY
1953
+ };
1954
+
1955
+ // auto grow cols
1956
+ if (this.options.autogrow_cols) {
1957
+ var prcol = this.placeholder_grid_data.col +
1958
+ this.placeholder_grid_data.size_x - 1;
1959
+
1960
+ // "- 1" due to adding at least 1 column in on_start_drag
1961
+ if (prcol >= this.cols - 1 && this.options.max_cols >= this.cols + 1) {
1962
+ this.add_faux_cols(1);
1963
+ this.set_dom_grid_width(this.cols + 1);
1964
+ this.drag_api.set_limits(this.container_width);
1965
+ }
1966
+
1967
+ this.collision_api.set_colliders(this.faux_grid);
1968
+ }
1969
+
1970
+ this.colliders_data = this.collision_api.get_closest_colliders(
1971
+ abs_offset);
1972
+
1973
+ this.on_overlapped_column_change(
1974
+ this.on_start_overlapping_column, this.on_stop_overlapping_column);
1975
+
1976
+ this.on_overlapped_row_change(
1977
+ this.on_start_overlapping_row, this.on_stop_overlapping_row);
1978
+
1979
+
1980
+ if (this.helper && this.$player) {
1981
+ this.$player.css({
1982
+ 'left': ui.position.left,
1983
+ 'top': ui.position.top
1984
+ });
1985
+ }
1986
+
1987
+ if (this.options.draggable.drag) {
1988
+ this.options.draggable.drag.call(this, event, ui);
1989
+ }
1990
+ };
1991
+
1992
+
1993
+ /**
1994
+ * This function is executed when the player stops being dragged.
1995
+ *
1996
+ * @method on_stop_drag
1997
+ * @param {Event} event The original browser event
1998
+ * @param {Object} ui A prepared ui object with useful drag-related data
1999
+ */
2000
+ fn.on_stop_drag = function(event, ui) {
2001
+ this.$helper.add(this.$player).add(this.$wrapper)
2002
+ .removeClass('dragging');
2003
+
2004
+ ui.position.left = ui.position.left + this.baseX;
2005
+ ui.position.top = ui.position.top + this.baseY;
2006
+ this.colliders_data = this.collision_api.get_closest_colliders(
2007
+ ui.position);
2008
+
2009
+ this.on_overlapped_column_change(
2010
+ this.on_start_overlapping_column,
2011
+ this.on_stop_overlapping_column
2012
+ );
2013
+
2014
+ this.on_overlapped_row_change(
2015
+ this.on_start_overlapping_row,
2016
+ this.on_stop_overlapping_row
2017
+ );
2018
+
2019
+ this.$player.addClass('player-revert').removeClass('player')
2020
+ .attr({
2021
+ 'data-col': this.placeholder_grid_data.col,
2022
+ 'data-row': this.placeholder_grid_data.row
2023
+ }).css({
2024
+ 'left': '',
2025
+ 'top': ''
2026
+ });
2027
+
2028
+ this.$changed = this.$changed.add(this.$player);
2029
+
2030
+ this.cells_occupied_by_player = this.get_cells_occupied(
2031
+ this.placeholder_grid_data);
2032
+ this.set_cells_player_occupies(
2033
+ this.placeholder_grid_data.col, this.placeholder_grid_data.row);
2034
+
2035
+ this.$player.coords().grid.row = this.placeholder_grid_data.row;
2036
+ this.$player.coords().grid.col = this.placeholder_grid_data.col;
2037
+
2038
+ if (this.options.draggable.stop) {
2039
+ this.options.draggable.stop.call(this, event, ui);
2040
+ }
2041
+
2042
+ this.$preview_holder.remove();
2043
+
2044
+ this.$player = null;
2045
+ this.$helper = null;
2046
+ this.placeholder_grid_data = {};
2047
+ this.player_grid_data = {};
2048
+ this.cells_occupied_by_placeholder = {};
2049
+ this.cells_occupied_by_player = {};
2050
+
2051
+ this.set_dom_grid_height();
2052
+ this.set_dom_grid_width();
2053
+
2054
+ if (this.options.autogrow_cols) {
2055
+ this.drag_api.set_limits(this.cols * this.min_widget_width);
2056
+ }
2057
+ };
2058
+
2059
+
2060
+ /**
2061
+ * This function is executed every time a widget starts to be resized.
2062
+ *
2063
+ * @method on_start_resize
2064
+ * @param {Event} event The original browser event
2065
+ * @param {Object} ui A prepared ui object with useful drag-related data
2066
+ */
2067
+ fn.on_start_resize = function(event, ui) {
2068
+ this.$resized_widget = ui.$player.closest('.gs-w');
2069
+ this.resize_coords = this.$resized_widget.coords();
2070
+ this.resize_wgd = this.resize_coords.grid;
2071
+ this.resize_initial_width = this.resize_coords.coords.width;
2072
+ this.resize_initial_height = this.resize_coords.coords.height;
2073
+ this.resize_initial_sizex = this.resize_coords.grid.size_x;
2074
+ this.resize_initial_sizey = this.resize_coords.grid.size_y;
2075
+ this.resize_initial_col = this.resize_coords.grid.col;
2076
+ this.resize_last_sizex = this.resize_initial_sizex;
2077
+ this.resize_last_sizey = this.resize_initial_sizey;
2078
+
2079
+ this.resize_max_size_x = Math.min(this.resize_wgd.max_size_x ||
2080
+ this.options.resize.max_size[0],
2081
+ this.options.max_cols - this.resize_initial_col + 1);
2082
+ this.resize_max_size_y = this.resize_wgd.max_size_y ||
2083
+ this.options.resize.max_size[1];
2084
+
2085
+ this.resize_min_size_x = (this.resize_wgd.min_size_x ||
2086
+ this.options.resize.min_size[0] || 1);
2087
+ this.resize_min_size_y = (this.resize_wgd.min_size_y ||
2088
+ this.options.resize.min_size[1] || 1);
2089
+
2090
+ this.resize_initial_last_col = this.get_highest_occupied_cell().col;
2091
+
2092
+ this.set_dom_grid_width(this.cols);
2093
+
2094
+ this.resize_dir = {
2095
+ right: ui.$player.is('.' + this.resize_handle_class + '-x'),
2096
+ bottom: ui.$player.is('.' + this.resize_handle_class + '-y')
2097
+ };
2098
+
2099
+ this.$resized_widget.css({
2100
+ 'min-width': this.options.widget_base_dimensions[0],
2101
+ 'min-height': this.options.widget_base_dimensions[1]
2102
+ });
2103
+
2104
+ var nodeName = this.$resized_widget.get(0).tagName;
2105
+ this.$resize_preview_holder = $('<' + nodeName + ' />', {
2106
+ 'class': 'preview-holder resize-preview-holder',
2107
+ 'data-row': this.$resized_widget.attr('data-row'),
2108
+ 'data-col': this.$resized_widget.attr('data-col'),
2109
+ 'css': {
2110
+ 'width': this.resize_initial_width,
2111
+ 'height': this.resize_initial_height
2112
+ }
2113
+ }).appendTo(this.$el);
2114
+
2115
+ this.$resized_widget.addClass('resizing');
2116
+
2117
+ if (this.options.resize.start) {
2118
+ this.options.resize.start.call(this, event, ui, this.$resized_widget);
2119
+ }
2120
+
2121
+ this.$el.trigger('gridster:resizestart');
2122
+ };
2123
+
2124
+
2125
+ /**
2126
+ * This function is executed every time a widget stops being resized.
2127
+ *
2128
+ * @method on_stop_resize
2129
+ * @param {Event} event The original browser event
2130
+ * @param {Object} ui A prepared ui object with useful drag-related data
2131
+ */
2132
+ fn.on_stop_resize = function(event, ui) {
2133
+ this.$resized_widget
2134
+ .removeClass('resizing')
2135
+ .css({
2136
+ 'width': '',
2137
+ 'height': ''
2138
+ });
2139
+
2140
+ delay($.proxy(function() {
2141
+ this.$resize_preview_holder
2142
+ .remove()
2143
+ .css({
2144
+ 'min-width': '',
2145
+ 'min-height': ''
2146
+ });
2147
+
2148
+ if (this.options.resize.stop) {
2149
+ this.options.resize.stop.call(this, event, ui, this.$resized_widget);
2150
+ }
2151
+
2152
+ this.$el.trigger('gridster:resizestop');
2153
+ }, this), 300);
2154
+
2155
+ this.set_dom_grid_width();
2156
+
2157
+ if (this.options.autogrow_cols) {
2158
+ this.drag_api.set_limits(this.cols * this.min_widget_width);
2159
+ }
2160
+ };
2161
+
2162
+
2163
+ /**
2164
+ * This function is executed when a widget is being resized.
2165
+ *
2166
+ * @method on_resize
2167
+ * @param {Event} event The original browser event
2168
+ * @param {Object} ui A prepared ui object with useful drag-related data
2169
+ */
2170
+ fn.on_resize = function(event, ui) {
2171
+ var rel_x = (ui.pointer.diff_left);
2172
+ var rel_y = (ui.pointer.diff_top);
2173
+ var wbd_x = this.options.widget_base_dimensions[0];
2174
+ var wbd_y = this.options.widget_base_dimensions[1];
2175
+ var margin_x = this.options.widget_margins[0];
2176
+ var margin_y = this.options.widget_margins[1];
2177
+ var max_size_x = this.resize_max_size_x;
2178
+ var min_size_x = this.resize_min_size_x;
2179
+ var max_size_y = this.resize_max_size_y;
2180
+ var min_size_y = this.resize_min_size_y;
2181
+ var autogrow = this.options.autogrow_cols;
2182
+ var width;
2183
+ var max_width = Infinity;
2184
+ var max_height = Infinity;
2185
+
2186
+ var inc_units_x = Math.ceil((rel_x / (wbd_x + margin_x * 2)) - 0.2);
2187
+ var inc_units_y = Math.ceil((rel_y / (wbd_y + margin_y * 2)) - 0.2);
2188
+
2189
+ var size_x = Math.max(1, this.resize_initial_sizex + inc_units_x);
2190
+ var size_y = Math.max(1, this.resize_initial_sizey + inc_units_y);
2191
+
2192
+ var max_cols = (this.container_width / this.min_widget_width) -
2193
+ this.resize_initial_col + 1;
2194
+ var limit_width = ((max_cols * this.min_widget_width) - margin_x * 2);
2195
+
2196
+ size_x = Math.max(Math.min(size_x, max_size_x), min_size_x);
2197
+ size_x = Math.min(max_cols, size_x);
2198
+ width = (max_size_x * wbd_x) + ((size_x - 1) * margin_x * 2);
2199
+ max_width = Math.min(width, limit_width);
2200
+ min_width = (min_size_x * wbd_x) + ((size_x - 1) * margin_x * 2);
2201
+
2202
+ size_y = Math.max(Math.min(size_y, max_size_y), min_size_y);
2203
+ max_height = (max_size_y * wbd_y) + ((size_y - 1) * margin_y * 2);
2204
+ min_height = (min_size_y * wbd_y) + ((size_y - 1) * margin_y * 2);
2205
+
2206
+ if (this.resize_dir.right) {
2207
+ size_y = this.resize_initial_sizey;
2208
+ } else if (this.resize_dir.bottom) {
2209
+ size_x = this.resize_initial_sizex;
2210
+ }
2211
+
2212
+ if (autogrow) {
2213
+ var last_widget_col = this.resize_initial_col + size_x - 1;
2214
+ if (autogrow && this.resize_initial_last_col <= last_widget_col) {
2215
+ this.set_dom_grid_width(Math.max(last_widget_col + 1, this.cols));
2216
+
2217
+ if (this.cols < last_widget_col) {
2218
+ this.add_faux_cols(last_widget_col - this.cols);
2219
+ }
2220
+ }
2221
+ }
2222
+
2223
+ var css_props = {};
2224
+ !this.resize_dir.bottom && (css_props.width = Math.max(Math.min(
2225
+ this.resize_initial_width + rel_x, max_width), min_width));
2226
+ !this.resize_dir.right && (css_props.height = Math.max(Math.min(
2227
+ this.resize_initial_height + rel_y, max_height), min_height));
2228
+
2229
+ this.$resized_widget.css(css_props);
2230
+
2231
+ if (size_x !== this.resize_last_sizex ||
2232
+ size_y !== this.resize_last_sizey) {
2233
+
2234
+ this.resize_widget(this.$resized_widget, size_x, size_y);
2235
+ this.set_dom_grid_width(this.cols);
2236
+
2237
+ this.$resize_preview_holder.css({
2238
+ 'width': '',
2239
+ 'height': ''
2240
+ }).attr({
2241
+ 'data-row': this.$resized_widget.attr('data-row'),
2242
+ 'data-sizex': size_x,
2243
+ 'data-sizey': size_y
2244
+ });
2245
+ }
2246
+
2247
+ if (this.options.resize.resize) {
2248
+ this.options.resize.resize.call(this, event, ui, this.$resized_widget);
2249
+ }
2250
+
2251
+ this.$el.trigger('gridster:resize');
2252
+
2253
+ this.resize_last_sizex = size_x;
2254
+ this.resize_last_sizey = size_y;
2255
+ };
2256
+
2257
+
2258
+ /**
2259
+ * Executes the callbacks passed as arguments when a column begins to be
2260
+ * overlapped or stops being overlapped.
2261
+ *
2262
+ * @param {Function} start_callback Function executed when a new column
2263
+ * begins to be overlapped. The column is passed as first argument.
2264
+ * @param {Function} stop_callback Function executed when a column stops
2265
+ * being overlapped. The column is passed as first argument.
2266
+ * @method on_overlapped_column_change
2267
+ * @return {Class} Returns the instance of the Gridster Class.
2268
+ */
2269
+ fn.on_overlapped_column_change = function(start_callback, stop_callback) {
2270
+ if (!this.colliders_data.length) {
2271
+ return this;
2272
+ }
2273
+ var cols = this.get_targeted_columns(
2274
+ this.colliders_data[0].el.data.col);
2275
+
2276
+ var last_n_cols = this.last_cols.length;
2277
+ var n_cols = cols.length;
2278
+ var i;
2279
+
2280
+ for (i = 0; i < n_cols; i++) {
2281
+ if ($.inArray(cols[i], this.last_cols) === -1) {
2282
+ (start_callback || $.noop).call(this, cols[i]);
2283
+ }
2284
+ }
2285
+
2286
+ for (i = 0; i< last_n_cols; i++) {
2287
+ if ($.inArray(this.last_cols[i], cols) === -1) {
2288
+ (stop_callback || $.noop).call(this, this.last_cols[i]);
2289
+ }
2290
+ }
2291
+
2292
+ this.last_cols = cols;
2293
+
2294
+ return this;
2295
+ };
2296
+
2297
+
2298
+ /**
2299
+ * Executes the callbacks passed as arguments when a row starts to be
2300
+ * overlapped or stops being overlapped.
2301
+ *
2302
+ * @param {Function} start_callback Function executed when a new row begins
2303
+ * to be overlapped. The row is passed as first argument.
2304
+ * @param {Function} end_callback Function executed when a row stops being
2305
+ * overlapped. The row is passed as first argument.
2306
+ * @method on_overlapped_row_change
2307
+ * @return {Class} Returns the instance of the Gridster Class.
2308
+ */
2309
+ fn.on_overlapped_row_change = function(start_callback, end_callback) {
2310
+ if (!this.colliders_data.length) {
2311
+ return this;
2312
+ }
2313
+ var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row);
2314
+ var last_n_rows = this.last_rows.length;
2315
+ var n_rows = rows.length;
2316
+ var i;
2317
+
2318
+ for (i = 0; i < n_rows; i++) {
2319
+ if ($.inArray(rows[i], this.last_rows) === -1) {
2320
+ (start_callback || $.noop).call(this, rows[i]);
2321
+ }
2322
+ }
2323
+
2324
+ for (i = 0; i < last_n_rows; i++) {
2325
+ if ($.inArray(this.last_rows[i], rows) === -1) {
2326
+ (end_callback || $.noop).call(this, this.last_rows[i]);
2327
+ }
2328
+ }
2329
+
2330
+ this.last_rows = rows;
2331
+ };
2332
+
2333
+
2334
+ /**
2335
+ * Sets the current position of the player
2336
+ *
2337
+ * @param {Number} col
2338
+ * @param {Number} row
2339
+ * @param {Boolean} no_player
2340
+ * @method set_player
2341
+ * @return {object}
2342
+ */
2343
+ fn.set_player = function(col, row, no_player) {
2344
+ var self = this;
2345
+ if (!no_player) {
2346
+ this.empty_cells_player_occupies();
2347
+ }
2348
+ var cell = !no_player ? self.colliders_data[0].el.data : {col: col};
2349
+ var to_col = cell.col;
2350
+ var to_row = row || cell.row;
2351
+
2352
+ this.player_grid_data = {
2353
+ col: to_col,
2354
+ row: to_row,
2355
+ size_y : this.player_grid_data.size_y,
2356
+ size_x : this.player_grid_data.size_x
2357
+ };
2358
+
2359
+ this.cells_occupied_by_player = this.get_cells_occupied(
2360
+ this.player_grid_data);
2361
+
2362
+ var $overlapped_widgets = this.get_widgets_overlapped(
2363
+ this.player_grid_data);
2364
+
2365
+ var constraints = this.widgets_constraints($overlapped_widgets);
2366
+
2367
+ this.manage_movements(constraints.can_go_up, to_col, to_row);
2368
+ this.manage_movements(constraints.can_not_go_up, to_col, to_row);
2369
+
2370
+ /* if there is not widgets overlapping in the new player position,
2371
+ * update the new placeholder position. */
2372
+ if (!$overlapped_widgets.length) {
2373
+ var pp = this.can_go_player_up(this.player_grid_data);
2374
+ if (pp !== false) {
2375
+ to_row = pp;
2376
+ }
2377
+ this.set_placeholder(to_col, to_row);
2378
+ }
2379
+
2380
+ return {
2381
+ col: to_col,
2382
+ row: to_row
2383
+ };
2384
+ };
2385
+
2386
+
2387
+ /**
2388
+ * See which of the widgets in the $widgets param collection can go to
2389
+ * a upper row and which not.
2390
+ *
2391
+ * @method widgets_contraints
2392
+ * @param {jQuery} $widgets A jQuery wrapped collection of
2393
+ * HTMLElements.
2394
+ * @return {object} Returns a literal Object with two keys: `can_go_up` &
2395
+ * `can_not_go_up`. Each contains a set of HTMLElements.
2396
+ */
2397
+ fn.widgets_constraints = function($widgets) {
2398
+ var $widgets_can_go_up = $([]);
2399
+ var $widgets_can_not_go_up;
2400
+ var wgd_can_go_up = [];
2401
+ var wgd_can_not_go_up = [];
2402
+
2403
+ $widgets.each($.proxy(function(i, w) {
2404
+ var $w = $(w);
2405
+ var wgd = $w.coords().grid;
2406
+ if (this.can_go_widget_up(wgd)) {
2407
+ $widgets_can_go_up = $widgets_can_go_up.add($w);
2408
+ wgd_can_go_up.push(wgd);
2409
+ } else {
2410
+ wgd_can_not_go_up.push(wgd);
2411
+ }
2412
+ }, this));
2413
+
2414
+ $widgets_can_not_go_up = $widgets.not($widgets_can_go_up);
2415
+
2416
+ return {
2417
+ can_go_up: Gridster.sort_by_row_asc(wgd_can_go_up),
2418
+ can_not_go_up: Gridster.sort_by_row_desc(wgd_can_not_go_up)
2419
+ };
2420
+ };
2421
+
2422
+
2423
+ /**
2424
+ * Sorts an Array of grid coords objects (representing the grid coords of
2425
+ * each widget) in descending way.
2426
+ *
2427
+ * @method manage_movements
2428
+ * @param {jQuery} $widgets A jQuery collection of HTMLElements
2429
+ * representing the widgets you want to move.
2430
+ * @param {Number} to_col The column to which we want to move the widgets.
2431
+ * @param {Number} to_row The row to which we want to move the widgets.
2432
+ * @return {Class} Returns the instance of the Gridster Class.
2433
+ */
2434
+ fn.manage_movements = function($widgets, to_col, to_row) {
2435
+ $.each($widgets, $.proxy(function(i, w) {
2436
+ var wgd = w;
2437
+ var $w = wgd.el;
2438
+
2439
+ var can_go_widget_up = this.can_go_widget_up(wgd);
2440
+
2441
+ if (can_go_widget_up) {
2442
+ //target CAN go up
2443
+ //so move widget up
2444
+ this.move_widget_to($w, can_go_widget_up);
2445
+ this.set_placeholder(to_col, can_go_widget_up + wgd.size_y);
2446
+
2447
+ } else {
2448
+ //target can't go up
2449
+ var can_go_player_up = this.can_go_player_up(
2450
+ this.player_grid_data);
2451
+
2452
+ if (!can_go_player_up) {
2453
+ // target can't go up
2454
+ // player cant't go up
2455
+ // so we need to move widget down to a position that dont
2456
+ // overlaps player
2457
+ var y = (to_row + this.player_grid_data.size_y) - wgd.row;
2458
+
2459
+ this.move_widget_down($w, y);
2460
+ this.set_placeholder(to_col, to_row);
2461
+ }
2462
+ }
2463
+ }, this));
2464
+
2465
+ return this;
2466
+ };
2467
+
2468
+ /**
2469
+ * Determines if there is a widget in the row and col given. Or if the
2470
+ * HTMLElement passed as first argument is the player.
2471
+ *
2472
+ * @method is_player
2473
+ * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of
2474
+ * HTMLElements.
2475
+ * @param {Number} [row] The column to which we want to move the widgets.
2476
+ * @return {Boolean} Returns true or false.
2477
+ */
2478
+ fn.is_player = function(col_or_el, row) {
2479
+ if (row && !this.gridmap[col_or_el]) { return false; }
2480
+ var $w = row ? this.gridmap[col_or_el][row] : col_or_el;
2481
+ return $w && ($w.is(this.$player) || $w.is(this.$helper));
2482
+ };
2483
+
2484
+
2485
+ /**
2486
+ * Determines if the widget that is being dragged is currently over the row
2487
+ * and col given.
2488
+ *
2489
+ * @method is_player_in
2490
+ * @param {Number} col The column to check.
2491
+ * @param {Number} row The row to check.
2492
+ * @return {Boolean} Returns true or false.
2493
+ */
2494
+ fn.is_player_in = function(col, row) {
2495
+ var c = this.cells_occupied_by_player || {};
2496
+ return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0;
2497
+ };
2498
+
2499
+
2500
+ /**
2501
+ * Determines if the placeholder is currently over the row and col given.
2502
+ *
2503
+ * @method is_placeholder_in
2504
+ * @param {Number} col The column to check.
2505
+ * @param {Number} row The row to check.
2506
+ * @return {Boolean} Returns true or false.
2507
+ */
2508
+ fn.is_placeholder_in = function(col, row) {
2509
+ var c = this.cells_occupied_by_placeholder || {};
2510
+ return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0;
2511
+ };
2512
+
2513
+
2514
+ /**
2515
+ * Determines if the placeholder is currently over the column given.
2516
+ *
2517
+ * @method is_placeholder_in_col
2518
+ * @param {Number} col The column to check.
2519
+ * @return {Boolean} Returns true or false.
2520
+ */
2521
+ fn.is_placeholder_in_col = function(col) {
2522
+ var c = this.cells_occupied_by_placeholder || [];
2523
+ return $.inArray(col, c.cols) >= 0;
2524
+ };
2525
+
2526
+
2527
+ /**
2528
+ * Determines if the cell represented by col and row params is empty.
2529
+ *
2530
+ * @method is_empty
2531
+ * @param {Number} col The column to check.
2532
+ * @param {Number} row The row to check.
2533
+ * @return {Boolean} Returns true or false.
2534
+ */
2535
+ fn.is_empty = function(col, row) {
2536
+ if (typeof this.gridmap[col] !== 'undefined') {
2537
+ if(typeof this.gridmap[col][row] !== 'undefined' &&
2538
+ this.gridmap[col][row] === false
2539
+ ) {
2540
+ return true;
2541
+ }
2542
+ return false;
2543
+ }
2544
+ return true;
2545
+ };
2546
+
2547
+
2548
+ /**
2549
+ * Determines if the cell represented by col and row params is occupied.
2550
+ *
2551
+ * @method is_occupied
2552
+ * @param {Number} col The column to check.
2553
+ * @param {Number} row The row to check.
2554
+ * @return {Boolean} Returns true or false.
2555
+ */
2556
+ fn.is_occupied = function(col, row) {
2557
+ if (!this.gridmap[col]) {
2558
+ return false;
2559
+ }
2560
+
2561
+ if (this.gridmap[col][row]) {
2562
+ return true;
2563
+ }
2564
+ return false;
2565
+ };
2566
+
2567
+
2568
+ /**
2569
+ * Determines if there is a widget in the cell represented by col/row params.
2570
+ *
2571
+ * @method is_widget
2572
+ * @param {Number} col The column to check.
2573
+ * @param {Number} row The row to check.
2574
+ * @return {Boolean|HTMLElement} Returns false if there is no widget,
2575
+ * else returns the jQuery HTMLElement
2576
+ */
2577
+ fn.is_widget = function(col, row) {
2578
+ var cell = this.gridmap[col];
2579
+ if (!cell) {
2580
+ return false;
2581
+ }
2582
+
2583
+ cell = cell[row];
2584
+
2585
+ if (cell) {
2586
+ return cell;
2587
+ }
2588
+
2589
+ return false;
2590
+ };
2591
+
2592
+
2593
+ /**
2594
+ * Determines if there is a widget in the cell represented by col/row
2595
+ * params and if this is under the widget that is being dragged.
2596
+ *
2597
+ * @method is_widget_under_player
2598
+ * @param {Number} col The column to check.
2599
+ * @param {Number} row The row to check.
2600
+ * @return {Boolean} Returns true or false.
2601
+ */
2602
+ fn.is_widget_under_player = function(col, row) {
2603
+ if (this.is_widget(col, row)) {
2604
+ return this.is_player_in(col, row);
2605
+ }
2606
+ return false;
2607
+ };
2608
+
2609
+
2610
+ /**
2611
+ * Get widgets overlapping with the player or with the object passed
2612
+ * representing the grid cells.
2613
+ *
2614
+ * @method get_widgets_under_player
2615
+ * @return {HTMLElement} Returns a jQuery collection of HTMLElements
2616
+ */
2617
+ fn.get_widgets_under_player = function(cells) {
2618
+ cells || (cells = this.cells_occupied_by_player || {cols: [], rows: []});
2619
+ var $widgets = $([]);
2620
+
2621
+ $.each(cells.cols, $.proxy(function(i, col) {
2622
+ $.each(cells.rows, $.proxy(function(i, row) {
2623
+ if(this.is_widget(col, row)) {
2624
+ $widgets = $widgets.add(this.gridmap[col][row]);
2625
+ }
2626
+ }, this));
2627
+ }, this));
2628
+
2629
+ return $widgets;
2630
+ };
2631
+
2632
+
2633
+ /**
2634
+ * Put placeholder at the row and column specified.
2635
+ *
2636
+ * @method set_placeholder
2637
+ * @param {Number} col The column to which we want to move the
2638
+ * placeholder.
2639
+ * @param {Number} row The row to which we want to move the
2640
+ * placeholder.
2641
+ * @return {Class} Returns the instance of the Gridster Class.
2642
+ */
2643
+ fn.set_placeholder = function(col, row) {
2644
+ var phgd = $.extend({}, this.placeholder_grid_data);
2645
+ var $nexts = this.widgets_below({
2646
+ col: phgd.col,
2647
+ row: phgd.row,
2648
+ size_y: phgd.size_y,
2649
+ size_x: phgd.size_x
2650
+ });
2651
+
2652
+ // Prevents widgets go out of the grid
2653
+ var right_col = (col + phgd.size_x - 1);
2654
+ if (right_col > this.cols) {
2655
+ col = col - (right_col - col);
2656
+ }
2657
+
2658
+ var moved_down = this.placeholder_grid_data.row < row;
2659
+ var changed_column = this.placeholder_grid_data.col !== col;
2660
+
2661
+ this.placeholder_grid_data.col = col;
2662
+ this.placeholder_grid_data.row = row;
2663
+
2664
+ this.cells_occupied_by_placeholder = this.get_cells_occupied(
2665
+ this.placeholder_grid_data);
2666
+
2667
+ this.$preview_holder.attr({
2668
+ 'data-row' : row,
2669
+ 'data-col' : col
2670
+ });
2671
+
2672
+ if (moved_down || changed_column) {
2673
+ $nexts.each($.proxy(function(i, widget) {
2674
+ this.move_widget_up(
2675
+ $(widget), this.placeholder_grid_data.col - col + phgd.size_y);
2676
+ }, this));
2677
+ }
2678
+
2679
+ var $widgets_under_ph = this.get_widgets_under_player(
2680
+ this.cells_occupied_by_placeholder);
2681
+
2682
+ if ($widgets_under_ph.length) {
2683
+ $widgets_under_ph.each($.proxy(function(i, widget) {
2684
+ var $w = $(widget);
2685
+ this.move_widget_down(
2686
+ $w, row + phgd.size_y - $w.data('coords').grid.row);
2687
+ }, this));
2688
+ }
2689
+
2690
+ };
2691
+
2692
+
2693
+ /**
2694
+ * Determines whether the player can move to a position above.
2695
+ *
2696
+ * @method can_go_player_up
2697
+ * @param {Object} widget_grid_data The actual grid coords object of the
2698
+ * player.
2699
+ * @return {Number|Boolean} If the player can be moved to an upper row
2700
+ * returns the row number, else returns false.
2701
+ */
2702
+ fn.can_go_player_up = function(widget_grid_data) {
2703
+ var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1;
2704
+ var result = true;
2705
+ var upper_rows = [];
2706
+ var min_row = 10000;
2707
+ var $widgets_under_player = this.get_widgets_under_player();
2708
+
2709
+ /* generate an array with columns as index and array with upper rows
2710
+ * empty as value */
2711
+ this.for_each_column_occupied(widget_grid_data, function(tcol) {
2712
+ var grid_col = this.gridmap[tcol];
2713
+ var r = p_bottom_row + 1;
2714
+ upper_rows[tcol] = [];
2715
+
2716
+ while (--r > 0) {
2717
+ if (this.is_empty(tcol, r) || this.is_player(tcol, r) ||
2718
+ this.is_widget(tcol, r) &&
2719
+ grid_col[r].is($widgets_under_player)
2720
+ ) {
2721
+ upper_rows[tcol].push(r);
2722
+ min_row = r < min_row ? r : min_row;
2723
+ } else {
2724
+ break;
2725
+ }
2726
+ }
2727
+
2728
+ if (upper_rows[tcol].length === 0) {
2729
+ result = false;
2730
+ return true; //break
2731
+ }
2732
+
2733
+ upper_rows[tcol].sort(function(a, b) {
2734
+ return a - b;
2735
+ });
2736
+ });
2737
+
2738
+ if (!result) { return false; }
2739
+
2740
+ return this.get_valid_rows(widget_grid_data, upper_rows, min_row);
2741
+ };
2742
+
2743
+
2744
+ /**
2745
+ * Determines whether a widget can move to a position above.
2746
+ *
2747
+ * @method can_go_widget_up
2748
+ * @param {Object} widget_grid_data The actual grid coords object of the
2749
+ * widget we want to check.
2750
+ * @return {Number|Boolean} If the widget can be moved to an upper row
2751
+ * returns the row number, else returns false.
2752
+ */
2753
+ fn.can_go_widget_up = function(widget_grid_data) {
2754
+ var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1;
2755
+ var result = true;
2756
+ var upper_rows = [];
2757
+ var min_row = 10000;
2758
+
2759
+ /* generate an array with columns as index and array with topmost rows
2760
+ * empty as value */
2761
+ this.for_each_column_occupied(widget_grid_data, function(tcol) {
2762
+ var grid_col = this.gridmap[tcol];
2763
+ upper_rows[tcol] = [];
2764
+
2765
+ var r = p_bottom_row + 1;
2766
+ // iterate over each row
2767
+ while (--r > 0) {
2768
+ if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) {
2769
+ if (!grid_col[r].is(widget_grid_data.el)) {
2770
+ break;
2771
+ }
2772
+ }
2773
+
2774
+ if (!this.is_player(tcol, r) &&
2775
+ !this.is_placeholder_in(tcol, r) &&
2776
+ !this.is_player_in(tcol, r)) {
2777
+ upper_rows[tcol].push(r);
2778
+ }
2779
+
2780
+ if (r < min_row) {
2781
+ min_row = r;
2782
+ }
2783
+ }
2784
+
2785
+ if (upper_rows[tcol].length === 0) {
2786
+ result = false;
2787
+ return true; //break
2788
+ }
2789
+
2790
+ upper_rows[tcol].sort(function(a, b) {
2791
+ return a - b;
2792
+ });
2793
+ });
2794
+
2795
+ if (!result) { return false; }
2796
+
2797
+ return this.get_valid_rows(widget_grid_data, upper_rows, min_row);
2798
+ };
2799
+
2800
+
2801
+ /**
2802
+ * Search a valid row for the widget represented by `widget_grid_data' in
2803
+ * the `upper_rows` array. Iteration starts from row specified in `min_row`.
2804
+ *
2805
+ * @method get_valid_rows
2806
+ * @param {Object} widget_grid_data The actual grid coords object of the
2807
+ * player.
2808
+ * @param {Array} upper_rows An array with columns as index and arrays
2809
+ * of valid rows as values.
2810
+ * @param {Number} min_row The upper row from which the iteration will start.
2811
+ * @return {Number|Boolean} Returns the upper row valid from the `upper_rows`
2812
+ * for the widget in question.
2813
+ */
2814
+ fn.get_valid_rows = function(widget_grid_data, upper_rows, min_row) {
2815
+ var p_top_row = widget_grid_data.row;
2816
+ var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1;
2817
+ var size_y = widget_grid_data.size_y;
2818
+ var r = min_row - 1;
2819
+ var valid_rows = [];
2820
+
2821
+ while (++r <= p_bottom_row ) {
2822
+ var common = true;
2823
+ $.each(upper_rows, function(col, rows) {
2824
+ if ($.isArray(rows) && $.inArray(r, rows) === -1) {
2825
+ common = false;
2826
+ }
2827
+ });
2828
+
2829
+ if (common === true) {
2830
+ valid_rows.push(r);
2831
+ if (valid_rows.length === size_y) {
2832
+ break;
2833
+ }
2834
+ }
2835
+ }
2836
+
2837
+ var new_row = false;
2838
+ if (size_y === 1) {
2839
+ if (valid_rows[0] !== p_top_row) {
2840
+ new_row = valid_rows[0] || false;
2841
+ }
2842
+ } else {
2843
+ if (valid_rows[0] !== p_top_row) {
2844
+ new_row = this.get_consecutive_numbers_index(
2845
+ valid_rows, size_y);
2846
+ }
2847
+ }
2848
+
2849
+ return new_row;
2850
+ };
2851
+
2852
+
2853
+ fn.get_consecutive_numbers_index = function(arr, size_y) {
2854
+ var max = arr.length;
2855
+ var result = [];
2856
+ var first = true;
2857
+ var prev = -1; // or null?
2858
+
2859
+ for (var i=0; i < max; i++) {
2860
+ if (first || arr[i] === prev + 1) {
2861
+ result.push(i);
2862
+ if (result.length === size_y) {
2863
+ break;
2864
+ }
2865
+ first = false;
2866
+ } else {
2867
+ result = [];
2868
+ first = true;
2869
+ }
2870
+
2871
+ prev = arr[i];
2872
+ }
2873
+
2874
+ return result.length >= size_y ? arr[result[0]] : false;
2875
+ };
2876
+
2877
+
2878
+ /**
2879
+ * Get widgets overlapping with the player.
2880
+ *
2881
+ * @method get_widgets_overlapped
2882
+ * @return {jQuery} Returns a jQuery collection of HTMLElements.
2883
+ */
2884
+ fn.get_widgets_overlapped = function() {
2885
+ var $w;
2886
+ var $widgets = $([]);
2887
+ var used = [];
2888
+ var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0);
2889
+ rows_from_bottom.reverse();
2890
+
2891
+ $.each(this.cells_occupied_by_player.cols, $.proxy(function(i, col) {
2892
+ $.each(rows_from_bottom, $.proxy(function(i, row) {
2893
+ // if there is a widget in the player position
2894
+ if (!this.gridmap[col]) { return true; } //next iteration
2895
+ var $w = this.gridmap[col][row];
2896
+ if (this.is_occupied(col, row) && !this.is_player($w) &&
2897
+ $.inArray($w, used) === -1
2898
+ ) {
2899
+ $widgets = $widgets.add($w);
2900
+ used.push($w);
2901
+ }
2902
+
2903
+ }, this));
2904
+ }, this));
2905
+
2906
+ return $widgets;
2907
+ };
2908
+
2909
+
2910
+ /**
2911
+ * This callback is executed when the player begins to collide with a column.
2912
+ *
2913
+ * @method on_start_overlapping_column
2914
+ * @param {Number} col The collided column.
2915
+ * @return {jQuery} Returns a jQuery collection of HTMLElements.
2916
+ */
2917
+ fn.on_start_overlapping_column = function(col) {
2918
+ this.set_player(col, false);
2919
+ };
2920
+
2921
+
2922
+ /**
2923
+ * A callback executed when the player begins to collide with a row.
2924
+ *
2925
+ * @method on_start_overlapping_row
2926
+ * @param {Number} row The collided row.
2927
+ * @return {jQuery} Returns a jQuery collection of HTMLElements.
2928
+ */
2929
+ fn.on_start_overlapping_row = function(row) {
2930
+ this.set_player(false, row);
2931
+ };
2932
+
2933
+
2934
+ /**
2935
+ * A callback executed when the the player ends to collide with a column.
2936
+ *
2937
+ * @method on_stop_overlapping_column
2938
+ * @param {Number} col The collided row.
2939
+ * @return {jQuery} Returns a jQuery collection of HTMLElements.
2940
+ */
2941
+ fn.on_stop_overlapping_column = function(col) {
2942
+ this.set_player(col, false);
2943
+
2944
+ var self = this;
2945
+ this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0],
2946
+ function(tcol, trow) {
2947
+ self.move_widget_up(this, self.player_grid_data.size_y);
2948
+ });
2949
+ };
2950
+
2951
+
2952
+ /**
2953
+ * This callback is executed when the player ends to collide with a row.
2954
+ *
2955
+ * @method on_stop_overlapping_row
2956
+ * @param {Number} row The collided row.
2957
+ * @return {jQuery} Returns a jQuery collection of HTMLElements.
2958
+ */
2959
+ fn.on_stop_overlapping_row = function(row) {
2960
+ this.set_player(false, row);
2961
+
2962
+ var self = this;
2963
+ var cols = this.cells_occupied_by_player.cols;
2964
+ for (var c = 0, cl = cols.length; c < cl; c++) {
2965
+ this.for_each_widget_below(cols[c], row, function(tcol, trow) {
2966
+ self.move_widget_up(this, self.player_grid_data.size_y);
2967
+ });
2968
+ }
2969
+ };
2970
+
2971
+
2972
+ /**
2973
+ * Move a widget to a specific row. The cell or cells must be empty.
2974
+ * If the widget has widgets below, all of these widgets will be moved also
2975
+ * if they can.
2976
+ *
2977
+ * @method move_widget_to
2978
+ * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the
2979
+ * widget is going to be moved.
2980
+ * @return {Class} Returns the instance of the Gridster Class.
2981
+ */
2982
+ fn.move_widget_to = function($widget, row) {
2983
+ var self = this;
2984
+ var widget_grid_data = $widget.coords().grid;
2985
+ var diff = row - widget_grid_data.row;
2986
+ var $next_widgets = this.widgets_below($widget);
2987
+
2988
+ var can_move_to_new_cell = this.can_move_to(
2989
+ widget_grid_data, widget_grid_data.col, row, $widget);
2990
+
2991
+ if (can_move_to_new_cell === false) {
2992
+ return false;
2993
+ }
2994
+
2995
+ this.remove_from_gridmap(widget_grid_data);
2996
+ widget_grid_data.row = row;
2997
+ this.add_to_gridmap(widget_grid_data);
2998
+ $widget.attr('data-row', row);
2999
+ this.$changed = this.$changed.add($widget);
3000
+
3001
+
3002
+ $next_widgets.each(function(i, widget) {
3003
+ var $w = $(widget);
3004
+ var wgd = $w.coords().grid;
3005
+ var can_go_up = self.can_go_widget_up(wgd);
3006
+ if (can_go_up && can_go_up !== wgd.row) {
3007
+ self.move_widget_to($w, can_go_up);
3008
+ }
3009
+ });
3010
+
3011
+ return this;
3012
+ };
3013
+
3014
+
3015
+ /**
3016
+ * Move up the specified widget and all below it.
3017
+ *
3018
+ * @method move_widget_up
3019
+ * @param {HTMLElement} $widget The widget you want to move.
3020
+ * @param {Number} [y_units] The number of cells that the widget has to move.
3021
+ * @return {Class} Returns the instance of the Gridster Class.
3022
+ */
3023
+ fn.move_widget_up = function($widget, y_units) {
3024
+ var el_grid_data = $widget.coords().grid;
3025
+ var actual_row = el_grid_data.row;
3026
+ var moved = [];
3027
+ var can_go_up = true;
3028
+ y_units || (y_units = 1);
3029
+
3030
+ if (!this.can_go_up($widget)) { return false; } //break;
3031
+
3032
+ this.for_each_column_occupied(el_grid_data, function(col) {
3033
+ // can_go_up
3034
+ if ($.inArray($widget, moved) === -1) {
3035
+ var widget_grid_data = $widget.coords().grid;
3036
+ var next_row = actual_row - y_units;
3037
+ next_row = this.can_go_up_to_row(
3038
+ widget_grid_data, col, next_row);
3039
+
3040
+ if (!next_row) {
3041
+ return true;
3042
+ }
3043
+
3044
+ var $next_widgets = this.widgets_below($widget);
3045
+
3046
+ this.remove_from_gridmap(widget_grid_data);
3047
+ widget_grid_data.row = next_row;
3048
+ this.add_to_gridmap(widget_grid_data);
3049
+ $widget.attr('data-row', widget_grid_data.row);
3050
+ this.$changed = this.$changed.add($widget);
3051
+
3052
+ moved.push($widget);
3053
+
3054
+ $next_widgets.each($.proxy(function(i, widget) {
3055
+ this.move_widget_up($(widget), y_units);
3056
+ }, this));
3057
+ }
3058
+ });
3059
+
3060
+ };
3061
+
3062
+
3063
+ /**
3064
+ * Move down the specified widget and all below it.
3065
+ *
3066
+ * @method move_widget_down
3067
+ * @param {jQuery} $widget The jQuery object representing the widget
3068
+ * you want to move.
3069
+ * @param {Number} y_units The number of cells that the widget has to move.
3070
+ * @return {Class} Returns the instance of the Gridster Class.
3071
+ */
3072
+ fn.move_widget_down = function($widget, y_units) {
3073
+ var el_grid_data, actual_row, moved, y_diff;
3074
+
3075
+ if (y_units <= 0) { return false; }
3076
+
3077
+ el_grid_data = $widget.coords().grid;
3078
+ actual_row = el_grid_data.row;
3079
+ moved = [];
3080
+ y_diff = y_units;
3081
+
3082
+ if (!$widget) { return false; }
3083
+
3084
+ if ($.inArray($widget, moved) === -1) {
3085
+
3086
+ var widget_grid_data = $widget.coords().grid;
3087
+ var next_row = actual_row + y_units;
3088
+ var $next_widgets = this.widgets_below($widget);
3089
+
3090
+ this.remove_from_gridmap(widget_grid_data);
3091
+
3092
+ $next_widgets.each($.proxy(function(i, widget) {
3093
+ var $w = $(widget);
3094
+ var wd = $w.coords().grid;
3095
+ var tmp_y = this.displacement_diff(
3096
+ wd, widget_grid_data, y_diff);
3097
+
3098
+ if (tmp_y > 0) {
3099
+ this.move_widget_down($w, tmp_y);
3100
+ }
3101
+ }, this));
3102
+
3103
+ widget_grid_data.row = next_row;
3104
+ this.update_widget_position(widget_grid_data, $widget);
3105
+ $widget.attr('data-row', widget_grid_data.row);
3106
+ this.$changed = this.$changed.add($widget);
3107
+
3108
+ moved.push($widget);
3109
+ }
3110
+ };
3111
+
3112
+
3113
+ /**
3114
+ * Check if the widget can move to the specified row, else returns the
3115
+ * upper row possible.
3116
+ *
3117
+ * @method can_go_up_to_row
3118
+ * @param {Number} widget_grid_data The current grid coords object of the
3119
+ * widget.
3120
+ * @param {Number} col The target column.
3121
+ * @param {Number} row The target row.
3122
+ * @return {Boolean|Number} Returns the row number if the widget can move
3123
+ * to the target position, else returns false.
3124
+ */
3125
+ fn.can_go_up_to_row = function(widget_grid_data, col, row) {
3126
+ var ga = this.gridmap;
3127
+ var result = true;
3128
+ var urc = []; // upper_rows_in_columns
3129
+ var actual_row = widget_grid_data.row;
3130
+ var r;
3131
+
3132
+ /* generate an array with columns as index and array with
3133
+ * upper rows empty in the column */
3134
+ this.for_each_column_occupied(widget_grid_data, function(tcol) {
3135
+ var grid_col = ga[tcol];
3136
+ urc[tcol] = [];
3137
+
3138
+ r = actual_row;
3139
+ while (r--) {
3140
+ if (this.is_empty(tcol, r) &&
3141
+ !this.is_placeholder_in(tcol, r)
3142
+ ) {
3143
+ urc[tcol].push(r);
3144
+ } else {
3145
+ break;
3146
+ }
3147
+ }
3148
+
3149
+ if (!urc[tcol].length) {
3150
+ result = false;
3151
+ return true;
3152
+ }
3153
+
3154
+ });
3155
+
3156
+ if (!result) { return false; }
3157
+
3158
+ /* get common rows starting from upper position in all the columns
3159
+ * that widget occupies */
3160
+ r = row;
3161
+ for (r = 1; r < actual_row; r++) {
3162
+ var common = true;
3163
+
3164
+ for (var uc = 0, ucl = urc.length; uc < ucl; uc++) {
3165
+ if (urc[uc] && $.inArray(r, urc[uc]) === -1) {
3166
+ common = false;
3167
+ }
3168
+ }
3169
+
3170
+ if (common === true) {
3171
+ result = r;
3172
+ break;
3173
+ }
3174
+ }
3175
+
3176
+ return result;
3177
+ };
3178
+
3179
+
3180
+ fn.displacement_diff = function(widget_grid_data, parent_bgd, y_units) {
3181
+ var actual_row = widget_grid_data.row;
3182
+ var diffs = [];
3183
+ var parent_max_y = parent_bgd.row + parent_bgd.size_y;
3184
+
3185
+ this.for_each_column_occupied(widget_grid_data, function(col) {
3186
+ var temp_y_units = 0;
3187
+
3188
+ for (var r = parent_max_y; r < actual_row; r++) {
3189
+ if (this.is_empty(col, r)) {
3190
+ temp_y_units = temp_y_units + 1;
3191
+ }
3192
+ }
3193
+
3194
+ diffs.push(temp_y_units);
3195
+ });
3196
+
3197
+ var max_diff = Math.max.apply(Math, diffs);
3198
+ y_units = (y_units - max_diff);
3199
+
3200
+ return y_units > 0 ? y_units : 0;
3201
+ };
3202
+
3203
+
3204
+ /**
3205
+ * Get widgets below a widget.
3206
+ *
3207
+ * @method widgets_below
3208
+ * @param {HTMLElement} $el The jQuery wrapped HTMLElement.
3209
+ * @return {jQuery} A jQuery collection of HTMLElements.
3210
+ */
3211
+ fn.widgets_below = function($el) {
3212
+ var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid;
3213
+ var self = this;
3214
+ var ga = this.gridmap;
3215
+ var next_row = el_grid_data.row + el_grid_data.size_y - 1;
3216
+ var $nexts = $([]);
3217
+
3218
+ this.for_each_column_occupied(el_grid_data, function(col) {
3219
+ self.for_each_widget_below(col, next_row, function(tcol, trow) {
3220
+ if (!self.is_player(this) && $.inArray(this, $nexts) === -1) {
3221
+ $nexts = $nexts.add(this);
3222
+ return true; // break
3223
+ }
3224
+ });
3225
+ });
3226
+
3227
+ return Gridster.sort_by_row_asc($nexts);
3228
+ };
3229
+
3230
+
3231
+ /**
3232
+ * Update the array of mapped positions with the new player position.
3233
+ *
3234
+ * @method set_cells_player_occupies
3235
+ * @param {Number} col The new player col.
3236
+ * @param {Number} col The new player row.
3237
+ * @return {Class} Returns the instance of the Gridster Class.
3238
+ */
3239
+ fn.set_cells_player_occupies = function(col, row) {
3240
+ this.remove_from_gridmap(this.placeholder_grid_data);
3241
+ this.placeholder_grid_data.col = col;
3242
+ this.placeholder_grid_data.row = row;
3243
+ this.add_to_gridmap(this.placeholder_grid_data, this.$player);
3244
+ return this;
3245
+ };
3246
+
3247
+
3248
+ /**
3249
+ * Remove from the array of mapped positions the reference to the player.
3250
+ *
3251
+ * @method empty_cells_player_occupies
3252
+ * @return {Class} Returns the instance of the Gridster Class.
3253
+ */
3254
+ fn.empty_cells_player_occupies = function() {
3255
+ this.remove_from_gridmap(this.placeholder_grid_data);
3256
+ return this;
3257
+ };
3258
+
3259
+
3260
+ fn.can_go_up = function($el) {
3261
+ var el_grid_data = $el.coords().grid;
3262
+ var initial_row = el_grid_data.row;
3263
+ var prev_row = initial_row - 1;
3264
+ var ga = this.gridmap;
3265
+ var upper_rows_by_column = [];
3266
+
3267
+ var result = true;
3268
+ if (initial_row === 1) { return false; }
3269
+
3270
+ this.for_each_column_occupied(el_grid_data, function(col) {
3271
+ var $w = this.is_widget(col, prev_row);
3272
+
3273
+ if (this.is_occupied(col, prev_row) ||
3274
+ this.is_player(col, prev_row) ||
3275
+ this.is_placeholder_in(col, prev_row) ||
3276
+ this.is_player_in(col, prev_row)
3277
+ ) {
3278
+ result = false;
3279
+ return true; //break
3280
+ }
3281
+ });
3282
+
3283
+ return result;
3284
+ };
3285
+
3286
+
3287
+ /**
3288
+ * Check if it's possible to move a widget to a specific col/row. It takes
3289
+ * into account the dimensions (`size_y` and `size_x` attrs. of the grid
3290
+ * coords object) the widget occupies.
3291
+ *
3292
+ * @method can_move_to
3293
+ * @param {Object} widget_grid_data The grid coords object that represents
3294
+ * the widget.
3295
+ * @param {Object} col The col to check.
3296
+ * @param {Object} row The row to check.
3297
+ * @param {Number} [max_row] The max row allowed.
3298
+ * @return {Boolean} Returns true if all cells are empty, else return false.
3299
+ */
3300
+ fn.can_move_to = function(widget_grid_data, col, row, max_row) {
3301
+ var ga = this.gridmap;
3302
+ var $w = widget_grid_data.el;
3303
+ var future_wd = {
3304
+ size_y: widget_grid_data.size_y,
3305
+ size_x: widget_grid_data.size_x,
3306
+ col: col,
3307
+ row: row
3308
+ };
3309
+ var result = true;
3310
+
3311
+ //Prevents widgets go out of the grid
3312
+ var right_col = col + widget_grid_data.size_x - 1;
3313
+ if (right_col > this.cols) {
3314
+ return false;
3315
+ }
3316
+
3317
+ if (max_row && max_row < row + widget_grid_data.size_y - 1) {
3318
+ return false;
3319
+ }
3320
+
3321
+ this.for_each_cell_occupied(future_wd, function(tcol, trow) {
3322
+ var $tw = this.is_widget(tcol, trow);
3323
+ if ($tw && (!widget_grid_data.el || $tw.is($w))) {
3324
+ result = false;
3325
+ }
3326
+ });
3327
+
3328
+ return result;
3329
+ };
3330
+
3331
+
3332
+ /**
3333
+ * Given the leftmost column returns all columns that are overlapping
3334
+ * with the player.
3335
+ *
3336
+ * @method get_targeted_columns
3337
+ * @param {Number} [from_col] The leftmost column.
3338
+ * @return {Array} Returns an array with column numbers.
3339
+ */
3340
+ fn.get_targeted_columns = function(from_col) {
3341
+ var max = (from_col || this.player_grid_data.col) +
3342
+ (this.player_grid_data.size_x - 1);
3343
+ var cols = [];
3344
+ for (var col = from_col; col <= max; col++) {
3345
+ cols.push(col);
3346
+ }
3347
+ return cols;
3348
+ };
3349
+
3350
+
3351
+ /**
3352
+ * Given the upper row returns all rows that are overlapping with the player.
3353
+ *
3354
+ * @method get_targeted_rows
3355
+ * @param {Number} [from_row] The upper row.
3356
+ * @return {Array} Returns an array with row numbers.
3357
+ */
3358
+ fn.get_targeted_rows = function(from_row) {
3359
+ var max = (from_row || this.player_grid_data.row) +
3360
+ (this.player_grid_data.size_y - 1);
3361
+ var rows = [];
3362
+ for (var row = from_row; row <= max; row++) {
3363
+ rows.push(row);
3364
+ }
3365
+ return rows;
3366
+ };
3367
+
3368
+ /**
3369
+ * Get all columns and rows that a widget occupies.
3370
+ *
3371
+ * @method get_cells_occupied
3372
+ * @param {Object} el_grid_data The grid coords object of the widget.
3373
+ * @return {Object} Returns an object like `{ cols: [], rows: []}`.
3374
+ */
3375
+ fn.get_cells_occupied = function(el_grid_data) {
3376
+ var cells = { cols: [], rows: []};
3377
+ var i;
3378
+ if (arguments[1] instanceof $) {
3379
+ el_grid_data = arguments[1].coords().grid;
3380
+ }
3381
+
3382
+ for (i = 0; i < el_grid_data.size_x; i++) {
3383
+ var col = el_grid_data.col + i;
3384
+ cells.cols.push(col);
3385
+ }
3386
+
3387
+ for (i = 0; i < el_grid_data.size_y; i++) {
3388
+ var row = el_grid_data.row + i;
3389
+ cells.rows.push(row);
3390
+ }
3391
+
3392
+ return cells;
3393
+ };
3394
+
3395
+
3396
+ /**
3397
+ * Iterate over the cells occupied by a widget executing a function for
3398
+ * each one.
3399
+ *
3400
+ * @method for_each_cell_occupied
3401
+ * @param {Object} el_grid_data The grid coords object that represents the
3402
+ * widget.
3403
+ * @param {Function} callback The function to execute on each column
3404
+ * iteration. Column and row are passed as arguments.
3405
+ * @return {Class} Returns the instance of the Gridster Class.
3406
+ */
3407
+ fn.for_each_cell_occupied = function(grid_data, callback) {
3408
+ this.for_each_column_occupied(grid_data, function(col) {
3409
+ this.for_each_row_occupied(grid_data, function(row) {
3410
+ callback.call(this, col, row);
3411
+ });
3412
+ });
3413
+ return this;
3414
+ };
3415
+
3416
+
3417
+ /**
3418
+ * Iterate over the columns occupied by a widget executing a function for
3419
+ * each one.
3420
+ *
3421
+ * @method for_each_column_occupied
3422
+ * @param {Object} el_grid_data The grid coords object that represents
3423
+ * the widget.
3424
+ * @param {Function} callback The function to execute on each column
3425
+ * iteration. The column number is passed as first argument.
3426
+ * @return {Class} Returns the instance of the Gridster Class.
3427
+ */
3428
+ fn.for_each_column_occupied = function(el_grid_data, callback) {
3429
+ for (var i = 0; i < el_grid_data.size_x; i++) {
3430
+ var col = el_grid_data.col + i;
3431
+ callback.call(this, col, el_grid_data);
3432
+ }
3433
+ };
3434
+
3435
+
3436
+ /**
3437
+ * Iterate over the rows occupied by a widget executing a function for
3438
+ * each one.
3439
+ *
3440
+ * @method for_each_row_occupied
3441
+ * @param {Object} el_grid_data The grid coords object that represents
3442
+ * the widget.
3443
+ * @param {Function} callback The function to execute on each column
3444
+ * iteration. The row number is passed as first argument.
3445
+ * @return {Class} Returns the instance of the Gridster Class.
3446
+ */
3447
+ fn.for_each_row_occupied = function(el_grid_data, callback) {
3448
+ for (var i = 0; i < el_grid_data.size_y; i++) {
3449
+ var row = el_grid_data.row + i;
3450
+ callback.call(this, row, el_grid_data);
3451
+ }
3452
+ };
3453
+
3454
+
3455
+
3456
+ fn._traversing_widgets = function(type, direction, col, row, callback) {
3457
+ var ga = this.gridmap;
3458
+ if (!ga[col]) { return; }
3459
+
3460
+ var cr, max;
3461
+ var action = type + '/' + direction;
3462
+ if (arguments[2] instanceof $) {
3463
+ var el_grid_data = arguments[2].coords().grid;
3464
+ col = el_grid_data.col;
3465
+ row = el_grid_data.row;
3466
+ callback = arguments[3];
3467
+ }
3468
+ var matched = [];
3469
+ var trow = row;
3470
+
3471
+
3472
+ var methods = {
3473
+ 'for_each/above': function() {
3474
+ while (trow--) {
3475
+ if (trow > 0 && this.is_widget(col, trow) &&
3476
+ $.inArray(ga[col][trow], matched) === -1
3477
+ ) {
3478
+ cr = callback.call(ga[col][trow], col, trow);
3479
+ matched.push(ga[col][trow]);
3480
+ if (cr) { break; }
3481
+ }
3482
+ }
3483
+ },
3484
+ 'for_each/below': function() {
3485
+ for (trow = row + 1, max = ga[col].length; trow < max; trow++) {
3486
+ if (this.is_widget(col, trow) &&
3487
+ $.inArray(ga[col][trow], matched) === -1
3488
+ ) {
3489
+ cr = callback.call(ga[col][trow], col, trow);
3490
+ matched.push(ga[col][trow]);
3491
+ if (cr) { break; }
3492
+ }
3493
+ }
3494
+ }
3495
+ };
3496
+
3497
+ if (methods[action]) {
3498
+ methods[action].call(this);
3499
+ }
3500
+ };
3501
+
3502
+
3503
+ /**
3504
+ * Iterate over each widget above the column and row specified.
3505
+ *
3506
+ * @method for_each_widget_above
3507
+ * @param {Number} col The column to start iterating.
3508
+ * @param {Number} row The row to start iterating.
3509
+ * @param {Function} callback The function to execute on each widget
3510
+ * iteration. The value of `this` inside the function is the jQuery
3511
+ * wrapped HTMLElement.
3512
+ * @return {Class} Returns the instance of the Gridster Class.
3513
+ */
3514
+ fn.for_each_widget_above = function(col, row, callback) {
3515
+ this._traversing_widgets('for_each', 'above', col, row, callback);
3516
+ return this;
3517
+ };
3518
+
3519
+
3520
+ /**
3521
+ * Iterate over each widget below the column and row specified.
3522
+ *
3523
+ * @method for_each_widget_below
3524
+ * @param {Number} col The column to start iterating.
3525
+ * @param {Number} row The row to start iterating.
3526
+ * @param {Function} callback The function to execute on each widget
3527
+ * iteration. The value of `this` inside the function is the jQuery wrapped
3528
+ * HTMLElement.
3529
+ * @return {Class} Returns the instance of the Gridster Class.
3530
+ */
3531
+ fn.for_each_widget_below = function(col, row, callback) {
3532
+ this._traversing_widgets('for_each', 'below', col, row, callback);
3533
+ return this;
3534
+ };
3535
+
3536
+
3537
+ /**
3538
+ * Returns the highest occupied cell in the grid.
3539
+ *
3540
+ * @method get_highest_occupied_cell
3541
+ * @return {Object} Returns an object with `col` and `row` numbers.
3542
+ */
3543
+ fn.get_highest_occupied_cell = function() {
3544
+ var r;
3545
+ var gm = this.gridmap;
3546
+ var rl = gm[1].length;
3547
+ var rows = [], cols = [];
3548
+ var row_in_col = [];
3549
+ for (var c = gm.length - 1; c >= 1; c--) {
3550
+ for (r = rl - 1; r >= 1; r--) {
3551
+ if (this.is_widget(c, r)) {
3552
+ rows.push(r);
3553
+ cols.push(c);
3554
+ break;
3555
+ }
3556
+ }
3557
+ }
3558
+
3559
+ return {
3560
+ col: Math.max.apply(Math, cols),
3561
+ row: Math.max.apply(Math, rows)
3562
+ };
3563
+ };
3564
+
3565
+
3566
+ fn.get_widgets_from = function(col, row) {
3567
+ var ga = this.gridmap;
3568
+ var $widgets = $();
3569
+
3570
+ if (col) {
3571
+ $widgets = $widgets.add(
3572
+ this.$widgets.filter(function() {
3573
+ var tcol = $(this).attr('data-col');
3574
+ return (tcol === col || tcol > col);
3575
+ })
3576
+ );
3577
+ }
3578
+
3579
+ if (row) {
3580
+ $widgets = $widgets.add(
3581
+ this.$widgets.filter(function() {
3582
+ var trow = $(this).attr('data-row');
3583
+ return (trow === row || trow > row);
3584
+ })
3585
+ );
3586
+ }
3587
+
3588
+ return $widgets;
3589
+ };
3590
+
3591
+
3592
+ /**
3593
+ * Set the current height of the parent grid.
3594
+ *
3595
+ * @method set_dom_grid_height
3596
+ * @return {Object} Returns the instance of the Gridster class.
3597
+ */
3598
+ fn.set_dom_grid_height = function(height) {
3599
+ if (typeof height === 'undefined') {
3600
+ var r = this.get_highest_occupied_cell().row;
3601
+ height = r * this.min_widget_height;
3602
+ }
3603
+
3604
+ this.container_height = height;
3605
+ this.$el.css('height', this.container_height);
3606
+ return this;
3607
+ };
3608
+
3609
+ /**
3610
+ * Set the current width of the parent grid.
3611
+ *
3612
+ * @method set_dom_grid_width
3613
+ * @return {Object} Returns the instance of the Gridster class.
3614
+ */
3615
+ fn.set_dom_grid_width = function(cols) {
3616
+ if (typeof cols === 'undefined') {
3617
+ cols = this.get_highest_occupied_cell().col;
3618
+ }
3619
+
3620
+ var max_cols = (this.options.autogrow_cols ? this.options.max_cols :
3621
+ this.cols);
3622
+
3623
+ cols = Math.min(max_cols, Math.max(cols, this.options.min_cols));
3624
+ this.container_width = cols * this.min_widget_width;
3625
+ this.$el.css('width', this.container_width);
3626
+ return this;
3627
+ };
3628
+
3629
+
3630
+ /**
3631
+ * It generates the neccessary styles to position the widgets.
3632
+ *
3633
+ * @method generate_stylesheet
3634
+ * @param {Number} rows Number of columns.
3635
+ * @param {Number} cols Number of rows.
3636
+ * @return {Object} Returns the instance of the Gridster class.
3637
+ */
3638
+ fn.generate_stylesheet = function(opts) {
3639
+ var styles = '';
3640
+ var max_size_x = this.options.max_size_x || this.cols;
3641
+ var max_rows = 0;
3642
+ var max_cols = 0;
3643
+ var i;
3644
+ var rules;
3645
+
3646
+ opts || (opts = {});
3647
+ opts.cols || (opts.cols = this.cols);
3648
+ opts.rows || (opts.rows = this.rows);
3649
+ opts.namespace || (opts.namespace = this.options.namespace);
3650
+ opts.widget_base_dimensions ||
3651
+ (opts.widget_base_dimensions = this.options.widget_base_dimensions);
3652
+ opts.widget_margins ||
3653
+ (opts.widget_margins = this.options.widget_margins);
3654
+ opts.min_widget_width = (opts.widget_margins[0] * 2) +
3655
+ opts.widget_base_dimensions[0];
3656
+ opts.min_widget_height = (opts.widget_margins[1] * 2) +
3657
+ opts.widget_base_dimensions[1];
3658
+
3659
+ // don't duplicate stylesheets for the same configuration
3660
+ var serialized_opts = $.param(opts);
3661
+ if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) {
3662
+ return false;
3663
+ }
3664
+
3665
+ this.generated_stylesheets.push(serialized_opts);
3666
+ Gridster.generated_stylesheets.push(serialized_opts);
3667
+
3668
+ /* generate CSS styles for cols */
3669
+ for (i = opts.cols; i >= 0; i--) {
3670
+ styles += (opts.namespace + ' [data-col="'+ (i + 1) + '"] { left:' +
3671
+ ((i * opts.widget_base_dimensions[0]) +
3672
+ (i * opts.widget_margins[0]) +
3673
+ ((i + 1) * opts.widget_margins[0])) + 'px; }\n');
3674
+ }
3675
+
3676
+ /* generate CSS styles for rows */
3677
+ for (i = opts.rows; i >= 0; i--) {
3678
+ styles += (opts.namespace + ' [data-row="' + (i + 1) + '"] { top:' +
3679
+ ((i * opts.widget_base_dimensions[1]) +
3680
+ (i * opts.widget_margins[1]) +
3681
+ ((i + 1) * opts.widget_margins[1]) ) + 'px; }\n');
3682
+ }
3683
+
3684
+ for (var y = 1; y <= opts.rows; y++) {
3685
+ styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' +
3686
+ (y * opts.widget_base_dimensions[1] +
3687
+ (y - 1) * (opts.widget_margins[1] * 2)) + 'px; }\n');
3688
+ }
3689
+
3690
+ for (var x = 1; x <= max_size_x; x++) {
3691
+ styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' +
3692
+ (x * opts.widget_base_dimensions[0] +
3693
+ (x - 1) * (opts.widget_margins[0] * 2)) + 'px; }\n');
3694
+ }
3695
+
3696
+ this.remove_style_tags();
3697
+
3698
+ return this.add_style_tag(styles);
3699
+ };
3700
+
3701
+
3702
+ /**
3703
+ * Injects the given CSS as string to the head of the document.
3704
+ *
3705
+ * @method add_style_tag
3706
+ * @param {String} css The styles to apply.
3707
+ * @return {Object} Returns the instance of the Gridster class.
3708
+ */
3709
+ fn.add_style_tag = function(css) {
3710
+ var d = document;
3711
+ var tag = d.createElement('style');
3712
+
3713
+ d.getElementsByTagName('head')[0].appendChild(tag);
3714
+ tag.setAttribute('type', 'text/css');
3715
+
3716
+ if (tag.styleSheet) {
3717
+ tag.styleSheet.cssText = css;
3718
+ } else {
3719
+ tag.appendChild(document.createTextNode(css));
3720
+ }
3721
+
3722
+ this.$style_tags = this.$style_tags.add(tag);
3723
+
3724
+ return this;
3725
+ };
3726
+
3727
+
3728
+ /**
3729
+ * Remove the style tag with the associated id from the head of the document
3730
+ *
3731
+ * @method remove_style_tag
3732
+ * @return {Object} Returns the instance of the Gridster class.
3733
+ */
3734
+ fn.remove_style_tags = function() {
3735
+ var all_styles = Gridster.generated_stylesheets;
3736
+ var ins_styles = this.generated_stylesheets;
3737
+
3738
+ this.$style_tags.remove();
3739
+
3740
+ Gridster.generated_stylesheets = $.map(all_styles, function(s) {
3741
+ if ($.inArray(s, ins_styles) === -1) { return s; }
3742
+ });
3743
+ };
3744
+
3745
+
3746
+ /**
3747
+ * Generates a faux grid to collide with it when a widget is dragged and
3748
+ * detect row or column that we want to go.
3749
+ *
3750
+ * @method generate_faux_grid
3751
+ * @param {Number} rows Number of columns.
3752
+ * @param {Number} cols Number of rows.
3753
+ * @return {Object} Returns the instance of the Gridster class.
3754
+ */
3755
+ fn.generate_faux_grid = function(rows, cols) {
3756
+ this.faux_grid = [];
3757
+ this.gridmap = [];
3758
+ var col;
3759
+ var row;
3760
+ for (col = cols; col > 0; col--) {
3761
+ this.gridmap[col] = [];
3762
+ for (row = rows; row > 0; row--) {
3763
+ this.add_faux_cell(row, col);
3764
+ }
3765
+ }
3766
+ return this;
3767
+ };
3768
+
3769
+
3770
+ /**
3771
+ * Add cell to the faux grid.
3772
+ *
3773
+ * @method add_faux_cell
3774
+ * @param {Number} row The row for the new faux cell.
3775
+ * @param {Number} col The col for the new faux cell.
3776
+ * @return {Object} Returns the instance of the Gridster class.
3777
+ */
3778
+ fn.add_faux_cell = function(row, col) {
3779
+ var coords = $({
3780
+ left: this.baseX + ((col - 1) * this.min_widget_width),
3781
+ top: this.baseY + (row -1) * this.min_widget_height,
3782
+ width: this.min_widget_width,
3783
+ height: this.min_widget_height,
3784
+ col: col,
3785
+ row: row,
3786
+ original_col: col,
3787
+ original_row: row
3788
+ }).coords();
3789
+
3790
+ if (!$.isArray(this.gridmap[col])) {
3791
+ this.gridmap[col] = [];
3792
+ }
3793
+
3794
+ this.gridmap[col][row] = false;
3795
+ this.faux_grid.push(coords);
3796
+
3797
+ return this;
3798
+ };
3799
+
3800
+
3801
+ /**
3802
+ * Add rows to the faux grid.
3803
+ *
3804
+ * @method add_faux_rows
3805
+ * @param {Number} rows The number of rows you want to add to the faux grid.
3806
+ * @return {Object} Returns the instance of the Gridster class.
3807
+ */
3808
+ fn.add_faux_rows = function(rows) {
3809
+ var actual_rows = this.rows;
3810
+ var max_rows = actual_rows + (rows || 1);
3811
+
3812
+ for (var r = max_rows; r > actual_rows; r--) {
3813
+ for (var c = this.cols; c >= 1; c--) {
3814
+ this.add_faux_cell(r, c);
3815
+ }
3816
+ }
3817
+
3818
+ this.rows = max_rows;
3819
+
3820
+ if (this.options.autogenerate_stylesheet) {
3821
+ this.generate_stylesheet();
3822
+ }
3823
+
3824
+ return this;
3825
+ };
3826
+
3827
+ /**
3828
+ * Add cols to the faux grid.
3829
+ *
3830
+ * @method add_faux_cols
3831
+ * @param {Number} cols The number of cols you want to add to the faux grid.
3832
+ * @return {Object} Returns the instance of the Gridster class.
3833
+ */
3834
+ fn.add_faux_cols = function(cols) {
3835
+ var actual_cols = this.cols;
3836
+ var max_cols = actual_cols + (cols || 1);
3837
+ max_cols = Math.min(max_cols, this.options.max_cols);
3838
+
3839
+ for (var c = actual_cols + 1; c <= max_cols; c++) {
3840
+ for (var r = this.rows; r >= 1; r--) {
3841
+ this.add_faux_cell(r, c);
3842
+ }
3843
+ }
3844
+
3845
+ this.cols = max_cols;
3846
+
3847
+ if (this.options.autogenerate_stylesheet) {
3848
+ this.generate_stylesheet();
3849
+ }
3850
+
3851
+ return this;
3852
+ };
3853
+
3854
+
3855
+ /**
3856
+ * Recalculates the offsets for the faux grid. You need to use it when
3857
+ * the browser is resized.
3858
+ *
3859
+ * @method recalculate_faux_grid
3860
+ * @return {Object} Returns the instance of the Gridster class.
3861
+ */
3862
+ fn.recalculate_faux_grid = function() {
3863
+ var aw = this.$wrapper.width();
3864
+ this.baseX = ($(window).width() - aw) / 2;
3865
+ this.baseY = this.$wrapper.offset().top;
3866
+
3867
+ $.each(this.faux_grid, $.proxy(function(i, coords) {
3868
+ this.faux_grid[i] = coords.update({
3869
+ left: this.baseX + (coords.data.col -1) * this.min_widget_width,
3870
+ top: this.baseY + (coords.data.row -1) * this.min_widget_height
3871
+ });
3872
+ }, this));
3873
+
3874
+ return this;
3875
+ };
3876
+
3877
+
3878
+ /**
3879
+ * Get all widgets in the DOM and register them.
3880
+ *
3881
+ * @method get_widgets_from_DOM
3882
+ * @return {Object} Returns the instance of the Gridster class.
3883
+ */
3884
+ fn.get_widgets_from_DOM = function() {
3885
+ var widgets_coords = this.$widgets.map($.proxy(function(i, widget) {
3886
+ var $w = $(widget);
3887
+ return this.dom_to_coords($w);
3888
+ }, this));
3889
+
3890
+ widgets_coords = Gridster.sort_by_row_and_col_asc(widgets_coords);
3891
+
3892
+ var changes = $(widgets_coords).map($.proxy(function(i, wgd) {
3893
+ return this.register_widget(wgd) || null;
3894
+ }, this));
3895
+
3896
+ if (changes.length) {
3897
+ this.$el.trigger('gridster:positionschanged');
3898
+ }
3899
+
3900
+ return this;
3901
+ };
3902
+
3903
+
3904
+ /**
3905
+ * Calculate columns and rows to be set based on the configuration
3906
+ * parameters, grid dimensions, etc ...
3907
+ *
3908
+ * @method generate_grid_and_stylesheet
3909
+ * @return {Object} Returns the instance of the Gridster class.
3910
+ */
3911
+ fn.generate_grid_and_stylesheet = function() {
3912
+ var aw = this.$wrapper.width();
3913
+ var max_cols = this.options.max_cols;
3914
+
3915
+ var cols = Math.floor(aw / this.min_widget_width) +
3916
+ this.options.extra_cols;
3917
+
3918
+ var actual_cols = this.$widgets.map(function() {
3919
+ return $(this).attr('data-col');
3920
+ }).get();
3921
+
3922
+ //needed to pass tests with phantomjs
3923
+ actual_cols.length || (actual_cols = [0]);
3924
+
3925
+ var min_cols = Math.max.apply(Math, actual_cols);
3926
+
3927
+ this.cols = Math.max(min_cols, cols, this.options.min_cols);
3928
+
3929
+ if (max_cols !== Infinity && max_cols >= min_cols && max_cols < this.cols) {
3930
+ this.cols = max_cols;
3931
+ }
3932
+
3933
+ // get all rows that could be occupied by the current widgets
3934
+ var max_rows = this.options.extra_rows;
3935
+ this.$widgets.each(function(i, w) {
3936
+ max_rows += (+$(w).attr('data-sizey'));
3937
+ });
3938
+
3939
+ this.rows = Math.max(max_rows, this.options.min_rows);
3940
+
3941
+ this.baseX = ($(window).width() - aw) / 2;
3942
+ this.baseY = this.$wrapper.offset().top;
3943
+
3944
+ if (this.options.autogenerate_stylesheet) {
3945
+ this.generate_stylesheet();
3946
+ }
3947
+
3948
+ return this.generate_faux_grid(this.rows, this.cols);
3949
+ };
3950
+
3951
+ /**
3952
+ * Destroy this gridster by removing any sign of its presence, making it easy to avoid memory leaks
3953
+ *
3954
+ * @method destroy
3955
+ * @param {Boolean} remove If true, remove gridster from DOM.
3956
+ * @return {Object} Returns the instance of the Gridster class.
3957
+ */
3958
+ fn.destroy = function(remove) {
3959
+ this.$el.removeData('gridster');
3960
+
3961
+ // remove bound callback on window resize
3962
+ $(window).unbind('.gridster');
3963
+
3964
+ if (this.drag_api) {
3965
+ this.drag_api.destroy();
3966
+ }
3967
+
3968
+ this.remove_style_tags();
3969
+
3970
+ remove && this.$el.remove();
3971
+
3972
+ return this;
3973
+ };
3974
+
3975
+
3976
+ //jQuery adapter
3977
+ $.fn.gridster = function(options) {
3978
+ return this.each(function() {
3979
+ if (! $(this).data('gridster')) {
3980
+ $(this).data('gridster', new Gridster( this, options ));
3981
+ }
3982
+ });
3983
+ };
3984
+
3985
+ return Gridster;
3986
+
3987
+ }));