change.js 28 KB

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