Node-Red configuration
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.

gridstack.js 68KB

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