Built files from Bizgaze WebServer
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

gridstack.js 73KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133
  1. /**
  2. * gridstack.js 1.1.2
  3. * https://gridstackjs.com/
  4. * (c) 2014-2020 Alain Dumesny, Dylan Weiss, Pavel Reznikov
  5. * gridstack.js may be freely distributed under the MIT license.
  6. * @preserve
  7. */
  8. (function(factory) {
  9. /* [alain] we compile jquery with our code, so no need to 'load' externally
  10. if (typeof define === 'function' && define.amd) {
  11. define(['jquery', 'exports'], factory);
  12. } else if (typeof exports !== 'undefined') {
  13. var jQueryModule;
  14. try { jQueryModule = require('jquery'); } catch (e) {}
  15. factory(jQueryModule || window.jQuery, exports);
  16. } else */{
  17. factory(window.jQuery, window);
  18. }
  19. })(function($, scope) {
  20. // checks for obsolete method names
  21. var obsolete = function(f, oldName, newName, rev) {
  22. var wrapper = function() {
  23. console.warn('gridstack.js: Function `' + oldName + '` is deprecated in ' + rev + ' and has been replaced ' +
  24. 'with `' + newName + '`. It will be **completely** removed in v1.0');
  25. return f.apply(this, arguments);
  26. };
  27. wrapper.prototype = f.prototype;
  28. return wrapper;
  29. };
  30. // checks for obsolete grid options (can be used for any fields, but msg is about options)
  31. var obsoleteOpts = function(opts, oldName, newName, rev) {
  32. if (opts[oldName] !== undefined) {
  33. opts[newName] = opts[oldName];
  34. console.warn('gridstack.js: Option `' + oldName + '` is deprecated in ' + rev + ' and has been replaced with `' +
  35. newName + '`. It will be **completely** removed in v1.0');
  36. }
  37. };
  38. // checks for obsolete grid options which are gone
  39. var obsoleteOptsDel = function(opts, oldName, rev, info) {
  40. if (opts[oldName] !== undefined) {
  41. console.warn('gridstack.js: Option `' + oldName + '` is deprecated in ' + rev + info);
  42. }
  43. };
  44. // checks for obsolete Jquery element attributes
  45. var obsoleteAttr = function(el, oldName, newName, rev) {
  46. var oldAttr = el.attr(oldName);
  47. if (oldAttr !== undefined) {
  48. el.attr(newName, oldAttr);
  49. console.warn('gridstack.js: attribute `' + oldName + '`=' + oldAttr + ' is deprecated on this object in ' + rev + ' and has been replaced with `' +
  50. newName + '`. It will be **completely** removed in v1.0');
  51. }
  52. };
  53. var Utils = {
  54. isIntercepted: function(a, b) {
  55. return !(a.x + a.width <= b.x || b.x + b.width <= a.x || a.y + a.height <= b.y || b.y + b.height <= a.y);
  56. },
  57. sort: function(nodes, dir, column) {
  58. if (!column) {
  59. var widths = nodes.map(function(node) { return node.x + node.width; });
  60. column = Math.max.apply(Math, widths);
  61. }
  62. if (dir === -1)
  63. return Utils.sortBy(nodes, function(n) { return -(n.x + n.y * column); });
  64. else
  65. return Utils.sortBy(nodes, function(n) { return (n.x + n.y * column); });
  66. },
  67. createStylesheet: function(id, parent) {
  68. var style = document.createElement('style');
  69. style.setAttribute('type', 'text/css');
  70. style.setAttribute('data-gs-style-id', id);
  71. if (style.styleSheet) {
  72. style.styleSheet.cssText = '';
  73. } else {
  74. style.appendChild(document.createTextNode(''));
  75. }
  76. if (!parent) { parent = document.getElementsByTagName('head')[0]; } // default to head
  77. parent.insertBefore(style, parent.firstChild);
  78. return style.sheet;
  79. },
  80. removeStylesheet: function(id) {
  81. $('STYLE[data-gs-style-id=' + id + ']').remove();
  82. },
  83. insertCSSRule: function(sheet, selector, rules, index) {
  84. if (typeof sheet.insertRule === 'function') {
  85. sheet.insertRule(selector + '{' + rules + '}', index);
  86. } else if (typeof sheet.addRule === 'function') {
  87. sheet.addRule(selector, rules, index);
  88. }
  89. },
  90. toBool: function(v) {
  91. if (typeof v === 'boolean') {
  92. return v;
  93. }
  94. if (typeof v === 'string') {
  95. v = v.toLowerCase();
  96. return !(v === '' || v === 'no' || v === 'false' || v === '0');
  97. }
  98. return Boolean(v);
  99. },
  100. _collisionNodeCheck: function(n) {
  101. return n !== this.node && Utils.isIntercepted(n, this.nn);
  102. },
  103. _didCollide: function(bn) {
  104. return Utils.isIntercepted({x: this.n.x, y: this.newY, width: this.n.width, height: this.n.height}, bn);
  105. },
  106. _isAddNodeIntercepted: function(n) {
  107. return Utils.isIntercepted({x: this.x, y: this.y, width: this.node.width, height: this.node.height}, n);
  108. },
  109. parseHeight: function(val) {
  110. var height = val;
  111. var heightUnit = 'px';
  112. if (height && typeof height === 'string') {
  113. var match = height.match(/^(-[0-9]+\.[0-9]+|[0-9]*\.[0-9]+|-[0-9]+|[0-9]+)(px|em|rem|vh|vw|%)?$/);
  114. if (!match) {
  115. throw new Error('Invalid height');
  116. }
  117. heightUnit = match[2] || 'px';
  118. height = parseFloat(match[1]);
  119. }
  120. return {height: height, unit: heightUnit};
  121. },
  122. without: function(array, item) {
  123. var index = array.indexOf(item);
  124. if (index !== -1) {
  125. array = array.slice(0);
  126. array.splice(index, 1);
  127. }
  128. return array;
  129. },
  130. sortBy: function(array, getter) {
  131. return array.slice(0).sort(function(left, right) {
  132. var valueLeft = getter(left);
  133. var valueRight = getter(right);
  134. if (valueRight === valueLeft) {
  135. return 0;
  136. }
  137. return valueLeft > valueRight ? 1 : -1;
  138. });
  139. },
  140. defaults: function(target) {
  141. var sources = Array.prototype.slice.call(arguments, 1);
  142. sources.forEach(function(source) {
  143. for (var prop in source) {
  144. if (Object.prototype.hasOwnProperty.call(source, prop) && (!Object.prototype.hasOwnProperty.call(target, prop) || target[prop] === undefined)) {
  145. target[prop] = source[prop];
  146. }
  147. }
  148. });
  149. return target;
  150. },
  151. clone: function(target) {
  152. return $.extend({}, target);
  153. },
  154. throttle: function(callback, delay) {
  155. var isWaiting = false;
  156. return function() {
  157. if (!isWaiting) {
  158. callback.apply(this, arguments);
  159. isWaiting = true;
  160. setTimeout(function() { isWaiting = false; }, delay);
  161. }
  162. };
  163. },
  164. removePositioningStyles: function(el) {
  165. var style = el[0].style;
  166. if (style.position) {
  167. style.removeProperty('position');
  168. }
  169. if (style.left) {
  170. style.removeProperty('left');
  171. }
  172. if (style.top) {
  173. style.removeProperty('top');
  174. }
  175. if (style.width) {
  176. style.removeProperty('width');
  177. }
  178. if (style.height) {
  179. style.removeProperty('height');
  180. }
  181. },
  182. getScrollParent: function(el) {
  183. var returnEl;
  184. if (el === null) {
  185. returnEl = null;
  186. } else if (el.scrollHeight > el.clientHeight) {
  187. returnEl = el;
  188. } else {
  189. returnEl = Utils.getScrollParent(el.parentNode);
  190. }
  191. return returnEl;
  192. },
  193. updateScrollPosition: function(el, ui, distance) {
  194. // is widget in view?
  195. var rect = el.getBoundingClientRect();
  196. var innerHeightOrClientHeight = (window.innerHeight || document.documentElement.clientHeight);
  197. if (rect.top < 0 ||
  198. rect.bottom > innerHeightOrClientHeight
  199. ) {
  200. // set scrollTop of first parent that scrolls
  201. // if parent is larger than el, set as low as possible
  202. // to get entire widget on screen
  203. var offsetDiffDown = rect.bottom - innerHeightOrClientHeight;
  204. var offsetDiffUp = rect.top;
  205. var scrollEl = Utils.getScrollParent(el);
  206. if (scrollEl !== null) {
  207. var prevScroll = scrollEl.scrollTop;
  208. if (rect.top < 0 && distance < 0) {
  209. // moving up
  210. if (el.offsetHeight > innerHeightOrClientHeight) {
  211. scrollEl.scrollTop += distance;
  212. } else {
  213. scrollEl.scrollTop += Math.abs(offsetDiffUp) > Math.abs(distance) ? distance : offsetDiffUp;
  214. }
  215. } else if (distance > 0) {
  216. // moving down
  217. if (el.offsetHeight > innerHeightOrClientHeight) {
  218. scrollEl.scrollTop += distance;
  219. } else {
  220. scrollEl.scrollTop += offsetDiffDown > distance ? distance : offsetDiffDown;
  221. }
  222. }
  223. // move widget y by amount scrolled
  224. ui.position.top += scrollEl.scrollTop - prevScroll;
  225. }
  226. }
  227. }
  228. };
  229. /**
  230. * @class GridStackDragDropPlugin
  231. * Base class for drag'n'drop plugin.
  232. */
  233. function GridStackDragDropPlugin(grid) {
  234. this.grid = grid;
  235. }
  236. GridStackDragDropPlugin.registeredPlugins = [];
  237. GridStackDragDropPlugin.registerPlugin = function(pluginClass) {
  238. GridStackDragDropPlugin.registeredPlugins.push(pluginClass);
  239. };
  240. GridStackDragDropPlugin.prototype.resizable = function(el, opts) {
  241. return this;
  242. };
  243. GridStackDragDropPlugin.prototype.draggable = function(el, opts) {
  244. return this;
  245. };
  246. GridStackDragDropPlugin.prototype.droppable = function(el, opts) {
  247. return this;
  248. };
  249. GridStackDragDropPlugin.prototype.isDroppable = function(el) {
  250. return false;
  251. };
  252. GridStackDragDropPlugin.prototype.on = function(el, eventName, callback) {
  253. return this;
  254. };
  255. var idSeq = 0;
  256. var GridStackEngine = function(column, onchange, float, maxRow, items) {
  257. this.column = column || 12;
  258. this.float = float || false;
  259. this.maxRow = maxRow || 0;
  260. this.nodes = items || [];
  261. this.onchange = onchange || function() {};
  262. this._addedNodes = [];
  263. this._removedNodes = [];
  264. this._batchMode = false;
  265. };
  266. GridStackEngine.prototype.batchUpdate = function() {
  267. if (this._batchMode) return;
  268. this._batchMode = true;
  269. this._prevFloat = this.float;
  270. this.float = true; // let things go anywhere for now... commit() will restore and possibly reposition
  271. };
  272. GridStackEngine.prototype.commit = function() {
  273. if (!this._batchMode) return;
  274. this._batchMode = false;
  275. this.float = this._prevFloat;
  276. delete this._prevFloat;
  277. this._packNodes();
  278. this._notify();
  279. };
  280. // For Meteor support: https://github.com/gridstack/gridstack.js/pull/272
  281. GridStackEngine.prototype.getNodeDataByDOMEl = function(el) {
  282. return this.nodes.find(function(node) { return el === node.el });
  283. };
  284. GridStackEngine.prototype._fixCollisions = function(node) {
  285. var self = this;
  286. this._sortNodes(-1);
  287. var nn = node;
  288. var hasLocked = Boolean(this.nodes.find(function(n) { return n.locked; }));
  289. if (!this.float && !hasLocked) {
  290. nn = {x: 0, y: node.y, width: this.column, height: node.height};
  291. }
  292. while (true) {
  293. var collisionNode = this.nodes.find(Utils._collisionNodeCheck, {node: node, nn: nn});
  294. if (!collisionNode) { return; }
  295. var moved;
  296. if (collisionNode.locked) {
  297. // if colliding with a locked item, move ourself instead
  298. moved = this.moveNode(node, node.x, collisionNode.y + collisionNode.height,
  299. node.width, node.height, true);
  300. } else {
  301. moved = this.moveNode(collisionNode, collisionNode.x, node.y + node.height,
  302. collisionNode.width, collisionNode.height, true);
  303. }
  304. if (!moved) { return; } // break inf loop if we couldn't move after all (ex: maxRow, fixed)
  305. }
  306. };
  307. GridStackEngine.prototype.isAreaEmpty = function(x, y, width, height) {
  308. var nn = {x: x || 0, y: y || 0, width: width || 1, height: height || 1};
  309. var collisionNode = this.nodes.find(function(n) {
  310. return Utils.isIntercepted(n, nn);
  311. });
  312. return !collisionNode;
  313. };
  314. GridStackEngine.prototype._sortNodes = function(dir) {
  315. this.nodes = Utils.sort(this.nodes, dir, this.column);
  316. };
  317. GridStackEngine.prototype._packNodes = function() {
  318. this._sortNodes();
  319. if (this.float) {
  320. this.nodes.forEach(function(n, i) {
  321. if (n._updating || n._packY === undefined || n.y === n._packY) {
  322. return;
  323. }
  324. var newY = n.y;
  325. while (newY >= n._packY) {
  326. var collisionNode = this.nodes
  327. .slice(0, i)
  328. .find(Utils._didCollide, {n: n, newY: newY});
  329. if (!collisionNode) {
  330. n._dirty = true;
  331. n.y = newY;
  332. }
  333. --newY;
  334. }
  335. }, this);
  336. } else {
  337. this.nodes.forEach(function(n, i) {
  338. if (n.locked) { return; }
  339. while (n.y > 0) {
  340. var newY = n.y - 1;
  341. var canBeMoved = i === 0;
  342. if (i > 0) {
  343. var collisionNode = this.nodes
  344. .slice(0, i)
  345. .find(Utils._didCollide, {n: n, newY: newY});
  346. canBeMoved = collisionNode === undefined;
  347. }
  348. if (!canBeMoved) { break; }
  349. // Note: must be dirty (from last position) for GridStack::OnChange CB to update positions
  350. // and move items back. The user 'change' CB should detect changes from the original
  351. // starting position instead.
  352. n._dirty = (n.y !== newY);
  353. n.y = newY;
  354. }
  355. }, this);
  356. }
  357. };
  358. GridStackEngine.prototype._prepareNode = function(node, resizing) {
  359. node = node || {};
  360. // if we're missing position, have the grid position us automatically (before we set them to 0,0)
  361. if (node.x === undefined || node.y === undefined || node.x === null || node.y === null) {
  362. node.autoPosition = true;
  363. }
  364. // assign defaults for missing required fields
  365. var defaults = {width: 1, height: 1, x: 0, y: 0};
  366. node = Utils.defaults(node, defaults);
  367. // convert any strings over
  368. node.x = parseInt(node.x);
  369. node.y = parseInt(node.y);
  370. node.width = parseInt(node.width);
  371. node.height = parseInt(node.height);
  372. node.autoPosition = node.autoPosition || false;
  373. node.noResize = node.noResize || false;
  374. node.noMove = node.noMove || false;
  375. // check for NaN (in case messed up strings were passed. can't do parseInt() || defaults.x above as 0 is valid #)
  376. if (Number.isNaN(node.x)) { node.x = defaults.x; node.autoPosition = true; }
  377. if (Number.isNaN(node.y)) { node.y = defaults.y; node.autoPosition = true; }
  378. if (Number.isNaN(node.width)) { node.width = defaults.width; }
  379. if (Number.isNaN(node.height)) { node.height = defaults.height; }
  380. if (node.maxWidth !== undefined) { node.width = Math.min(node.width, node.maxWidth); }
  381. if (node.maxHeight !== undefined) { node.height = Math.min(node.height, node.maxHeight); }
  382. if (node.minWidth !== undefined) { node.width = Math.max(node.width, node.minWidth); }
  383. if (node.minHeight !== undefined) { node.height = Math.max(node.height, node.minHeight); }
  384. if (node.width > this.column) {
  385. node.width = this.column;
  386. } else if (node.width < 1) {
  387. node.width = 1;
  388. }
  389. if (this.maxRow && node.height > this.maxRow) {
  390. node.height = this.maxRow;
  391. } else if (node.height < 1) {
  392. node.height = 1;
  393. }
  394. if (node.x < 0) {
  395. node.x = 0;
  396. }
  397. if (node.y < 0) {
  398. node.y = 0;
  399. }
  400. if (node.x + node.width > this.column) {
  401. if (resizing) {
  402. node.width = this.column - node.x;
  403. } else {
  404. node.x = this.column - node.width;
  405. }
  406. }
  407. if (this.maxRow && node.y + node.height > this.maxRow) {
  408. if (resizing) {
  409. node.height = this.maxRow - node.y;
  410. } else {
  411. node.y = this.maxRow - node.height;
  412. }
  413. }
  414. return node;
  415. };
  416. GridStackEngine.prototype._notify = function() {
  417. if (this._batchMode) { return; }
  418. var args = Array.prototype.slice.call(arguments, 0);
  419. args[0] = (args[0] === undefined ? [] : (Array.isArray(args[0]) ? args[0] : [args[0]]) );
  420. args[1] = (args[1] === undefined ? true : args[1]);
  421. var dirtyNodes = args[0].concat(this.getDirtyNodes());
  422. this.onchange(dirtyNodes, args[1]);
  423. };
  424. GridStackEngine.prototype.cleanNodes = function() {
  425. if (this._batchMode) { return; }
  426. this.nodes.forEach(function(n) { delete n._dirty; });
  427. };
  428. GridStackEngine.prototype.getDirtyNodes = function(verify) {
  429. // compare original X,Y,W,H (or entire node?) instead as _dirty can be a temporary state
  430. if (verify) {
  431. var dirtNodes = [];
  432. this.nodes.forEach(function (n) {
  433. if (n._dirty) {
  434. if (n.y === n._origY && n.x === n._origX && n.width === n._origW && n.height === n._origH) {
  435. delete n._dirty;
  436. } else {
  437. dirtNodes.push(n);
  438. }
  439. }
  440. });
  441. return dirtNodes;
  442. }
  443. return this.nodes.filter(function(n) { return n._dirty; });
  444. };
  445. GridStackEngine.prototype.addNode = function(node, triggerAddEvent) {
  446. node = this._prepareNode(node);
  447. node._id = node._id || ++idSeq;
  448. if (node.autoPosition) {
  449. this._sortNodes();
  450. for (var i = 0;; ++i) {
  451. var x = i % this.column;
  452. var y = Math.floor(i / this.column);
  453. if (x + node.width > this.column) {
  454. continue;
  455. }
  456. if (!this.nodes.find(Utils._isAddNodeIntercepted, {x: x, y: y, node: node})) {
  457. node.x = x;
  458. node.y = y;
  459. delete node.autoPosition; // found our slot
  460. break;
  461. }
  462. }
  463. }
  464. this.nodes.push(node);
  465. if (triggerAddEvent) {
  466. this._addedNodes.push(node);
  467. }
  468. this._fixCollisions(node);
  469. this._packNodes();
  470. this._notify();
  471. return node;
  472. };
  473. GridStackEngine.prototype.removeNode = function(node, detachNode) {
  474. detachNode = (detachNode === undefined ? true : detachNode);
  475. this._removedNodes.push(node);
  476. node._id = null; // hint that node is being removed
  477. this.nodes = Utils.without(this.nodes, node);
  478. this._packNodes();
  479. this._notify(node, detachNode);
  480. };
  481. GridStackEngine.prototype.removeAll = function(detachNode) {
  482. delete this._layouts;
  483. if (this.nodes.length === 0) { return; }
  484. detachNode = (detachNode === undefined ? true : detachNode);
  485. this.nodes.forEach(function(n) { n._id = null; }); // hint that node is being removed
  486. this._removedNodes = this.nodes;
  487. this.nodes = [];
  488. this._notify(this._removedNodes, detachNode);
  489. };
  490. GridStackEngine.prototype.canMoveNode = function(node, x, y, width, height) {
  491. if (!this.isNodeChangedPosition(node, x, y, width, height)) {
  492. return false;
  493. }
  494. var hasLocked = Boolean(this.nodes.find(function(n) { return n.locked; }));
  495. if (!this.maxRow && !hasLocked) {
  496. return true;
  497. }
  498. var clonedNode;
  499. var clone = new GridStackEngine(
  500. this.column,
  501. null,
  502. this.float,
  503. 0,
  504. this.nodes.map(function(n) {
  505. if (n === node) {
  506. clonedNode = $.extend({}, n);
  507. return clonedNode;
  508. }
  509. return $.extend({}, n);
  510. }));
  511. if (!clonedNode) { return true;}
  512. clone.moveNode(clonedNode, x, y, width, height);
  513. var res = true;
  514. if (hasLocked) {
  515. res &= !Boolean(clone.nodes.find(function(n) {
  516. return n !== clonedNode && Boolean(n.locked) && Boolean(n._dirty);
  517. }));
  518. }
  519. if (this.maxRow) {
  520. res &= clone.getRow() <= this.maxRow;
  521. }
  522. return res;
  523. };
  524. GridStackEngine.prototype.canBePlacedWithRespectToHeight = function(node) {
  525. if (!this.maxRow) {
  526. return true;
  527. }
  528. var clone = new GridStackEngine(
  529. this.column,
  530. null,
  531. this.float,
  532. 0,
  533. this.nodes.map(function(n) { return $.extend({}, n); }));
  534. clone.addNode(node);
  535. return clone.getRow() <= this.maxRow;
  536. };
  537. GridStackEngine.prototype.isNodeChangedPosition = function(node, x, y, width, height) {
  538. if (typeof x !== 'number') { x = node.x; }
  539. if (typeof y !== 'number') { y = node.y; }
  540. if (typeof width !== 'number') { width = node.width; }
  541. if (typeof height !== 'number') { height = node.height; }
  542. if (node.maxWidth !== undefined) { width = Math.min(width, node.maxWidth); }
  543. if (node.maxHeight !== undefined) { height = Math.min(height, node.maxHeight); }
  544. if (node.minWidth !== undefined) { width = Math.max(width, node.minWidth); }
  545. if (node.minHeight !== undefined) { height = Math.max(height, node.minHeight); }
  546. if (node.x === x && node.y === y && node.width === width && node.height === height) {
  547. return false;
  548. }
  549. return true;
  550. };
  551. GridStackEngine.prototype.moveNode = function(node, x, y, width, height, noPack) {
  552. if (node.locked) { return null; }
  553. if (typeof x !== 'number') { x = node.x; }
  554. if (typeof y !== 'number') { y = node.y; }
  555. if (typeof width !== 'number') { width = node.width; }
  556. if (typeof height !== 'number') { height = node.height; }
  557. // constrain the passed in values and check if we're still changing our node
  558. var resizing = (node.width !== width || node.height !== height);
  559. var nn = { x: x, y: y, width: width, height: height,
  560. maxWidth: node.maxWidth, maxHeight: node.maxHeight, minWidth: node.minWidth, minHeight: node.minHeight};
  561. nn = this._prepareNode(nn, resizing);
  562. if (node.x === nn.x && node.y === nn.y && node.width === nn.width && node.height === nn.height) {
  563. return null;
  564. }
  565. node._dirty = true;
  566. node.x = node.lastTriedX = nn.x;
  567. node.y = node.lastTriedY = nn.y;
  568. node.width = node.lastTriedWidth = nn.width;
  569. node.height = node.lastTriedHeight = nn.height;
  570. this._fixCollisions(node);
  571. if (!noPack) {
  572. this._packNodes();
  573. this._notify();
  574. }
  575. return node;
  576. };
  577. GridStackEngine.prototype.getRow = function() {
  578. return this.nodes.reduce(function(memo, n) { return Math.max(memo, n.y + n.height); }, 0);
  579. };
  580. GridStackEngine.prototype.beginUpdate = function(node) {
  581. if (node._updating) return;
  582. node._updating = true;
  583. this.nodes.forEach(function(n) { n._packY = n.y; });
  584. };
  585. GridStackEngine.prototype.endUpdate = function() {
  586. var n = this.nodes.find(function(n) { return n._updating; });
  587. if (n) {
  588. n._updating = false;
  589. this.nodes.forEach(function(n) { delete n._packY; });
  590. }
  591. };
  592. /**
  593. * Construct a grid item from the given element and options
  594. * @param {GridStackElement} el
  595. * @param {GridstackOptions} opts
  596. */
  597. var GridStack = function(el, opts) {
  598. var self = this;
  599. var oneColumnMode, _prevColumn, isAutoCellHeight;
  600. opts = opts || {};
  601. this.$el = $(el); // TODO: legacy code
  602. this.el = this.$el.get(0); // exposed HTML element to the user
  603. obsoleteOpts(opts, 'width', 'column', 'v0.5.3');
  604. obsoleteOpts(opts, 'height', 'maxRow', 'v0.5.3');
  605. obsoleteOptsDel(opts, 'oneColumnModeClass', 'v0.6.3', '. Use class `.grid-stack-1` instead');
  606. // container attributes
  607. obsoleteAttr(this.$el, 'data-gs-width', 'data-gs-column', 'v0.5.3');
  608. obsoleteAttr(this.$el, 'data-gs-height', 'data-gs-max-row', 'v0.5.3');
  609. obsoleteAttr(this.$el, 'data-gs-current-height', 'data-gs-current-row', 'v1.0.0');
  610. opts.itemClass = opts.itemClass || 'grid-stack-item';
  611. var isNested = this.$el.closest('.' + opts.itemClass).length > 0;
  612. // if row property exists, replace minRow and maxRow instead
  613. if (opts.row) {
  614. opts.minRow = opts.maxRow = opts.row;
  615. delete opts.row;
  616. }
  617. var rowAttr = parseInt(this.$el.attr('data-gs-row'));
  618. // elements attributes override any passed options (like CSS style) - merge the two together
  619. this.opts = Utils.defaults(opts, {
  620. column: parseInt(this.$el.attr('data-gs-column')) || 12,
  621. minRow: rowAttr ? rowAttr : parseInt(this.$el.attr('data-gs-min-row')) || 0,
  622. maxRow: rowAttr ? rowAttr : parseInt(this.$el.attr('data-gs-max-row')) || 0,
  623. itemClass: 'grid-stack-item',
  624. placeholderClass: 'grid-stack-placeholder',
  625. placeholderText: '',
  626. handle: '.grid-stack-item-content',
  627. handleClass: null,
  628. cellHeight: 60,
  629. verticalMargin: 20,
  630. auto: true,
  631. minWidth: 768,
  632. float: false,
  633. staticGrid: false,
  634. _class: 'grid-stack-instance-' + (Math.random() * 10000).toFixed(0),
  635. animate: Boolean(this.$el.attr('data-gs-animate')) || false,
  636. alwaysShowResizeHandle: opts.alwaysShowResizeHandle || false,
  637. resizable: Utils.defaults(opts.resizable || {}, {
  638. autoHide: !(opts.alwaysShowResizeHandle || false),
  639. handles: 'se'
  640. }),
  641. draggable: Utils.defaults(opts.draggable || {}, {
  642. handle: (opts.handleClass ? '.' + opts.handleClass : (opts.handle ? opts.handle : '')) ||
  643. '.grid-stack-item-content',
  644. scroll: false,
  645. appendTo: 'body'
  646. }),
  647. disableDrag: opts.disableDrag || false,
  648. disableResize: opts.disableResize || false,
  649. rtl: 'auto',
  650. removable: false,
  651. removableOptions: Utils.defaults(opts.removableOptions || {}, {
  652. accept: '.' + opts.itemClass
  653. }),
  654. removeTimeout: 2000,
  655. verticalMarginUnit: 'px',
  656. cellHeightUnit: 'px',
  657. disableOneColumnMode: opts.disableOneColumnMode || false,
  658. oneColumnModeDomSort: opts.oneColumnModeDomSort,
  659. ddPlugin: null
  660. });
  661. if (this.opts.ddPlugin === false) {
  662. this.opts.ddPlugin = GridStackDragDropPlugin;
  663. } else if (this.opts.ddPlugin === null) {
  664. this.opts.ddPlugin = GridStackDragDropPlugin.registeredPlugins[0] || GridStackDragDropPlugin;
  665. }
  666. this.dd = new this.opts.ddPlugin(this);
  667. if (this.opts.rtl === 'auto') {
  668. this.opts.rtl = this.$el.css('direction') === 'rtl';
  669. }
  670. if (this.opts.rtl) {
  671. this.$el.addClass('grid-stack-rtl');
  672. }
  673. this.opts.isNested = isNested;
  674. isAutoCellHeight = (this.opts.cellHeight === 'auto');
  675. if (isAutoCellHeight) {
  676. // make the cell square initially
  677. self.cellHeight(self.cellWidth(), true);
  678. } else {
  679. this.cellHeight(this.opts.cellHeight, true);
  680. }
  681. this.verticalMargin(this.opts.verticalMargin, true);
  682. this.$el.addClass(this.opts._class);
  683. this._setStaticClass();
  684. if (isNested) {
  685. this.$el.addClass('grid-stack-nested');
  686. }
  687. this._initStyles();
  688. this.engine = new GridStackEngine(this.opts.column, function(nodes, detachNode) {
  689. detachNode = (detachNode === undefined ? true : detachNode);
  690. var maxHeight = 0;
  691. this.nodes.forEach(function(n) {
  692. maxHeight = Math.max(maxHeight, n.y + n.height);
  693. });
  694. nodes.forEach(function(n) {
  695. if (detachNode && n._id === null) {
  696. if (n.el) {
  697. $(n.el).remove();
  698. }
  699. } else {
  700. $(n.el)
  701. .attr('data-gs-x', n.x)
  702. .attr('data-gs-y', n.y)
  703. .attr('data-gs-width', n.width)
  704. .attr('data-gs-height', n.height);
  705. }
  706. });
  707. self._updateStyles(maxHeight + 10);
  708. }, this.opts.float, this.opts.maxRow);
  709. if (this.opts.auto) {
  710. var elements = [];
  711. var _this = this;
  712. this.$el.children('.' + this.opts.itemClass + ':not(.' + this.opts.placeholderClass + ')')
  713. .each(function(index, el) {
  714. el = $(el);
  715. var x = parseInt(el.attr('data-gs-x'));
  716. var y = parseInt(el.attr('data-gs-y'));
  717. elements.push({
  718. el: el.get(0),
  719. // if x,y are missing (autoPosition) add them to end of list - but keep their respective DOM order
  720. i: (Number.isNaN(x) ? 1000 : x) + (Number.isNaN(y) ? 1000 : y) * _this.opts.column
  721. });
  722. });
  723. Utils.sortBy(elements, function(x) { return x.i; }).forEach(function(item) {
  724. this._prepareElement(item.el);
  725. }, this);
  726. }
  727. this.engine._saveInitial(); // initial start of items
  728. this.setAnimation(this.opts.animate);
  729. this.placeholder = $(
  730. '<div class="' + this.opts.placeholderClass + ' ' + this.opts.itemClass + '">' +
  731. '<div class="placeholder-content">' + this.opts.placeholderText + '</div></div>').hide();
  732. this._updateContainerHeight();
  733. this._updateHeightsOnResize = Utils.throttle(function() {
  734. self.cellHeight(self.cellWidth(), false);
  735. }, 100);
  736. /**
  737. * called when we are being resized - check if the one Column Mode needs to be turned on/off
  738. * and remember the prev columns we used.
  739. */
  740. this.onResizeHandler = function() {
  741. if (isAutoCellHeight) {
  742. self._updateHeightsOnResize();
  743. }
  744. if (!self.opts.disableOneColumnMode && (window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth) <= self.opts.minWidth) {
  745. if (self.oneColumnMode) { return }
  746. self.oneColumnMode = true;
  747. self.column(1);
  748. } else {
  749. if (!self.oneColumnMode) { return }
  750. self.oneColumnMode = false;
  751. self.column(self._prevColumn);
  752. }
  753. };
  754. $(window).resize(this.onResizeHandler);
  755. this.onResizeHandler();
  756. if (!self.opts.staticGrid && typeof self.opts.removable === 'string') {
  757. var trashZone = $(self.opts.removable);
  758. if (!this.dd.isDroppable(trashZone)) {
  759. this.dd.droppable(trashZone, self.opts.removableOptions);
  760. }
  761. this.dd
  762. .on(trashZone, 'dropover', function(event, ui) {
  763. var el = $(ui.draggable);
  764. var node = el.data('_gridstack_node');
  765. if (!node || node._grid !== self) {
  766. return;
  767. }
  768. el.data('inTrashZone', true);
  769. self._setupRemovingTimeout(el);
  770. })
  771. .on(trashZone, 'dropout', function(event, ui) {
  772. var el = $(ui.draggable);
  773. var node = el.data('_gridstack_node');
  774. if (!node || node._grid !== self) {
  775. return;
  776. }
  777. el.data('inTrashZone', false);
  778. self._clearRemovingTimeout(el);
  779. });
  780. }
  781. if (!self.opts.staticGrid && self.opts.acceptWidgets) {
  782. var draggingElement = null;
  783. var onDrag = function(event, ui) {
  784. var el = draggingElement;
  785. var node = el.data('_gridstack_node');
  786. var pos = self.getCellFromPixel({left: event.pageX, top: event.pageY}, true);
  787. var x = Math.max(0, pos.x);
  788. var y = Math.max(0, pos.y);
  789. if (!node._added) {
  790. node._added = true;
  791. node.el = el.get(0);
  792. node.autoPosition = true;
  793. node.x = x;
  794. node.y = y;
  795. self.engine.cleanNodes();
  796. self.engine.beginUpdate(node);
  797. self.engine.addNode(node);
  798. self.$el.append(self.placeholder);
  799. self.placeholder
  800. .attr('data-gs-x', node.x)
  801. .attr('data-gs-y', node.y)
  802. .attr('data-gs-width', node.width)
  803. .attr('data-gs-height', node.height)
  804. .show();
  805. node.el = self.placeholder.get(0);
  806. node._beforeDragX = node.x;
  807. node._beforeDragY = node.y;
  808. self._updateContainerHeight();
  809. }
  810. if (!self.engine.canMoveNode(node, x, y)) {
  811. return;
  812. }
  813. self.engine.moveNode(node, x, y);
  814. self._updateContainerHeight();
  815. };
  816. this.dd
  817. .droppable(self.$el, {
  818. accept: function(el) {
  819. el = $(el);
  820. var node = el.data('_gridstack_node');
  821. if (node && node._grid === self) {
  822. return false;
  823. }
  824. return el.is(self.opts.acceptWidgets === true ? '.grid-stack-item' : self.opts.acceptWidgets);
  825. }
  826. })
  827. .on(self.$el, 'dropover', function(event, ui) {
  828. var el = $(ui.draggable);
  829. var width, height;
  830. // see if we already have a node with widget/height and check for attributes
  831. var origNode = el.data('_gridstack_node');
  832. if (!origNode || !origNode.width || !origNode.height) {
  833. var w = parseInt(el.attr('data-gs-width'));
  834. if (w > 0) { origNode = origNode || {}; origNode.width = w; }
  835. var h = parseInt(el.attr('data-gs-height'));
  836. if (h > 0) { origNode = origNode || {}; origNode.height = h; }
  837. }
  838. // if not calculate the grid size based on element outer size
  839. // height: Each row is cellHeight + verticalMargin, until last one which has no margin below
  840. var cellWidth = self.cellWidth();
  841. var cellHeight = self.cellHeight();
  842. var verticalMargin = self.opts.verticalMargin;
  843. width = origNode && origNode.width ? origNode.width : Math.ceil(el.outerWidth() / cellWidth);
  844. height = origNode && origNode.height ? origNode.height : Math.round((el.outerHeight() + verticalMargin) / (cellHeight + verticalMargin));
  845. draggingElement = el;
  846. var node = self.engine._prepareNode({width: width, height: height, _added: false, _temporary: true});
  847. node.isOutOfGrid = true;
  848. el.data('_gridstack_node', node);
  849. el.data('_gridstack_node_orig', origNode);
  850. el.on('drag', onDrag);
  851. return false; // prevent parent from receiving msg (which may be grid as well)
  852. })
  853. .on(self.$el, 'dropout', function(event, ui) {
  854. // jquery-ui bug. Must verify widget is being dropped out
  855. // check node variable that gets set when widget is out of grid
  856. var el = $(ui.draggable);
  857. if (!el.data('_gridstack_node')) {
  858. return;
  859. }
  860. var node = el.data('_gridstack_node');
  861. if (!node.isOutOfGrid) {
  862. return;
  863. }
  864. el.unbind('drag', onDrag);
  865. node.el = null;
  866. self.engine.removeNode(node);
  867. self.placeholder.detach();
  868. self._updateContainerHeight();
  869. el.data('_gridstack_node', el.data('_gridstack_node_orig'));
  870. return false; // prevent parent from receiving msg (which may be grid as well)
  871. })
  872. .on(self.$el, 'drop', function(event, ui) {
  873. self.placeholder.detach();
  874. var node = $(ui.draggable).data('_gridstack_node');
  875. node.isOutOfGrid = false;
  876. node._grid = self;
  877. var el = $(ui.draggable).clone(false);
  878. el.data('_gridstack_node', node);
  879. var originalNode = $(ui.draggable).data('_gridstack_node_orig');
  880. if (originalNode !== undefined && originalNode._grid !== undefined) {
  881. originalNode._grid._triggerRemoveEvent();
  882. }
  883. $(ui.helper).remove();
  884. node.el = el.get(0);
  885. self.placeholder.hide();
  886. Utils.removePositioningStyles(el);
  887. el.find('div.ui-resizable-handle').remove();
  888. el
  889. .attr('data-gs-x', node.x)
  890. .attr('data-gs-y', node.y)
  891. .attr('data-gs-width', node.width)
  892. .attr('data-gs-height', node.height)
  893. .addClass(self.opts.itemClass)
  894. .enableSelection()
  895. .removeData('draggable')
  896. .removeClass('ui-draggable ui-draggable-dragging ui-draggable-disabled')
  897. .unbind('drag', onDrag);
  898. self.$el.append(el);
  899. self._prepareElementsByNode(el, node);
  900. self._updateContainerHeight();
  901. self.engine._addedNodes.push(node);
  902. self._triggerAddEvent();
  903. self._triggerChangeEvent();
  904. self.engine.endUpdate();
  905. $(ui.draggable).unbind('drag', onDrag);
  906. $(ui.draggable).removeData('_gridstack_node');
  907. $(ui.draggable).removeData('_gridstack_node_orig');
  908. self.$el.trigger('dropped', [originalNode, node]);
  909. return false; // prevent parent from receiving msg (which may be grid as well)
  910. });
  911. }
  912. };
  913. GridStack.prototype._triggerChangeEvent = function(/*forceTrigger*/) {
  914. if (this.engine._batchMode) { return; }
  915. var elements = this.engine.getDirtyNodes(true); // verify they really changed
  916. if (elements && elements.length) {
  917. this.engine._layoutsNodesChange(elements);
  918. this._triggerEvent('change', elements);
  919. }
  920. this.engine._saveInitial(); // we called, now reset initial values & dirty flags
  921. };
  922. GridStack.prototype._triggerAddEvent = function() {
  923. if (this.engine._batchMode) { return; }
  924. if (this.engine._addedNodes && this.engine._addedNodes.length > 0) {
  925. this.engine._layoutsNodesChange(this.engine._addedNodes);
  926. // prevent added nodes from also triggering 'change' event (which is called next)
  927. this.engine._addedNodes.forEach(function (n) { delete n._dirty; });
  928. this._triggerEvent('added', this.engine._addedNodes);
  929. this.engine._addedNodes = [];
  930. }
  931. };
  932. GridStack.prototype._triggerRemoveEvent = function() {
  933. if (this.engine._batchMode) { return; }
  934. if (this.engine._removedNodes && this.engine._removedNodes.length > 0) {
  935. this._triggerEvent('removed', this.engine._removedNodes);
  936. this.engine._removedNodes = [];
  937. }
  938. };
  939. GridStack.prototype._triggerEvent = function(name, data) {
  940. var event = new CustomEvent(name, {detail: data});
  941. this.el.dispatchEvent(event);
  942. };
  943. GridStack.prototype._initStyles = function() {
  944. if (this._stylesId) {
  945. Utils.removeStylesheet(this._stylesId);
  946. }
  947. this._stylesId = 'gridstack-style-' + (Math.random() * 100000).toFixed();
  948. // insert style to parent (instead of 'head') to support WebComponent
  949. this._styles = Utils.createStylesheet(this._stylesId, this.el.parentNode);
  950. if (this._styles !== null) {
  951. this._styles._max = 0;
  952. }
  953. };
  954. GridStack.prototype._updateStyles = function(maxHeight) {
  955. if (this._styles === null || this._styles === undefined) {
  956. return;
  957. }
  958. var prefix = '.' + this.opts._class + ' .' + this.opts.itemClass;
  959. var self = this;
  960. var getHeight;
  961. if (maxHeight === undefined) {
  962. maxHeight = this._styles._max;
  963. }
  964. this._initStyles();
  965. this._updateContainerHeight();
  966. if (!this.opts.cellHeight) { // The rest will be handled by CSS
  967. return ;
  968. }
  969. if (this._styles._max !== 0 && maxHeight <= this._styles._max) { // Keep this._styles._max increasing
  970. return ;
  971. }
  972. if (!this.opts.verticalMargin || this.opts.cellHeightUnit === this.opts.verticalMarginUnit) {
  973. getHeight = function(nbRows, nbMargins) {
  974. return (self.opts.cellHeight * nbRows + self.opts.verticalMargin * nbMargins) +
  975. self.opts.cellHeightUnit;
  976. };
  977. } else {
  978. getHeight = function(nbRows, nbMargins) {
  979. if (!nbRows || !nbMargins) {
  980. return (self.opts.cellHeight * nbRows + self.opts.verticalMargin * nbMargins) +
  981. self.opts.cellHeightUnit;
  982. }
  983. return 'calc(' + ((self.opts.cellHeight * nbRows) + self.opts.cellHeightUnit) + ' + ' +
  984. ((self.opts.verticalMargin * nbMargins) + self.opts.verticalMarginUnit) + ')';
  985. };
  986. }
  987. if (this._styles._max === 0) {
  988. Utils.insertCSSRule(this._styles, prefix, 'min-height: ' + getHeight(1, 0) + ';', 0);
  989. }
  990. if (maxHeight > this._styles._max) {
  991. for (var i = this._styles._max; i < maxHeight; ++i) {
  992. Utils.insertCSSRule(this._styles,
  993. prefix + '[data-gs-height="' + (i + 1) + '"]',
  994. 'height: ' + getHeight(i + 1, i) + ';',
  995. i
  996. );
  997. Utils.insertCSSRule(this._styles,
  998. prefix + '[data-gs-min-height="' + (i + 1) + '"]',
  999. 'min-height: ' + getHeight(i + 1, i) + ';',
  1000. i
  1001. );
  1002. Utils.insertCSSRule(this._styles,
  1003. prefix + '[data-gs-max-height="' + (i + 1) + '"]',
  1004. 'max-height: ' + getHeight(i + 1, i) + ';',
  1005. i
  1006. );
  1007. Utils.insertCSSRule(this._styles,
  1008. prefix + '[data-gs-y="' + i + '"]',
  1009. 'top: ' + getHeight(i, i) + ';',
  1010. i
  1011. );
  1012. }
  1013. this._styles._max = maxHeight;
  1014. }
  1015. };
  1016. GridStack.prototype._updateContainerHeight = function() {
  1017. if (this.engine._batchMode) { return; }
  1018. var row = this.engine.getRow();
  1019. if (row < this.opts.minRow) {
  1020. row = this.opts.minRow;
  1021. }
  1022. // check for css min height. Each row is cellHeight + verticalMargin, until last one which has no margin below
  1023. var cssMinHeight = parseInt(this.$el.css('min-height'));
  1024. if (cssMinHeight > 0) {
  1025. var verticalMargin = this.opts.verticalMargin;
  1026. var minRow = Math.round((cssMinHeight + verticalMargin) / (this.cellHeight() + verticalMargin));
  1027. if (row < minRow) {
  1028. row = minRow;
  1029. }
  1030. }
  1031. this.$el.attr('data-gs-current-row', row);
  1032. if (!this.opts.cellHeight) {
  1033. return ;
  1034. }
  1035. if (!this.opts.verticalMargin) {
  1036. this.$el.css('height', (row * (this.opts.cellHeight)) + this.opts.cellHeightUnit);
  1037. } else if (this.opts.cellHeightUnit === this.opts.verticalMarginUnit) {
  1038. this.$el.css('height', (row * (this.opts.cellHeight + this.opts.verticalMargin) -
  1039. this.opts.verticalMargin) + this.opts.cellHeightUnit);
  1040. } else {
  1041. this.$el.css('height', 'calc(' + ((row * (this.opts.cellHeight)) + this.opts.cellHeightUnit) +
  1042. ' + ' + ((row * (this.opts.verticalMargin - 1)) + this.opts.verticalMarginUnit) + ')');
  1043. }
  1044. };
  1045. GridStack.prototype._setupRemovingTimeout = function(el) {
  1046. var self = this;
  1047. var node = $(el).data('_gridstack_node');
  1048. if (node._removeTimeout || !self.opts.removable) {
  1049. return;
  1050. }
  1051. node._removeTimeout = setTimeout(function() {
  1052. el.addClass('grid-stack-item-removing');
  1053. node._isAboutToRemove = true;
  1054. }, self.opts.removeTimeout);
  1055. };
  1056. GridStack.prototype._clearRemovingTimeout = function(el) {
  1057. var node = $(el).data('_gridstack_node');
  1058. if (!node._removeTimeout) {
  1059. return;
  1060. }
  1061. clearTimeout(node._removeTimeout);
  1062. node._removeTimeout = null;
  1063. el.removeClass('grid-stack-item-removing');
  1064. node._isAboutToRemove = false;
  1065. };
  1066. GridStack.prototype._prepareElementsByNode = function(el, node) {
  1067. var self = this;
  1068. var cellWidth;
  1069. var cellFullHeight; // internal cellHeight + v-margin
  1070. var dragOrResize = function(event, ui) {
  1071. var x = Math.round(ui.position.left / cellWidth);
  1072. var y = Math.floor((ui.position.top + cellFullHeight / 2) / cellFullHeight);
  1073. var width;
  1074. var height;
  1075. if (event.type === 'drag') {
  1076. var distance = ui.position.top - node._prevYPix;
  1077. node._prevYPix = ui.position.top;
  1078. Utils.updateScrollPosition(el[0], ui, distance);
  1079. if (el.data('inTrashZone') || x < 0 || x >= self.engine.column || y < 0 ||
  1080. (!self.engine.float && y > self.engine.getRow())) {
  1081. if (!node._temporaryRemoved) {
  1082. if (self.opts.removable === true) {
  1083. self._setupRemovingTimeout(el);
  1084. }
  1085. x = node._beforeDragX;
  1086. y = node._beforeDragY;
  1087. self.placeholder.detach();
  1088. self.placeholder.hide();
  1089. self.engine.removeNode(node);
  1090. self._updateContainerHeight();
  1091. node._temporaryRemoved = true;
  1092. } else {
  1093. return;
  1094. }
  1095. } else {
  1096. self._clearRemovingTimeout(el);
  1097. if (node._temporaryRemoved) {
  1098. self.engine.addNode(node);
  1099. self.placeholder
  1100. .attr('data-gs-x', x)
  1101. .attr('data-gs-y', y)
  1102. .attr('data-gs-width', width)
  1103. .attr('data-gs-height', height)
  1104. .show();
  1105. self.$el.append(self.placeholder);
  1106. node.el = self.placeholder.get(0);
  1107. node._temporaryRemoved = false;
  1108. }
  1109. }
  1110. } else if (event.type === 'resize') {
  1111. if (x < 0) return;
  1112. width = Math.round(ui.size.width / cellWidth);
  1113. height = Math.round((ui.size.height + self.verticalMargin()) / cellFullHeight);
  1114. }
  1115. // width and height are undefined if not resizing
  1116. var lastTriedWidth = width !== undefined ? width : node.lastTriedWidth;
  1117. var lastTriedHeight = height !== undefined ? height : node.lastTriedHeight;
  1118. if (!self.engine.canMoveNode(node, x, y, width, height) ||
  1119. (node.lastTriedX === x && node.lastTriedY === y &&
  1120. node.lastTriedWidth === lastTriedWidth && node.lastTriedHeight === lastTriedHeight)) {
  1121. return;
  1122. }
  1123. node.lastTriedX = x;
  1124. node.lastTriedY = y;
  1125. node.lastTriedWidth = width;
  1126. node.lastTriedHeight = height;
  1127. self.engine.moveNode(node, x, y, width, height);
  1128. self._updateContainerHeight();
  1129. if (event.type === 'resize') {
  1130. $(event.target).trigger('gsresize', node);
  1131. }
  1132. };
  1133. var onStartMoving = function(event, ui) {
  1134. self.$el.append(self.placeholder);
  1135. var o = $(this);
  1136. self.engine.cleanNodes();
  1137. self.engine.beginUpdate(node);
  1138. cellWidth = self.cellWidth();
  1139. var strictCellHeight = self.cellHeight(); // heigh without v-margin
  1140. // compute height with v-margin (Note: we add 1 margin as last row is missing it)
  1141. cellFullHeight = (self.$el.height() + self.verticalMargin()) / parseInt(self.$el.attr('data-gs-current-row'));
  1142. self.placeholder
  1143. .attr('data-gs-x', o.attr('data-gs-x'))
  1144. .attr('data-gs-y', o.attr('data-gs-y'))
  1145. .attr('data-gs-width', o.attr('data-gs-width'))
  1146. .attr('data-gs-height', o.attr('data-gs-height'))
  1147. .show();
  1148. node.el = self.placeholder.get(0);
  1149. node._beforeDragX = node.x;
  1150. node._beforeDragY = node.y;
  1151. node._prevYPix = ui.position.top;
  1152. var minHeight = (node.minHeight || 1);
  1153. var verticalMargin = self.opts.verticalMargin;
  1154. // mineHeight - Each row is cellHeight + verticalMargin, until last one which has no margin below
  1155. self.dd.resizable(el, 'option', 'minWidth', cellWidth * (node.minWidth || 1));
  1156. self.dd.resizable(el, 'option', 'minHeight', (strictCellHeight * minHeight) + (minHeight - 1) * verticalMargin);
  1157. if (event.type === 'resizestart') {
  1158. o.find('.grid-stack-item').trigger('resizestart');
  1159. }
  1160. };
  1161. var onEndMoving = function(event, ui) {
  1162. var o = $(this);
  1163. if (!o.data('_gridstack_node')) {
  1164. return;
  1165. }
  1166. // var forceNotify = false; what is the point of calling 'change' event with no data, when the 'removed' event is already called ?
  1167. self.placeholder.detach();
  1168. node.el = o.get(0);
  1169. self.placeholder.hide();
  1170. if (node._isAboutToRemove) {
  1171. // forceNotify = true;
  1172. var gridToNotify = el.data('_gridstack_node')._grid;
  1173. gridToNotify._triggerRemoveEvent();
  1174. el.removeData('_gridstack_node');
  1175. el.remove();
  1176. } else {
  1177. self._clearRemovingTimeout(el);
  1178. if (!node._temporaryRemoved) {
  1179. Utils.removePositioningStyles(o);
  1180. o
  1181. .attr('data-gs-x', node.x)
  1182. .attr('data-gs-y', node.y)
  1183. .attr('data-gs-width', node.width)
  1184. .attr('data-gs-height', node.height);
  1185. } else {
  1186. Utils.removePositioningStyles(o);
  1187. o
  1188. .attr('data-gs-x', node._beforeDragX)
  1189. .attr('data-gs-y', node._beforeDragY)
  1190. .attr('data-gs-width', node.width)
  1191. .attr('data-gs-height', node.height);
  1192. node.x = node._beforeDragX;
  1193. node.y = node._beforeDragY;
  1194. node._temporaryRemoved = false;
  1195. self.engine.addNode(node);
  1196. }
  1197. }
  1198. self._updateContainerHeight();
  1199. self._triggerChangeEvent(/*forceNotify*/);
  1200. self.engine.endUpdate();
  1201. var nestedGrids = o.find('.grid-stack');
  1202. if (nestedGrids.length && event.type === 'resizestop') {
  1203. nestedGrids.each(function(index, el) {
  1204. el.gridstack.onResizeHandler();
  1205. });
  1206. o.find('.grid-stack-item').trigger('resizestop');
  1207. o.find('.grid-stack-item').trigger('gsresizestop');
  1208. }
  1209. if (event.type === 'resizestop') {
  1210. self.$el.trigger('gsresizestop', o);
  1211. }
  1212. };
  1213. this.dd
  1214. .draggable(el, {
  1215. start: onStartMoving,
  1216. stop: onEndMoving,
  1217. drag: dragOrResize
  1218. })
  1219. .resizable(el, {
  1220. start: onStartMoving,
  1221. stop: onEndMoving,
  1222. resize: dragOrResize
  1223. });
  1224. if (node.noMove || this.opts.disableDrag || this.opts.staticGrid) {
  1225. this.dd.draggable(el, 'disable');
  1226. }
  1227. if (node.noResize || this.opts.disableResize || this.opts.staticGrid) {
  1228. this.dd.resizable(el, 'disable');
  1229. }
  1230. this._writeAttr(el, node);
  1231. };
  1232. GridStack.prototype._prepareElement = function(el, triggerAddEvent) {
  1233. triggerAddEvent = triggerAddEvent !== undefined ? triggerAddEvent : false;
  1234. var self = this;
  1235. el = $(el);
  1236. el.addClass(this.opts.itemClass);
  1237. var node = this._readAttr(el, {el: el.get(0), _grid: self});
  1238. node = self.engine.addNode(node, triggerAddEvent);
  1239. el.data('_gridstack_node', node);
  1240. this._prepareElementsByNode(el, node);
  1241. };
  1242. /** call to write any default attributes back to element */
  1243. GridStack.prototype._writeAttr = function(el, node) {
  1244. if (!node) { return; }
  1245. el = $(el);
  1246. // Note: passing null removes the attr in jquery
  1247. if (node.x !== undefined) { el.attr('data-gs-x', node.x); }
  1248. if (node.y !== undefined) { el.attr('data-gs-y', node.y); }
  1249. if (node.width !== undefined) { el.attr('data-gs-width', node.width); }
  1250. if (node.height !== undefined) { el.attr('data-gs-height', node.height); }
  1251. if (node.autoPosition !== undefined) { el.attr('data-gs-auto-position', node.autoPosition ? true : null); }
  1252. if (node.minWidth !== undefined) { el.attr('data-gs-min-width', node.minWidth); }
  1253. if (node.maxWidth !== undefined) { el.attr('data-gs-max-width', node.maxWidth); }
  1254. if (node.minHeight !== undefined) { el.attr('data-gs-min-height', node.minHeight); }
  1255. if (node.maxHeight !== undefined) { el.attr('data-gs-max-height', node.maxHeight); }
  1256. if (node.noResize !== undefined) { el.attr('data-gs-no-resize', node.noResize ? true : null); }
  1257. if (node.noMove !== undefined) { el.attr('data-gs-no-move', node.noMove ? true : null); }
  1258. if (node.locked !== undefined) { el.attr('data-gs-locked', node.locked ? true : null); }
  1259. if (node.resizeHandles !== undefined) { el.attr('data-gs-resize-handles', node.resizeHandles); }
  1260. if (node.id !== undefined) { el.attr('data-gs-id', node.id); }
  1261. };
  1262. /** call to read any default attributes back to element */
  1263. GridStack.prototype._readAttr = function(el, node) {
  1264. el = $(el);
  1265. node = node || {};
  1266. node.x = el.attr('data-gs-x');
  1267. node.y = el.attr('data-gs-y');
  1268. node.width = el.attr('data-gs-width');
  1269. node.height = el.attr('data-gs-height');
  1270. node.autoPosition = Utils.toBool(el.attr('data-gs-auto-position'));
  1271. node.maxWidth = el.attr('data-gs-max-width');
  1272. node.minWidth = el.attr('data-gs-min-width');
  1273. node.maxHeight = el.attr('data-gs-max-height');
  1274. node.minHeight = el.attr('data-gs-min-height');
  1275. node.noResize = Utils.toBool(el.attr('data-gs-no-resize'));
  1276. node.noMove = Utils.toBool(el.attr('data-gs-no-move'));
  1277. node.locked = Utils.toBool(el.attr('data-gs-locked'));
  1278. node.resizeHandles = el.attr('data-gs-resize-handles');
  1279. node.id = el.attr('data-gs-id');
  1280. return node;
  1281. };
  1282. GridStack.prototype.setAnimation = function(enable) {
  1283. if (enable) {
  1284. this.$el.addClass('grid-stack-animate');
  1285. } else {
  1286. this.$el.removeClass('grid-stack-animate');
  1287. }
  1288. };
  1289. GridStack.prototype.addWidget = function(el, opt, y, width, height, autoPosition, minWidth, maxWidth, minHeight, maxHeight, id) {
  1290. // new way of calling with an object - make sure all items have been properly initialized
  1291. if (opt === undefined || typeof opt === 'object') {
  1292. // Tempting to initialize the passed in opt with default and valid values, but this break knockout demos
  1293. // as the actual value are filled in when _prepareElement() calls el.attr('data-gs-xyz) before adding the node.
  1294. // opt = this.engine._prepareNode(opt);
  1295. } else {
  1296. // old legacy way of calling with items spelled out - call us back with single object instead (so we can properly initialized values)
  1297. return this.addWidget(el, {x: opt, y: y, width: width, height: height, autoPosition: autoPosition,
  1298. minWidth: minWidth, maxWidth: maxWidth, minHeight: minHeight, maxHeight: maxHeight, id: id});
  1299. }
  1300. el = $(el);
  1301. if (opt) { // see knockout above
  1302. // make sure we load any DOM attributes that are not specified in passed in options (which override)
  1303. domAttr = this._readAttr(el);
  1304. Utils.defaults(opt, domAttr);
  1305. this.engine._prepareNode(opt);
  1306. }
  1307. this._writeAttr(el, opt);
  1308. this.$el.append(el);
  1309. return this.makeWidget(el);
  1310. };
  1311. GridStack.prototype.makeWidget = function(el) {
  1312. el = $(el);
  1313. this._prepareElement(el, true);
  1314. this._updateContainerHeight();
  1315. this._triggerAddEvent();
  1316. this._triggerChangeEvent(true); // trigger any other changes
  1317. return el.get(0);
  1318. };
  1319. GridStack.prototype.willItFit = function(x, y, width, height, autoPosition) {
  1320. var node = {x: x, y: y, width: width, height: height, autoPosition: autoPosition};
  1321. return this.engine.canBePlacedWithRespectToHeight(node);
  1322. };
  1323. GridStack.prototype.removeWidget = function(el, detachNode) {
  1324. el = $(el);
  1325. var node = el.data('_gridstack_node');
  1326. // For Meteor support: https://github.com/gridstack/gridstack.js/pull/272
  1327. if (!node) {
  1328. node = this.engine.getNodeDataByDOMEl(el.get(0));
  1329. }
  1330. if (!node || node.el.parentElement !== this.el) return; // not our child!
  1331. // remove our DOM data (circular link) and drag&drop permanently
  1332. el.removeData('_gridstack_node');
  1333. this.dd.draggable(el, 'destroy').resizable(el, 'destroy');
  1334. this.engine.removeNode(node, detachNode);
  1335. this._triggerRemoveEvent();
  1336. this._triggerChangeEvent(true); // trigger any other changes
  1337. };
  1338. GridStack.prototype.removeAll = function(detachNode) {
  1339. // always remove our DOM data (circular link) before list gets emptied and drag&drop permanently
  1340. this.engine.nodes.forEach(function(node) {
  1341. var el = $(node.el);
  1342. el.removeData('_gridstack_node');
  1343. this.dd.draggable(el, 'destroy').resizable(el, 'destroy');
  1344. }, this);
  1345. this.engine.removeAll(detachNode);
  1346. this._triggerRemoveEvent();
  1347. };
  1348. GridStack.prototype.destroy = function(detachGrid) {
  1349. $(window).off('resize', this.onResizeHandler);
  1350. if (detachGrid === false) {
  1351. this.removeAll(false);
  1352. this.$el.removeClass(this.opts._class);
  1353. delete this.$el.get(0).gridstack;
  1354. } else {
  1355. this.$el.remove();
  1356. }
  1357. Utils.removeStylesheet(this._stylesId);
  1358. if (this.engine) {
  1359. this.engine = null;
  1360. }
  1361. };
  1362. GridStack.prototype.resizable = function(el, val) {
  1363. var self = this;
  1364. el = $(el);
  1365. el.each(function(index, el) {
  1366. el = $(el);
  1367. var node = el.data('_gridstack_node');
  1368. if (!node) { return; }
  1369. node.noResize = !(val || false);
  1370. if (node.noResize) {
  1371. self.dd.resizable(el, 'disable');
  1372. } else {
  1373. self.dd.resizable(el, 'enable');
  1374. }
  1375. });
  1376. return this;
  1377. };
  1378. GridStack.prototype.movable = function(el, val) {
  1379. var self = this;
  1380. el = $(el);
  1381. el.each(function(index, el) {
  1382. el = $(el);
  1383. var node = el.data('_gridstack_node');
  1384. if (!node) { return; }
  1385. node.noMove = !(val || false);
  1386. if (node.noMove) {
  1387. self.dd.draggable(el, 'disable');
  1388. el.removeClass('ui-draggable-handle');
  1389. } else {
  1390. self.dd.draggable(el, 'enable');
  1391. el.addClass('ui-draggable-handle');
  1392. }
  1393. });
  1394. return this;
  1395. };
  1396. GridStack.prototype.enableMove = function(doEnable, includeNewWidgets) {
  1397. this.movable(this.$el.children('.' + this.opts.itemClass), doEnable);
  1398. if (includeNewWidgets) {
  1399. this.opts.disableDrag = !doEnable;
  1400. }
  1401. };
  1402. GridStack.prototype.enableResize = function(doEnable, includeNewWidgets) {
  1403. this.resizable(this.$el.children('.' + this.opts.itemClass), doEnable);
  1404. if (includeNewWidgets) {
  1405. this.opts.disableResize = !doEnable;
  1406. }
  1407. };
  1408. GridStack.prototype.disable = function() {
  1409. this.movable(this.$el.children('.' + this.opts.itemClass), false);
  1410. this.resizable(this.$el.children('.' + this.opts.itemClass), false);
  1411. this.$el.trigger('disable');
  1412. };
  1413. GridStack.prototype.enable = function() {
  1414. this.movable(this.$el.children('.' + this.opts.itemClass), true);
  1415. this.resizable(this.$el.children('.' + this.opts.itemClass), true);
  1416. this.$el.trigger('enable');
  1417. };
  1418. GridStack.prototype.locked = function(el, val) {
  1419. el = $(el);
  1420. el.each(function(index, el) {
  1421. el = $(el);
  1422. var node = el.data('_gridstack_node');
  1423. if (!node) { return; }
  1424. node.locked = (val || false);
  1425. el.attr('data-gs-locked', node.locked ? 'yes' : null);
  1426. });
  1427. return this;
  1428. };
  1429. GridStack.prototype.maxHeight = function(el, val) {
  1430. el = $(el);
  1431. el.each(function(index, el) {
  1432. el = $(el);
  1433. var node = el.data('_gridstack_node');
  1434. if (!node) { return; }
  1435. if (!isNaN(val)) {
  1436. node.maxHeight = (val || false);
  1437. el.attr('data-gs-max-height', val);
  1438. }
  1439. });
  1440. return this;
  1441. };
  1442. GridStack.prototype.minHeight = function(el, val) {
  1443. el = $(el);
  1444. el.each(function(index, el) {
  1445. el = $(el);
  1446. var node = el.data('_gridstack_node');
  1447. if (!node) { return; }
  1448. if (!isNaN(val)) {
  1449. node.minHeight = (val || false);
  1450. el.attr('data-gs-min-height', val);
  1451. }
  1452. });
  1453. return this;
  1454. };
  1455. GridStack.prototype.maxWidth = function(el, val) {
  1456. el = $(el);
  1457. el.each(function(index, el) {
  1458. el = $(el);
  1459. var node = el.data('_gridstack_node');
  1460. if (!node) { return; }
  1461. if (!isNaN(val)) {
  1462. node.maxWidth = (val || false);
  1463. el.attr('data-gs-max-width', val);
  1464. }
  1465. });
  1466. return this;
  1467. };
  1468. GridStack.prototype.minWidth = function(el, val) {
  1469. el = $(el);
  1470. el.each(function(index, el) {
  1471. el = $(el);
  1472. var node = el.data('_gridstack_node');
  1473. if (!node) { return; }
  1474. if (!isNaN(val)) {
  1475. node.minWidth = (val || false);
  1476. el.attr('data-gs-min-width', val);
  1477. }
  1478. });
  1479. return this;
  1480. };
  1481. GridStack.prototype._updateElement = function(el, callback) {
  1482. el = $(el).first();
  1483. var node = el.data('_gridstack_node');
  1484. if (!node) { return; }
  1485. var self = this;
  1486. self.engine.cleanNodes();
  1487. self.engine.beginUpdate(node);
  1488. callback.call(this, el, node);
  1489. self._updateContainerHeight();
  1490. self._triggerChangeEvent();
  1491. self.engine.endUpdate();
  1492. };
  1493. GridStack.prototype.resize = function(el, width, height) {
  1494. this._updateElement(el, function(el, node) {
  1495. width = (width !== null && width !== undefined) ? width : node.width;
  1496. height = (height !== null && height !== undefined) ? height : node.height;
  1497. this.engine.moveNode(node, node.x, node.y, width, height);
  1498. });
  1499. };
  1500. GridStack.prototype.move = function(el, x, y) {
  1501. this._updateElement(el, function(el, node) {
  1502. x = (x !== null && x !== undefined) ? x : node.x;
  1503. y = (y !== null && y !== undefined) ? y : node.y;
  1504. this.engine.moveNode(node, x, y, node.width, node.height);
  1505. });
  1506. };
  1507. GridStack.prototype.update = function(el, x, y, width, height) {
  1508. this._updateElement(el, function(el, node) {
  1509. x = (x !== null && x !== undefined) ? x : node.x;
  1510. y = (y !== null && y !== undefined) ? y : node.y;
  1511. width = (width !== null && width !== undefined) ? width : node.width;
  1512. height = (height !== null && height !== undefined) ? height : node.height;
  1513. this.engine.moveNode(node, x, y, width, height);
  1514. });
  1515. };
  1516. /**
  1517. * relayout grid items to reclaim any empty space
  1518. */
  1519. GridStack.prototype.compact = function() {
  1520. if (this.engine.nodes.length === 0) { return; }
  1521. this.batchUpdate();
  1522. this.engine._sortNodes();
  1523. var nodes = this.engine.nodes;
  1524. this.engine.nodes = []; // pretend we have no nodes to conflict layout to start with...
  1525. nodes.forEach(function(node) {
  1526. if (!node.noMove && !node.locked) {
  1527. node.autoPosition = true;
  1528. }
  1529. this.engine.addNode(node, false); // 'false' for add event trigger
  1530. node._dirty = true; // force attr update
  1531. }, this);
  1532. this.commit();
  1533. };
  1534. GridStack.prototype.verticalMargin = function(val, noUpdate) {
  1535. if (val === undefined) {
  1536. return this.opts.verticalMargin;
  1537. }
  1538. var heightData = Utils.parseHeight(val);
  1539. if (this.opts.verticalMarginUnit === heightData.unit && this.opts.maxRow === heightData.height) {
  1540. return ;
  1541. }
  1542. this.opts.verticalMarginUnit = heightData.unit;
  1543. this.opts.verticalMargin = heightData.height;
  1544. if (!noUpdate) {
  1545. this._updateStyles();
  1546. }
  1547. };
  1548. /** set/get the current cell height value */
  1549. GridStack.prototype.cellHeight = function(val, noUpdate) {
  1550. // getter - returns the opts stored height else compute it...
  1551. if (val === undefined) {
  1552. if (this.opts.cellHeight && this.opts.cellHeight !== 'auto') {
  1553. return this.opts.cellHeight;
  1554. }
  1555. // compute the height taking margin into account (each row has margin other than last one)
  1556. var o = this.$el.children('.' + this.opts.itemClass).first();
  1557. var height = o.attr('data-gs-height');
  1558. var verticalMargin = this.opts.verticalMargin;
  1559. return Math.round((o.outerHeight() - (height - 1) * verticalMargin) / height);
  1560. }
  1561. // setter - updates the cellHeight value if they changed
  1562. var heightData = Utils.parseHeight(val);
  1563. if (this.opts.cellHeightUnit === heightData.unit && this.opts.cellHeight === heightData.height) {
  1564. return ;
  1565. }
  1566. this.opts.cellHeightUnit = heightData.unit;
  1567. this.opts.cellHeight = heightData.height;
  1568. if (!noUpdate) {
  1569. this._updateStyles();
  1570. }
  1571. };
  1572. GridStack.prototype.cellWidth = function() {
  1573. // TODO: take margin into account ($horizontal_padding in .scss) to make cellHeight='auto' square ? (see 810-many-columns.html)
  1574. return Math.round(this.$el.outerWidth() / this.opts.column);
  1575. };
  1576. GridStack.prototype.getCellFromPixel = function(position, useOffset) {
  1577. var containerPos = (useOffset !== undefined && useOffset) ?
  1578. this.$el.offset() : this.$el.position();
  1579. var relativeLeft = position.left - containerPos.left;
  1580. var relativeTop = position.top - containerPos.top;
  1581. var columnWidth = Math.floor(this.$el.width() / this.opts.column);
  1582. var rowHeight = Math.floor(this.$el.height() / parseInt(this.$el.attr('data-gs-current-row')));
  1583. return {x: Math.floor(relativeLeft / columnWidth), y: Math.floor(relativeTop / rowHeight)};
  1584. };
  1585. GridStack.prototype.batchUpdate = function() {
  1586. this.engine.batchUpdate();
  1587. };
  1588. GridStack.prototype.commit = function() {
  1589. this.engine.commit();
  1590. this._triggerRemoveEvent();
  1591. this._triggerAddEvent();
  1592. this._triggerChangeEvent();
  1593. };
  1594. GridStack.prototype.isAreaEmpty = function(x, y, width, height) {
  1595. return this.engine.isAreaEmpty(x, y, width, height);
  1596. };
  1597. GridStack.prototype.setStatic = function(staticValue) {
  1598. this.opts.staticGrid = (staticValue === true);
  1599. this.enableMove(!staticValue);
  1600. this.enableResize(!staticValue);
  1601. this._setStaticClass();
  1602. };
  1603. GridStack.prototype._setStaticClass = function() {
  1604. var staticClassName = 'grid-stack-static';
  1605. if (this.opts.staticGrid === true) {
  1606. this.$el.addClass(staticClassName);
  1607. } else {
  1608. this.$el.removeClass(staticClassName);
  1609. }
  1610. };
  1611. /** called whenever a node is added or moved - updates the cached layouts */
  1612. GridStackEngine.prototype._layoutsNodesChange = function(nodes) {
  1613. if (!this._layouts || this._ignoreLayoutsNodeChange) return;
  1614. // remove smaller layouts - we will re-generate those on the fly... larger ones need to update
  1615. this._layouts.forEach(function(layout, column) {
  1616. if (!layout || column === this.column) return;
  1617. if (column < this.column) {
  1618. this._layouts[column] = undefined;
  1619. }
  1620. else {
  1621. // we save the original x,y,w (h isn't cached) to see what actually changed to propagate better.
  1622. // Note: we don't need to check against out of bound scaling/moving as that will be done when using those cache values.
  1623. nodes.forEach(function(node) {
  1624. var n = layout.find(function(l) { return l._id === node._id });
  1625. if (!n) return; // no cache for new nodes. Will use those values.
  1626. var ratio = column / this.column;
  1627. // Y changed, push down same amount
  1628. // TODO: detect doing item 'swaps' will help instead of move (especially in 1 column mode)
  1629. if (node.y !== node._origY) {
  1630. n.y += (node.y - node._origY);
  1631. }
  1632. // X changed, scale from new position
  1633. if (node.x !== node._origX) {
  1634. n.x = Math.round(node.x * ratio);
  1635. }
  1636. // width changed, scale from new width
  1637. if (node.width !== node._origW) {
  1638. n.width = Math.round(node.width * ratio);
  1639. }
  1640. // ...height always carries over from cache
  1641. }, this);
  1642. }
  1643. }, this);
  1644. }
  1645. /**
  1646. * Called to scale the widget width & position up/down based on the column change.
  1647. * Note we store previous layouts (especially original ones) to make it possible to go
  1648. * from say 12 -> 1 -> 12 and get back to where we were.
  1649. *
  1650. * oldColumn: previous number of columns
  1651. * column: new column number
  1652. * nodes?: different sorted list (ex: DOM order) instead of current list
  1653. */
  1654. GridStackEngine.prototype._updateNodeWidths = function(oldColumn, column, nodes) {
  1655. if (!this.nodes.length || oldColumn === column) { return; }
  1656. // cache the current layout in case they want to go back (like 12 -> 1 -> 12) as it requires original data
  1657. var copy = [this.nodes.length];
  1658. this.nodes.forEach(function(n, i) {copy[i] = {x: n.x, y: n.y, width: n.width, _id: n._id}}); // only thing we change is x,y,w and id to find it back
  1659. this._layouts = this._layouts || []; // use array to find larger quick
  1660. this._layouts[oldColumn] = copy;
  1661. // if we're going to 1 column and using DOM order rather than default sorting, then generate that layout
  1662. if (column === 1 && nodes && nodes.length) {
  1663. var top = 0;
  1664. nodes.forEach(function(n) {
  1665. n.x = 0;
  1666. n.width = 1;
  1667. n.y = Math.max(n.y, top);
  1668. top = n.y + n.height;
  1669. });
  1670. } else {
  1671. nodes = Utils.sort(this.nodes, -1, oldColumn); // current column reverse sorting so we can insert last to front (limit collision)
  1672. }
  1673. // see if we have cached previous layout.
  1674. var cacheNodes = this._layouts[column] || [];
  1675. // if not AND we are going up in size start with the largest layout as down-scaling is more accurate
  1676. var lastIndex = this._layouts.length - 1;
  1677. if (cacheNodes.length === 0 && column > oldColumn && column < lastIndex) {
  1678. cacheNodes = this._layouts[lastIndex] || [];
  1679. if (cacheNodes.length) {
  1680. // pretend we came from that larger column by assigning those values as starting point
  1681. oldColumn = lastIndex;
  1682. cacheNodes.forEach(function(cacheNode) {
  1683. var j = nodes.findIndex(function(n) {return n && n._id === cacheNode._id});
  1684. if (j !== -1) {
  1685. // still current, use cache info positions
  1686. nodes[j].x = cacheNode.x;
  1687. nodes[j].y = cacheNode.y;
  1688. nodes[j].width = cacheNode.width;
  1689. }
  1690. });
  1691. cacheNodes = []; // we still don't have new column cached data... will generate from larger one.
  1692. }
  1693. }
  1694. // if we found cache re-use those nodes that are still current
  1695. var newNodes = [];
  1696. cacheNodes.forEach(function(cacheNode) {
  1697. var j = nodes.findIndex(function(n) {return n && n._id === cacheNode._id});
  1698. if (j !== -1) {
  1699. // still current, use cache info positions
  1700. nodes[j].x = cacheNode.x;
  1701. nodes[j].y = cacheNode.y;
  1702. nodes[j].width = cacheNode.width;
  1703. newNodes.push(nodes[j]);
  1704. nodes[j] = null; // erase it so we know what's left
  1705. }
  1706. });
  1707. // ...and add any extra non-cached ones
  1708. var ratio = column / oldColumn;
  1709. nodes.forEach(function(node) {
  1710. if (!node) return;
  1711. node.x = (column === 1 ? 0 : Math.round(node.x * ratio));
  1712. node.width = ((column === 1 || oldColumn === 1) ? 1 : (Math.round(node.width * ratio) || 1));
  1713. newNodes.push(node);
  1714. });
  1715. // finally relayout them in reverse order (to get correct placement)
  1716. newNodes = Utils.sort(newNodes, -1, column);
  1717. this._ignoreLayoutsNodeChange = true;
  1718. this.batchUpdate();
  1719. this.nodes = []; // pretend we have no nodes to start with (we use same structures) to simplify layout
  1720. newNodes.forEach(function(node) {
  1721. this.addNode(node, false); // 'false' for add event trigger
  1722. node._dirty = true; // force attr update
  1723. }, this);
  1724. this.commit();
  1725. delete this._ignoreLayoutsNodeChange;
  1726. }
  1727. /** called to save initial position/size */
  1728. GridStackEngine.prototype._saveInitial = function() {
  1729. this.nodes.forEach(function(n) {
  1730. n._origX = n.x;
  1731. n._origY = n.y;
  1732. n._origW = n.width;
  1733. n._origH = n.height;
  1734. delete n._dirty;
  1735. });
  1736. }
  1737. /**
  1738. * set/get number of columns in the grid. Will attempt to update existing widgets
  1739. * to conform to new number of columns. Requires `gridstack-extra.css` or `gridstack-extra.min.css` for [2-11],
  1740. * else you will need to generate correct CSS (see https://github.com/gridstack/gridstack.js#change-grid-columns)
  1741. * @param column - Integer > 0 (default 12).
  1742. * @param doNotPropagate if true existing widgets will not be updated (optional)
  1743. */
  1744. GridStack.prototype.column = function(column, doNotPropagate) {
  1745. // getter - returns the opts stored mode
  1746. if (column === undefined) {
  1747. return this.opts.column;
  1748. }
  1749. // setter
  1750. if (this.opts.column === column) { return; }
  1751. var oldColumn = this.opts.column;
  1752. // if we go into 1 column mode (which happens if we're sized less than minWidth unless disableOneColumnMode is on)
  1753. // then remember the original columns so we can restore.
  1754. if (column === 1) {
  1755. this._prevColumn = oldColumn;
  1756. } else {
  1757. delete this._prevColumn;
  1758. }
  1759. this.$el.removeClass('grid-stack-' + oldColumn);
  1760. this.$el.addClass('grid-stack-' + column);
  1761. this.opts.column = this.engine.column = column;
  1762. if (doNotPropagate === true) { return; }
  1763. // update the items now - see if the dom order nodes should be passed instead (else default to current list)
  1764. var domNodes;
  1765. if (this.opts.oneColumnModeDomSort && column === 1) {
  1766. domNodes = [];
  1767. this.$el.children('.' + this.opts.itemClass).each(function(index, el) {
  1768. var node = $(el).data('_gridstack_node');
  1769. if (node) { domNodes.push(node); }
  1770. });
  1771. if (!domNodes.length) { domNodes = undefined; }
  1772. }
  1773. this.engine._updateNodeWidths(oldColumn, column, domNodes);
  1774. // and trigger our event last...
  1775. this.engine._ignoreLayoutsNodeChange = true;
  1776. this._triggerChangeEvent();
  1777. delete this.engine._ignoreLayoutsNodeChange;
  1778. };
  1779. GridStack.prototype.float = function(val) {
  1780. // getter - returns the opts stored mode
  1781. if (val === undefined) {
  1782. return this.opts.float || false;
  1783. }
  1784. // setter - updates the mode and relayout if gravity is back on
  1785. if (this.opts.float === val) { return; }
  1786. this.opts.float = this.engine.float = val || false;
  1787. if (!val) {
  1788. this.engine._packNodes();
  1789. this.engine._notify();
  1790. this._triggerChangeEvent();
  1791. }
  1792. };
  1793. GridStack.prototype.getRow = function() {
  1794. return this.engine.getRow();
  1795. }
  1796. /** Event handler that extracts our CustomEvent data out automatically for receiving custom
  1797. * notifications (see doc for supported events)
  1798. */
  1799. GridStack.prototype.on = function(eventName, callback) {
  1800. // check for array of names being passed instead
  1801. if (eventName.indexOf(' ') !== -1) {
  1802. var names = eventName.split(' ');
  1803. names.forEach(function(name) { this.on(name, callback) }, this);
  1804. return;
  1805. }
  1806. if (eventName === 'change' || eventName === 'added' || eventName === 'removed') {
  1807. // native CustomEvent handlers - cash the generic handlers so we can remove
  1808. this._gsEventHandler = this._gsEventHandler || {};
  1809. this._gsEventHandler[eventName] = function(event) { callback(event, event.detail) };
  1810. this.el.addEventListener(eventName, this._gsEventHandler[eventName]);
  1811. } else {
  1812. // still JQuery events
  1813. this.$el.on(eventName, callback);
  1814. }
  1815. }
  1816. /** unsubscribe from the 'on' event */
  1817. GridStack.prototype.off = function(eventName) {
  1818. // check for array of names being passed instead
  1819. if (eventName.indexOf(' ') !== -1) {
  1820. var names = eventName.split(' ');
  1821. names.forEach(function(name) { this.off(name, callback) }, this);
  1822. return;
  1823. }
  1824. if (eventName === 'change' || eventName === 'added' || eventName === 'removed') {
  1825. // remove native CustomEvent handlers
  1826. if (this._gsEventHandler && this._gsEventHandler[eventName]) {
  1827. this.el.removeEventListener(eventName, this._gsEventHandler[eventName]);
  1828. delete this._gsEventHandler[eventName];
  1829. }
  1830. } else {
  1831. // still JQuery events
  1832. this.$el.off(eventName);
  1833. }
  1834. }
  1835. // legacy method renames
  1836. GridStack.prototype.setGridWidth = obsolete(GridStack.prototype.column, 'setGridWidth', 'column', 'v0.5.3');
  1837. GridStack.prototype.setColumn = obsolete(GridStack.prototype.column, 'setColumn', 'column', 'v0.6.4');
  1838. GridStackEngine.prototype.getGridHeight = obsolete(GridStackEngine.prototype.getRow, 'getGridHeight', 'getRow', 'v1.0.0');
  1839. scope.GridStack = GridStack;
  1840. scope.GridStack.Utils = Utils;
  1841. scope.GridStack.Engine = GridStackEngine;
  1842. scope.GridStack.DragDropPlugin = GridStackDragDropPlugin;
  1843. /**
  1844. * initializing the HTML element, or selector string, into a grid will return the grid. Calling it again will
  1845. * simply return the existing instance (ignore any passed options).
  1846. */
  1847. GridStack.init = function(opts, elOrString) {
  1848. if (!elOrString) { elOrString = '.grid-stack' }
  1849. var el = $(elOrString).get(0);
  1850. if (!el) return;
  1851. if (!el.gridstack) {
  1852. el.gridstack = new GridStack(el, Utils.clone(opts));
  1853. }
  1854. return el.gridstack
  1855. };
  1856. /**
  1857. * Will initialize a list of elements (given a selector) and return an array of grids.
  1858. */
  1859. GridStack.initAll = function(opts, selector) {
  1860. if (!selector) { selector = '.grid-stack' }
  1861. var grids = [];
  1862. $(selector).each(function(index, el) {
  1863. if (!el.gridstack) {
  1864. el.gridstack = new GridStack(el, Utils.clone(opts));
  1865. }
  1866. grids.push(el.gridstack);
  1867. });
  1868. return grids;
  1869. };
  1870. return scope.GridStack;
  1871. });