property_field.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. //iTop Designer widget for editing properties line by line
  2. $(function()
  3. {
  4. // the widget definition, where "itop" is the namespace,
  5. // "property_field" the widget name
  6. $.widget( "itop.property_field",
  7. {
  8. // default options
  9. options:
  10. {
  11. parent_selector: null,
  12. field_id: '',
  13. get_field_value: null,
  14. equals: null,
  15. submit_to: 'index.php',
  16. submit_parameters: {operation: 'async_action'},
  17. do_apply: null,
  18. do_cancel: null,
  19. auto_apply: false
  20. },
  21. // the constructor
  22. _create: function()
  23. {
  24. var me = this;
  25. this.element
  26. .addClass( "itop-property-field" )
  27. .bind('apply_changes.itop-property-field', function(){me._do_apply();} );
  28. this.bModified = false;
  29. if (this.options.field_id != '')
  30. {
  31. // In case there is an hidden input having the same id (somewhere else in the page), the change event does not occur unless the input loses the focus
  32. // To reduce the impact, let's handle keyup as well
  33. $('#'+this.options.field_id, this.element).bind('change.itop-property-field keyup.itop-property-field', function() { me._on_change(); });
  34. this.value = this._get_field_value();
  35. }
  36. this.element.find(".prop_apply").bind('click.itop-property-field', function() { me._do_apply(); });
  37. this.element.find(".prop_cancel").bind('click.itop-property-field', function() { me._do_cancel(); });
  38. this._refresh();
  39. },
  40. // called when created, and later when changing options
  41. _refresh: function()
  42. {
  43. if (this.bModified)
  44. {
  45. this.element.addClass("itop-property-field-modified");
  46. this.element.find(".prop_icon span.ui-icon-circle-check").css({visibility: ''});
  47. this.element.find(".prop_icon span.ui-icon-circle-close").css({visibility: ''});
  48. }
  49. else
  50. {
  51. this.element.removeClass("itop-property-field-modified");
  52. this.element.find(".prop_icon span.ui-icon-circle-check").css({visibility: 'hidden'});
  53. this.element.find(".prop_icon span.ui-icon-circle-close").css({visibility: 'hidden'});
  54. }
  55. },
  56. // events bound via _bind are removed automatically
  57. // revert other modifications here
  58. _destroy: function()
  59. {
  60. this.element.removeClass( "itop-property-field" );
  61. this.element.removeClass("itop-property-field-modified");
  62. },
  63. // _setOptions is called with a hash of all options that are changing
  64. // always refresh when changing options
  65. _setOptions: function()
  66. {
  67. // in 1.9 would use _superApply
  68. this._superApply(arguments);
  69. this._refresh();
  70. },
  71. // _setOption is called for each individual option that is changing
  72. _setOption: function( key, value )
  73. {
  74. // in 1.9 would use _super
  75. this._superApply(arguments);
  76. },
  77. _on_change: function()
  78. {
  79. var new_value = this._get_field_value();
  80. if (this._equals(new_value, this.value))
  81. {
  82. this.bModified = false;
  83. }
  84. else
  85. {
  86. this.bModified = true;
  87. if (this.options.auto_apply)
  88. {
  89. this._do_apply();
  90. }
  91. }
  92. this._refresh();
  93. },
  94. _equals: function( value1, value2 )
  95. {
  96. if (this.options.equals === null)
  97. {
  98. return value1 == value2;
  99. }
  100. else
  101. {
  102. return this.options.equals(value1, value2);
  103. }
  104. },
  105. _get_field_value: function()
  106. {
  107. if (this.options.get_field_value === null)
  108. {
  109. var oField = $('#'+this.options.field_id, this.element);
  110. if (oField.attr('type') == 'checkbox')
  111. {
  112. return (oField.attr('checked') == 'checked');
  113. }
  114. else
  115. {
  116. return oField.val();
  117. }
  118. }
  119. else
  120. {
  121. return this.options.get_field_value();
  122. }
  123. },
  124. _get_committed_value: function()
  125. {
  126. return { name: $('#'+this.options.field_id, this.element).attr('name'), value: this.value };
  127. },
  128. _do_apply: function()
  129. {
  130. if (this.options.parent_selector)
  131. {
  132. $(this.options.parent_selector).trigger('mark_as_modified');
  133. }
  134. if (this.options.do_apply)
  135. {
  136. // specific behavior...
  137. if (this.options.do_apply())
  138. {
  139. this.bModified = false;
  140. this.previous_value = this.value;
  141. this.value = this._get_field_value();
  142. this._refresh();
  143. }
  144. }
  145. else
  146. {
  147. // Validate the field
  148. sFormId = this.element.closest('form').attr('id');
  149. var oField = $('#'+this.options.field_id, this.element);
  150. oField.trigger('validate');
  151. if ( $.inArray(this.options.field_id, oFormValidation[sFormId]) == -1)
  152. {
  153. this.bModified = false;
  154. this.previous_value = this.value;
  155. this.value = this._get_field_value();
  156. this._do_submit();
  157. this._refresh();
  158. }
  159. }
  160. },
  161. _do_cancel: function()
  162. {
  163. if (this.options.do_cancel)
  164. {
  165. // specific behavior...
  166. this.options.do_cancel();
  167. }
  168. else
  169. {
  170. this.bModified = false;
  171. var oField = $('#'+this.options.field_id, this.element);
  172. if (oField.attr('type') == 'checkbox')
  173. {
  174. if (this.value)
  175. {
  176. oField.attr('checked', true);
  177. }
  178. else
  179. {
  180. oField.removeAttr('checked');
  181. }
  182. }
  183. else
  184. {
  185. oField.val(this.value);
  186. }
  187. this._refresh();
  188. oField.trigger('reverted', {type: 'designer', previous_value: this.value });
  189. oField.trigger('validate');
  190. }
  191. },
  192. _do_submit: function()
  193. {
  194. var oData = {};
  195. this.element.closest('form').find(':input[type=hidden]').each(function()
  196. {
  197. // Hidden form fields
  198. oData[$(this).attr('name')] = $(this).val();
  199. });
  200. this.element.closest('form').find('.itop-property-field').each(function()
  201. {
  202. var oWidget = $(this).data('itopProperty_field');
  203. if (oWidget && oWidget._is_visible())
  204. {
  205. var oVal = oWidget._get_committed_value();
  206. oData[oVal.name] = oVal.value;
  207. }
  208. });
  209. oPostedData = this.options.submit_parameters;
  210. oPostedData.params = oData;
  211. oPostedData.params.updated = [ $('#'+this.options.field_id, this.element).attr('name') ]; // only one field updated in this case
  212. oPostedData.params.previous_values = {};
  213. oPostedData.params.previous_values[oPostedData.params.updated] = this.previous_value; // pass also the previous value(s)
  214. $.post(this.options.submit_to, oPostedData, function(data)
  215. {
  216. $('#prop_submit_result').html(data);
  217. });
  218. },
  219. _is_visible: function()
  220. {
  221. return this.element.is(':visible');
  222. }
  223. });
  224. });
  225. var oFormValidation = {};
  226. function ValidateWithPattern(sFieldId, bMandatory, sPattern, sFormId, aForbiddenValues, sExplainForbiddenValues)
  227. {
  228. var currentVal = $('#'+sFieldId).val();
  229. var bValid = true;
  230. var sMessage = null;
  231. if (bMandatory && (currentVal == ''))
  232. {
  233. bValid = false;
  234. }
  235. if ((sPattern != '') && (currentVal != ''))
  236. {
  237. re = new RegExp(sPattern);
  238. bValid = re.test(currentVal);
  239. }
  240. if (aForbiddenValues)
  241. {
  242. for(var i in aForbiddenValues)
  243. {
  244. if (aForbiddenValues[i] == currentVal)
  245. {
  246. bValid = false;
  247. sMessage = sExplainForbiddenValues;
  248. break;
  249. }
  250. }
  251. }
  252. if (oFormValidation[sFormId] == undefined) oFormValidation[sFormId] = [];
  253. if (!bValid)
  254. {
  255. $('#v_'+sFieldId).addClass('ui-state-error');
  256. iFieldIdPos = jQuery.inArray(sFieldId, oFormValidation[sFormId]);
  257. if (iFieldIdPos == -1)
  258. {
  259. oFormValidation[sFormId].push(sFieldId);
  260. }
  261. if (sMessage)
  262. {
  263. $('#'+sFieldId).attr('title', sMessage).tooltip();
  264. if ($('#'+sFieldId).is(":focus"))
  265. {
  266. $('#'+sFieldId).tooltip('open');
  267. }
  268. }
  269. }
  270. else
  271. {
  272. $('#v_'+sFieldId).removeClass('ui-state-error');
  273. if ($('#'+sFieldId).data('uiTooltip'))
  274. {
  275. $('#'+sFieldId).tooltip('close');
  276. }
  277. $('#'+sFieldId).removeAttr('title');
  278. // Remove the element from the array
  279. iFieldIdPos = jQuery.inArray(sFieldId, oFormValidation[sFormId]);
  280. if (iFieldIdPos > -1)
  281. {
  282. oFormValidation[sFormId].splice(iFieldIdPos, 1);
  283. }
  284. }
  285. }
  286. function ValidateInteger(sFieldId, bMandatory, sFormId, iMin, iMax, sExplainFormat)
  287. {
  288. var currentVal = $('#'+sFieldId).val();
  289. var bValid = true;
  290. var sMessage = null;
  291. if (bMandatory && (currentVal == ''))
  292. {
  293. bValid = false;
  294. }
  295. re = new RegExp('^$|^-?[0-9]+$');
  296. bValid = re.test(currentVal);
  297. if (bValid && (currentVal != ''))
  298. {
  299. // It is a valid number, let's check the boundaries
  300. var iValue = parseInt(currentVal, 10);
  301. if ((iMin != null) && (iValue < iMin))
  302. {
  303. bValid = false;
  304. }
  305. if ((iMax != null) && (iValue > iMax))
  306. {
  307. bValid = false;
  308. }
  309. if (!bValid && (sExplainFormat != undefined))
  310. {
  311. sMessage = sExplainFormat;
  312. }
  313. }
  314. if (oFormValidation[sFormId] == undefined) oFormValidation[sFormId] = [];
  315. if (!bValid)
  316. {
  317. $('#v_'+sFieldId).addClass('ui-state-error');
  318. iFieldIdPos = jQuery.inArray(sFieldId, oFormValidation[sFormId]);
  319. if (iFieldIdPos == -1)
  320. {
  321. oFormValidation[sFormId].push(sFieldId);
  322. }
  323. if (sMessage)
  324. {
  325. $('#'+sFieldId).attr('title', sMessage).tooltip();
  326. if ($('#'+sFieldId).is(":focus"))
  327. {
  328. $('#'+sFieldId).tooltip('open');
  329. }
  330. }
  331. }
  332. else
  333. {
  334. $('#v_'+sFieldId).removeClass('ui-state-error');
  335. if ($('#'+sFieldId).data('uiTooltip'))
  336. {
  337. $('#'+sFieldId).tooltip('close');
  338. }
  339. $('#'+sFieldId).removeAttr('title');
  340. // Remove the element from the array
  341. iFieldIdPos = jQuery.inArray(sFieldId, oFormValidation[sFormId]);
  342. if (iFieldIdPos > -1)
  343. {
  344. oFormValidation[sFormId].splice(iFieldIdPos, 1);
  345. }
  346. }
  347. }
  348. function ValidateForm(sFormId, bValidateAll)
  349. {
  350. oFormValidation[sFormId] = [];
  351. if (bValidateAll)
  352. {
  353. $('#'+sFormId+' :input').trigger('validate');
  354. }
  355. else
  356. {
  357. // Only the visible fields
  358. $('#'+sFormId+' :input:visible').each(function() {
  359. $(this).trigger('validate');
  360. });
  361. }
  362. return oFormValidation[sFormId];
  363. }
  364. function ReadFormParams(sFormId)
  365. {
  366. var oMap = { };
  367. $('#'+sFormId+' :input').each( function() {
  368. if ($(this).parent().is(':visible'))
  369. {
  370. var sName = $(this).attr('name');
  371. if (sName && sName != '')
  372. {
  373. if (this.type == 'checkbox')
  374. {
  375. oMap[sName] = ($(this).attr('checked') == 'checked');
  376. }
  377. else
  378. {
  379. oMap[sName] = $(this).val();
  380. }
  381. }
  382. }
  383. });
  384. return oMap;
  385. }
  386. function SubmitForm(sFormId, onSubmitResult)
  387. {
  388. var aErrors = ValidateForm(sFormId, false);
  389. if (aErrors.length == 0)
  390. {
  391. var oMap = ReadFormParams(sFormId);
  392. oMap.module_name = sCurrentModule;
  393. $('#'+sFormId+' :input').each( function() {
  394. if ($(this).parent().is(':visible'))
  395. {
  396. var sName = $(this).attr('name');
  397. if (sName && sName != '')
  398. {
  399. if (this.type == 'checkbox')
  400. {
  401. oMap[sName] = ($(this).attr('checked') == 'checked');
  402. }
  403. else
  404. {
  405. oMap[sName] = $(this).val();
  406. }
  407. }
  408. }
  409. });
  410. $.post(GetAbsoluteUrlAppRoot()+'designer/module.php', oMap, function(data)
  411. {
  412. onSubmitResult(data);
  413. });
  414. }
  415. else
  416. {
  417. // TODO: better error reporting !!!
  418. alert('Please fill all the fields before continuing...');
  419. }
  420. }
  421. function RemoveSubForm(sId, sUrl, oParams)
  422. {
  423. $.post(sUrl, oParams, function(data) {
  424. $('body').append(data);
  425. });
  426. }
  427. function AddSubForm(sId, sUrl, oParams)
  428. {
  429. var aIndexes = JSON.parse($('#'+sId).val());
  430. var iLast = aIndexes[aIndexes.length - 1];
  431. var iNewIdx = 1 + iLast;
  432. oParams.new_index = iNewIdx;
  433. $.post(sUrl, oParams, function(data) {
  434. $('body').append(data);
  435. });
  436. }
  437. function InitFormSelectorField(sId, sSelector)
  438. {
  439. $('#'+sId).bind('change reverted init', function() {
  440. // Mark all the direct children as hidden
  441. $('tr[data-selector="'+sSelector+'"]').attr('data-state', 'hidden');
  442. // Mark the selected one as visible
  443. var sSelectedHierarchy = sSelector+'-'+this.value;
  444. $('tr[data-path="'+sSelectedHierarchy+'"]').attr('data-state', 'visible');
  445. // Show all items behind the current one
  446. $('tr[data-path^="'+sSelector+'"]').show();
  447. // Hide items behind the current one as soon as it is behind a hidden node (or itself is marked as hidden)
  448. $('tr[data-path^="'+sSelector+'"][data-state="hidden"]').each(function() {
  449. $(this).hide();
  450. var sPath = $(this).attr('data-path');
  451. $('tr[data-path^="'+sPath+'/"]').hide();
  452. });
  453. }).trigger('init'); // initial refresh
  454. }