controls.js 21 KB

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