jquery.grid.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. /**
  2. * Grid()
  3. * by Bachir Soussi-Chiadmi www.g-u-i.net
  4. * version 2.0
  5. */
  6. (function($) {
  7. function Grid(ctx, settings){
  8. var _defaults = {
  9. children:'', // tell which dom elmt to target, by default $ctx.children();
  10. cell_w: 20, cell_h: 20,
  11. cell_padding:{t:5,r:5,b:5,l:5}, // with or without padding cell width and height are the same
  12. padding:{t:5,r:5,b:5,l:5}, // padding (in cell) inside the grid where not placed element
  13. gouttiere: 5, // space between cells h and v
  14. lines: 20, columns: 20,// how much line and columns grid has
  15. grille: false, // see the grid with cell's free space infos
  16. groove: 0, // add groove attitude (randomize placement)
  17. positioning_mode:'random', // positioning_mode play with with the position on items
  18. max_distance:0, // distance max between prev item and current item. O = no limit
  19. min_distance:0, // distance min between prev item and current item. O = no limit
  20. },
  21. $ctx = $(ctx),
  22. _cells = new Array(),
  23. _positions = new Array(),
  24. _grid = this;
  25. this.ctx = $ctx;
  26. this.settings = $.extend({}, _defaults, settings);
  27. this.settings.init_maxdist = this.settings.max_distance;
  28. this.settings.init_mindist = this.settings.min_distance;
  29. // $.log('this.settings', this.settings);
  30. // set dom grille visualsation context if enabled
  31. if (this.settings.grille) {
  32. $ctx.append('<div class="grille"><div>');
  33. $('.grille', $ctx).css({
  34. 'width': '100%',
  35. 'height': '100%',
  36. 'position': 'relative'
  37. });
  38. }
  39. // create all empty cells
  40. for (var l = 0; l < this.settings.lines; l++) {
  41. _cells[l] = new Array();
  42. for (var c = 0; c < this.settings.columns; c++) {
  43. _cells[l][c] = new Cell(this, c, l);
  44. };
  45. };
  46. function _setPrevCell(){
  47. // _grid.prev_cell = false;
  48. switch(_grid.settings.positioning_mode){
  49. case 'lefttop': // add items from top left to bootm rif-ght
  50. _grid.prev_cell = _cells[0][0];
  51. break;
  52. case 'center': // add items from center of grid to both side randomly
  53. _grid.prev_cell = _cells[Math.floor(_grid.settings.lines*.5)][Math.floor(_grid.settings.columns*.5)];
  54. break;
  55. case 'borders': // add items from center of grid to both side randomly
  56. var side = Math.floor(Math.random()*4);
  57. // $.log('side = '+side);
  58. switch(side){
  59. case 0 :
  60. _grid.prev_cell = _cells[0][Math.floor(_grid.settings.columns*Math.random())];
  61. break;
  62. case 1 :
  63. _grid.prev_cell = _cells[_grid.settings.lines-1][Math.floor(_grid.settings.columns*Math.random())];
  64. break;
  65. case 2 :
  66. _grid.prev_cell = _cells[Math.floor(_grid.settings.lines*Math.random())][0];
  67. break;
  68. case 3 :
  69. _grid.prev_cell = _cells[Math.floor(_grid.settings.lines*Math.random())][_grid.settings.columns-1];
  70. break;
  71. }
  72. break;
  73. case 'random': // add items randomly
  74. default:
  75. _grid.prev_cell = _cells[Math.floor(_grid.settings.lines*Math.random())][Math.floor(_grid.settings.columns*Math.random())];
  76. break;
  77. }
  78. }
  79. _setPrevCell();
  80. /**
  81. * placeTargets()
  82. */
  83. function _placeTargets(){
  84. _grid.settings.max_distance = _grid.settings.init_maxdist;
  85. _grid.settings.min_distance = _grid.settings.init_mindist;
  86. // parse targets
  87. var $targets = _grid.settings.children != '' ? $(_grid.settings.children+':not(.grid-placed)', $ctx) : $ctx.children(':not(.grid-placed)');
  88. $targets.each(function() {
  89. var $this = $(this),
  90. // get the width and height ( ! on cell unit ! how much cells it takes ? ) of item to be positioned
  91. this_cells_w = Math.ceil($this.width() / (_grid.settings.cell_w + _grid.settings.gouttiere)),
  92. this_cells_h = Math.ceil($this.height() / (_grid.settings.cell_h + _grid.settings.gouttiere)),
  93. // get free cells function of width and height of item
  94. free_cells = new Array();
  95. while(free_cells.length == 0){
  96. free_cells = getFreeCells(this_cells_w, this_cells_h);
  97. if(free_cells.length == 0)
  98. _grid.settings.max_distance = _grid.settings.max_distance*1.2;
  99. }
  100. if(free_cells.length > 0){
  101. // positioning mode, to change this mode uncomment/comment lines after description
  102. switch(_grid.settings.positioning_mode){
  103. case 'lefttop': // add items from top left to bootm rif-ght
  104. var cell_num = 0,
  105. cell = free_cells[cell_num];
  106. break;
  107. default: // add items from center of grid to both side randomly
  108. // var cell_num = Math.floor((free_cells.length-1)*0.5 + (-2+Math.random()*4));
  109. // cell_num = cell_num > free_cells.length-1 ? free_cells.length-1 : ( cell_num < 0 ? 0 : cell_num );
  110. // var cell = free_cells[cell_num];
  111. var cell_num = Math.round(Math.random() * (free_cells.length - 1)),
  112. cell = free_cells[cell_num];
  113. break;
  114. }
  115. _grid.prev_cell = cell;
  116. var line_limit_loop = cell.row() + (this_cells_h),
  117. column_limit_loop = cell.column() + (this_cells_w);
  118. for (var l = cell.row(); l < line_limit_loop; l++) {
  119. for (var c = cell.column(); c < column_limit_loop; c++) {
  120. var temp_cell = _cells[l][c];
  121. temp_cell.setFull();
  122. };
  123. };
  124. // get tp and left coordinates
  125. // should be directly recorded in the cell object
  126. var top = cell.row() * (_grid.settings.cell_h + _grid.settings.gouttiere),
  127. left = cell.column() * (_grid.settings.cell_w + _grid.settings.gouttiere),
  128. groove = _grid.settings.groove;
  129. // set groove ;)
  130. top = !groove ? top: top + Math.round(Math.random() * groove - groove / 2);
  131. left = !groove ? left: left + Math.round(Math.random() * groove - groove / 2);
  132. // apply ccs position
  133. top += _grid.settings.cell_padding.t;
  134. left += _grid.settings.cell_padding.l;
  135. var event = jQuery.Event("grid_positioned");
  136. event.top = top;
  137. event.left = left;
  138. $this.css({
  139. 'top': top,
  140. 'left': left
  141. }).addClass('grid-positioned').trigger(event);
  142. }else{
  143. // $.log('GRID no free cells left');
  144. // if no free cells exist
  145. $this.css({
  146. 'border': '2px solid red',
  147. 'opacity': 0.2
  148. }).trigger('grid_removed');//.remove();
  149. }
  150. $this.addClass('grid-placed').trigger('grid_placed');
  151. }); // end children each
  152. }
  153. /**
  154. * getFreeCells()
  155. *
  156. * @ width in cells
  157. * @ height in cells
  158. *
  159. * return array of cell objects
  160. *
  161. */
  162. function getFreeCells(this_cells_w, this_cells_h){
  163. var free_cells = new Array(),
  164. padding = _grid.settings.padding,
  165. // max_dist = _grid.settings.max_distance,
  166. // min_dist = _grid.settings.min_distance,
  167. max_dist_l = _grid.settings.lines * _grid.settings.max_distance,
  168. min_dist_l = _grid.settings.lines * _grid.settings.min_distance,
  169. max_dist_c = _grid.settings.columns * _grid.settings.max_distance,
  170. min_dist_c = _grid.settings.columns * _grid.settings.min_distance,
  171. prev_cell = _grid.prev_cell;
  172. // $.log()
  173. for (var l = 0; l < _grid.settings.lines; l++) {
  174. for (var c = 0; c < _grid.settings.columns; c++) {
  175. var cell = _cells[l][c];
  176. /*
  177. TODO about the max_dist keep in mind that all element will not always be 1 cell elemnts !!
  178. */
  179. // here is a big stuff of conditionning
  180. // we check a lot of things
  181. // 1 - basicly if the cell is free
  182. // 2 - if the cell is under the padding limits
  183. // 3 - if she has enought space arround her
  184. // 4 - if we got a prev cell
  185. // 4.1 - if we got a max_dist != 0
  186. // 4.1.1 - if the cell is under the max limits
  187. // 4.2 - if we got a min_dist != 0
  188. // 4.2.1 - if the cell is behind the min limits
  189. // if (cell.isFree() // if cell is free
  190. // && (cell.row() > padding.t && cell.row() < _grid.settings.lines - padding.b
  191. // && cell.column() > padding.l && cell.column() < _grid.settings.columns - padding.r
  192. // )
  193. // && (
  194. // (cell.getAround().right + 1) >= this_cells_w // if enought free space on the right
  195. // && (cell.getAround().bottom + 1) >= this_cells_h // if enought free space on the bottom
  196. // )
  197. // && ( !_grid.prev_cell // else if _grid.prev_cell undefined it's true
  198. // || ( // check max_dist AND min_dist
  199. // (!max_dist // if max_distance is 0 return (stop condition) true
  200. // || ( cell.row() < prev_cell.row()+max_dist && cell.row() > prev_cell.row()-max_dist
  201. // && cell.column() < prev_cell.column()+max_dist && cell.column() > prev_cell.column()-max_dist )// if we got max_distance and prev_cell we check the distance
  202. // )
  203. // &&
  204. // (!min_dist // if min_distance is 0 return (stop condition) true
  205. // || ( (cell.row() > prev_cell.row()+min_dist || cell.row() < prev_cell.row()-min_dist)
  206. // || (cell.column() > prev_cell.column()+min_dist || cell.column() < prev_cell.column()-min_dist) )// if we got min_distance and prev_cell we check the distance
  207. // )
  208. // )
  209. // )
  210. // ) { // true of first big stuff if
  211. if (cell.isFree() // if cell is free
  212. && (cell.row() > padding.t && cell.row() < _grid.settings.lines - padding.b
  213. && cell.column() > padding.l && cell.column() < _grid.settings.columns - padding.r
  214. )
  215. && (
  216. (cell.getAround().right + 1) >= this_cells_w // if enought free space on the right
  217. && (cell.getAround().bottom + 1) >= this_cells_h // if enought free space on the bottom
  218. )
  219. && ( !_grid.prev_cell // else if _grid.prev_cell undefined it's true
  220. || ( // check max_dist AND min_dist
  221. ((!max_dist_l && !max_dist_c) // if max_dist_l AND max_dist_c are 0 return (stop condition) true
  222. || ( (!max_dist_l || (cell.row() < prev_cell.row()+max_dist_l && cell.row() > prev_cell.row()-max_dist_l)) // if max_dist_l is 0, return true, so abort "between test" on lines
  223. && (!max_dist_c || (cell.column() < prev_cell.column()+max_dist_c && cell.column() > prev_cell.column()-max_dist_c)) ) // if max_dist_c is 0, return true, so abort "between test" on columns
  224. ) // if we got max_distance and prev_cell we check the distance
  225. &&
  226. ((!min_dist_l && !min_dist_c) // if min_dist_l AND min_dist_c are 0 return (stop condition) true
  227. || ( (!min_dist_l || (cell.row() > prev_cell.row()+min_dist_c || cell.row() < prev_cell.row()-min_dist_c))
  228. || (!min_dist_c || (cell.column() > prev_cell.column()+min_dist_c || cell.column() < prev_cell.column()-min_dist_c)) )// if we got min_distance and prev_cell we check the distance
  229. )
  230. )
  231. )
  232. ) { // true of first big stuff if
  233. var good = true;
  234. for (var i = 1; i < this_cells_w; i++) { // for each free cells needed on right
  235. var temp_cell = _cells[l][c + i];
  236. if ((temp_cell.getAround().bottom + 1) < this_cells_h) { // if enought free space on bottom
  237. good = false;
  238. break;
  239. }
  240. };
  241. if (good) free_cells.push(cell);
  242. }
  243. };
  244. };
  245. return free_cells;
  246. }
  247. /**
  248. * Cell()
  249. */
  250. function Cell(g, c, r) {
  251. var _grid = g,
  252. _column = c,
  253. _row = r,
  254. _free = true,
  255. _free_cell_right = _grid.settings.columns - 1 - c,
  256. _free_cell_bottom = _grid.settings.lines - 1 - l;
  257. if (_grid.settings.grille) {
  258. var _table = '<table>';
  259. _table += '<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>';
  260. // _table += '<tr><td>&nbsp;</td><td class="state">'+_free+'</td><td class="fr">'+_free_cell_right+'</td></tr>';
  261. _table += '<tr><td>&nbsp;</td><td>&nbsp;</td><td class="fr">' + _free_cell_right + '</td></tr>';
  262. _table += '<tr><td>&nbsp;</td><td class="fb">' + _free_cell_bottom + '</td><td>&nbsp;</td></tr>';
  263. _table += '</table>';
  264. _grid.ctx.find('.grille').append('<div class="cell-' + l + '-' + c + '">' + _table + '<div>');
  265. var _dom = $('.cell-' + l + '-' + c, _grid.ctx.find('.grille'));
  266. _dom.css({
  267. 'position': 'absolute',
  268. 'top': _row * (_grid.settings.cell_h + _grid.settings.gouttiere) + _grid.settings.cell_padding.t,
  269. 'left': _column * (_grid.settings.cell_w + _grid.settings.gouttiere) + _grid.settings.cell_padding.l,
  270. 'width': _grid.settings.cell_w,
  271. 'height': _grid.settings.cell_h,
  272. 'backgroundColor': '#00FF00',
  273. 'opacity': 0.6
  274. });
  275. // $('td', _dom).css({
  276. // 'fontSize': '8px'
  277. // });
  278. }
  279. this.isFree = function() { return _free; };
  280. this.getPos = function() { return { c: _column, l: _row }; };
  281. this.column = function() { return _column; };
  282. this.row = function() { return _row; };
  283. this.getAround = function() { return { right: _free_cell_right, bottom: _free_cell_bottom }; };
  284. this.setFull = function() {
  285. _free = false;
  286. _free_cell_bottom = -1;
  287. _free_cell_right = -1;
  288. if (_grid.settings.grille) {
  289. _dom.css({
  290. 'backgroundColor': '#0000FF'
  291. });
  292. $('.fb', _dom).html(_free_cell_bottom);
  293. $('.fr', _dom).html(_free_cell_right);
  294. }
  295. _propageFreeCells();
  296. };
  297. this.setFreeCellsRight = function(right) {
  298. if (_free) {
  299. // $.log('_setFreeCellRigth | _free_cell_right = ' + _free_cell_right + ' | new right = ' + right);
  300. _free_cell_right = right;
  301. if (_grid.settings.grille) $('.fr', _dom).html(_free_cell_right);
  302. _propageFreeCellsLeft();
  303. }
  304. };
  305. this.setFreeCellsBottom = function(bottom) {
  306. if (_free) {
  307. // $.log('_setFreeCellsBottom | _free_cell_bottom = ' + _free_cell_bottom + ' | new right = ' + bottom);
  308. _free_cell_bottom = bottom;
  309. if (_grid.settings.grille) $('.fb', _dom).html(_free_cell_bottom);
  310. _propageFreeCellsTop();
  311. }
  312. };
  313. function _propageFreeCells() {
  314. // $.log('_propageFreeCells');
  315. _propageFreeCellsLeft();
  316. _propageFreeCellsTop();
  317. };
  318. function _propageFreeCellsLeft() {
  319. // $.log('_propageFreeCellsLeft | _column = '+_column);
  320. if (_column > 0) {
  321. var left_cel = _cells[_row][_column - 1];
  322. left_cel.setFreeCellsRight(_free_cell_right + 1);
  323. }
  324. };
  325. function _propageFreeCellsTop() {
  326. // $.log('_propageFreeCellsTop | _row = '+_row);
  327. if (_row > 0) {
  328. var top_cel = _cells[_row - 1][_column];
  329. top_cel.setFreeCellsBottom(_free_cell_bottom + 1);
  330. }
  331. };
  332. if (typeof Cell.initialized == "undefined") {
  333. Cell.prototype.infos = function() {
  334. //console.log('cell | column = ' + this.column() + ', line = ' + this.row() + ', free = ' + this.isFree() + ', free cell right = ' + this.getAround().right + ', free cell bottom = ' + this.getAround().bottom);
  335. };
  336. Cell.initialized = true;
  337. }
  338. }; // cell()
  339. if (typeof Node.initialized == "undefined") {
  340. Grid.prototype.refresh = function() {
  341. _placeTargets();
  342. };
  343. Grid.prototype.resetPrevCell = function() {
  344. _setPrevCell();
  345. // this.prev_cell = false;
  346. };
  347. Node.initialized = true;
  348. }
  349. };
  350. $.fn.grid = function(settings) {
  351. return this.each(function(index) {
  352. $(this).data('grid', new Grid(this, settings));
  353. });
  354. }
  355. })(jQuery);