controls.js 20 KB

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