PageRenderTime 33ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/files/gridster.js/0.5.2/jquery.gridster.js

https://gitlab.com/Mirros/jsdelivr
JavaScript | 1559 lines | 922 code | 284 blank | 353 comment | 135 complexity | 1abac28028c5132d3d5edfce12d97d5b MD5 | raw file
  1. /*! gridster.js - v0.5.2 - 2014-06-16
  2. * http://gridster.net/
  3. * Copyright (c) 2014 ducksboard; Licensed MIT */
  4. ;(function($, window, document, undefined){
  5. /**
  6. * Creates objects with coordinates (x1, y1, x2, y2, cx, cy, width, height)
  7. * to simulate DOM elements on the screen.
  8. * Coords is used by Gridster to create a faux grid with any DOM element can
  9. * collide.
  10. *
  11. * @class Coords
  12. * @param {HTMLElement|Object} obj The jQuery HTMLElement or a object with: left,
  13. * top, width and height properties.
  14. * @return {Object} Coords instance.
  15. * @constructor
  16. */
  17. function Coords(obj) {
  18. if (obj[0] && $.isPlainObject(obj[0])) {
  19. this.data = obj[0];
  20. }else {
  21. this.el = obj;
  22. }
  23. this.isCoords = true;
  24. this.coords = {};
  25. this.init();
  26. return this;
  27. }
  28. var fn = Coords.prototype;
  29. fn.init = function(){
  30. this.set();
  31. this.original_coords = this.get();
  32. };
  33. fn.set = function(update, not_update_offsets) {
  34. var el = this.el;
  35. if (el && !update) {
  36. this.data = el.offset();
  37. this.data.width = el.width();
  38. this.data.height = el.height();
  39. }
  40. if (el && update && !not_update_offsets) {
  41. var offset = el.offset();
  42. this.data.top = offset.top;
  43. this.data.left = offset.left;
  44. }
  45. var d = this.data;
  46. typeof d.left === 'undefined' && (d.left = d.x1);
  47. typeof d.top === 'undefined' && (d.top = d.y1);
  48. this.coords.x1 = d.left;
  49. this.coords.y1 = d.top;
  50. this.coords.x2 = d.left + d.width;
  51. this.coords.y2 = d.top + d.height;
  52. this.coords.cx = d.left + (d.width / 2);
  53. this.coords.cy = d.top + (d.height / 2);
  54. this.coords.width = d.width;
  55. this.coords.height = d.height;
  56. this.coords.el = el || false ;
  57. return this;
  58. };
  59. fn.update = function(data){
  60. if (!data && !this.el) {
  61. return this;
  62. }
  63. if (data) {
  64. var new_data = $.extend({}, this.data, data);
  65. this.data = new_data;
  66. return this.set(true, true);
  67. }
  68. this.set(true);
  69. return this;
  70. };
  71. fn.get = function(){
  72. return this.coords;
  73. };
  74. fn.destroy = function() {
  75. this.el.removeData('coords');
  76. delete this.el;
  77. };
  78. //jQuery adapter
  79. $.fn.coords = function() {
  80. if (this.data('coords') ) {
  81. return this.data('coords');
  82. }
  83. var ins = new Coords(this, arguments[0]);
  84. this.data('coords', ins);
  85. return ins;
  86. };
  87. }(jQuery, window, document));
  88. ;(function($, window, document, undefined){
  89. var defaults = {
  90. colliders_context: document.body,
  91. overlapping_region: 'C'
  92. // ,on_overlap: function(collider_data){},
  93. // on_overlap_start : function(collider_data){},
  94. // on_overlap_stop : function(collider_data){}
  95. };
  96. /**
  97. * Detects collisions between a DOM element against other DOM elements or
  98. * Coords objects.
  99. *
  100. * @class Collision
  101. * @uses Coords
  102. * @param {HTMLElement} el The jQuery wrapped HTMLElement.
  103. * @param {HTMLElement|Array} colliders Can be a jQuery collection
  104. * of HTMLElements or an Array of Coords instances.
  105. * @param {Object} [options] An Object with all options you want to
  106. * overwrite:
  107. * @param {String} [options.overlapping_region] Determines when collision
  108. * is valid, depending on the overlapped area. Values can be: 'N', 'S',
  109. * 'W', 'E', 'C' or 'all'. Default is 'C'.
  110. * @param {Function} [options.on_overlap_start] Executes a function the first
  111. * time each `collider ` is overlapped.
  112. * @param {Function} [options.on_overlap_stop] Executes a function when a
  113. * `collider` is no longer collided.
  114. * @param {Function} [options.on_overlap] Executes a function when the
  115. * mouse is moved during the collision.
  116. * @return {Object} Collision instance.
  117. * @constructor
  118. */
  119. function Collision(el, colliders, options) {
  120. this.options = $.extend(defaults, options);
  121. this.$element = el;
  122. this.last_colliders = [];
  123. this.last_colliders_coords = [];
  124. this.set_colliders(colliders);
  125. this.init();
  126. }
  127. var fn = Collision.prototype;
  128. fn.init = function() {
  129. this.find_collisions();
  130. };
  131. fn.overlaps = function(a, b) {
  132. var x = false;
  133. var y = false;
  134. if ((b.x1 >= a.x1 && b.x1 <= a.x2) ||
  135. (b.x2 >= a.x1 && b.x2 <= a.x2) ||
  136. (a.x1 >= b.x1 && a.x2 <= b.x2)
  137. ) { x = true; }
  138. if ((b.y1 >= a.y1 && b.y1 <= a.y2) ||
  139. (b.y2 >= a.y1 && b.y2 <= a.y2) ||
  140. (a.y1 >= b.y1 && a.y2 <= b.y2)
  141. ) { y = true; }
  142. return (x && y);
  143. };
  144. fn.detect_overlapping_region = function(a, b){
  145. var regionX = '';
  146. var regionY = '';
  147. if (a.y1 > b.cy && a.y1 < b.y2) { regionX = 'N'; }
  148. if (a.y2 > b.y1 && a.y2 < b.cy) { regionX = 'S'; }
  149. if (a.x1 > b.cx && a.x1 < b.x2) { regionY = 'W'; }
  150. if (a.x2 > b.x1 && a.x2 < b.cx) { regionY = 'E'; }
  151. return (regionX + regionY) || 'C';
  152. };
  153. fn.calculate_overlapped_area_coords = function(a, b){
  154. var x1 = Math.max(a.x1, b.x1);
  155. var y1 = Math.max(a.y1, b.y1);
  156. var x2 = Math.min(a.x2, b.x2);
  157. var y2 = Math.min(a.y2, b.y2);
  158. return $({
  159. left: x1,
  160. top: y1,
  161. width : (x2 - x1),
  162. height: (y2 - y1)
  163. }).coords().get();
  164. };
  165. fn.calculate_overlapped_area = function(coords){
  166. return (coords.width * coords.height);
  167. };
  168. fn.manage_colliders_start_stop = function(new_colliders_coords, start_callback, stop_callback){
  169. var last = this.last_colliders_coords;
  170. for (var i = 0, il = last.length; i < il; i++) {
  171. if ($.inArray(last[i], new_colliders_coords) === -1) {
  172. start_callback.call(this, last[i]);
  173. }
  174. }
  175. for (var j = 0, jl = new_colliders_coords.length; j < jl; j++) {
  176. if ($.inArray(new_colliders_coords[j], last) === -1) {
  177. stop_callback.call(this, new_colliders_coords[j]);
  178. }
  179. }
  180. };
  181. fn.find_collisions = function(player_data_coords){
  182. var self = this;
  183. var overlapping_region = this.options.overlapping_region;
  184. var colliders_coords = [];
  185. var colliders_data = [];
  186. var $colliders = (this.colliders || this.$colliders);
  187. var count = $colliders.length;
  188. var player_coords = self.$element.coords()
  189. .update(player_data_coords || false).get();
  190. while(count--){
  191. var $collider = self.$colliders ?
  192. $($colliders[count]) : $colliders[count];
  193. var $collider_coords_ins = ($collider.isCoords) ?
  194. $collider : $collider.coords();
  195. var collider_coords = $collider_coords_ins.get();
  196. var overlaps = self.overlaps(player_coords, collider_coords);
  197. if (!overlaps) {
  198. continue;
  199. }
  200. var region = self.detect_overlapping_region(
  201. player_coords, collider_coords);
  202. //todo: make this an option
  203. if (region === overlapping_region || overlapping_region === 'all') {
  204. var area_coords = self.calculate_overlapped_area_coords(
  205. player_coords, collider_coords);
  206. var area = self.calculate_overlapped_area(area_coords);
  207. var collider_data = {
  208. area: area,
  209. area_coords : area_coords,
  210. region: region,
  211. coords: collider_coords,
  212. player_coords: player_coords,
  213. el: $collider
  214. };
  215. if (self.options.on_overlap) {
  216. self.options.on_overlap.call(this, collider_data);
  217. }
  218. colliders_coords.push($collider_coords_ins);
  219. colliders_data.push(collider_data);
  220. }
  221. }
  222. if (self.options.on_overlap_stop || self.options.on_overlap_start) {
  223. this.manage_colliders_start_stop(colliders_coords,
  224. self.options.on_overlap_start, self.options.on_overlap_stop);
  225. }
  226. this.last_colliders_coords = colliders_coords;
  227. return colliders_data;
  228. };
  229. fn.get_closest_colliders = function(player_data_coords){
  230. var colliders = this.find_collisions(player_data_coords);
  231. colliders.sort(function(a, b) {
  232. /* if colliders are being overlapped by the "C" (center) region,
  233. * we have to set a lower index in the array to which they are placed
  234. * above in the grid. */
  235. if (a.region === 'C' && b.region === 'C') {
  236. if (a.coords.y1 < b.coords.y1 || a.coords.x1 < b.coords.x1) {
  237. return - 1;
  238. }else{
  239. return 1;
  240. }
  241. }
  242. if (a.area < b.area) {
  243. return 1;
  244. }
  245. return 1;
  246. });
  247. return colliders;
  248. };
  249. fn.set_colliders = function(colliders) {
  250. if (typeof colliders === 'string' || colliders instanceof $) {
  251. this.$colliders = $(colliders,
  252. this.options.colliders_context).not(this.$element);
  253. }else{
  254. this.colliders = $(colliders);
  255. }
  256. };
  257. //jQuery adapter
  258. $.fn.collision = function(collider, options) {
  259. return new Collision( this, collider, options );
  260. };
  261. }(jQuery, window, document));
  262. ;(function(window, undefined) {
  263. /* Delay, debounce and throttle functions taken from underscore.js
  264. *
  265. * Copyright (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and
  266. * Investigative Reporters & Editors
  267. *
  268. * Permission is hereby granted, free of charge, to any person
  269. * obtaining a copy of this software and associated documentation
  270. * files (the "Software"), to deal in the Software without
  271. * restriction, including without limitation the rights to use,
  272. * copy, modify, merge, publish, distribute, sublicense, and/or sell
  273. * copies of the Software, and to permit persons to whom the
  274. * Software is furnished to do so, subject to the following
  275. * conditions:
  276. *
  277. * The above copyright notice and this permission notice shall be
  278. * included in all copies or substantial portions of the Software.
  279. *
  280. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  281. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  282. * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  283. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  284. * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  285. * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  286. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  287. * OTHER DEALINGS IN THE SOFTWARE.
  288. */
  289. window.delay = function(func, wait) {
  290. var args = Array.prototype.slice.call(arguments, 2);
  291. return setTimeout(function(){ return func.apply(null, args); }, wait);
  292. };
  293. window.debounce = function(func, wait, immediate) {
  294. var timeout;
  295. return function() {
  296. var context = this, args = arguments;
  297. var later = function() {
  298. timeout = null;
  299. if (!immediate) func.apply(context, args);
  300. };
  301. if (immediate && !timeout) func.apply(context, args);
  302. clearTimeout(timeout);
  303. timeout = setTimeout(later, wait);
  304. };
  305. };
  306. window.throttle = function(func, wait) {
  307. var context, args, timeout, throttling, more, result;
  308. var whenDone = debounce(
  309. function(){ more = throttling = false; }, wait);
  310. return function() {
  311. context = this; args = arguments;
  312. var later = function() {
  313. timeout = null;
  314. if (more) func.apply(context, args);
  315. whenDone();
  316. };
  317. if (!timeout) timeout = setTimeout(later, wait);
  318. if (throttling) {
  319. more = true;
  320. } else {
  321. result = func.apply(context, args);
  322. }
  323. whenDone();
  324. throttling = true;
  325. return result;
  326. };
  327. };
  328. })(window);
  329. ;(function($, window, document, undefined) {
  330. var defaults = {
  331. items: 'li',
  332. distance: 1,
  333. limit: true,
  334. offset_left: 0,
  335. autoscroll: true,
  336. ignore_dragging: ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'], // or function
  337. handle: null,
  338. container_width: 0, // 0 == auto
  339. move_element: true,
  340. helper: false, // or 'clone'
  341. remove_helper: true
  342. // drag: function(e) {},
  343. // start : function(e, ui) {},
  344. // stop : function(e) {}
  345. };
  346. var $window = $(window);
  347. var dir_map = { x : 'left', y : 'top' };
  348. var isTouch = !!('ontouchstart' in window);
  349. var pointer_events = {
  350. start: 'touchstart.gridster-draggable mousedown.gridster-draggable',
  351. move: 'touchmove.gridster-draggable mousemove.gridster-draggable',
  352. end: 'touchend.gridster-draggable mouseup.gridster-draggable'
  353. };
  354. var capitalize = function(str) {
  355. return str.charAt(0).toUpperCase() + str.slice(1);
  356. };
  357. /**
  358. * Basic drag implementation for DOM elements inside a container.
  359. * Provide start/stop/drag callbacks.
  360. *
  361. * @class Draggable
  362. * @param {HTMLElement} el The HTMLelement that contains all the widgets
  363. * to be dragged.
  364. * @param {Object} [options] An Object with all options you want to
  365. * overwrite:
  366. * @param {HTMLElement|String} [options.items] Define who will
  367. * be the draggable items. Can be a CSS Selector String or a
  368. * collection of HTMLElements.
  369. * @param {Number} [options.distance] Distance in pixels after mousedown
  370. * the mouse must move before dragging should start.
  371. * @param {Boolean} [options.limit] Constrains dragging to the width of
  372. * the container
  373. * @param {offset_left} [options.offset_left] Offset added to the item
  374. * that is being dragged.
  375. * @param {Number} [options.drag] Executes a callback when the mouse is
  376. * moved during the dragging.
  377. * @param {Number} [options.start] Executes a callback when the drag
  378. * starts.
  379. * @param {Number} [options.stop] Executes a callback when the drag stops.
  380. * @return {Object} Returns `el`.
  381. * @constructor
  382. */
  383. function Draggable(el, options) {
  384. this.options = $.extend({}, defaults, options);
  385. this.$document = $(document);
  386. this.$container = $(el);
  387. this.$dragitems = $(this.options.items, this.$container);
  388. this.is_dragging = false;
  389. this.player_min_left = 0 + this.options.offset_left;
  390. this.init();
  391. }
  392. var fn = Draggable.prototype;
  393. fn.init = function() {
  394. var pos = this.$container.css('position');
  395. this.calculate_dimensions();
  396. this.$container.css('position', pos === 'static' ? 'relative' : pos);
  397. this.disabled = false;
  398. this.events();
  399. $(window).bind('resize.gridster-draggable',
  400. throttle($.proxy(this.calculate_dimensions, this), 200));
  401. };
  402. fn.events = function() {
  403. this.$container.on('selectstart.gridster-draggable',
  404. $.proxy(this.on_select_start, this));
  405. this.$container.on(pointer_events.start, this.options.items,
  406. $.proxy(this.drag_handler, this));
  407. this.$document.on(pointer_events.end, $.proxy(function(e) {
  408. this.is_dragging = false;
  409. if (this.disabled) { return; }
  410. this.$document.off(pointer_events.move);
  411. if (this.drag_start) {
  412. this.on_dragstop(e);
  413. }
  414. }, this));
  415. };
  416. fn.get_actual_pos = function($el) {
  417. var pos = $el.position();
  418. return pos;
  419. };
  420. fn.get_mouse_pos = function(e) {
  421. if (e.originalEvent && e.originalEvent.touches) {
  422. var oe = e.originalEvent;
  423. e = oe.touches.length ? oe.touches[0] : oe.changedTouches[0];
  424. }
  425. return {
  426. left: e.clientX,
  427. top: e.clientY
  428. };
  429. };
  430. fn.get_offset = function(e) {
  431. e.preventDefault();
  432. var mouse_actual_pos = this.get_mouse_pos(e);
  433. var diff_x = Math.round(
  434. mouse_actual_pos.left - this.mouse_init_pos.left);
  435. var diff_y = Math.round(mouse_actual_pos.top - this.mouse_init_pos.top);
  436. var left = Math.round(this.el_init_offset.left +
  437. diff_x - this.baseX + $(window).scrollLeft() - this.win_offset_x);
  438. var top = Math.round(this.el_init_offset.top +
  439. diff_y - this.baseY + $(window).scrollTop() - this.win_offset_y);
  440. if (this.options.limit) {
  441. if (left > this.player_max_left) {
  442. left = this.player_max_left;
  443. } else if(left < this.player_min_left) {
  444. left = this.player_min_left;
  445. }
  446. }
  447. return {
  448. position: {
  449. left: left,
  450. top: top
  451. },
  452. pointer: {
  453. left: mouse_actual_pos.left,
  454. top: mouse_actual_pos.top,
  455. diff_left: diff_x + ($(window).scrollLeft() - this.win_offset_x),
  456. diff_top: diff_y + ($(window).scrollTop() - this.win_offset_y)
  457. }
  458. };
  459. };
  460. fn.get_drag_data = function(e) {
  461. var offset = this.get_offset(e);
  462. offset.$player = this.$player;
  463. offset.$helper = this.helper ? this.$helper : this.$player;
  464. return offset;
  465. };
  466. fn.set_limits = function(container_width) {
  467. container_width || (container_width = this.$container.width());
  468. this.player_max_left = (container_width - this.player_width +
  469. - this.options.offset_left);
  470. this.options.container_width = container_width;
  471. return this;
  472. };
  473. fn.scroll_in = function(axis, data) {
  474. var dir_prop = dir_map[axis];
  475. var area_size = 50;
  476. var scroll_inc = 30;
  477. var is_x = axis === 'x';
  478. var window_size = is_x ? this.window_width : this.window_height;
  479. var doc_size = is_x ? $(document).width() : $(document).height();
  480. var player_size = is_x ? this.$player.width() : this.$player.height();
  481. var next_scroll;
  482. var scroll_offset = $window['scroll' + capitalize(dir_prop)]();
  483. var min_window_pos = scroll_offset;
  484. var max_window_pos = min_window_pos + window_size;
  485. var mouse_next_zone = max_window_pos - area_size; // down/right
  486. var mouse_prev_zone = min_window_pos + area_size; // up/left
  487. var abs_mouse_pos = min_window_pos + data.pointer[dir_prop];
  488. var max_player_pos = (doc_size - window_size + player_size);
  489. if (abs_mouse_pos >= mouse_next_zone) {
  490. next_scroll = scroll_offset + scroll_inc;
  491. if (next_scroll < max_player_pos) {
  492. $window['scroll' + capitalize(dir_prop)](next_scroll);
  493. this['scroll_offset_' + axis] += scroll_inc;
  494. }
  495. }
  496. if (abs_mouse_pos <= mouse_prev_zone) {
  497. next_scroll = scroll_offset - scroll_inc;
  498. if (next_scroll > 0) {
  499. $window['scroll' + capitalize(dir_prop)](next_scroll);
  500. this['scroll_offset_' + axis] -= scroll_inc;
  501. }
  502. }
  503. return this;
  504. };
  505. fn.manage_scroll = function(data) {
  506. this.scroll_in('x', data);
  507. this.scroll_in('y', data);
  508. };
  509. fn.calculate_dimensions = function(e) {
  510. this.window_height = $window.height();
  511. this.window_width = $window.width();
  512. };
  513. fn.drag_handler = function(e) {
  514. var node = e.target.nodeName;
  515. // skip if drag is disabled, or click was not done with the mouse primary button
  516. if (this.disabled || e.which !== 1 && !isTouch) {
  517. return;
  518. }
  519. if (this.ignore_drag(e)) {
  520. return;
  521. }
  522. var self = this;
  523. var first = true;
  524. this.$player = $(e.currentTarget);
  525. this.el_init_pos = this.get_actual_pos(this.$player);
  526. this.mouse_init_pos = this.get_mouse_pos(e);
  527. this.offsetY = this.mouse_init_pos.top - this.el_init_pos.top;
  528. this.$document.on(pointer_events.move, function(mme) {
  529. var mouse_actual_pos = self.get_mouse_pos(mme);
  530. var diff_x = Math.abs(
  531. mouse_actual_pos.left - self.mouse_init_pos.left);
  532. var diff_y = Math.abs(
  533. mouse_actual_pos.top - self.mouse_init_pos.top);
  534. if (!(diff_x > self.options.distance ||
  535. diff_y > self.options.distance)
  536. ) {
  537. return false;
  538. }
  539. if (first) {
  540. first = false;
  541. self.on_dragstart.call(self, mme);
  542. return false;
  543. }
  544. if (self.is_dragging === true) {
  545. self.on_dragmove.call(self, mme);
  546. }
  547. return false;
  548. });
  549. if (!isTouch) { return false; }
  550. };
  551. fn.on_dragstart = function(e) {
  552. e.preventDefault();
  553. if (this.is_dragging) { return this; }
  554. this.drag_start = this.is_dragging = true;
  555. var offset = this.$container.offset();
  556. this.baseX = Math.round(offset.left);
  557. this.baseY = Math.round(offset.top);
  558. this.initial_container_width = this.options.container_width || this.$container.width();
  559. if (this.options.helper === 'clone') {
  560. this.$helper = this.$player.clone()
  561. .appendTo(this.$container).addClass('helper');
  562. this.helper = true;
  563. } else {
  564. this.helper = false;
  565. }
  566. this.win_offset_y = $(window).scrollTop();
  567. this.win_offset_x = $(window).scrollLeft();
  568. this.scroll_offset_y = 0;
  569. this.scroll_offset_x = 0;
  570. this.el_init_offset = this.$player.offset();
  571. this.player_width = this.$player.width();
  572. this.player_height = this.$player.height();
  573. this.set_limits(this.options.container_width);
  574. if (this.options.start) {
  575. this.options.start.call(this.$player, e, this.get_drag_data(e));
  576. }
  577. return false;
  578. };
  579. fn.on_dragmove = function(e) {
  580. var data = this.get_drag_data(e);
  581. this.options.autoscroll && this.manage_scroll(data);
  582. if (this.options.move_element) {
  583. (this.helper ? this.$helper : this.$player).css({
  584. 'position': 'absolute',
  585. 'left' : data.position.left,
  586. 'top' : data.position.top
  587. });
  588. }
  589. var last_position = this.last_position || data.position;
  590. data.prev_position = last_position;
  591. if (this.options.drag) {
  592. this.options.drag.call(this.$player, e, data);
  593. }
  594. this.last_position = data.position;
  595. return false;
  596. };
  597. fn.on_dragstop = function(e) {
  598. var data = this.get_drag_data(e);
  599. this.drag_start = false;
  600. if (this.options.stop) {
  601. this.options.stop.call(this.$player, e, data);
  602. }
  603. if (this.helper && this.options.remove_helper) {
  604. this.$helper.remove();
  605. }
  606. return false;
  607. };
  608. fn.on_select_start = function(e) {
  609. if (this.disabled) { return; }
  610. if (this.ignore_drag(e)) {
  611. return;
  612. }
  613. return false;
  614. };
  615. fn.enable = function() {
  616. this.disabled = false;
  617. };
  618. fn.disable = function() {
  619. this.disabled = true;
  620. };
  621. fn.destroy = function() {
  622. this.disable();
  623. this.$container.off('.gridster-draggable');
  624. this.$document.off('.gridster-draggable');
  625. $(window).off('.gridster-draggable');
  626. $.removeData(this.$container, 'drag');
  627. };
  628. fn.ignore_drag = function(event) {
  629. if (this.options.handle) {
  630. return !$(event.target).is(this.options.handle);
  631. }
  632. if ($.isFunction(this.options.ignore_dragging)) {
  633. return this.options.ignore_dragging(event);
  634. }
  635. return $(event.target).is(this.options.ignore_dragging.join(', '));
  636. };
  637. //jQuery adapter
  638. $.fn.drag = function ( options ) {
  639. return new Draggable(this, options);
  640. };
  641. }(jQuery, window, document));
  642. ;(function($, window, document, undefined) {
  643. var defaults = {
  644. namespace: '',
  645. widget_selector: 'li',
  646. widget_margins: [10, 10],
  647. widget_base_dimensions: [400, 225],
  648. extra_rows: 0,
  649. extra_cols: 0,
  650. min_cols: 1,
  651. max_cols: Infinity,
  652. min_rows: 15,
  653. max_size_x: false,
  654. autogrow_cols: false,
  655. autogenerate_stylesheet: true,
  656. avoid_overlapped_widgets: true,
  657. serialize_params: function($w, wgd) {
  658. return {
  659. col: wgd.col,
  660. row: wgd.row,
  661. size_x: wgd.size_x,
  662. size_y: wgd.size_y
  663. };
  664. },
  665. collision: {},
  666. draggable: {
  667. items: '.gs-w',
  668. distance: 4
  669. },
  670. resize: {
  671. enabled: false,
  672. axes: ['both'],
  673. handle_append_to: '',
  674. handle_class: 'gs-resize-handle',
  675. max_size: [Infinity, Infinity],
  676. min_size: [1, 1]
  677. }
  678. };
  679. /**
  680. * @class Gridster
  681. * @uses Draggable
  682. * @uses Collision
  683. * @param {HTMLElement} el The HTMLelement that contains all the widgets.
  684. * @param {Object} [options] An Object with all options you want to
  685. * overwrite:
  686. * @param {HTMLElement|String} [options.widget_selector] Define who will
  687. * be the draggable widgets. Can be a CSS Selector String or a
  688. * collection of HTMLElements
  689. * @param {Array} [options.widget_margins] Margin between widgets.
  690. * The first index for the horizontal margin (left, right) and
  691. * the second for the vertical margin (top, bottom).
  692. * @param {Array} [options.widget_base_dimensions] Base widget dimensions
  693. * in pixels. The first index for the width and the second for the
  694. * height.
  695. * @param {Number} [options.extra_cols] Add more columns in addition to
  696. * those that have been calculated.
  697. * @param {Number} [options.extra_rows] Add more rows in addition to
  698. * those that have been calculated.
  699. * @param {Number} [options.min_cols] The minimum required columns.
  700. * @param {Number} [options.max_cols] The maximum columns possible (set to null
  701. * for no maximum).
  702. * @param {Number} [options.min_rows] The minimum required rows.
  703. * @param {Number} [options.max_size_x] The maximum number of columns
  704. * that a widget can span.
  705. * @param {Boolean} [options.autogenerate_stylesheet] If true, all the
  706. * CSS required to position all widgets in their respective columns
  707. * and rows will be generated automatically and injected to the
  708. * `<head>` of the document. You can set this to false, and write
  709. * your own CSS targeting rows and cols via data-attributes like so:
  710. * `[data-col="1"] { left: 10px; }`
  711. * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded
  712. * from the DOM can be overlapped. It is helpful if the positions were
  713. * bad stored in the database or if there was any conflict.
  714. * @param {Function} [options.serialize_params] Return the data you want
  715. * for each widget in the serialization. Two arguments are passed:
  716. * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid
  717. * coords object (`col`, `row`, `size_x`, `size_y`).
  718. * @param {Object} [options.collision] An Object with all options for
  719. * Collision class you want to overwrite. See Collision docs for
  720. * more info.
  721. * @param {Object} [options.draggable] An Object with all options for
  722. * Draggable class you want to overwrite. See Draggable docs for more
  723. * info.
  724. * @param {Object} [options.resize] An Object with resize config
  725. * options.
  726. * @param {Boolean} [options.resize.enabled] Set to true to enable
  727. * resizing.
  728. * @param {Array} [options.resize.axes] Axes in which widgets can be
  729. * resized. Possible values: ['x', 'y', 'both'].
  730. * @param {String} [options.resize.handle_append_to] Set a valid CSS
  731. * selector to append resize handles to.
  732. * @param {String} [options.resize.handle_class] CSS class name used
  733. * by resize handles.
  734. * @param {Array} [options.resize.max_size] Limit widget dimensions
  735. * when resizing. Array values should be integers:
  736. * `[max_cols_occupied, max_rows_occupied]`
  737. * @param {Array} [options.resize.min_size] Limit widget dimensions
  738. * when resizing. Array values should be integers:
  739. * `[min_cols_occupied, min_rows_occupied]`
  740. * @param {Function} [options.resize.start] Function executed
  741. * when resizing starts.
  742. * @param {Function} [otions.resize.resize] Function executed
  743. * during the resizing.
  744. * @param {Function} [options.resize.stop] Function executed
  745. * when resizing stops.
  746. *
  747. * @constructor
  748. */
  749. function Gridster(el, options) {
  750. this.options = $.extend(true, {}, defaults, options);
  751. this.$el = $(el);
  752. this.$wrapper = this.$el.parent();
  753. this.$widgets = this.$el.children(
  754. this.options.widget_selector).addClass('gs-w');
  755. this.widgets = [];
  756. this.$changed = $([]);
  757. this.wrapper_width = this.$wrapper.width();
  758. this.min_widget_width = (this.options.widget_margins[0] * 2) +
  759. this.options.widget_base_dimensions[0];
  760. this.min_widget_height = (this.options.widget_margins[1] * 2) +
  761. this.options.widget_base_dimensions[1];
  762. this.generated_stylesheets = [];
  763. this.$style_tags = $([]);
  764. this.init();
  765. }
  766. Gridster.generated_stylesheets = [];
  767. var fn = Gridster.prototype;
  768. fn.init = function() {
  769. this.options.resize.enabled && this.setup_resize();
  770. this.generate_grid_and_stylesheet();
  771. this.get_widgets_from_DOM();
  772. this.set_dom_grid_height();
  773. this.set_dom_grid_width();
  774. this.$wrapper.addClass('ready');
  775. this.draggable();
  776. this.options.resize.enabled && this.resizable();
  777. $(window).bind('resize.gridster', throttle(
  778. $.proxy(this.recalculate_faux_grid, this), 200));
  779. };
  780. /**
  781. * Disables dragging.
  782. *
  783. * @method disable
  784. * @return {Class} Returns the instance of the Gridster Class.
  785. */
  786. fn.disable = function() {
  787. this.$wrapper.find('.player-revert').removeClass('player-revert');
  788. this.drag_api.disable();
  789. return this;
  790. };
  791. /**
  792. * Enables dragging.
  793. *
  794. * @method enable
  795. * @return {Class} Returns the instance of the Gridster Class.
  796. */
  797. fn.enable = function() {
  798. this.drag_api.enable();
  799. return this;
  800. };
  801. /**
  802. * Disables drag-and-drop widget resizing.
  803. *
  804. * @method disable
  805. * @return {Class} Returns instance of gridster Class.
  806. */
  807. fn.disable_resize = function() {
  808. this.$el.addClass('gs-resize-disabled');
  809. this.resize_api.disable();
  810. return this;
  811. };
  812. /**
  813. * Enables drag-and-drop widget resizing.
  814. *
  815. * @method enable
  816. * @return {Class} Returns instance of gridster Class.
  817. */
  818. fn.enable_resize = function() {
  819. this.$el.removeClass('gs-resize-disabled');
  820. this.resize_api.enable();
  821. return this;
  822. };
  823. /**
  824. * Add a new widget to the grid.
  825. *
  826. * @method add_widget
  827. * @param {String|HTMLElement} html The string representing the HTML of the widget
  828. * or the HTMLElement.
  829. * @param {Number} [size_x] The nº of rows the widget occupies horizontally.
  830. * @param {Number} [size_y] The nº of columns the widget occupies vertically.
  831. * @param {Number} [col] The column the widget should start in.
  832. * @param {Number} [row] The row the widget should start in.
  833. * @param {Array} [max_size] max_size Maximun size (in units) for width and height.
  834. * @param {Array} [min_size] min_size Minimum size (in units) for width and height.
  835. * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing.
  836. * the widget that was just created.
  837. */
  838. fn.add_widget = function(html, size_x, size_y, col, row, max_size, min_size) {
  839. var pos;
  840. size_x || (size_x = 1);
  841. size_y || (size_y = 1);
  842. if (!col & !row) {
  843. pos = this.next_position(size_x, size_y);
  844. }else{
  845. pos = {
  846. col: col,
  847. row: row,
  848. size_x: size_x,
  849. size_y: size_y
  850. };
  851. this.empty_cells(col, row, size_x, size_y);
  852. }
  853. var $w = $(html).attr({
  854. 'data-col': pos.col,
  855. 'data-row': pos.row,
  856. 'data-sizex' : size_x,
  857. 'data-sizey' : size_y
  858. }).addClass('gs-w').appendTo(this.$el).hide();
  859. this.$widgets = this.$widgets.add($w);
  860. this.register_widget($w);
  861. this.add_faux_rows(pos.size_y);
  862. //this.add_faux_cols(pos.size_x);
  863. if (max_size) {
  864. this.set_widget_max_size($w, max_size);
  865. }
  866. if (min_size) {
  867. this.set_widget_min_size($w, min_size);
  868. }
  869. this.set_dom_grid_width();
  870. this.set_dom_grid_height();
  871. this.drag_api.set_limits(this.cols * this.min_widget_width);
  872. return $w.fadeIn();
  873. };
  874. /**
  875. * Change widget size limits.
  876. *
  877. * @method set_widget_min_size
  878. * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement
  879. * representing the widget or an index representing the desired widget.
  880. * @param {Array} min_size Minimum size (in units) for width and height.
  881. * @return {HTMLElement} Returns instance of gridster Class.
  882. */
  883. fn.set_widget_min_size = function($widget, min_size) {
  884. $widget = typeof $widget === 'number' ?
  885. this.$widgets.eq($widget) : $widget;
  886. if (!$widget.length) { return this; }
  887. var wgd = $widget.data('coords').grid;
  888. wgd.min_size_x = min_size[0];
  889. wgd.min_size_y = min_size[1];
  890. return this;
  891. };
  892. /**
  893. * Change widget size limits.
  894. *
  895. * @method set_widget_max_size
  896. * @param {HTMLElement|Number} $widget The jQuery wrapped HTMLElement
  897. * representing the widget or an index representing the desired widget.
  898. * @param {Array} max_size Maximun size (in units) for width and height.
  899. * @return {HTMLElement} Returns instance of gridster Class.
  900. */
  901. fn.set_widget_max_size = function($widget, max_size) {
  902. $widget = typeof $widget === 'number' ?
  903. this.$widgets.eq($widget) : $widget;
  904. if (!$widget.length) { return this; }
  905. var wgd = $widget.data('coords').grid;
  906. wgd.max_size_x = max_size[0];
  907. wgd.max_size_y = max_size[1];
  908. return this;
  909. };
  910. /**
  911. * Append the resize handle into a widget.
  912. *
  913. * @method add_resize_handle
  914. * @param {HTMLElement} $widget The jQuery wrapped HTMLElement
  915. * representing the widget.
  916. * @return {HTMLElement} Returns instance of gridster Class.
  917. */
  918. fn.add_resize_handle = function($w) {
  919. var append_to = this.options.resize.handle_append_to;
  920. $(this.resize_handle_tpl).appendTo( append_to ? $(append_to, $w) : $w);
  921. return this;
  922. };
  923. /**
  924. * Change the size of a widget. Width is limited to the current grid width.
  925. *
  926. * @method resize_widget
  927. * @param {HTMLElement} $widget The jQuery wrapped HTMLElement
  928. * representing the widget.
  929. * @param {Number} size_x The number of columns that will occupy the widget.
  930. * By default <code>size_x</code> is limited to the space available from
  931. * the column where the widget begins, until the last column to the right.
  932. * @param {Number} size_y The number of rows that will occupy the widget.
  933. * @param {Function} [callback] Function executed when the widget is removed.
  934. * @return {HTMLElement} Returns $widget.
  935. */
  936. fn.resize_widget = function($widget, size_x, size_y, callback) {
  937. var wgd = $widget.coords().grid;
  938. var col = wgd.col;
  939. var max_cols = this.options.max_cols;
  940. var old_size_y = wgd.size_y;
  941. var old_col = wgd.col;
  942. var new_col = old_col;
  943. size_x || (size_x = wgd.size_x);
  944. size_y || (size_y = wgd.size_y);
  945. if (max_cols !== Infinity) {
  946. size_x = Math.min(size_x, max_cols - col + 1);
  947. }
  948. if (size_y > old_size_y) {
  949. this.add_faux_rows(Math.max(size_y - old_size_y, 0));
  950. }
  951. var player_rcol = (col + size_x - 1);
  952. if (player_rcol > this.cols) {
  953. this.add_faux_cols(player_rcol - this.cols);
  954. }
  955. var new_grid_data = {
  956. col: new_col,
  957. row: wgd.row,
  958. size_x: size_x,
  959. size_y: size_y
  960. };
  961. this.mutate_widget_in_gridmap($widget, wgd, new_grid_data);
  962. this.set_dom_grid_height();
  963. this.set_dom_grid_width();
  964. if (callback) {
  965. callback.call(this, new_grid_data.size_x, new_grid_data.size_y);
  966. }
  967. return $widget;
  968. };
  969. /**
  970. * Mutate widget dimensions and position in the grid map.
  971. *
  972. * @method mutate_widget_in_gridmap
  973. * @param {HTMLElement} $widget The jQuery wrapped HTMLElement
  974. * representing the widget to mutate.
  975. * @param {Object} wgd Current widget grid data (col, row, size_x, size_y).
  976. * @param {Object} new_wgd New widget grid data.
  977. * @return {HTMLElement} Returns instance of gridster Class.
  978. */
  979. fn.mutate_widget_in_gridmap = function($widget, wgd, new_wgd) {
  980. var old_size_x = wgd.size_x;
  981. var old_size_y = wgd.size_y;
  982. var old_cells_occupied = this.get_cells_occupied(wgd);
  983. var new_cells_occupied = this.get_cells_occupied(new_wgd);
  984. var empty_cols = [];
  985. $.each(old_cells_occupied.cols, function(i, col) {
  986. if ($.inArray(col, new_cells_occupied.cols) === -1) {
  987. empty_cols.push(col);
  988. }
  989. });
  990. var occupied_cols = [];
  991. $.each(new_cells_occupied.cols, function(i, col) {
  992. if ($.inArray(col, old_cells_occupied.cols) === -1) {
  993. occupied_cols.push(col);
  994. }
  995. });
  996. var empty_rows = [];
  997. $.each(old_cells_occupied.rows, function(i, row) {
  998. if ($.inArray(row, new_cells_occupied.rows) === -1) {
  999. empty_rows.push(row);
  1000. }
  1001. });
  1002. var occupied_rows = [];
  1003. $.each(new_cells_occupied.rows, function(i, row) {
  1004. if ($.inArray(row, old_cells_occupied.rows) === -1) {
  1005. occupied_rows.push(row);
  1006. }
  1007. });
  1008. this.remove_from_gridmap(wgd);
  1009. if (occupied_cols.length) {
  1010. var cols_to_empty = [
  1011. new_wgd.col, new_wgd.row, new_wgd.size_x, Math.min(old_size_y, new_wgd.size_y), $widget
  1012. ];
  1013. this.empty_cells.apply(this, cols_to_empty);
  1014. }
  1015. if (occupied_rows.length) {
  1016. var rows_to_empty = [new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget];
  1017. this.empty_cells.apply(this, rows_to_empty);
  1018. }
  1019. // not the same that wgd = new_wgd;
  1020. wgd.col = new_wgd.col;
  1021. wgd.row = new_wgd.row;
  1022. wgd.size_x = new_wgd.size_x;
  1023. wgd.size_y = new_wgd.size_y;
  1024. this.add_to_gridmap(new_wgd, $widget);
  1025. $widget.removeClass('player-revert');
  1026. //update coords instance attributes
  1027. $widget.data('coords').update({
  1028. width: (new_wgd.size_x * this.options.widget_base_dimensions[0] +
  1029. ((new_wgd.size_x - 1) * this.options.widget_margins[0]) * 2),
  1030. height: (new_wgd.size_y * this.options.widget_base_dimensions[1] +
  1031. ((new_wgd.size_y - 1) * this.options.widget_margins[1]) * 2)
  1032. });
  1033. $widget.attr({
  1034. 'data-col': new_wgd.col,
  1035. 'data-row': new_wgd.row,
  1036. 'data-sizex': new_wgd.size_x,
  1037. 'data-sizey': new_wgd.size_y
  1038. });
  1039. if (empty_cols.length) {
  1040. var cols_to_remove_holes = [
  1041. empty_cols[0], new_wgd.row,
  1042. empty_cols.length,
  1043. Math.min(old_size_y, new_wgd.size_y),
  1044. $widget
  1045. ];
  1046. this.remove_empty_cells.apply(this, cols_to_remove_holes);
  1047. }
  1048. if (empty_rows.length) {
  1049. var rows_to_remove_holes = [
  1050. new_wgd.col, new_wgd.row, new_wgd.size_x, new_wgd.size_y, $widget
  1051. ];
  1052. this.remove_empty_cells.apply(this, rows_to_remove_holes);
  1053. }
  1054. this.move_widget_up($widget);
  1055. return this;
  1056. };
  1057. /**
  1058. * Move down widgets in cells represented by the arguments col, row, size_x,
  1059. * size_y
  1060. *
  1061. * @method empty_cells
  1062. * @param {Number} col The column where the group of cells begin.
  1063. * @param {Number} row The row where the group of cells begin.
  1064. * @param {Number} size_x The number of columns that the group of cells
  1065. * occupy.
  1066. * @param {Number} size_y The number of rows that the group of cells
  1067. * occupy.
  1068. * @param {HTMLElement} $exclude Exclude widgets from being moved.
  1069. * @return {Class} Returns the instance of the Gridster Class.
  1070. */
  1071. fn.empty_cells = function(col, row, size_x, size_y, $exclude) {
  1072. var $nexts = this.widgets_below({
  1073. col: col,
  1074. row: row - size_y,
  1075. size_x: size_x,
  1076. size_y: size_y
  1077. });
  1078. $nexts.not($exclude).each($.proxy(function(i, w) {
  1079. var wgd = $(w).coords().grid;
  1080. if (!(wgd.row <= (row + size_y - 1))) { return; }
  1081. var diff = (row + size_y) - wgd.row;
  1082. this.move_widget_down($(w), diff);
  1083. }, this));
  1084. this.set_dom_grid_height();
  1085. return this;
  1086. };
  1087. /**
  1088. * Move up widgets below cells represented by the arguments col, row, size_x,
  1089. * size_y.
  1090. *
  1091. * @method remove_empty_cells
  1092. * @param {Number} col The column where the group of cells begin.
  1093. * @param {Number} row The row where the group of cells begin.
  1094. * @param {Number} size_x The number of columns that the group of cells
  1095. * occupy.
  1096. * @param {Number} size_y The number of rows that the group of cells
  1097. * occupy.
  1098. * @param {HTMLElement} exclude Exclude widgets from being moved.
  1099. * @return {Class} Returns the instance of the Gridster Class.
  1100. */
  1101. fn.remove_empty_cells = function(col, row, size_x, size_y, exclude) {
  1102. var $nexts = this.widgets_below({
  1103. col: col,
  1104. row: row,
  1105. size_x: size_x,
  1106. size_y: size_y
  1107. });
  1108. $nexts.not(exclude).each($.proxy(function(i, widget) {
  1109. this.move_widget_up( $(widget), size_y );
  1110. }, this));
  1111. this.set_dom_grid_height();
  1112. return this;
  1113. };
  1114. /**
  1115. * Get the most left column below to add a new widget.
  1116. *
  1117. * @method next_position
  1118. * @param {Number} size_x The nº of rows the widget occupies horizontally.
  1119. * @param {Number} size_y The nº of columns the widget occupies vertically.
  1120. * @return {Object} Returns a grid coords object representing the future
  1121. * widget coords.
  1122. */
  1123. fn.next_position = function(size_x, size_y) {
  1124. size_x || (size_x = 1);
  1125. size_y || (size_y = 1);
  1126. var ga = this.gridmap;
  1127. var cols_l = ga.length;
  1128. var valid_pos = [];
  1129. var rows_l;
  1130. for (var c = 1; c < cols_l; c++) {
  1131. rows_l = ga[c].length;
  1132. for (var r = 1; r <= rows_l; r++) {
  1133. var can_move_to = this.can_move_to({
  1134. size_x: size_x,
  1135. size_y: size_y
  1136. }, c, r);
  1137. if (can_move_to) {
  1138. valid_pos.push({
  1139. col: c,
  1140. row: r,
  1141. size_y: size_y,
  1142. size_x: size_x
  1143. });
  1144. }
  1145. }
  1146. }
  1147. if (valid_pos.length) {
  1148. return this.sort_by_row_and_col_asc(valid_pos)[0];
  1149. }
  1150. return false;
  1151. };
  1152. /**
  1153. * Remove a widget from the grid.
  1154. *
  1155. * @method remove_widget
  1156. * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove.
  1157. * @param {Boolean|Function} silent If true, widgets below the removed one
  1158. * will not move up. If a Function is passed it will be used as callback.
  1159. * @param {Function} callback Function executed when the widget is removed.
  1160. * @return {Class} Returns the instance of the Gridster Class.
  1161. */
  1162. fn.remove_widget = function(el, silent, callback) {
  1163. var $el = el instanceof $ ? el : $(el);
  1164. var wgd = $el.coords().grid;
  1165. // if silent is a function assume it's a callback
  1166. if ($.isFunction(silent)) {
  1167. callback = silent;
  1168. silent = false;
  1169. }
  1170. this.cells_occupied_by_placeholder = {};
  1171. this.$widgets = this.$widgets.not($el);
  1172. var $nexts = this.widgets_below($el);
  1173. this.remove_from_gridmap(wgd);
  1174. $el.fadeOut($.proxy(function() {
  1175. $el.remove();
  1176. if (!silent) {
  1177. $nexts.each($.proxy(function(i, widget) {
  1178. this.move_widget_up( $(widget), wgd.size_y );
  1179. }, this));
  1180. }
  1181. this.set_dom_grid_height();
  1182. if (callback) {
  1183. callback.call(this, el);
  1184. }
  1185. }, this));
  1186. return this;
  1187. };
  1188. /**
  1189. * Remove all widgets from the grid.
  1190. *
  1191. * @method remove_all_widgets
  1192. * @param {Function} callback Function executed for each widget removed.
  1193. * @return {Class} Returns the instance of the Gridster Class.
  1194. */
  1195. fn.remove_all_widgets = function(callback) {
  1196. this.$widgets.each($.proxy(function(i, el){
  1197. this.remove_widget(el, true, callback);
  1198. }, this));
  1199. return this;
  1200. };
  1201. /**
  1202. * Returns a serialized array of the widgets in the grid.
  1203. *
  1204. * @method serialize
  1205. * @param {HTMLElement} [$widgets] The collection of jQuery wrapped
  1206. * HTMLElements you want to serialize. If no argument is passed all widgets
  1207. * will be serialized.
  1208. * @return {Array} Returns an Array of Objects with the data specified in
  1209. * the serialize_params option.
  1210. */
  1211. fn.serialize = function($widgets) {
  1212. $widgets || ($widgets = this.$widgets);
  1213. var result = [];
  1214. $widgets.each($.proxy(function(i, widget) {
  1215. result.push(this.options.serialize_params(
  1216. $(widget), $(widget).coords().grid ) );
  1217. }, this));
  1218. return result;
  1219. };
  1220. /**
  1221. * Returns a serialized array of the widgets that have changed their
  1222. * position.
  1223. *
  1224. * @method serialize_changed
  1225. * @return {Array} Returns an Array of Objects with the data specified in
  1226. * the serialize_params option.
  1227. */
  1228. fn.serialize_changed = function() {
  1229. return this.serialize(this.$changed);
  1230. };
  1231. /**
  1232. * Creates the grid coords object representing the widget a add it to the
  1233. * mapped array of positions.
  1234. *
  1235. * @method register_widget
  1236. * @return {Array} Returns the instance of the Gridster class.
  1237. */
  1238. fn.register_widget = function($el) {
  1239. var wgd = {
  1240. 'col': parseInt($el.attr('data-col'), 10),
  1241. 'row': parseInt($el.attr('data-row'), 10),
  1242. 'size_x': parseInt($el.attr('data-sizex'), 10),
  1243. 'size_y': parseInt($el.attr('data-sizey'), 10),
  1244. 'max_size_x': parseInt($el.attr('data-max-sizex'), 10) || false,
  1245. 'max_size_y': parseInt($el.attr('data-max-sizey'), 10) || false,
  1246. 'min_size_x': parseInt($el.attr('data-min-sizex'), 10) || false,
  1247. 'min_size_y': parseInt($el.attr('data-min-sizey'), 10) || false,
  1248. 'el': $el
  1249. };
  1250. if (this.options.avoid_overlapped_widgets &&
  1251. !this.can_move_to(
  1252. {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row)
  1253. ) {
  1254. $.extend(wgd, this.next_position(wgd.size_x, wgd.size_y));
  1255. $el.attr({
  1256. 'data-col': wgd.col,
  1257. 'data-row': wgd.row,
  1258. 'data-sizex': wgd.size_x,
  1259. 'data-sizey': wgd.size_y
  1260. });
  1261. }
  1262. // attach Coord object to player data-coord attribute
  1263. $el.data('coords', $el.coords());
  1264. // Extend Coord object with grid position info
  1265. $el.data('coords').grid = wgd;
  1266. this.add_to_gridmap(wgd, $el);
  1267. this.options.resize.enabled && this.add_resize_handle($el);
  1268. return this;
  1269. };
  1270. /**
  1271. * Update in the mapped array of positions the value of cells represented by
  1272. * the grid coords object passed in the `grid_data` param.
  1273. *
  1274. * @param {Object} grid_data The grid coords object representing the cells
  1275. * to update in the