zoom_plot_jqplot.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. "use strict";
  2. // TODO: change the axis
  3. /**
  4. ** @fileoverview JavaScript functions used on /table/search
  5. **
  6. ** @requires jQuery
  7. ** @requires js/functions.js
  8. **/
  9. /* global changeValueFieldType, verifyAfterSearchFieldChange */
  10. // js/table/change.js
  11. /**
  12. ** Display Help/Info
  13. **/
  14. function displayHelp() {
  15. $('<div></div>').append(Messages.strDisplayHelp).appendTo('#page_content').dialog({
  16. width: 450,
  17. height: 'auto',
  18. title: Messages.strHelpTitle
  19. });
  20. return false;
  21. }
  22. /**
  23. ** Extend the array object for max function
  24. ** @param array
  25. **/
  26. Array.max = function (array) {
  27. return Math.max.apply(Math, array);
  28. };
  29. /**
  30. ** Extend the array object for min function
  31. ** @param array
  32. **/
  33. Array.min = function (array) {
  34. return Math.min.apply(Math, array);
  35. };
  36. /**
  37. ** Checks if a string contains only numeric value
  38. ** @param n: String (to be checked)
  39. **/
  40. function isNumeric(n) {
  41. return !isNaN(parseFloat(n)) && isFinite(n);
  42. }
  43. /**
  44. ** Checks if an object is empty
  45. ** @param n: Object (to be checked)
  46. **/
  47. function isEmpty(obj) {
  48. var name;
  49. for (name in obj) {
  50. return false;
  51. }
  52. return true;
  53. }
  54. /**
  55. ** Converts a date/time into timestamp
  56. ** @param val String Date
  57. ** @param type String Field type(datetime/timestamp/time/date)
  58. **/
  59. function getTimeStamp(val, type) {
  60. if (type.toString().search(/datetime/i) !== -1 || type.toString().search(/timestamp/i) !== -1) {
  61. return $.datepicker.parseDateTime('yy-mm-dd', 'HH:mm:ss', val);
  62. } else if (type.toString().search(/time/i) !== -1) {
  63. return $.datepicker.parseDateTime('yy-mm-dd', 'HH:mm:ss', '1970-01-01 ' + val);
  64. } else if (type.toString().search(/date/i) !== -1) {
  65. return $.datepicker.parseDate('yy-mm-dd', val);
  66. }
  67. }
  68. /**
  69. ** Classifies the field type into numeric,timeseries or text
  70. ** @param field: field type (as in database structure)
  71. **/
  72. function getType(field) {
  73. if (field.toString().search(/int/i) !== -1 || field.toString().search(/decimal/i) !== -1 || field.toString().search(/year/i) !== -1) {
  74. return 'numeric';
  75. } else if (field.toString().search(/time/i) !== -1 || field.toString().search(/date/i) !== -1) {
  76. return 'time';
  77. } else {
  78. return 'text';
  79. }
  80. }
  81. /**
  82. * Unbind all event handlers before tearing down a page
  83. */
  84. AJAX.registerTeardown('table/zoom_plot_jqplot.js', function () {
  85. $('#tableid_0').off('change');
  86. $('#tableid_1').off('change');
  87. $('#tableid_2').off('change');
  88. $('#tableid_3').off('change');
  89. $('#inputFormSubmitId').off('click');
  90. $('#togglesearchformlink').off('click');
  91. $(document).off('keydown', '#dataDisplay :input');
  92. $('button.button-reset').off('click');
  93. $('div#resizer').off('resizestop');
  94. $('div#querychart').off('jqplotDataClick');
  95. });
  96. AJAX.registerOnload('table/zoom_plot_jqplot.js', function () {
  97. var currentChart = null;
  98. var searchedDataKey = null;
  99. var xLabel = $('#tableid_0').val();
  100. var yLabel = $('#tableid_1').val(); // will be updated via Ajax
  101. var xType = $('#types_0').val();
  102. var yType = $('#types_1').val();
  103. var dataLabel = $('#dataLabel').val(); // Get query result
  104. var searchedData;
  105. try {
  106. searchedData = JSON.parse($('#querydata').html());
  107. } catch (err) {
  108. searchedData = null;
  109. } // adding event listener on select after AJAX request
  110. var comparisonOperatorOnChange = function comparisonOperatorOnChange() {
  111. var tableRows = $('#inputSection select.column-operator');
  112. $.each(tableRows, function (index, item) {
  113. $(item).on('change', function () {
  114. changeValueFieldType(this, index);
  115. verifyAfterSearchFieldChange(index, '#zoom_search_form');
  116. });
  117. });
  118. };
  119. /**
  120. ** Input form submit on field change
  121. **/
  122. // first column choice corresponds to the X axis
  123. $('#tableid_0').on('change', function () {
  124. // AJAX request for field type, collation, operators, and value field
  125. $.post('index.php?route=/table/zoom-search', {
  126. 'ajax_request': true,
  127. 'change_tbl_info': true,
  128. 'server': CommonParams.get('server'),
  129. 'db': CommonParams.get('db'),
  130. 'table': CommonParams.get('table'),
  131. 'field': $('#tableid_0').val(),
  132. 'it': 0
  133. }, function (data) {
  134. $('#tableFieldsId').find('tr').eq(1).find('td').eq(0).html(data.field_type);
  135. $('#tableFieldsId').find('tr').eq(1).find('td').eq(1).html(data.field_collation);
  136. $('#tableFieldsId').find('tr').eq(1).find('td').eq(2).html(data.field_operators);
  137. $('#tableFieldsId').find('tr').eq(1).find('td').eq(3).html(data.field_value);
  138. xLabel = $('#tableid_0').val();
  139. $('#types_0').val(data.field_type);
  140. xType = data.field_type;
  141. $('#collations_0').val(data.field_collations);
  142. comparisonOperatorOnChange();
  143. Functions.addDateTimePicker();
  144. });
  145. }); // second column choice corresponds to the Y axis
  146. $('#tableid_1').on('change', function () {
  147. // AJAX request for field type, collation, operators, and value field
  148. $.post('index.php?route=/table/zoom-search', {
  149. 'ajax_request': true,
  150. 'change_tbl_info': true,
  151. 'server': CommonParams.get('server'),
  152. 'db': CommonParams.get('db'),
  153. 'table': CommonParams.get('table'),
  154. 'field': $('#tableid_1').val(),
  155. 'it': 1
  156. }, function (data) {
  157. $('#tableFieldsId').find('tr').eq(2).find('td').eq(0).html(data.field_type);
  158. $('#tableFieldsId').find('tr').eq(2).find('td').eq(1).html(data.field_collation);
  159. $('#tableFieldsId').find('tr').eq(2).find('td').eq(2).html(data.field_operators);
  160. $('#tableFieldsId').find('tr').eq(2).find('td').eq(3).html(data.field_value);
  161. yLabel = $('#tableid_1').val();
  162. $('#types_1').val(data.field_type);
  163. yType = data.field_type;
  164. $('#collations_1').val(data.field_collations);
  165. comparisonOperatorOnChange();
  166. Functions.addDateTimePicker();
  167. });
  168. });
  169. $('#tableid_2').on('change', function () {
  170. // AJAX request for field type, collation, operators, and value field
  171. $.post('index.php?route=/table/zoom-search', {
  172. 'ajax_request': true,
  173. 'change_tbl_info': true,
  174. 'server': CommonParams.get('server'),
  175. 'db': CommonParams.get('db'),
  176. 'table': CommonParams.get('table'),
  177. 'field': $('#tableid_2').val(),
  178. 'it': 2
  179. }, function (data) {
  180. $('#tableFieldsId').find('tr').eq(4).find('td').eq(0).html(data.field_type);
  181. $('#tableFieldsId').find('tr').eq(4).find('td').eq(1).html(data.field_collation);
  182. $('#tableFieldsId').find('tr').eq(4).find('td').eq(2).html(data.field_operators);
  183. $('#tableFieldsId').find('tr').eq(4).find('td').eq(3).html(data.field_value);
  184. $('#types_2').val(data.field_type);
  185. $('#collations_2').val(data.field_collations);
  186. comparisonOperatorOnChange();
  187. Functions.addDateTimePicker();
  188. });
  189. });
  190. $('#tableid_3').on('change', function () {
  191. // AJAX request for field type, collation, operators, and value field
  192. $.post('index.php?route=/table/zoom-search', {
  193. 'ajax_request': true,
  194. 'change_tbl_info': true,
  195. 'server': CommonParams.get('server'),
  196. 'db': CommonParams.get('db'),
  197. 'table': CommonParams.get('table'),
  198. 'field': $('#tableid_3').val(),
  199. 'it': 3
  200. }, function (data) {
  201. $('#tableFieldsId').find('tr').eq(5).find('td').eq(0).html(data.field_type);
  202. $('#tableFieldsId').find('tr').eq(5).find('td').eq(1).html(data.field_collation);
  203. $('#tableFieldsId').find('tr').eq(5).find('td').eq(2).html(data.field_operators);
  204. $('#tableFieldsId').find('tr').eq(5).find('td').eq(3).html(data.field_value);
  205. $('#types_3').val(data.field_type);
  206. $('#collations_3').val(data.field_collations);
  207. comparisonOperatorOnChange();
  208. Functions.addDateTimePicker();
  209. });
  210. });
  211. /**
  212. * Input form validation
  213. **/
  214. $('#inputFormSubmitId').on('click', function () {
  215. if ($('#tableid_0').get(0).selectedIndex === 0 || $('#tableid_1').get(0).selectedIndex === 0) {
  216. Functions.ajaxShowMessage(Messages.strInputNull);
  217. } else if (xLabel === yLabel) {
  218. Functions.ajaxShowMessage(Messages.strSameInputs);
  219. }
  220. });
  221. /**
  222. ** Prepare a div containing a link, otherwise it's incorrectly displayed
  223. ** after a couple of clicks
  224. **/
  225. $('<div id="togglesearchformdiv"><a id="togglesearchformlink"></a></div>').insertAfter('#zoom_search_form') // don't show it until we have results on-screen
  226. .hide();
  227. $('#togglesearchformlink').html(Messages.strShowSearchCriteria).on('click', function () {
  228. var $link = $(this);
  229. $('#zoom_search_form').slideToggle();
  230. if ($link.text() === Messages.strHideSearchCriteria) {
  231. $link.text(Messages.strShowSearchCriteria);
  232. } else {
  233. $link.text(Messages.strHideSearchCriteria);
  234. } // avoid default click action
  235. return false;
  236. });
  237. /**
  238. ** Set dialog properties for the data display form
  239. **/
  240. var buttonOptions = {};
  241. /*
  242. * Handle saving of a row in the editor
  243. */
  244. buttonOptions[Messages.strSave] = function () {
  245. // Find changed values by comparing form values with selectedRow Object
  246. var newValues = {}; // Stores the values changed from original
  247. var sqlTypes = {};
  248. var it = 0;
  249. var xChange = false;
  250. var yChange = false;
  251. var key;
  252. var tempGetVal = function tempGetVal() {
  253. return $(this).val();
  254. };
  255. for (key in selectedRow) {
  256. var oldVal = selectedRow[key];
  257. var newVal = $('#edit_fields_null_id_' + it).prop('checked') ? null : $('#edit_fieldID_' + it).val();
  258. if (newVal instanceof Array) {
  259. // when the column is of type SET
  260. newVal = $('#edit_fieldID_' + it).map(tempGetVal).get().join(',');
  261. }
  262. if (oldVal !== newVal) {
  263. selectedRow[key] = newVal;
  264. newValues[key] = newVal;
  265. if (key === xLabel) {
  266. xChange = true;
  267. searchedData[searchedDataKey][xLabel] = newVal;
  268. } else if (key === yLabel) {
  269. yChange = true;
  270. searchedData[searchedDataKey][yLabel] = newVal;
  271. }
  272. }
  273. var $input = $('#edit_fieldID_' + it);
  274. if ($input.hasClass('bit')) {
  275. sqlTypes[key] = 'bit';
  276. } else {
  277. sqlTypes[key] = null;
  278. }
  279. it++;
  280. } // End data update
  281. // Update the chart series and replot
  282. if (xChange || yChange) {
  283. // Logic similar to plot generation, replot only if xAxis changes or yAxis changes.
  284. // Code includes a lot of checks so as to replot only when necessary
  285. if (xChange) {
  286. xCord[searchedDataKey] = selectedRow[xLabel]; // [searchedDataKey][0] contains the x value
  287. if (xType === 'numeric') {
  288. series[0][searchedDataKey][0] = selectedRow[xLabel];
  289. } else if (xType === 'time') {
  290. series[0][searchedDataKey][0] = getTimeStamp(selectedRow[xLabel], $('#types_0').val());
  291. } else {
  292. series[0][searchedDataKey][0] = ''; // TODO: text values
  293. }
  294. currentChart.series[0].data = series[0]; // TODO: axis changing
  295. currentChart.replot();
  296. }
  297. if (yChange) {
  298. yCord[searchedDataKey] = selectedRow[yLabel]; // [searchedDataKey][1] contains the y value
  299. if (yType === 'numeric') {
  300. series[0][searchedDataKey][1] = selectedRow[yLabel];
  301. } else if (yType === 'time') {
  302. series[0][searchedDataKey][1] = getTimeStamp(selectedRow[yLabel], $('#types_1').val());
  303. } else {
  304. series[0][searchedDataKey][1] = ''; // TODO: text values
  305. }
  306. currentChart.series[0].data = series[0]; // TODO: axis changing
  307. currentChart.replot();
  308. }
  309. } // End plot update
  310. // Generate SQL query for update
  311. if (!isEmpty(newValues)) {
  312. var sqlQuery = 'UPDATE `' + CommonParams.get('table') + '` SET ';
  313. for (key in newValues) {
  314. sqlQuery += '`' + key + '`=';
  315. var value = newValues[key]; // null
  316. if (value === null) {
  317. sqlQuery += 'NULL, '; // empty
  318. } else if (value.trim() === '') {
  319. sqlQuery += '\'\', '; // other
  320. } else {
  321. // type explicitly identified
  322. if (sqlTypes[key] !== null) {
  323. if (sqlTypes[key] === 'bit') {
  324. sqlQuery += 'b\'' + value + '\', ';
  325. } // type not explicitly identified
  326. } else {
  327. if (!isNumeric(value)) {
  328. sqlQuery += '\'' + value + '\', ';
  329. } else {
  330. sqlQuery += value + ', ';
  331. }
  332. }
  333. }
  334. } // remove two extraneous characters ', '
  335. sqlQuery = sqlQuery.substring(0, sqlQuery.length - 2);
  336. sqlQuery += ' WHERE ' + Sql.urlDecode(searchedData[searchedDataKey].where_clause);
  337. $.post('index.php?route=/sql', {
  338. 'server': CommonParams.get('server'),
  339. 'db': CommonParams.get('db'),
  340. 'ajax_request': true,
  341. 'sql_query': sqlQuery,
  342. 'inline_edit': false
  343. }, function (data) {
  344. if (typeof data !== 'undefined' && data.success === true) {
  345. $('#sqlqueryresultsouter').html(data.sql_query);
  346. Functions.highlightSql($('#sqlqueryresultsouter'));
  347. } else {
  348. Functions.ajaxShowMessage(data.error, false);
  349. }
  350. }); // End $.post
  351. } // End database update
  352. $('#dataDisplay').dialog('close');
  353. };
  354. buttonOptions[Messages.strCancel] = function () {
  355. $(this).dialog('close');
  356. };
  357. $('#dataDisplay').dialog({
  358. autoOpen: false,
  359. title: Messages.strDataPointContent,
  360. modal: true,
  361. buttons: buttonOptions,
  362. width: $('#dataDisplay').width() + 80,
  363. open: function open() {
  364. $(this).find('input[type=checkbox]').css('margin', '0.5em');
  365. }
  366. });
  367. /**
  368. * Attach Ajax event handlers for input fields
  369. * in the dialog. Used to submit the Ajax
  370. * request when the ENTER key is pressed.
  371. */
  372. $(document).on('keydown', '#dataDisplay :input', function (e) {
  373. if (e.which === 13) {
  374. // 13 is the ENTER key
  375. e.preventDefault();
  376. if (typeof buttonOptions[Messages.strSave] === 'function') {
  377. buttonOptions[Messages.strSave].call();
  378. }
  379. }
  380. });
  381. /*
  382. * Generate plot using jqplot
  383. */
  384. if (searchedData !== null) {
  385. $('#zoom_search_form').slideToggle().hide();
  386. $('#togglesearchformlink').text(Messages.strShowSearchCriteria);
  387. $('#togglesearchformdiv').show();
  388. var selectedRow;
  389. var series = [];
  390. var xCord = [];
  391. var yCord = [];
  392. var xVal;
  393. var yVal;
  394. var format;
  395. var options = {
  396. series: [// for a scatter plot
  397. {
  398. showLine: false
  399. }],
  400. grid: {
  401. drawBorder: false,
  402. shadow: false,
  403. background: 'rgba(0,0,0,0)'
  404. },
  405. axes: {
  406. xaxis: {
  407. label: $('#tableid_0').val(),
  408. labelRenderer: $.jqplot.CanvasAxisLabelRenderer
  409. },
  410. yaxis: {
  411. label: $('#tableid_1').val(),
  412. labelRenderer: $.jqplot.CanvasAxisLabelRenderer
  413. }
  414. },
  415. highlighter: {
  416. show: true,
  417. tooltipAxes: 'y',
  418. yvalues: 2,
  419. // hide the first y value
  420. formatString: '<span class="hide">%s</span>%s'
  421. },
  422. cursor: {
  423. show: true,
  424. zoom: true,
  425. showTooltip: false
  426. }
  427. }; // If data label is not set, do not show tooltips
  428. if (dataLabel === '') {
  429. options.highlighter.show = false;
  430. } // Classify types as either numeric,time,text
  431. xType = getType(xType);
  432. yType = getType(yType); // could have multiple series but we'll have just one
  433. series[0] = [];
  434. if (xType === 'time') {
  435. var originalXType = $('#types_0').val();
  436. if (originalXType === 'date') {
  437. format = '%Y-%m-%d';
  438. } // TODO: does not seem to work
  439. // else if (originalXType === 'time') {
  440. // format = '%H:%M';
  441. // } else {
  442. // format = '%Y-%m-%d %H:%M';
  443. // }
  444. $.extend(options.axes.xaxis, {
  445. renderer: $.jqplot.DateAxisRenderer,
  446. tickOptions: {
  447. formatString: format
  448. }
  449. });
  450. }
  451. if (yType === 'time') {
  452. var originalYType = $('#types_1').val();
  453. if (originalYType === 'date') {
  454. format = '%Y-%m-%d';
  455. }
  456. $.extend(options.axes.yaxis, {
  457. renderer: $.jqplot.DateAxisRenderer,
  458. tickOptions: {
  459. formatString: format
  460. }
  461. });
  462. }
  463. $.each(searchedData, function (key, value) {
  464. if (xType === 'numeric') {
  465. xVal = parseFloat(value[xLabel]);
  466. }
  467. if (xType === 'time') {
  468. xVal = getTimeStamp(value[xLabel], originalXType);
  469. }
  470. if (yType === 'numeric') {
  471. yVal = parseFloat(value[yLabel]);
  472. }
  473. if (yType === 'time') {
  474. yVal = getTimeStamp(value[yLabel], originalYType);
  475. }
  476. series[0].push([xVal, yVal, // extra Y values
  477. value[dataLabel], // for highlighter
  478. // (may set an undefined value)
  479. value.where_clause, // for click on point
  480. key, // key from searchedData
  481. value.where_clause_sign]);
  482. }); // under IE 8, the initial display is mangled; after a manual
  483. // resizing, it's ok
  484. // under IE 9, everything is fine
  485. currentChart = $.jqplot('querychart', series, options);
  486. currentChart.resetZoom();
  487. $('button.button-reset').on('click', function (event) {
  488. event.preventDefault();
  489. currentChart.resetZoom();
  490. });
  491. $('div#resizer').resizable();
  492. $('div#resizer').on('resizestop', function () {
  493. // make room so that the handle will still appear
  494. $('div#querychart').height($('div#resizer').height() * 0.96);
  495. $('div#querychart').width($('div#resizer').width() * 0.96);
  496. currentChart.replot({
  497. resetAxes: true
  498. });
  499. });
  500. $('div#querychart').on('jqplotDataClick', function (event, seriesIndex, pointIndex, data) {
  501. searchedDataKey = data[4]; // key from searchedData (global)
  502. var fieldId = 0;
  503. var postParams = {
  504. 'ajax_request': true,
  505. 'get_data_row': true,
  506. 'server': CommonParams.get('server'),
  507. 'db': CommonParams.get('db'),
  508. 'table': CommonParams.get('table'),
  509. 'where_clause': data[3],
  510. 'where_clause_sign': data[5]
  511. };
  512. $.post('index.php?route=/table/zoom-search', postParams, function (data) {
  513. // Row is contained in data.row_info,
  514. // now fill the displayResultForm with row values
  515. var key;
  516. for (key in data.row_info) {
  517. var $field = $('#edit_fieldID_' + fieldId);
  518. var $fieldNull = $('#edit_fields_null_id_' + fieldId);
  519. if (data.row_info[key] === null) {
  520. $fieldNull.prop('checked', true);
  521. $field.val('');
  522. } else {
  523. $fieldNull.prop('checked', false);
  524. if ($field.attr('multiple')) {
  525. // when the column is of type SET
  526. $field.val(data.row_info[key].split(','));
  527. } else {
  528. $field.val(data.row_info[key]);
  529. }
  530. }
  531. fieldId++;
  532. }
  533. selectedRow = data.row_info;
  534. });
  535. $('#dataDisplay').dialog('open');
  536. });
  537. }
  538. $('#help_dialog').on('click', function () {
  539. displayHelp();
  540. });
  541. });