controls.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  1. // Licensed to Cloudera, Inc. under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. Cloudera, Inc. licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. var searchRenderers = {
  17. 'rowkey': { //class to tag selection
  18. select: /[^\,\{\[]+(([\{\[][^\}\]]+[\}\]])+|)([^\,]+|)/g, //select the substring to process, useful as JS has no lookbehinds old: ([^,]+\[([^,]+(,|)+)+\]|[^,]+)
  19. tag: /.+/g, //select the matches to wrap with tags
  20. nested: {
  21. 'scan': { select: /\+[0-9 ]+/g, tag: /.+/g},
  22. 'columns': { select: /\[.+\]/g, tag: /[^:,\[\]]+:([^,\[\]]+|)/g}, //forced to do this select due to lack of lookbehinds /[\[\]]/g
  23. 'prefix': { select: /[^\*]+\*/g, tag: /\*/g},
  24. 'filter': {
  25. select: /\{[^\{\}]+\}/,
  26. tag:/.+/g,
  27. nested: {
  28. 'linker': {
  29. select: /AND|OR|SKIP|WHILE/g,
  30. tag: /.+/g
  31. }/*,
  32. 'compare_op': {
  33. select: /[\<\=\!\>]{1,2}/g,
  34. tag: /.+/g
  35. }*/ //will be added eventually after html bug is figured out
  36. }
  37. }
  38. }
  39. }
  40. };
  41. var DataTableViewModel = function(options)
  42. {
  43. var self = this, _defaults = {
  44. name: '',
  45. columns: [],
  46. items: [],
  47. reload: function()
  48. {
  49. },
  50. el:''
  51. };
  52. options = ko.utils.extend(_defaults,options);
  53. ListViewModel.apply(this, [options]);
  54. self.name = ko.observable(options.name);
  55. self.searchQuery.subscribe(function(value)
  56. {
  57. self._table.fnFilter(value);
  58. });
  59. self.columns = ko.observableArray(options.columns);
  60. self._el = $('table[data-datasource="' + options.el + '"]');
  61. self._table = null;
  62. self._initTable = function()
  63. {
  64. if(!self._table)
  65. {
  66. self._table = self._el.dataTable({
  67. "aoColumnDefs": [
  68. { "bSortable": false, "aTargets": [ 0 ] }
  69. ],
  70. "sDom": 'tr',//this has to be in, change to sDom so you can call filter()
  71. 'bAutoWidth':false,
  72. "iDisplayLength": -1});
  73. return self._table;
  74. }
  75. };
  76. self.sort = function(viewModel, event) {
  77. var el = $(event.currentTarget);
  78. };
  79. var _reload = self.reload;
  80. self.reload = function(callback)
  81. {
  82. logGA('view_cluster');
  83. if(self._table)
  84. {
  85. self._table.fnClearTable();
  86. self._table.fnDestroy();
  87. self._table = null;
  88. }
  89. _reload(function()
  90. {
  91. self._initTable();
  92. if(callback!=null)
  93. callback();
  94. });
  95. };
  96. self.canDrop = ko.computed(function() {
  97. var selected = self.selected();
  98. if(selected.length <= 0) return false;
  99. for(var i=0; i<selected.length; i++) {
  100. if(selected[i].enabled()) return false;
  101. }
  102. return true;
  103. });
  104. self.canDisable = ko.computed(function() {
  105. var selected = self.selected();
  106. if(selected.length <= 0) return false;
  107. for(var i=0; i<selected.length; i++) {
  108. if(!selected[i].enabled()) return false;
  109. }
  110. return true;
  111. });
  112. self.canEnable = ko.computed(function() {
  113. var selected = self.selected();
  114. if(selected.length <= 0) return false;
  115. for(var i=0; i<selected.length; i++) {
  116. if(selected[i].enabled()) return false;
  117. }
  118. return true;
  119. });
  120. };
  121. //a Listview of Listviews
  122. var SmartViewModel = function(options)
  123. {
  124. var self = this;
  125. options = ko.utils.extend({
  126. name: '',
  127. items: [],
  128. reload: function()
  129. {
  130. },
  131. el:'',
  132. sortFields: {
  133. 'Row Key': function(a, b)
  134. {
  135. return a.row.localeCompare(b.row);
  136. },
  137. 'Column Count': function(a, b)
  138. {
  139. a = a.items().length;
  140. b = b.items().length;
  141. if(a > b)
  142. return 1;
  143. if(a < b)
  144. return -1;
  145. return 0;
  146. },
  147. 'Row Key Length': function(a, b)
  148. {
  149. a = a.row.length;
  150. b = b.row.length;
  151. if(a > b)
  152. return 1;
  153. if(a < b)
  154. return -1;
  155. return 0;
  156. }
  157. }
  158. }, options);
  159. ListViewModel.apply(this, [options]); //items: [ListView.items[],ListView.items[]]
  160. self.columnFamilies = ko.observableArray();
  161. self.name = ko.observable(options.name);
  162. self.name.subscribe(function(){
  163. self.querySet.removeAll();
  164. self.querySet.push(new QuerySetPeice({
  165. 'row_key': 'null',
  166. 'scan_length': 10,
  167. 'prefix': 'false'
  168. }));
  169. self._reloadcfs();
  170. }); //fix and decouple
  171. self.lastReloadTime = ko.observable(1);
  172. //self.columnFamilies.subscribe(function(){self.reload();});
  173. self.searchQuery.subscribe(function goToRow(value) //make this as nice as the renderfucnction and split into two, also fire not down on keyup events
  174. {
  175. var inputs = value.match(searchRenderers['rowkey']['select']);
  176. self.querySet.removeAll();
  177. for(var i=0;i<inputs.length;i++)
  178. {
  179. if(inputs[i].trim() != "" && inputs[i].trim() != ',')
  180. {
  181. var p = inputs[i].split('+');
  182. var scan = p.length > 1 ? parseInt(p[1].trim()) : 0;
  183. var extract = inputs[i].match(searchRenderers['rowkey']['nested']['columns']['select']);
  184. var columns = extract != null ? extract[0].match(searchRenderers['rowkey']['nested']['columns']['tag']) : [];
  185. var filter = inputs[i].match(searchRenderers['rowkey']['nested']['filter']['select']);
  186. self.querySet.push(new QuerySetPeice({
  187. 'row_key': p[0].replace(/[\[\{].+[\]\}]|\*/g,'').trim(), //clean up with column regex selectors instead
  188. 'scan_length': scan ? scan + 1 : 1,
  189. 'columns': columns,
  190. 'prefix': inputs[i].match(searchRenderers['rowkey']['nested']['prefix']['select']) != null,
  191. 'filter': filter != null && filter.length > 0 ? escape(filter[0].slice(1, -1)) : null
  192. }));
  193. }
  194. }
  195. routie(app.cluster() + '/' + app.views.tabledata.name() +'/query/' + value);
  196. self.evaluateQuery();
  197. });
  198. self._reloadcfs = function()
  199. {
  200. return API.queryTable("getColumnDescriptors").done(function(data)
  201. {
  202. self.columnFamilies.removeAll();
  203. var keys = Object.keys(data);
  204. for(var i=0;i<keys.length;i++)
  205. {
  206. self.columnFamilies.push(new ColumnFamily({name:keys[i], enabled:false}));
  207. }
  208. self.reload();
  209. });
  210. };
  211. self.columnQuery = ko.observable("");
  212. self.columnQuery.subscribe(function(query)
  213. {
  214. $(self.items()).each(function()
  215. {
  216. this.searchQuery(query);
  217. });
  218. });
  219. self.rows = ko.computed(function()
  220. {
  221. var a = [];
  222. var items = this.items();
  223. for(var i=0; i<items.length; i++)
  224. {
  225. a.push(items[i].row);
  226. }
  227. return a;
  228. }, self);
  229. self.querySet = ko.observableArray();
  230. self.validateQuery = function()
  231. {
  232. $(self.querySet()).each(function()
  233. {
  234. this.validate();
  235. this.editing(false);
  236. });
  237. };
  238. self.addQuery = function()
  239. {
  240. self.validateQuery();
  241. self.querySet.push(new QuerySetPeice({onValidate: function()
  242. {
  243. //self.reload();
  244. }}))
  245. };
  246. self.evaluateQuery = function(callback)
  247. {
  248. self.validateQuery();
  249. self.reload(callback);
  250. };
  251. var _reload = self.reload;
  252. self.reload = function(callback)
  253. {
  254. logGA('view_table');
  255. self.truncated = ko.observable(false);
  256. var queryStart = new Date();
  257. _reload(function()
  258. {
  259. self.lastReloadTime((new Date() - queryStart)/1000);
  260. if(callback!=null)
  261. callback();
  262. self.isLoading(false);
  263. });
  264. };
  265. self.truncated = ko.observable(false);
  266. self.truncateLimit = ko.observable(1500);
  267. self.truncateCount = ko.observable(0);
  268. };
  269. var SmartViewDataRow = function(options)
  270. {
  271. var self = this;
  272. options = ko.utils.extend({
  273. sortFields: {
  274. 'Column Family': function(a, b)
  275. {
  276. return a.name.localeCompare(b.name);
  277. },
  278. 'Column Name': function(a, b)
  279. {
  280. return a.name.split(':')[1].localeCompare(b.name.split(':')[1]);
  281. },
  282. 'Cell Value': function(a, b)
  283. {
  284. a = a.value.length;
  285. b = b.value.length;
  286. if(a > b)
  287. return 1;
  288. if(a < b)
  289. return -1;
  290. return 0;
  291. },
  292. 'Timestamp': function(a, b)
  293. {
  294. a = parseInt(a.timestamp);
  295. b = parseInt(b.timestamp);
  296. if(a > b)
  297. return 1;
  298. if(a < b)
  299. return -1;
  300. return 0;
  301. },
  302. 'MIME Type': function()
  303. {
  304. },
  305. 'Column Name Length': function(a, b)
  306. {
  307. a = a.name.split(':')[1].length;
  308. b = b.name.split(':')[1].length;
  309. if(a > b)
  310. return 1;
  311. if(a < b)
  312. return -1;
  313. return 0;
  314. }
  315. }
  316. }, options);
  317. DataRow.apply(self,[options]);
  318. ListViewModel.apply(self,[options]);
  319. self.displayedItems = ko.observableArray();
  320. self.displayRangeStart = 0;
  321. self.displayRangeLength = 20;
  322. self.items.subscribe(function()
  323. {
  324. self.displayedItems([]);
  325. self.updateDisplayedItems();
  326. });
  327. self.searchQuery.subscribe(function(searchValue)
  328. {
  329. self.scrollLoadSource = ko.computed(function(){
  330. return self.items().filter(function(column)
  331. {
  332. return column.name.toLowerCase().indexOf(searchValue.toLowerCase()) != -1;
  333. });
  334. });
  335. self.displayRangeLength = 20;
  336. self.updateDisplayedItems();
  337. });
  338. self.scrollLoadSource = self.items;
  339. self.updateDisplayedItems = function()
  340. {
  341. var x = self.displayRangeStart;
  342. self.displayedItems(self.scrollLoadSource().slice(x, x + self.displayRangeLength));
  343. };
  344. self.resetScrollLoad = function()
  345. {
  346. self.scrollLoadSource = self.items;
  347. self.updateDisplayedItems();
  348. };
  349. self.toggleSelectedCollapse = function()
  350. {
  351. if(self.displayedItems().length == self.displayRangeStart + self.displayRangeLength)
  352. {
  353. self.displayedItems(self.displayedItems().filter(function(item)
  354. {
  355. return item.isSelected();
  356. }));
  357. self.scrollLoadSource = self.displayedItems;
  358. }
  359. else
  360. {
  361. self.resetScrollLoad();
  362. }
  363. };
  364. self.onScroll = function(target, ev)
  365. {
  366. if($(ev.target).scrollLeft() == ev.target.scrollWidth - ev.target.clientWidth && self.displayedItems().length < self.scrollLoadSource().length)
  367. {
  368. self.displayRangeLength += 15;
  369. self.updateDisplayedItems();
  370. }
  371. };
  372. self.drop = function(cont)
  373. {
  374. function doDrop()
  375. {
  376. self.isLoading(true);
  377. return API.queryTable('deleteAllRow', self.row, "{}").complete(function()
  378. {
  379. app.views.tabledata.items.remove(self); //decouple later
  380. self.isLoading(false);
  381. });
  382. }
  383. (cont === true) ? doDrop() : confirm("Confirm Delete", 'Delete row ' + self.row + '? (This cannot be undone)', doDrop);
  384. };
  385. self.setItems = function(cols)
  386. {
  387. var colKeys = Object.keys(cols);
  388. var items = [];
  389. for(var q=0;q<colKeys.length;q++)
  390. {
  391. items.push(new ColumnRow({name: colKeys[q],
  392. timestamp: cols[colKeys[q]].timestamp,
  393. value: cols[colKeys[q]].value,
  394. parent: self}));
  395. }
  396. self.items(items);
  397. return self.items();
  398. };
  399. self.selectAllVisible = function(){
  400. for(t=0;t<self.displayedItems().length;t++)
  401. self.displayedItems()[t].isSelected(true);
  402. return self;
  403. };
  404. self.toggleSelectAllVisible = function()
  405. {
  406. if(self.selected().length != self.displayedItems().length)
  407. return self.selectAllVisible();
  408. return self.deselectAll();
  409. };
  410. self.push = function(item)
  411. {
  412. var column = new ColumnRow(item);
  413. self.items.push(column);
  414. };
  415. var _reload = self.reload;
  416. self.reload = function(callback)
  417. {
  418. logGA('get_row');
  419. _reload(function()
  420. {
  421. if(callback!=null)
  422. callback();
  423. self.isLoading(false);
  424. });
  425. };
  426. };
  427. var ColumnRow = function(options)
  428. {
  429. var self = this;
  430. ko.utils.extend(self,options);
  431. DataRow.apply(self,[options]);
  432. self.value = ko.observable(self.value);
  433. self.history = new CellHistoryPage({row: self.parent.row, column: self.name, timestamp: self.timestamp, items: []});
  434. self.drop = function(cont)
  435. {
  436. function doDrop()
  437. {
  438. logGA('filter_columns');
  439. self.parent.isLoading(true);
  440. return API.queryTable('deleteColumn', self.parent.row, self.name).done(function(data)
  441. {
  442. self.parent.items.remove(self);
  443. if(self.parent.items().length > 0)
  444. self.parent.reload(); //change later
  445. self.parent.isLoading(false);
  446. });
  447. }
  448. (cont === true) ? doDrop() : confirm("Confirm Delete", "Are you sure you want to drop this column?", doDrop);
  449. };
  450. self.reload = function(callback, skipPut)
  451. {
  452. self.isLoading(true);
  453. API.queryTable('get', self.parent.row, self.name, 'null').done(function(data)
  454. {
  455. if(data.length > 0 && !skipPut)
  456. self.value(data[0].value);
  457. if(typeof callback !== "undefined" && callback != null)
  458. callback();
  459. self.isLoading(false);
  460. });
  461. };
  462. self.value.subscribe(function(value)
  463. {
  464. //change transport prep to object wrapper
  465. logGA('put_column');
  466. API.queryTable('putColumn', self.parent.row, self.name, "hbase-post-key-" + JSON.stringify(value)).done(function(data)
  467. {
  468. self.reload(function(){}, true);
  469. });
  470. self.editing(false);
  471. });
  472. self.editing = ko.observable(false);
  473. self.isLoading = ko.observable(false); //move to baseclass
  474. };
  475. var SortDropDownView = function(options)
  476. {
  477. var self = this;
  478. options = ko.utils.extend({
  479. sortFields: {},
  480. target: null
  481. }, options);
  482. BaseModel.apply(self,[options]);
  483. self.target = options.target;
  484. self.sortAsc = ko.observable(true);
  485. self.sortAsc.subscribe(function(){self.sort()});
  486. self.sortField = ko.observable("");
  487. self.sortField.subscribe(function(){self.sort()});
  488. self.sortFields = ko.observableArray(Object.keys(options.sortFields)); // change to ko.computed?
  489. self.sortFunctionHash = ko.observable(options.sortFields);
  490. self.toggleSortMode = function()
  491. {
  492. self.sortAsc(!self.sortAsc());
  493. };
  494. self.sort = function()
  495. {
  496. if (!self.target || !(self.sortFields().length > 0)) return;
  497. self.target.sort(function(a, b)
  498. {
  499. return (self.sortAsc() ? 1 : -1) * self.sortFunctionHash()[self.sortField() ? self.sortField() : self.sortFields()[0]](a,b); //all sort functions must sort by ASC for default
  500. });
  501. };
  502. };
  503. var TableDataRow = function(options)
  504. {
  505. var self = this;
  506. options = ko.utils.extend({
  507. name:"",
  508. enabled:true
  509. }, options);
  510. DataRow.apply(self,[options]);
  511. self.name = options['name'];
  512. self.enabled = ko.observable(options['enabled']);
  513. self.toggle = function(viewModel,event){
  514. var action = ['enable','disable'][self.enabled() << 0], el = $(event.currentTarget);
  515. confirm("Confirm "+action, "Are you sure you want to " + action + " this table?", function() //gotta i18n this!
  516. {
  517. el.showIndicator();
  518. return self[action](el).complete(function()
  519. {
  520. el.hideIndicator();
  521. });
  522. });
  523. };
  524. self.enable = function(el)
  525. {
  526. return API.queryCluster('enableTable',self.name).complete(function()
  527. {
  528. self.enabled(true);
  529. });
  530. };
  531. self.disable = function(el)
  532. {
  533. return API.queryCluster('disableTable',self.name).complete(function()
  534. {
  535. self.enabled(false);
  536. });
  537. };
  538. self.drop = function(el)
  539. {
  540. return API.queryCluster('deleteTable',self.name);
  541. };
  542. };
  543. var QuerySetPeice = function(options)
  544. {
  545. var self = this;
  546. options = ko.utils.extend({
  547. row_key: "null",
  548. scan_length: 1,
  549. prefix: false,
  550. columns: [],
  551. filter: null,
  552. onValidate: function(){}
  553. }, options);
  554. BaseModel.apply(self,[options]);
  555. self.row_key = ko.observable(options.row_key);
  556. self.scan_length = ko.observable(options.scan_length);
  557. self.columns = ko.observableArray(options.columns);
  558. self.prefix = ko.observable(options.prefix);
  559. self.filter = ko.observable(options.filter);
  560. self.editing = ko.observable(true);
  561. self.validate = function()
  562. {
  563. if(self.scan_length() <= 0 || self.row_key() == "")
  564. return app.views.tabledata.querySet.remove(self); //change later
  565. return options.onValidate();
  566. };
  567. self.row_key.subscribe(self.validate.bind());
  568. self.scan_length.subscribe(self.validate.bind());
  569. };
  570. var ColumnFamily = function(options)
  571. {
  572. this.name = options.name;
  573. this.enabled = ko.observable(options.enabled);
  574. this.toggle = function()
  575. {
  576. this.enabled(!this.enabled());
  577. app.views.tabledata.reload();
  578. };
  579. }
  580. //tagsearch
  581. var tagsearch = function()
  582. {
  583. var self = this;
  584. self.tags = ko.observableArray();
  585. self.mode = ko.observable('idle');
  586. self.cur_input = ko.observable('');
  587. self.hints = ko.observableArray([
  588. {
  589. hint: 'End Query',
  590. shortcut: ',',
  591. mode: ['rowkey', 'prefix', 'scan'],
  592. selected: false
  593. },
  594. {
  595. hint: 'Mark Row Prefix',
  596. shortcut: '*',
  597. mode: ['rowkey'],
  598. selected: false
  599. },
  600. {
  601. hint: 'Start Scan',
  602. shortcut: '+',
  603. mode: ['rowkey', 'prefix'],
  604. selected: false
  605. },
  606. {
  607. hint: 'Start Select Columns',
  608. shortcut: '[',
  609. mode: ['rowkey', 'prefix'],
  610. selected: false
  611. },
  612. {
  613. hint: 'End Column/Family',
  614. shortcut: ',',
  615. mode: ['columns'],
  616. selected: false
  617. },
  618. {
  619. hint: 'End Select Columns',
  620. shortcut: ']',
  621. mode: ['columns'],
  622. selected: false
  623. },
  624. {
  625. hint: 'Start FilterString',
  626. shortcut: '{',
  627. mode: ['rowkey'],
  628. selected: false
  629. },
  630. {
  631. hint: 'End FilterString',
  632. shortcut: '}',
  633. mode: ['filter'],
  634. selected: false
  635. }
  636. ]);
  637. self.activeHints = ko.computed(function()
  638. {
  639. var ret = [];
  640. $(self.hints()).each(function(i, hint)
  641. {
  642. if (hint.mode.indexOf(self.mode()) > -1)
  643. ret.push(hint);
  644. });
  645. return ret;
  646. });
  647. self.activeHint = ko.observable(-1);
  648. self.modes =
  649. {
  650. 'rowkey': {
  651. hint: 'Row Key Value',
  652. type: 'String'
  653. },
  654. 'scan': {
  655. hint: 'Length of Scan or Row Key',
  656. type: 'Integer'
  657. },
  658. 'columns': {
  659. hint: 'Column Family: Column Name',
  660. type: 'String'
  661. },
  662. 'prefix': {
  663. hint: 'Rows starting with',
  664. type: 'String'
  665. },
  666. 'filter': {
  667. hint: 'Thrift FilterString',
  668. type: 'String'
  669. }
  670. }
  671. self.modeQueue = ['idle'];
  672. self.focused = ko.observable(false);
  673. self.insertTag = function(tag)
  674. {
  675. var mode = tag.indexOf('+') != -1 ? 'scan' : 'rowkey';
  676. var tag = {value: tag, tag: mode} //parse mode
  677. self.tags.push(tag);
  678. }
  679. self.render = function(input, renderers)
  680. {
  681. var keys = Object.keys(renderers);
  682. for(var i=0; i<keys.length; i++)
  683. {
  684. input = input.replace(renderers[keys[i]].select, function(selected)
  685. {
  686. var hasMatched = false;
  687. var processed = selected.replace(renderers[keys[i]].tag, function(tagged)
  688. {
  689. hasMatched = true;
  690. return "<span class='" + keys[i] + " tagsearchTag' title='" + keys[i] + "' data-toggle='tooltip'>" + ('nested' in renderers[keys[i]] ? self.render(tagged, renderers[keys[i]].nested) : tagged) + "</span>";
  691. });
  692. if(hasMatched && renderers[keys[i]]['strip'])
  693. processed = processed.replace(renderers[keys[i]].strip, '');
  694. return processed;
  695. });
  696. }
  697. return input;
  698. };
  699. self.updateMode = function(value)
  700. {
  701. var selection = value.slice(0, self.selectionEnd());
  702. var endindex = selection.slice(selection.lastIndexOf(',')).indexOf(',');
  703. if(endindex == -1) endindex = selection.length;
  704. var lastbit = value.substring(selection.lastIndexOf(','), endindex).trim();
  705. if(lastbit == "," || lastbit == "")
  706. {
  707. self.mode('idle');
  708. return;
  709. }
  710. var tokens = "[]+,*{}";
  711. var m = 'rowkey';
  712. for(var i=selection.length - 1; i>=0; i--)
  713. {
  714. if(tokens.indexOf(selection[i]) != -1)
  715. {
  716. if(selection[i] == '{')
  717. m = 'filter';
  718. else if(selection[i] == '[')
  719. m = 'columns';
  720. else if(selection[i] == ']' || selection[i] == '}')
  721. m = 'rowkey';
  722. else if(selection[i] == '+')
  723. m = 'scan';
  724. else if(selection[i] == '-')
  725. m = 'prefix';
  726. break;
  727. }
  728. }
  729. self.mode(m.trim());
  730. };
  731. self.selectionStart = ko.observable(0);
  732. self.selectionEnd = ko.observable(0);
  733. self.hintText = ko.computed(function()
  734. {
  735. var value = self.cur_input();
  736. var selection = value.slice(0, self.selectionEnd());
  737. var index = selection.lastIndexOf(',') + 1;
  738. var endindex = value.slice(index).indexOf(',');
  739. endindex = endindex == -1 ? value.length : endindex;
  740. var pre = value.substring(index, index + endindex);
  741. var s = self.selectionStart() - index, e = self.selectionEnd() - index;
  742. if(s == e)
  743. e += 1;
  744. s = s < 0 ? 0 : s;
  745. e = e > pre.length ? pre.length : e;
  746. return pre.slice(0, s) + "<span class='selection'>" + pre.slice(s, e) + "</span>" + pre.slice(e);
  747. });
  748. self.onKeyDown = function(target, ev)
  749. {
  750. if(ev.keyCode == 13 && self.cur_input().slice(self.cur_input().lastIndexOf(',')).trim() != ",")
  751. {
  752. self.evaluate();
  753. return false;
  754. }
  755. setTimeout(self.updateMenu, 1);
  756. return true;
  757. };
  758. self.updateMenu = function() {
  759. try{
  760. var pos = getEditablePosition(document.getElementById('search-tags'));
  761. self.selectionStart(pos);
  762. self.selectionEnd(pos);
  763. } catch (err) {}
  764. self.updateMode(self.cur_input());
  765. };
  766. self.evaluate = function()
  767. {
  768. app.views.tabledata.searchQuery(self.cur_input());
  769. };
  770. $('#search-tags').blur(function(){
  771. self.focused(false);
  772. });
  773. self.doBlur = function() {
  774. if(self.cur_input().trim() == "") {
  775. function doClick() {
  776. $('#search-tags').html('');
  777. setTimeout(function() {
  778. $('#search-tags').focus();
  779. }, 1);
  780. }
  781. $('#search-tags').html('<small>' + $('#search-tags').data("placeholder") + '</small>').one('click', doClick).find('small').on('mousedown', doClick);
  782. }
  783. }
  784. $('#search-tags').focus(function(){
  785. self.focused(true);
  786. });
  787. };
  788. var CellHistoryPage = function(options)
  789. {
  790. var self = this;
  791. self.items = ko.observableArray(options.items);
  792. self.loading = ko.observable(false);
  793. self.reload = function(timestamp, append)
  794. {
  795. if(!timestamp)
  796. timestamp = options.timestamp
  797. API.queryTable("getVerTs", options.row, options.column, timestamp, 10, 'null').done(function(res)
  798. {
  799. self.loading = ko.observable(true);
  800. if(!append)
  801. self.items(res);
  802. else
  803. self.items(self.items() + res);
  804. self.loading = ko.observable(false);
  805. });
  806. };
  807. };