Built files from Bizgaze WebServer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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. });