Table.hx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. package hide.comp.cdb;
  2. import js.jquery.Helper.*;
  3. enum DisplayMode {
  4. Table;
  5. Properties;
  6. AllProperties;
  7. }
  8. class Table extends Component {
  9. public var editor : Editor;
  10. public var parent : Table;
  11. public var sheet : cdb.Sheet;
  12. public var lines : Array<Line>;
  13. public var displayMode(default,null) : DisplayMode;
  14. public var columns : Array<cdb.Data.Column>;
  15. public var view : cdb.DiffFile.SheetView;
  16. public function new(editor, sheet, root, mode) {
  17. super(null,root);
  18. this.displayMode = mode;
  19. this.editor = editor;
  20. this.sheet = sheet;
  21. saveDisplayKey = "cdb/"+sheet.name;
  22. @:privateAccess for( t in editor.tables )
  23. if( t.sheet.path == sheet.path )
  24. trace("Dup CDB table!");
  25. @:privateAccess editor.tables.push(this);
  26. root.addClass("cdb-sheet");
  27. root.addClass("s_" + sheet.name);
  28. if( editor.view != null ) {
  29. var cname = parent == null ? null : sheet.parent.sheet.columns[sheet.parent.column].name;
  30. if( parent == null )
  31. view = editor.view.get(sheet.name);
  32. else if( parent.view.sub != null )
  33. view = parent.view.sub.get(cname);
  34. if( view == null ) {
  35. if( parent != null && parent.canEditColumn(cname) )
  36. view = { insert : true, edit : [for( c in sheet.columns ) c.name], sub : {} };
  37. else
  38. view = { insert : false, edit : [], sub : {} };
  39. }
  40. }
  41. refresh();
  42. }
  43. public function getRealSheet() {
  44. return sheet.realSheet;
  45. }
  46. public function canInsert() {
  47. if( sheet.props.dataFiles != null ) return false;
  48. return view == null || view.insert;
  49. }
  50. public function canEditColumn( name : String ) {
  51. return view == null || (view.edit != null && view.edit.indexOf(name) >= 0);
  52. }
  53. public function close() {
  54. for( t in @:privateAccess editor.tables.copy() )
  55. if( t.parent == this )
  56. t.close();
  57. element.remove();
  58. dispose();
  59. }
  60. public function dispose() {
  61. editor.tables.remove(this);
  62. }
  63. public function refresh() {
  64. element.empty();
  65. columns = view == null || view.show == null ? sheet.columns : [for( c in sheet.columns ) if( view.show.indexOf(c.name) >= 0 ) c];
  66. switch( displayMode ) {
  67. case Table:
  68. refreshTable();
  69. case Properties, AllProperties:
  70. refreshProperties();
  71. }
  72. }
  73. function cloneTableHead() {
  74. var target = element.find('thead').first().find('.head');
  75. if (target.length == 0)
  76. return;
  77. var target_children = target.children();
  78. J(".floating-thead").remove();
  79. var clone = J("<div>").addClass("floating-thead");
  80. for (i in 0...target_children.length) {
  81. var targetElt = target_children.eq(i);
  82. var elt = targetElt.clone(true); // clone with events
  83. elt.width(targetElt.width());
  84. elt.css("max-width", targetElt.width());
  85. var txt = elt[0].innerHTML;
  86. elt.empty();
  87. J("<span>" + txt + "</span>").appendTo(elt);
  88. clone.append(elt);
  89. }
  90. J('.cdb').prepend(clone);
  91. }
  92. function refreshTable() {
  93. var cols = J("<thead>").addClass("head");
  94. J("<th>").addClass("start").appendTo(cols);
  95. lines = [for( index in 0...sheet.lines.length ) {
  96. var l = J("<tr>");
  97. var head = J("<td>").addClass("start").text("" + index);
  98. head.appendTo(l);
  99. var line = new Line(this, columns, index, l);
  100. head.mousedown(function(e) {
  101. if( e.which == 3 ) {
  102. editor.popupLine(line);
  103. e.preventDefault();
  104. return;
  105. }
  106. });
  107. l.click(function(e) {
  108. if( e.which == 3 ) {
  109. e.preventDefault();
  110. return;
  111. }
  112. editor.cursor.clickLine(line, e.shiftKey);
  113. });
  114. line;
  115. }];
  116. var colCount = columns.length;
  117. for( c in columns ) {
  118. var editProps = editor.getColumnProps(c);
  119. var col = J("<th>");
  120. col.text(c.name);
  121. col.addClass( "t_"+c.type.getName().substr(1).toLowerCase() );
  122. col.addClass( "n_" + c.name );
  123. col.attr("title", c.name);
  124. col.toggleClass("hidden", !editor.isColumnVisible(c));
  125. col.toggleClass("cat", editProps.categories != null);
  126. if(editProps.categories != null)
  127. for(c in editProps.categories)
  128. col.addClass("cat-" + c);
  129. if( c.documentation != null ) {
  130. col.attr("title", c.documentation);
  131. new Element('<i style="margin-left: 5px" class="ico ico-book"/>').appendTo(col);
  132. }
  133. if( sheet.props.displayColumn == c.name )
  134. col.addClass("display");
  135. col.mousedown(function(e) {
  136. if( e.which == 3 ) {
  137. editor.popupColumn(this, c);
  138. e.preventDefault();
  139. return;
  140. }
  141. });
  142. col.dblclick(function(_) {
  143. if( editor.view == null ) editor.editColumn(getRealSheet(), c);
  144. });
  145. cols.append(col);
  146. }
  147. element.append(cols);
  148. var tbody = J("<tbody>");
  149. var snext = 0, hidden = false;
  150. for( i in 0...lines.length+1 ) {
  151. while( sheet.separators[snext] == i ) {
  152. var sep = makeSeparator(snext, colCount);
  153. sep.element.appendTo(tbody);
  154. if( sep.hidden != null ) hidden = sep.hidden;
  155. snext++;
  156. }
  157. if( i == lines.length ) break;
  158. var line = lines[i];
  159. if( hidden )
  160. line.hide();
  161. else
  162. line.create();
  163. tbody.append(line.element);
  164. }
  165. element.append(tbody);
  166. if( colCount == 0 ) {
  167. var l = J('<tr><td><input type="button" value="Add a column"/></td></tr>').find("input").click(function(_) {
  168. editor.newColumn(sheet);
  169. });
  170. element.append(l);
  171. } else if( sheet.lines.length == 0 && canInsert() ) {
  172. var l = J('<tr><td colspan="${columns.length + 1}"><input type="button" value="Insert Line"/></td></tr>');
  173. l.find("input").click(function(_) {
  174. editor.insertLine(this);
  175. editor.cursor.set(this);
  176. });
  177. element.append(l);
  178. }
  179. if( sheet.parent == null ) {
  180. cols.ready(cloneTableHead);
  181. cols.on("resize", cloneTableHead);
  182. }
  183. }
  184. function makeSeparator( sindex : Int, colCount : Int ) : { element : Element, hidden : Null<Bool> } {
  185. var sep = J("<tr>").addClass("separator").append('<td colspan="${colCount+1}"><a href="#" class="toggle"></a><span></span></td>');
  186. var content = sep.find("span");
  187. var toggle = sep.find("a");
  188. var title = if( sheet.props.separatorTitles != null ) sheet.props.separatorTitles[sindex] else null;
  189. function getLines() {
  190. var snext = 0, sref = -1;
  191. var out = [];
  192. for( i in 0...lines.length ) {
  193. while( sheet.separators[snext] == i ) {
  194. var title = if( sheet.props.separatorTitles != null ) sheet.props.separatorTitles[snext] else null;
  195. if( title != null ) sref = snext;
  196. snext++;
  197. }
  198. if( sref == sindex )
  199. out.push(lines[i]);
  200. }
  201. return out;
  202. }
  203. var hidden : Bool;
  204. function sync() {
  205. hidden = title == null ? null : getDisplayState("sep/"+title) == false;
  206. toggle.css({ display : title == null ? "none" : "" });
  207. toggle.text(hidden ? "🡆" : "🡇");
  208. content.text(title == null ? "" : title+(hidden ? " ("+getLines().length+")" : ""));
  209. sep.toggleClass("sep-hidden", hidden == true);
  210. }
  211. sep.contextmenu(function(e) {
  212. var opts : Array<hide.comp.ContextMenu.ContextMenuItem> = [
  213. { label : "Expand All", click : function() {
  214. element.find("tr.separator.sep-hidden a.toggle").click();
  215. }},
  216. { label : "Collapse All", click : function() {
  217. element.find("tr.separator").not(".sep-hidden").find("a.toggle").click();
  218. }},
  219. ];
  220. if( sheet.props.dataFiles != null && title != null )
  221. opts.unshift({
  222. label : "Open",
  223. click : function() {
  224. ide.openFile(title);
  225. },
  226. });
  227. new hide.comp.ContextMenu(opts);
  228. });
  229. sep.dblclick(function(e) {
  230. if( !canInsert() ) return;
  231. content.empty();
  232. J("<input>").appendTo(content).focus().val(title == null ? "" : title).blur(function(_) {
  233. title = JTHIS.val();
  234. JTHIS.remove();
  235. if( title == "" ) title = null;
  236. var old = sheet.props.separatorTitles;
  237. var titles = sheet.props.separatorTitles;
  238. if( titles == null ) titles = [] else titles = titles.copy();
  239. while( titles.length < sindex )
  240. titles.push(null);
  241. titles[sindex] = title;
  242. while( titles[titles.length - 1] == null && titles.length > 0 )
  243. titles.pop();
  244. if( titles.length == 0 ) titles = null;
  245. editor.beginChanges();
  246. sheet.props.separatorTitles = titles;
  247. editor.endChanges();
  248. sync();
  249. }).keypress(function(e) {
  250. e.stopPropagation();
  251. }).keydown(function(e) {
  252. if( e.keyCode == 13 ) { JTHIS.blur(); e.preventDefault(); } else if( e.keyCode == 27 ) content.text(title);
  253. e.stopPropagation();
  254. });
  255. });
  256. sync();
  257. toggle.dblclick(function(e) e.stopPropagation());
  258. toggle.click(function(e) {
  259. hidden = !hidden;
  260. saveDisplayState("sep/"+title, !hidden);
  261. sync();
  262. for( l in getLines() ) {
  263. if( hidden ) l.hide() else l.create();
  264. }
  265. editor.updateFilter();
  266. });
  267. return { hidden : hidden, element : sep };
  268. }
  269. function refreshProperties() {
  270. lines = [];
  271. var available = [];
  272. var props = sheet.lines[0];
  273. var isLarge = false;
  274. for( c in columns ) {
  275. if( c.type.match(TList | TProperties) ) isLarge = true;
  276. if( c.opt && props != null && !Reflect.hasField(props,c.name) && displayMode != AllProperties ) {
  277. available.push(c);
  278. continue;
  279. }
  280. var v = Reflect.field(props, c.name);
  281. var l = new Element("<tr>").appendTo(element);
  282. var th = new Element("<th>").text(c.name).appendTo(l);
  283. var td = new Element("<td>").addClass("c").appendTo(l);
  284. if( c.documentation != null ) {
  285. th.attr("title", c.documentation);
  286. new Element('<i style="margin-left: 5px" class="ico ico-book"/>').appendTo(th);
  287. }
  288. var line = new Line(this, [c], lines.length, l);
  289. var cell = new Cell(td, line, c);
  290. lines.push(line);
  291. th.mousedown(function(e) {
  292. if( e.which == 3 ) {
  293. editor.popupColumn(this, c, cell);
  294. editor.cursor.clickCell(cell, false);
  295. e.preventDefault();
  296. return;
  297. }
  298. });
  299. }
  300. if( isLarge )
  301. element.parent().addClass("cdb-large");
  302. // add/edit properties
  303. var end = new Element("<tr>").appendTo(element);
  304. end = new Element("<td>").attr("colspan", "2").appendTo(end);
  305. var sel = new Element("<select class='insertField'>").appendTo(end);
  306. new Element("<option>").attr("value", "").text("--- Choose ---").appendTo(sel);
  307. var canInsert = false;
  308. for( c in available )
  309. if( canEditColumn(c.name) ) {
  310. var opt = J("<option>").attr("value",c.name).text(c.name).appendTo(sel);
  311. if( c.documentation != null ) opt.attr("title", c.documentation);
  312. canInsert = true;
  313. }
  314. if( editor.view == null )
  315. J("<option>").attr("value","$new").text("New property...").appendTo(sel);
  316. else if( !canInsert )
  317. end.remove();
  318. sel.change(function(e) {
  319. var v = sel.val();
  320. if( v == "" )
  321. return;
  322. sel.val("");
  323. editor.element.focus();
  324. if( v == "$new" ) {
  325. editor.newColumn(sheet, null, function(c) {
  326. if( c.opt ) insertProperty(c.name);
  327. });
  328. return;
  329. }
  330. insertProperty(v);
  331. });
  332. }
  333. function insertProperty( p : String ) {
  334. var props = sheet.lines[0];
  335. for( c in sheet.columns )
  336. if( c.name == p ) {
  337. var val = editor.base.getDefault(c, true, sheet);
  338. editor.beginChanges();
  339. Reflect.setField(props, c.name, val);
  340. editor.endChanges();
  341. refresh();
  342. for( l in lines )
  343. if( l.cells[0].column == c ) {
  344. l.cells[0].focus();
  345. break;
  346. }
  347. return true;
  348. }
  349. return false;
  350. }
  351. public function sortBy(col: cdb.Data.Column) {
  352. editor.beginChanges();
  353. var group : Array<Dynamic> = [];
  354. var startIndex = 0;
  355. function sort() {
  356. group.sort(function(a, b) {
  357. var val1 = Reflect.field(a, col.name);
  358. var val2 = Reflect.field(b, col.name);
  359. return Reflect.compare(val1, val2);
  360. });
  361. for(i in 0...group.length)
  362. sheet.lines[startIndex + i] = group[i];
  363. }
  364. for(i in 0...lines.length) {
  365. var sep = sheet.separators.indexOf(i) >= 0;
  366. if(sep) {
  367. sort();
  368. group = [];
  369. startIndex = i;
  370. }
  371. group.push(lines[i].obj);
  372. }
  373. sort();
  374. editor.endChanges();
  375. refresh();
  376. }
  377. public function toggleList( cell : Cell, ?immediate : Bool, ?make : Void -> SubTable ) {
  378. var line = cell.line;
  379. var cur = line.subTable;
  380. if( cur != null ) {
  381. cur.close();
  382. if( cur.cell == cell ) return; // toggle
  383. }
  384. var sub = make == null ? new SubTable(editor, cell) : make();
  385. sub.show(immediate);
  386. editor.cursor.set(sub);
  387. }
  388. public function refreshList( cell : Cell, ?make : Void -> SubTable ) {
  389. var line = cell.line;
  390. var cur = line.subTable;
  391. if( cur != null ) {
  392. cur.immediateClose();
  393. var sub = make == null ? new SubTable(editor, cell) : make();
  394. sub.show(true);
  395. }
  396. }
  397. function toString() {
  398. return "Table#"+sheet.name;
  399. }
  400. }