change.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. /**
  2. * @fileoverview function used in table data manipulation pages
  3. *
  4. * @requires jQuery
  5. * @requires jQueryUI
  6. * @requires js/functions.js
  7. *
  8. */
  9. /* global extendingValidatorMessages */ // templates/javascript/variables.twig
  10. /* global openGISEditor, gisEditorLoaded, loadJSAndGISEditor, loadGISEditor */ // js/gis_data_editor.js
  11. /**
  12. * Modify form controls when the "NULL" checkbox is checked
  13. *
  14. * @param {string} theType the MySQL field type
  15. * @param {string} urlField the urlencoded field name - OBSOLETE
  16. * @param {string} md5Field the md5 hashed field name
  17. * @param {string} multiEdit the multi_edit row sequence number
  18. *
  19. * @return {boolean} always true
  20. */
  21. function nullify(theType, urlField, md5Field, multiEdit) {
  22. var rowForm = document.forms.insertForm;
  23. if (typeof rowForm.elements['funcs' + multiEdit + '[' + md5Field + ']'] !== 'undefined') {
  24. rowForm.elements['funcs' + multiEdit + '[' + md5Field + ']'].selectedIndex = -1;
  25. }
  26. // "ENUM" field with more than 20 characters
  27. if (Number(theType) === 1) {
  28. rowForm.elements['fields' + multiEdit + '[' + md5Field + ']'][1].selectedIndex = -1;
  29. // Other "ENUM" field
  30. } else if (Number(theType) === 2) {
  31. var elts = rowForm.elements['fields' + multiEdit + '[' + md5Field + ']'];
  32. // when there is just one option in ENUM:
  33. if (elts.checked) {
  34. elts.checked = false;
  35. } else {
  36. var eltsCnt = elts.length;
  37. for (var i = 0; i < eltsCnt; i++) {
  38. elts[i].checked = false;
  39. } // end for
  40. } // end if
  41. // "SET" field
  42. } else if (Number(theType) === 3) {
  43. rowForm.elements['fields' + multiEdit + '[' + md5Field + '][]'].selectedIndex = -1;
  44. // Foreign key field (drop-down)
  45. } else if (Number(theType) === 4) {
  46. rowForm.elements['fields' + multiEdit + '[' + md5Field + ']'].selectedIndex = -1;
  47. // foreign key field (with browsing icon for foreign values)
  48. } else if (Number(theType) === 6) {
  49. rowForm.elements['fields' + multiEdit + '[' + md5Field + ']'].value = '';
  50. // Other field types
  51. } else /* if (theType === 5)*/{
  52. rowForm.elements['fields' + multiEdit + '[' + md5Field + ']'].value = '';
  53. } // end if... else if... else
  54. return true;
  55. } // end of the 'nullify()' function
  56. /**
  57. * javascript DateTime format validation.
  58. * its used to prevent adding default (0000-00-00 00:00:00) to database when user enter wrong values
  59. * Start of validation part
  60. */
  61. // function checks the number of days in febuary
  62. function daysInFebruary(year) {
  63. return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28;
  64. }
  65. // function to convert single digit to double digit
  66. function fractionReplace(number) {
  67. var num = parseInt(number, 10);
  68. return num >= 1 && num <= 9 ? '0' + num : '00';
  69. }
  70. /* function to check the validity of date
  71. * The following patterns are accepted in this validation (accepted in mysql as well)
  72. * 1) 2001-12-23
  73. * 2) 2001-1-2
  74. * 3) 02-12-23
  75. * 4) And instead of using '-' the following punctuations can be used (+,.,*,^,@,/) All these are accepted by mysql as well. Therefore no issues
  76. */
  77. function isDate(val, tmstmp) {
  78. var value = val.replace(/[.|*|^|+|//|@]/g, '-');
  79. var arrayVal = value.split('-');
  80. for (var a = 0; a < arrayVal.length; a++) {
  81. if (arrayVal[a].length === 1) {
  82. arrayVal[a] = fractionReplace(arrayVal[a]);
  83. }
  84. }
  85. value = arrayVal.join('-');
  86. var pos = 2;
  87. var dtexp = new RegExp(/^([0-9]{4})-(((01|03|05|07|08|10|12)-((0[0-9])|([1-2][0-9])|(3[0-1])))|((02|04|06|09|11)-((0[0-9])|([1-2][0-9])|30))|((00)-(00)))$/);
  88. if (value.length === 8) {
  89. pos = 0;
  90. }
  91. if (dtexp.test(value)) {
  92. var month = parseInt(value.substring(pos + 3, pos + 5), 10);
  93. var day = parseInt(value.substring(pos + 6, pos + 8), 10);
  94. var year = parseInt(value.substring(0, pos + 2), 10);
  95. if (month === 2 && day > daysInFebruary(year)) {
  96. return false;
  97. }
  98. if (value.substring(0, pos + 2).length === 2) {
  99. year = parseInt('20' + value.substring(0, pos + 2), 10);
  100. }
  101. if (tmstmp === true) {
  102. if (year < 1978) {
  103. return false;
  104. }
  105. if (year > 2038 || year > 2037 && day > 19 && month >= 1 || year > 2037 && month > 1) {
  106. return false;
  107. }
  108. }
  109. } else {
  110. return false;
  111. }
  112. return true;
  113. }
  114. /* function to check the validity of time
  115. * The following patterns are accepted in this validation (accepted in mysql as well)
  116. * 1) 2:3:4
  117. * 2) 2:23:43
  118. * 3) 2:23:43.123456
  119. */
  120. function isTime(val) {
  121. var arrayVal = val.split(':');
  122. for (var a = 0, l = arrayVal.length; a < l; a++) {
  123. if (arrayVal[a].length === 1) {
  124. arrayVal[a] = fractionReplace(arrayVal[a]);
  125. }
  126. }
  127. var newVal = arrayVal.join(':');
  128. var tmexp = new RegExp(/^(-)?(([0-7]?[0-9][0-9])|(8[0-2][0-9])|(83[0-8])):((0[0-9])|([1-5][0-9])):((0[0-9])|([1-5][0-9]))(\.[0-9]{1,6}){0,1}$/);
  129. return tmexp.test(newVal);
  130. }
  131. /**
  132. * To check whether insert section is ignored or not
  133. * @param {string} multiEdit
  134. * @return {boolean}
  135. */
  136. function checkForCheckbox(multiEdit) {
  137. if ($('#insert_ignore_' + multiEdit).length) {
  138. return $('#insert_ignore_' + multiEdit).is(':unchecked');
  139. }
  140. return true;
  141. }
  142. // used in Search page mostly for INT fields
  143. // eslint-disable-next-line no-unused-vars
  144. function verifyAfterSearchFieldChange(index, searchFormId) {
  145. var $thisInput = $('input[name=\'criteriaValues[' + index + ']\']');
  146. // Add data-skip-validators attribute to skip validation in changeValueFieldType function
  147. if ($('#fieldID_' + index).data('data-skip-validators')) {
  148. $(searchFormId).validate().destroy();
  149. return;
  150. }
  151. // validation for integer type
  152. if ($thisInput.data('type') === 'INT' || $thisInput.data('type') === 'TINYINT') {
  153. // Trim spaces if it's an integer
  154. $thisInput.val($thisInput.val().trim());
  155. var hasMultiple = $thisInput.prop('multiple');
  156. if (hasMultiple) {
  157. $(searchFormId).validate({
  158. // update errors as we write
  159. onkeyup: function (element) {
  160. $(element).valid();
  161. }
  162. });
  163. // validator method for IN(...), NOT IN(...)
  164. // BETWEEN and NOT BETWEEN
  165. // See all possible syntaxes in tests of https://regexr.com/7h1eq
  166. jQuery.validator.addMethod('validationFunctionForMultipleInt', function (value) {
  167. if (value === '') {
  168. return true;
  169. }
  170. return value.replace(/ /g, '').match(/^(((0x[0-9a-f]+)|([+-]?([0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)(e[+-]?[0-9]+)?))(,|$))+$/i) !== null;
  171. }, Messages.strEnterValidNumber);
  172. validateMultipleIntField($thisInput, true);
  173. } else {
  174. // validator method for INTs
  175. // See all possible syntaxes in tests of https://regexr.com/7h1ci
  176. jQuery.validator.addMethod('validationFunctionForInt', function (value) {
  177. if (value === '') {
  178. return true;
  179. }
  180. return value.match(/^(0x[0-9a-f]+$)|([+-]?([0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)(e[+-]?[0-9]+)?)$/i) !== null;
  181. }, Messages.strEnterValidNumber);
  182. $(searchFormId).validate({
  183. // update errors as we write
  184. onkeyup: function (element) {
  185. $(element).valid();
  186. }
  187. });
  188. validateIntField($thisInput, true);
  189. }
  190. // Update error on dropdown change
  191. $thisInput.valid();
  192. }
  193. }
  194. /**
  195. * Validate the an input contains multiple int values
  196. * @param {jQuery} jqueryInput the Jquery object
  197. * @param {boolean} returnValueIfFine the value to return if the validator passes
  198. * @return {void}
  199. */
  200. function validateMultipleIntField(jqueryInput, returnValueIfFine) {
  201. // removing previous rules
  202. jqueryInput.rules('remove');
  203. jqueryInput.rules('add', {
  204. validationFunctionForMultipleInt: {
  205. param: jqueryInput.value,
  206. depends: function () {
  207. return returnValueIfFine;
  208. }
  209. }
  210. });
  211. }
  212. /**
  213. * Validate the an input contains an int value
  214. * @param {jQuery} jqueryInput the Jquery object
  215. * @param {boolean} returnValueIfIsNumber the value to return if the validator passes
  216. * @return {void}
  217. */
  218. function validateIntField(jqueryInput, returnValueIfIsNumber) {
  219. var mini = parseInt(jqueryInput.data('min'));
  220. var maxi = parseInt(jqueryInput.data('max'));
  221. // removing previous rules
  222. jqueryInput.rules('remove');
  223. jqueryInput.rules('add', {
  224. validationFunctionForInt: {
  225. param: jqueryInput.value,
  226. depends: function () {
  227. return returnValueIfIsNumber;
  228. }
  229. },
  230. min: {
  231. param: mini,
  232. depends: function () {
  233. if (isNaN(jqueryInput.val())) {
  234. return false;
  235. } else {
  236. return returnValueIfIsNumber;
  237. }
  238. }
  239. },
  240. max: {
  241. param: maxi,
  242. depends: function () {
  243. if (isNaN(jqueryInput.val())) {
  244. return false;
  245. } else {
  246. return returnValueIfIsNumber;
  247. }
  248. }
  249. }
  250. });
  251. }
  252. function verificationsAfterFieldChange(urlField, multiEdit, theType) {
  253. var evt = window.event || arguments.callee.caller.arguments[0];
  254. var target = evt.target || evt.srcElement;
  255. var $thisInput = $(':input[name^=\'fields[multi_edit][' + multiEdit + '][' + urlField + ']\']');
  256. // the function drop-down that corresponds to this input field
  257. var $thisFunction = $('select[name=\'funcs[multi_edit][' + multiEdit + '][' + urlField + ']\']');
  258. var functionSelected = false;
  259. if (typeof $thisFunction.val() !== 'undefined' && $thisFunction.val() !== null && $thisFunction.val().length > 0) {
  260. functionSelected = true;
  261. }
  262. // To generate the textbox that can take the salt
  263. var newSaltBox = '<br><input type=text name=salt[multi_edit][' + multiEdit + '][' + urlField + ']' + ' id=salt_' + target.id + ' placeholder=\'' + Messages.strEncryptionKey + '\'>';
  264. // If encrypting or decrypting functions that take salt as input is selected append the new textbox for salt
  265. if (target.value === 'AES_ENCRYPT' || target.value === 'AES_DECRYPT' || target.value === 'DES_ENCRYPT' || target.value === 'DES_DECRYPT' || target.value === 'ENCRYPT') {
  266. if (!$('#salt_' + target.id).length) {
  267. $thisInput.after(newSaltBox);
  268. }
  269. } else {
  270. // Remove the textbox for salt
  271. $('#salt_' + target.id).prev('br').remove();
  272. $('#salt_' + target.id).remove();
  273. }
  274. var couldFetchRules = false;
  275. try {
  276. // See: issue #18792 - In some weird cases the input goes away before it validates
  277. // And it breaks jquery, this is a well known jquery bug with different trigger schemes
  278. $('#' + target.id).rules();
  279. couldFetchRules = true;
  280. } catch (error) {
  281. console.log(error);
  282. }
  283. if (couldFetchRules) {
  284. // Remove possible blocking rules if the user changed functions
  285. $('#' + target.id).rules('remove', 'validationFunctionForMd5');
  286. $('#' + target.id).rules('remove', 'validationFunctionForAesDesEncrypt');
  287. if (target.value === 'MD5') {
  288. $('#' + target.id).rules('add', {
  289. validationFunctionForMd5: {
  290. param: $thisInput,
  291. depends: function () {
  292. return checkForCheckbox(multiEdit);
  293. }
  294. }
  295. });
  296. }
  297. if (target.value === 'DES_ENCRYPT' || target.value === 'AES_ENCRYPT') {
  298. $('#' + target.id).rules('add', {
  299. validationFunctionForAesDesEncrypt: {
  300. param: $thisInput,
  301. depends: function () {
  302. return checkForCheckbox(multiEdit);
  303. }
  304. }
  305. });
  306. }
  307. }
  308. if (target.value === 'HEX' && theType.substring(0, 3) === 'int') {
  309. // Add note when HEX function is selected on a int
  310. var newHexInfo = '<br><p id="note' + target.id + '">' + Messages.HexConversionInfo + '</p>';
  311. if (!$('#note' + target.id).length) {
  312. $thisInput.after(newHexInfo);
  313. }
  314. } else {
  315. $('#note' + target.id).prev('br').remove();
  316. $('#note' + target.id).remove();
  317. }
  318. // Unchecks the corresponding "NULL" control
  319. $('input[name=\'fields_null[multi_edit][' + multiEdit + '][' + urlField + ']\']').prop('checked', false);
  320. // Unchecks the Ignore checkbox for the current row
  321. $('input[name=\'insert_ignore_' + multiEdit + '\']').prop('checked', false);
  322. var charExceptionHandling;
  323. if (theType.substring(0, 4) === 'char') {
  324. charExceptionHandling = theType.substring(5, 6);
  325. } else if (theType.substring(0, 7) === 'varchar') {
  326. charExceptionHandling = theType.substring(8, 9);
  327. }
  328. if (functionSelected) {
  329. $thisInput.removeAttr('min');
  330. $thisInput.removeAttr('max');
  331. // @todo: put back attributes if corresponding function is deselected
  332. }
  333. if ($thisInput.data('rulesadded') === null && !functionSelected) {
  334. // call validate before adding rules
  335. $($thisInput[0].form).validate();
  336. // validate for date time
  337. if (theType === 'datetime' || theType === 'time' || theType === 'date' || theType === 'timestamp') {
  338. $thisInput.rules('add', {
  339. validationFunctionForDateTime: {
  340. param: theType,
  341. depends: function () {
  342. return checkForCheckbox(multiEdit);
  343. }
  344. }
  345. });
  346. }
  347. // validation for integer type
  348. if ($thisInput.data('type') === 'INT') {
  349. validateIntField($thisInput, checkForCheckbox(multiEdit));
  350. // validation for CHAR types
  351. } else if ($thisInput.data('type') === 'CHAR') {
  352. var maxlen = $thisInput.data('maxlength');
  353. if (typeof maxlen !== 'undefined') {
  354. if (maxlen <= 4) {
  355. maxlen = charExceptionHandling;
  356. }
  357. $thisInput.rules('add', {
  358. maxlength: {
  359. param: maxlen,
  360. depends: function () {
  361. return checkForCheckbox(multiEdit);
  362. }
  363. }
  364. });
  365. }
  366. // validate binary & blob types
  367. } else if ($thisInput.data('type') === 'HEX') {
  368. $thisInput.rules('add', {
  369. validationFunctionForHex: {
  370. param: true,
  371. depends: function () {
  372. return checkForCheckbox(multiEdit);
  373. }
  374. }
  375. });
  376. }
  377. $thisInput.data('rulesadded', true);
  378. } else if ($thisInput.data('rulesadded') === true && functionSelected) {
  379. // remove any rules added
  380. $thisInput.rules('remove');
  381. // remove any error messages
  382. $thisInput.removeClass('error').removeAttr('aria-invalid').siblings('.error').remove();
  383. $thisInput.data('rulesadded', null);
  384. }
  385. }
  386. /* End of fields validation*/
  387. /**
  388. * Unbind all event handlers before tearing down a page
  389. */
  390. AJAX.registerTeardown('table/change.js', function () {
  391. $(document).off('click', 'span.open_gis_editor');
  392. $(document).off('click', 'input[name^=\'insert_ignore_\']');
  393. $(document).off('click', 'input[name=\'gis_data[save]\']');
  394. $(document).off('click', 'input.checkbox_null');
  395. $('select[name="submit_type"]').off('change');
  396. $(document).off('change', '#insert_rows');
  397. });
  398. /**
  399. * Ajax handlers for Change Table page
  400. *
  401. * Actions Ajaxified here:
  402. * Submit Data to be inserted into the table.
  403. * Restart insertion with 'N' rows.
  404. */
  405. AJAX.registerOnload('table/change.js', function () {
  406. if ($('#insertForm').length) {
  407. // validate the comment form when it is submitted
  408. $('#insertForm').validate();
  409. jQuery.validator.addMethod('validationFunctionForHex', function (value) {
  410. return value.match(/^[a-f0-9]*$/i) !== null;
  411. });
  412. jQuery.validator.addMethod('validationFunctionForMd5', function (value, element, options) {
  413. return !(value.substring(0, 3) === 'MD5' && typeof options.data('maxlength') !== 'undefined' && options.data('maxlength') < 32);
  414. });
  415. jQuery.validator.addMethod('validationFunctionForAesDesEncrypt', function (value, element, options) {
  416. var funType = value.substring(0, 3);
  417. if (funType !== 'AES' && funType !== 'DES') {
  418. return false;
  419. }
  420. var dataType = options.data('type');
  421. if (dataType === 'HEX' || dataType === 'CHAR') {
  422. return true;
  423. }
  424. return false;
  425. });
  426. jQuery.validator.addMethod('validationFunctionForDateTime', function (value, element, options) {
  427. var dtValue = value;
  428. var theType = options;
  429. if (theType === 'date') {
  430. return isDate(dtValue);
  431. } else if (theType === 'time') {
  432. return isTime(dtValue);
  433. } else if (theType === 'datetime' || theType === 'timestamp') {
  434. var tmstmp = false;
  435. dtValue = dtValue.trim();
  436. if (dtValue === 'CURRENT_TIMESTAMP' || dtValue === 'current_timestamp()') {
  437. return true;
  438. }
  439. if (theType === 'timestamp') {
  440. tmstmp = true;
  441. }
  442. if (dtValue === '0000-00-00 00:00:00') {
  443. return true;
  444. }
  445. var dv = dtValue.indexOf(' ');
  446. if (dv === -1) {
  447. // Only the date component, which is valid
  448. return isDate(dtValue, tmstmp);
  449. }
  450. return isDate(dtValue.substring(0, dv), tmstmp) && isTime(dtValue.substring(dv + 1));
  451. }
  452. });
  453. }
  454. /*
  455. * message extending script must be run
  456. * after initiation of functions
  457. */
  458. extendingValidatorMessages();
  459. $.datepicker.initialized = false;
  460. $(document).on('click', 'span.open_gis_editor', function (event) {
  461. event.preventDefault();
  462. var $span = $(this);
  463. // Current value
  464. var value = $span.parent('td').children('input[type=\'text\']').val();
  465. // Field name
  466. var field = $span.parents('tr').children('td').first().find('input[type=\'hidden\']').val();
  467. // Column type
  468. var type = $span.parents('tr').find('span.column_type').text();
  469. // Names of input field and null checkbox
  470. var inputName = $span.parent('td').children('input[type=\'text\']').attr('name');
  471. openGISEditor();
  472. if (!gisEditorLoaded) {
  473. loadJSAndGISEditor(value, field, type, inputName);
  474. } else {
  475. loadGISEditor(value, field, type, inputName);
  476. }
  477. });
  478. /**
  479. * Forced validation check of fields
  480. */
  481. $(document).on('click', 'input[name^=\'insert_ignore_\']', function () {
  482. $('#insertForm').valid();
  483. });
  484. /**
  485. * Uncheck the null checkbox as geometry data is placed on the input field
  486. */
  487. $(document).on('click', 'input[name=\'gis_data[save]\']', function () {
  488. var inputName = $('form#gis_data_editor_form').find('input[name=\'input_name\']').val();
  489. var currentRow = $('input[name=\'' + inputName + '\']').parents('tr');
  490. var $nullCheckbox = currentRow.find('.checkbox_null');
  491. $nullCheckbox.prop('checked', false);
  492. var rowId = currentRow.find('.open_gis_editor').data('row-id');
  493. // Unchecks the Ignore checkbox for the current row
  494. $('input[name=\'insert_ignore_' + rowId + '\']').prop('checked', false);
  495. });
  496. /**
  497. * Handles all current checkboxes for Null; this only takes care of the
  498. * checkboxes on currently displayed rows as the rows generated by
  499. * "Continue insertion" are handled in the "Continue insertion" code
  500. *
  501. */
  502. $(document).on('click', 'input.checkbox_null', function () {
  503. nullify(
  504. // use hidden fields populated by /table/change
  505. $(this).siblings('.nullify_code').val(), $(this).closest('tr').find('input:hidden').first().val(), $(this).siblings('.hashed_field').val(), $(this).siblings('.multi_edit').val());
  506. });
  507. /**
  508. * Reset the auto_increment column to 0 when selecting any of the
  509. * insert options in submit_type-dropdown. Only perform the reset
  510. * when we are in edit-mode, and not in insert-mode(no previous value
  511. * available).
  512. */
  513. $('select[name="submit_type"]').on('change', function () {
  514. var thisElemSubmitTypeVal = $(this).val();
  515. var $table = $('table.insertRowTable');
  516. var autoIncrementColumn = $table.find('input[name^="auto_increment"]');
  517. autoIncrementColumn.each(function () {
  518. var $thisElemAIField = $(this);
  519. var thisElemName = $thisElemAIField.attr('name');
  520. var prevValueField = $table.find('input[name="' + thisElemName.replace('auto_increment', 'fields_prev') + '"]');
  521. var valueField = $table.find('input[name="' + thisElemName.replace('auto_increment', 'fields') + '"]');
  522. var previousValue = $(prevValueField).val();
  523. if (previousValue !== undefined) {
  524. if (thisElemSubmitTypeVal === 'insert' || thisElemSubmitTypeVal === 'insertignore' || thisElemSubmitTypeVal === 'showinsert') {
  525. $(valueField).val(null);
  526. } else {
  527. $(valueField).val(previousValue);
  528. }
  529. }
  530. });
  531. });
  532. /**
  533. * Handle ENTER key when press on Continue insert with field
  534. */
  535. $('#insert_rows').on('keypress', function (e) {
  536. var key = e.which;
  537. if (key === 13) {
  538. addNewContinueInsertionFields(e);
  539. }
  540. });
  541. /**
  542. * Continue Insertion form
  543. */
  544. $(document).on('change', '#insert_rows', addNewContinueInsertionFields);
  545. });
  546. function addNewContinueInsertionFields(event) {
  547. event.preventDefault();
  548. /**
  549. * @var columnCount Number of number of columns table has.
  550. */
  551. var columnCount = $('table.insertRowTable').first().find('tr').has('input[name*=\'fields_name\']').length;
  552. /**
  553. * @var curr_rows Number of current insert rows already on page
  554. */
  555. var currRows = $('table.insertRowTable').length;
  556. /**
  557. * @var target_rows Number of rows the user wants
  558. */
  559. var targetRows = $('#insert_rows').val();
  560. // remove all datepickers
  561. $('input.datefield, input.datetimefield').each(function () {
  562. $(this).datepicker('destroy');
  563. });
  564. if (currRows < targetRows) {
  565. var tempIncrementIndex = function () {
  566. var $thisElement = $(this);
  567. /**
  568. * Extract the index from the name attribute for all input/select fields and increment it
  569. * name is of format funcs[multi_edit][10][<long random string of alphanum chars>]
  570. */
  571. /**
  572. * @var this_name String containing name of the input/select elements
  573. */
  574. var thisName = $thisElement.attr('name');
  575. /** split {@link thisName} at [10], so we have the parts that can be concatenated later */
  576. var nameParts = thisName.split(/\[\d+\]/);
  577. /** extract the [10] from {@link nameParts} */
  578. var oldRowIndexString = thisName.match(/\[\d+\]/)[0];
  579. /** extract 10 - had to split into two steps to accomodate double digits */
  580. var oldRowIndex = parseInt(oldRowIndexString.match(/\d+/)[0], 10);
  581. /** calculate next index i.e. 11 */
  582. newRowIndex = oldRowIndex + 1;
  583. /** generate the new name i.e. funcs[multi_edit][11][foobarbaz] */
  584. var newName = nameParts[0] + '[' + newRowIndex + ']' + nameParts[1];
  585. var hashedField = nameParts[1].match(/\[(.+)\]/)[1];
  586. $thisElement.attr('name', newName);
  587. /** If element is select[name*='funcs'], update id */
  588. if ($thisElement.is('select[name*=\'funcs\']')) {
  589. var thisId = $thisElement.attr('id');
  590. var idParts = thisId.split(/_/);
  591. var oldIdIndex = idParts[1];
  592. var prevSelectedValue = $('#field_' + oldIdIndex + '_1').val();
  593. var newIdIndex = parseInt(oldIdIndex) + columnCount;
  594. var newId = 'field_' + newIdIndex + '_1';
  595. $thisElement.attr('id', newId);
  596. $thisElement.find('option').filter(function () {
  597. return $(this).text() === prevSelectedValue;
  598. }).attr('selected', 'selected');
  599. // If salt field is there then update its id.
  600. var nextSaltInput = $thisElement.parent().next('td').next('td').find('input[name*=\'salt\']');
  601. if (nextSaltInput.length !== 0) {
  602. nextSaltInput.attr('id', 'salt_' + newId);
  603. }
  604. }
  605. // handle input text fields and textareas
  606. if ($thisElement.is('.textfield') || $thisElement.is('.char') || $thisElement.is('textarea')) {
  607. // do not remove the 'value' attribute for ENUM columns
  608. // special handling for radio fields after updating ids to unique - see below
  609. if ($thisElement.closest('tr').find('span.column_type').html() !== 'enum') {
  610. $thisElement.val($thisElement.closest('tr').find('span.default_value').html());
  611. }
  612. $thisElement.off('change')
  613. // Remove onchange attribute that was placed
  614. // by /table/change; it refers to the wrong row index
  615. .attr('onchange', null)
  616. // Keep these values to be used when the element
  617. // will change
  618. .data('hashed_field', hashedField).data('new_row_index', newRowIndex).on('change', function () {
  619. var $changedElement = $(this);
  620. verificationsAfterFieldChange($changedElement.data('hashed_field'), $changedElement.data('new_row_index'), $changedElement.closest('tr').find('span.column_type').html());
  621. });
  622. }
  623. if ($thisElement.is('.checkbox_null')) {
  624. $thisElement
  625. // this event was bound earlier by jQuery but
  626. // to the original row, not the cloned one, so unbind()
  627. .off('click')
  628. // Keep these values to be used when the element
  629. // will be clicked
  630. .data('hashed_field', hashedField).data('new_row_index', newRowIndex).on('click', function () {
  631. var $changedElement = $(this);
  632. nullify($changedElement.siblings('.nullify_code').val(), $thisElement.closest('tr').find('input:hidden').first().val(), $changedElement.data('hashed_field'), '[multi_edit][' + $changedElement.data('new_row_index') + ']');
  633. });
  634. }
  635. };
  636. var tempReplaceAnchor = function () {
  637. var $anchor = $(this);
  638. var newValue = 'rownumber=' + newRowIndex;
  639. // needs improvement in case something else inside
  640. // the href contains this pattern
  641. var newHref = $anchor.attr('href').replace(/rownumber=\d+/, newValue);
  642. $anchor.attr('href', newHref);
  643. };
  644. var restoreValue = function () {
  645. if ($(this).closest('tr').find('span.column_type').html() === 'enum') {
  646. if ($(this).val() === $checkedValue) {
  647. $(this).prop('checked', true);
  648. } else {
  649. $(this).prop('checked', false);
  650. }
  651. }
  652. };
  653. while (currRows < targetRows) {
  654. /**
  655. * @var $last_row Object referring to the last row
  656. */
  657. var $lastRow = $('#insertForm').find('.insertRowTable').last();
  658. // need to access this at more than one level
  659. // (also needs improvement because it should be calculated
  660. // just once per cloned row, not once per column)
  661. var newRowIndex = 0;
  662. var $checkedValue = $lastRow.find('input:checked').val();
  663. // Clone the insert tables
  664. $lastRow.clone(true, true).insertBefore('#actions_panel').find('input[name*=multi_edit],select[name*=multi_edit],textarea[name*=multi_edit]').each(tempIncrementIndex).end().find('.foreign_values_anchor').each(tempReplaceAnchor);
  665. var $oldRow = $lastRow.find('.textfield');
  666. $oldRow.each(restoreValue);
  667. // set the value of enum field of new row to default
  668. var $newRow = $('#insertForm').find('.insertRowTable').last();
  669. $newRow.find('.textfield').each(function () {
  670. if ($(this).closest('tr').find('span.column_type').html() === 'enum') {
  671. if ($(this).val() === $(this).closest('tr').find('span.default_value').html()) {
  672. $(this).prop('checked', true);
  673. } else {
  674. $(this).prop('checked', false);
  675. }
  676. }
  677. });
  678. // Insert/Clone the ignore checkboxes
  679. if (currRows === 1) {
  680. $('<input id="insert_ignore_1" type="checkbox" name="insert_ignore_1" checked="checked">').insertBefore($('table.insertRowTable').last()).after('<label for="insert_ignore_1">' + Messages.strIgnore + '</label>');
  681. } else {
  682. /**
  683. * @var $last_checkbox Object reference to the last checkbox in #insertForm
  684. */
  685. var $lastCheckbox = $('#insertForm').children('input:checkbox').last();
  686. /** name of {@link $lastCheckbox} */
  687. var lastCheckboxName = $lastCheckbox.attr('name');
  688. /** index of {@link $lastCheckbox} */
  689. var lastCheckboxIndex = parseInt(lastCheckboxName.match(/\d+/), 10);
  690. /** name of new {@link $lastCheckbox} */
  691. var newName = lastCheckboxName.replace(/\d+/, lastCheckboxIndex + 1);
  692. $('<br><div class="clearfloat"></div>').insertBefore($('table.insertRowTable').last());
  693. $lastCheckbox.clone().attr({
  694. 'id': newName,
  695. 'name': newName
  696. }).prop('checked', true).insertBefore($('table.insertRowTable').last());
  697. $('label[for^=insert_ignore]').last().clone().attr('for', newName).insertBefore($('table.insertRowTable').last());
  698. $('<br>').insertBefore($('table.insertRowTable').last());
  699. }
  700. currRows++;
  701. }
  702. // recompute tabindex for text fields and other controls at footer;
  703. // IMO it's not really important to handle the tabindex for
  704. // function and Null
  705. var tabIndex = 0;
  706. $('.textfield, .char, textarea').each(function () {
  707. tabIndex++;
  708. $(this).attr('tabindex', tabIndex);
  709. // update the IDs of textfields to ensure that they are unique
  710. $(this).attr('id', 'field_' + tabIndex + '_3');
  711. });
  712. $('.control_at_footer').each(function () {
  713. tabIndex++;
  714. $(this).attr('tabindex', tabIndex);
  715. });
  716. } else if (currRows > targetRows) {
  717. /**
  718. * Displays alert if data loss possible on decrease
  719. * of rows.
  720. */
  721. var checkLock = jQuery.isEmptyObject(AJAX.lockedTargets);
  722. if (checkLock || confirm(Messages.strConfirmRowChange) === true) {
  723. while (currRows > targetRows) {
  724. $('input[id^=insert_ignore]').last().nextUntil('fieldset').addBack().remove();
  725. currRows--;
  726. }
  727. } else {
  728. document.getElementById('insert_rows').value = currRows;
  729. }
  730. }
  731. // Add all the required datepickers back
  732. Functions.addDateTimePicker();
  733. }
  734. // eslint-disable-next-line no-unused-vars
  735. function changeValueFieldType(elem, searchIndex) {
  736. var fieldsValue = $('input#fieldID_' + searchIndex);
  737. if (0 === fieldsValue.size()) {
  738. return;
  739. }
  740. var type = $(elem).val();
  741. if ('LIKE' === type || 'LIKE %...%' === type || 'NOT LIKE' === type || 'NOT LIKE %...%' === type) {
  742. $('#fieldID_' + searchIndex).data('data-skip-validators', true);
  743. return;
  744. } else {
  745. $('#fieldID_' + searchIndex).data('data-skip-validators', false);
  746. }
  747. if ('IN (...)' === type || 'NOT IN (...)' === type || 'BETWEEN' === type || 'NOT BETWEEN' === type) {
  748. $('#fieldID_' + searchIndex).prop('multiple', true);
  749. } else {
  750. $('#fieldID_' + searchIndex).prop('multiple', false);
  751. }
  752. }