tbl_change.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. /* vim: set expandtab sw=4 ts=4 sts=4: */
  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. /**
  11. * Modify form controls when the "NULL" checkbox is checked
  12. *
  13. * @param theType string the MySQL field type
  14. * @param urlField string the urlencoded field name - OBSOLETE
  15. * @param md5Field string the md5 hashed field name
  16. * @param multi_edit string the multi_edit row sequence number
  17. *
  18. * @return boolean always true
  19. */
  20. function nullify(theType, urlField, md5Field, multi_edit)
  21. {
  22. var rowForm = document.forms['insertForm'];
  23. if (typeof(rowForm.elements['funcs' + multi_edit + '[' + md5Field + ']']) != 'undefined') {
  24. rowForm.elements['funcs' + multi_edit + '[' + md5Field + ']'].selectedIndex = -1;
  25. }
  26. // "ENUM" field with more than 20 characters
  27. if (theType == 1) {
  28. rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'][1].selectedIndex = -1;
  29. }
  30. // Other "ENUM" field
  31. else if (theType == 2) {
  32. var elts = rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'];
  33. // when there is just one option in ENUM:
  34. if (elts.checked) {
  35. elts.checked = false;
  36. } else {
  37. var elts_cnt = elts.length;
  38. for (var i = 0; i < elts_cnt; i++ ) {
  39. elts[i].checked = false;
  40. } // end for
  41. } // end if
  42. }
  43. // "SET" field
  44. else if (theType == 3) {
  45. rowForm.elements['fields' + multi_edit + '[' + md5Field + '][]'].selectedIndex = -1;
  46. }
  47. // Foreign key field (drop-down)
  48. else if (theType == 4) {
  49. rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'].selectedIndex = -1;
  50. }
  51. // foreign key field (with browsing icon for foreign values)
  52. else if (theType == 6) {
  53. rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'].value = '';
  54. }
  55. // Other field types
  56. else /*if (theType == 5)*/ {
  57. rowForm.elements['fields' + multi_edit + '[' + md5Field + ']'].value = '';
  58. } // end if... else if... else
  59. return true;
  60. } // end of the 'nullify()' function
  61. /**
  62. * javascript DateTime format validation.
  63. * its used to prevent adding default (0000-00-00 00:00:00) to database when user enter wrong values
  64. * Start of validation part
  65. */
  66. //function checks the number of days in febuary
  67. function daysInFebruary (year)
  68. {
  69. return (((year % 4 == 0) && ( (!(year % 100 == 0)) || (year % 400 == 0))) ? 29 : 28 );
  70. }
  71. //function to convert single digit to double digit
  72. function fractionReplace(num)
  73. {
  74. num = parseInt(num);
  75. return num >= 1 && num <= 9 ? '0' + num : '00';
  76. }
  77. /* function to check the validity of date
  78. * The following patterns are accepted in this validation (accepted in mysql as well)
  79. * 1) 2001-12-23
  80. * 2) 2001-1-2
  81. * 3) 02-12-23
  82. * 4) And instead of using '-' the following punctuations can be used (+,.,*,^,@,/) All these are accepted by mysql as well. Therefore no issues
  83. */
  84. function isDate(val,tmstmp)
  85. {
  86. val = val.replace(/[.|*|^|+|//|@]/g,'-');
  87. var arrayVal = val.split("-");
  88. for (var a=0;a<arrayVal.length;a++) {
  89. if (arrayVal[a].length==1) {
  90. arrayVal[a]=fractionReplace(arrayVal[a]);
  91. }
  92. }
  93. val=arrayVal.join("-");
  94. var pos=2;
  95. 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)))$/);
  96. if (val.length == 8) {
  97. pos=0;
  98. }
  99. if (dtexp.test(val)) {
  100. var month=parseInt(val.substring(pos+3,pos+5));
  101. var day=parseInt(val.substring(pos+6,pos+8));
  102. var year=parseInt(val.substring(0,pos+2));
  103. if (month == 2 && day > daysInFebruary(year)) {
  104. return false;
  105. }
  106. if (val.substring(0, pos + 2).length == 2) {
  107. year = parseInt("20" + val.substring(0,pos+2));
  108. }
  109. if (tmstmp == true) {
  110. if (year < 1978) {
  111. return false;
  112. }
  113. if (year > 2038 || (year > 2037 && day > 19 && month >= 1) || (year > 2037 && month > 1)) {
  114. return false;
  115. }
  116. }
  117. } else {
  118. return false;
  119. }
  120. return true;
  121. }
  122. /* function to check the validity of time
  123. * The following patterns are accepted in this validation (accepted in mysql as well)
  124. * 1) 2:3:4
  125. * 2) 2:23:43
  126. */
  127. function isTime(val)
  128. {
  129. var arrayVal = val.split(":");
  130. for (var a = 0, l = arrayVal.length; a < l; a++) {
  131. if (arrayVal[a].length == 1) {
  132. arrayVal[a] = fractionReplace(arrayVal[a]);
  133. }
  134. }
  135. val = arrayVal.join(":");
  136. var tmexp = new RegExp(/^(([0-1][0-9])|(2[0-3])):((0[0-9])|([1-5][0-9])):((0[0-9])|([1-5][0-9]))$/);
  137. return tmexp.test(val);
  138. }
  139. function verificationsAfterFieldChange(urlField, multi_edit, theType)
  140. {
  141. var evt = window.event || arguments.callee.caller.arguments[0];
  142. var target = evt.target || evt.srcElement;
  143. // Unchecks the corresponding "NULL" control
  144. $("input[name='fields_null[multi_edit][" + multi_edit + "][" + urlField + "]']").prop('checked', false);
  145. // Unchecks the Ignore checkbox for the current row
  146. $("input[name='insert_ignore_" + multi_edit + "']").prop('checked', false);
  147. var $this_input = $("input[name='fields[multi_edit][" + multi_edit + "][" + urlField + "]']");
  148. // Does this field come from datepicker?
  149. if ($this_input.data('comes_from') == 'datepicker') {
  150. // Yes, so do not validate because the final value is not yet in
  151. // the field and hopefully the datepicker returns a valid date+time
  152. $this_input.removeClass("invalid_value");
  153. return true;
  154. }
  155. if (target.name.substring(0,6)=="fields") {
  156. // validate for date time
  157. if (theType=="datetime"||theType=="time"||theType=="date"||theType=="timestamp") {
  158. $this_input.removeClass("invalid_value");
  159. var dt_value = $this_input.val();
  160. if (theType=="date"){
  161. if (! isDate(dt_value)) {
  162. $this_input.addClass("invalid_value");
  163. return false;
  164. }
  165. } else if (theType=="time") {
  166. if (! isTime(dt_value)) {
  167. $this_input.addClass("invalid_value");
  168. return false;
  169. }
  170. } else if (theType=="datetime"||theType=="timestamp") {
  171. var tmstmp=false;
  172. if (dt_value == "CURRENT_TIMESTAMP") {
  173. return true;
  174. }
  175. if (theType=="timestamp") {
  176. tmstmp=true;
  177. }
  178. if (dt_value=="0000-00-00 00:00:00") {
  179. return true;
  180. }
  181. var dv=dt_value.indexOf(" ");
  182. if (dv==-1) {
  183. $this_input.addClass("invalid_value");
  184. return false;
  185. } else {
  186. if (! (isDate(dt_value.substring(0,dv),tmstmp) && isTime(dt_value.substring(dv+1)))) {
  187. $this_input.addClass("invalid_value");
  188. return false;
  189. }
  190. }
  191. }
  192. }
  193. //validate for integer type
  194. if (theType.substring(0,3) == "int"){
  195. $this_input.removeClass("invalid_value");
  196. if (isNaN($this_input.val())){
  197. $this_input.addClass("invalid_value");
  198. return false;
  199. }
  200. }
  201. }
  202. }
  203. /* End of datetime validation*/
  204. /**
  205. * Unbind all event handlers before tearing down a page
  206. */
  207. AJAX.registerTeardown('tbl_change.js', function() {
  208. $('span.open_gis_editor').die('click');
  209. $("input[name='gis_data[save]']").die('click');
  210. $('input.checkbox_null').die('click');
  211. $('select[name="submit_type"]').unbind('change');
  212. $("#insert_rows").die('change');
  213. });
  214. /**
  215. * Ajax handlers for Change Table page
  216. *
  217. * Actions Ajaxified here:
  218. * Submit Data to be inserted into the table.
  219. * Restart insertion with 'N' rows.
  220. */
  221. AJAX.registerOnload('tbl_change.js', function() {
  222. $.datepicker.initialized = false;
  223. $('span.open_gis_editor').live('click', function(event) {
  224. event.preventDefault();
  225. var $span = $(this);
  226. // Current value
  227. var value = $span.parent('td').children("input[type='text']").val();
  228. // Field name
  229. var field = $span.parents('tr').children('td:first').find("input[type='hidden']").val();
  230. // Column type
  231. var type = $span.parents('tr').find('span.column_type').text();
  232. // Names of input field and null checkbox
  233. var input_name = $span.parent('td').children("input[type='text']").attr('name');
  234. //Token
  235. var token = $("input[name='token']").val();
  236. openGISEditor();
  237. if (!gisEditorLoaded) {
  238. loadJSAndGISEditor(value, field, type, input_name, token);
  239. } else {
  240. loadGISEditor(value, field, type, input_name, token);
  241. }
  242. });
  243. /**
  244. * Uncheck the null checkbox as geometry data is placed on the input field
  245. */
  246. $("input[name='gis_data[save]']").live('click', function(event) {
  247. var input_name = $('form#gis_data_editor_form').find("input[name='input_name']").val();
  248. var $null_checkbox = $("input[name='" + input_name + "']").parents('tr').find('.checkbox_null');
  249. $null_checkbox.prop('checked', false);
  250. });
  251. /**
  252. * Handles all current checkboxes for Null; this only takes care of the
  253. * checkboxes on currently displayed rows as the rows generated by
  254. * "Continue insertion" are handled in the "Continue insertion" code
  255. *
  256. */
  257. $('input.checkbox_null').live('click', function(e) {
  258. nullify(
  259. // use hidden fields populated by tbl_change.php
  260. $(this).siblings('.nullify_code').val(),
  261. $(this).closest('tr').find('input:hidden').first().val(),
  262. $(this).siblings('.hashed_field').val(),
  263. $(this).siblings('.multi_edit').val()
  264. );
  265. });
  266. /**
  267. * Reset the auto_increment column to 0 when selecting any of the
  268. * insert options in submit_type-dropdown. Only perform the reset
  269. * when we are in edit-mode, and not in insert-mode(no previous value
  270. * available).
  271. */
  272. $('select[name="submit_type"]').bind('change', function (e) {
  273. var $table = $('table.insertRowTable');
  274. var auto_increment_column = $table.find('input[name^="auto_increment"]').attr('name');
  275. if (auto_increment_column) {
  276. var prev_value_field = $table.find('input[name="' + auto_increment_column.replace('auto_increment', 'fields_prev') + '"]');
  277. var value_field = $table.find('input[name="' + auto_increment_column.replace('auto_increment', 'fields') + '"]');
  278. var previous_value = $(prev_value_field).val();
  279. if (previous_value !== undefined) {
  280. if ($(this).val() == 'insert' || $(this).val() == 'insertignore' || $(this).val() == 'showinsert' ) {
  281. $(value_field).val(0);
  282. } else {
  283. $(value_field).val(previous_value);
  284. }
  285. }
  286. }
  287. });
  288. /**
  289. * Continue Insertion form
  290. */
  291. $("#insert_rows").live('change', function(event) {
  292. event.preventDefault();
  293. /**
  294. * @var curr_rows Number of current insert rows already on page
  295. */
  296. var curr_rows = $("table.insertRowTable").length;
  297. /**
  298. * @var target_rows Number of rows the user wants
  299. */
  300. var target_rows = $("#insert_rows").val();
  301. // remove all datepickers
  302. $('input.datefield, input.datetimefield').each(function(){
  303. $(this).datepicker('destroy');
  304. });
  305. if (curr_rows < target_rows ) {
  306. while( curr_rows < target_rows ) {
  307. /**
  308. * @var $last_row Object referring to the last row
  309. */
  310. var $last_row = $("#insertForm").find(".insertRowTable:last");
  311. // need to access this at more than one level
  312. // (also needs improvement because it should be calculated
  313. // just once per cloned row, not once per column)
  314. var new_row_index = 0;
  315. //Clone the insert tables
  316. $last_row
  317. .clone()
  318. .insertBefore("#actions_panel")
  319. .find('input[name*=multi_edit],select[name*=multi_edit],textarea[name*=multi_edit]')
  320. .each(function() {
  321. var $this_element = $(this);
  322. /**
  323. * Extract the index from the name attribute for all input/select fields and increment it
  324. * name is of format funcs[multi_edit][10][<long random string of alphanum chars>]
  325. */
  326. /**
  327. * @var this_name String containing name of the input/select elements
  328. */
  329. var this_name = $this_element.attr('name');
  330. /** split {@link this_name} at [10], so we have the parts that can be concatenated later */
  331. var name_parts = this_name.split(/\[\d+\]/);
  332. /** extract the [10] from {@link name_parts} */
  333. var old_row_index_string = this_name.match(/\[\d+\]/)[0];
  334. /** extract 10 - had to split into two steps to accomodate double digits */
  335. var old_row_index = parseInt(old_row_index_string.match(/\d+/)[0]);
  336. /** calculate next index i.e. 11 */
  337. new_row_index = old_row_index + 1;
  338. /** generate the new name i.e. funcs[multi_edit][11][foobarbaz] */
  339. var new_name = name_parts[0] + '[' + new_row_index + ']' + name_parts[1];
  340. var hashed_field = name_parts[1].match(/\[(.+)\]/)[1];
  341. $this_element.attr('name', new_name);
  342. if ($this_element.is('.textfield')) {
  343. // do not remove the 'value' attribute for ENUM columns
  344. if ($this_element.closest('tr').find('span.column_type').html() != 'enum') {
  345. $this_element.val($this_element.closest('tr').find('span.default_value').html());
  346. }
  347. $this_element
  348. .unbind('change')
  349. // Remove onchange attribute that was placed
  350. // by tbl_change.php; it refers to the wrong row index
  351. .attr('onchange', null)
  352. // Keep these values to be used when the element
  353. // will change
  354. .data('hashed_field', hashed_field)
  355. .data('new_row_index', new_row_index)
  356. .bind('change', function(e) {
  357. var $changed_element = $(this);
  358. verificationsAfterFieldChange(
  359. $changed_element.data('hashed_field'),
  360. $changed_element.data('new_row_index'),
  361. $changed_element.closest('tr').find('span.column_type').html()
  362. );
  363. });
  364. }
  365. if ($this_element.is('.checkbox_null')) {
  366. $this_element
  367. // this event was bound earlier by jQuery but
  368. // to the original row, not the cloned one, so unbind()
  369. .unbind('click')
  370. // Keep these values to be used when the element
  371. // will be clicked
  372. .data('hashed_field', hashed_field)
  373. .data('new_row_index', new_row_index)
  374. .bind('click', function(e) {
  375. var $changed_element = $(this);
  376. nullify(
  377. $changed_element.siblings('.nullify_code').val(),
  378. $this_element.closest('tr').find('input:hidden').first().val(),
  379. $changed_element.data('hashed_field'),
  380. '[multi_edit][' + $changed_element.data('new_row_index') + ']'
  381. );
  382. });
  383. }
  384. }) // end each
  385. .end()
  386. .find('.foreign_values_anchor')
  387. .each(function() {
  388. var $anchor = $(this);
  389. var new_value = 'rownumber=' + new_row_index;
  390. // needs improvement in case something else inside
  391. // the href contains this pattern
  392. var new_href = $anchor.attr('href').replace(/rownumber=\d+/, new_value);
  393. $anchor.attr('href', new_href );
  394. });
  395. //Insert/Clone the ignore checkboxes
  396. if (curr_rows == 1 ) {
  397. $('<input id="insert_ignore_1" type="checkbox" name="insert_ignore_1" checked="checked" />')
  398. .insertBefore("table.insertRowTable:last")
  399. .after('<label for="insert_ignore_1">' + PMA_messages['strIgnore'] + '</label>');
  400. }
  401. else {
  402. /**
  403. * @var $last_checkbox Object reference to the last checkbox in #insertForm
  404. */
  405. var $last_checkbox = $("#insertForm").children('input:checkbox:last');
  406. /** name of {@link $last_checkbox} */
  407. var last_checkbox_name = $last_checkbox.attr('name');
  408. /** index of {@link $last_checkbox} */
  409. var last_checkbox_index = parseInt(last_checkbox_name.match(/\d+/));
  410. /** name of new {@link $last_checkbox} */
  411. var new_name = last_checkbox_name.replace(/\d+/,last_checkbox_index+1);
  412. $last_checkbox
  413. .clone()
  414. .attr({'id':new_name, 'name': new_name})
  415. .prop('checked', true)
  416. .add('label[for^=insert_ignore]:last')
  417. .clone()
  418. .attr('for', new_name)
  419. .before('<br />')
  420. .insertBefore("table.insertRowTable:last");
  421. }
  422. curr_rows++;
  423. }
  424. // recompute tabindex for text fields and other controls at footer;
  425. // IMO it's not really important to handle the tabindex for
  426. // function and Null
  427. var tabindex = 0;
  428. $('.textfield, .char')
  429. .each(function() {
  430. tabindex++;
  431. $(this).attr('tabindex', tabindex);
  432. // update the IDs of textfields to ensure that they are unique
  433. $(this).attr('id', "field_" + tabindex + "_3");
  434. });
  435. $('.control_at_footer')
  436. .each(function() {
  437. tabindex++;
  438. $(this).attr('tabindex', tabindex);
  439. });
  440. // Add all the required datepickers back
  441. $('input.datefield, input.datetimefield').each(function(){
  442. PMA_addDatepicker($(this));
  443. });
  444. } else if ( curr_rows > target_rows) {
  445. while(curr_rows > target_rows) {
  446. $("input[id^=insert_ignore]:last")
  447. .nextUntil("fieldset")
  448. .andSelf()
  449. .remove();
  450. curr_rows--;
  451. }
  452. }
  453. })
  454. });