field_set.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. // Copyright (C) 2010-2016 Combodo SARL
  2. //
  3. // This file is part of iTop.
  4. //
  5. // iTop is free software; you can redistribute it and/or modify
  6. // it under the terms of the GNU Affero General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // iTop is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU Affero General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU Affero General Public License
  16. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  17. //iTop Field set
  18. //Used by itop.form_handler and itop.subform_field to list their fields
  19. ;
  20. $(function()
  21. {
  22. // the widget definition, where 'itop' is the namespace,
  23. // 'field_set' the widget name
  24. $.widget( 'itop.field_set',
  25. {
  26. // default options
  27. options:
  28. {
  29. field_identifier_attr: 'data-field-id', // convention: fields are rendered into a div and are identified by this attribute
  30. fields_list: null,
  31. fields_impacts: {},
  32. touched_fields: [],
  33. is_valid: true,
  34. form_path: '',
  35. script_element: null,
  36. style_element: null
  37. },
  38. buildData:
  39. {
  40. script_code: '',
  41. style_code: ''
  42. },
  43. // the constructor
  44. _create: function()
  45. {
  46. var me = this;
  47. this.element
  48. .addClass('field_set');
  49. this.element
  50. .bind('field_change', function(oEvent, oData){
  51. me._onFieldChange(oEvent, oData);
  52. })
  53. .bind('update_form', function(oEvent, oData){
  54. me._onUpdateForm(oEvent, oData);
  55. })
  56. .bind('get_current_values', function(oEvent, oData){
  57. return me._onGetCurrentValues(oEvent, oData);
  58. })
  59. .bind('validate', function(oEvent, oData){
  60. if (oData === undefined)
  61. {
  62. oData = {};
  63. }
  64. return me._onValidate(oEvent, oData);
  65. });
  66. // Creating DOM elements if not using user's specifics
  67. if(this.options.script_element === null)
  68. {
  69. this.options.script_element = $('<script type="text/javascript"></script>');
  70. this.element.after(this.options.script_element);
  71. }
  72. if(this.options.style_element === null)
  73. {
  74. this.options.style_element = $('<style></style>');
  75. this.element.before(this.options.style_element);
  76. }
  77. // Building the form
  78. if(this.options.fields_list !== null)
  79. {
  80. this.buildForm();
  81. }
  82. },
  83. // called when created, and later when changing options
  84. _refresh: function()
  85. {
  86. },
  87. // events bound via _bind are removed automatically
  88. // revert other modifications here
  89. _destroy: function()
  90. {
  91. this.element
  92. .removeClass('field_set');
  93. },
  94. // _setOptions is called with a hash of all options that are changing
  95. // always refresh when changing options
  96. _setOptions: function()
  97. {
  98. this._superApply(arguments);
  99. },
  100. // _setOption is called for each individual option that is changing
  101. _setOption: function( key, value )
  102. {
  103. this._super( key, value );
  104. },
  105. getField: function (sFieldId)
  106. {
  107. return this.element.find('[' + this.options.field_identifier_attr + '="' + sFieldId + '"][data-form-path="' + this.options.form_path + '"]');
  108. },
  109. _onGetCurrentValues: function(oEvent, oData)
  110. {
  111. oEvent.stopPropagation();
  112. var oResult = {};
  113. for(var i in this.options.fields_list)
  114. {
  115. var oField = this.options.fields_list[i];
  116. if(this.getField(oField.id).hasClass('form_field'))
  117. {
  118. oResult[oField.id] = this.getField(oField.id).triggerHandler('get_current_value');
  119. }
  120. else
  121. {
  122. console.log('Field set : Cannot retrieve current value from field [' + this.options.field_identifier_attr + '="' + oField.id + '"][data-form-path="' + this.options.form_path + '"] as it seems to have no itop.form_field widget attached.');
  123. }
  124. }
  125. return oResult;
  126. },
  127. _getRequestedFields: function(sSourceFieldName)
  128. {
  129. var aFieldsName = [];
  130. if(this.options.fields_impacts[sSourceFieldName] !== undefined)
  131. {
  132. for(var i in this.options.fields_impacts[sSourceFieldName])
  133. {
  134. aFieldsName.push(this.options.fields_impacts[sSourceFieldName][i]);
  135. }
  136. }
  137. return aFieldsName;
  138. },
  139. _onFieldChange: function(oEvent, oData)
  140. {
  141. oEvent.stopPropagation();
  142. // Set field as touched so we know that we have to do checks on it later
  143. if(this.options.touched_fields.indexOf(oData.name) < 0)
  144. {
  145. this.options.touched_fields.push(oData.name);
  146. }
  147. // Validate the field
  148. var oResult = this.getField(oData.name).triggerHandler('validate', {touched_fields_only: true});
  149. if ( (oResult !== undefined) && !oResult.is_valid)
  150. {
  151. this.options.is_valid = false;
  152. }
  153. var oRequestedFields = this._getRequestedFields(oData.name);
  154. if(oRequestedFields.length > 0)
  155. {
  156. this.element.trigger('update_fields', {form_path: this.options.form_path, requested_fields: oRequestedFields});
  157. }
  158. },
  159. _onUpdateForm: function(oEvent, oData)
  160. {
  161. oEvent.stopPropagation();
  162. this.buildData.script_code = '';
  163. this.buildData.style_code = '';
  164. for (var i in oData.updated_fields)
  165. {
  166. var oUpdatedField = oData.updated_fields[i];
  167. this.options.fields_list[oUpdatedField.id] = oUpdatedField;
  168. this._prepareField(oUpdatedField.id);
  169. }
  170. // Adding code to the dom
  171. // Note : We use text() instead of append(), otherwise the code will be interpreted as DOM tags (text + <img /> + ...) and can break some browsers
  172. this.options.script_element.text( this.options.script_element.text() + '\n\n// Appended by update on ' + Date() + '\n' + this.buildData.script_code);
  173. this.options.style_element.text( this.options.style_element.text() + '\n\n// Appended by update on ' + Date() + '\n' + this.buildData.style_code);
  174. // Evaluating script code as adding it to dom did not executed it (only script from update !)
  175. eval(this.buildData.script_code);
  176. },
  177. _onValidate: function(oEvent, oData)
  178. {
  179. oEvent.stopPropagation();
  180. this.options.is_valid = true;
  181. var aFieldsToValidate = [];
  182. if ((oData.touched_fields_only !== undefined) && (oData.touched_fields_only === true))
  183. {
  184. aFieldsToValidate = this.options.touched_fields;
  185. }
  186. else
  187. {
  188. // TODO : Requires IE9+ Object.keys(this.options.fields_list);
  189. for (var sFieldId in this.options.fields_list)
  190. {
  191. aFieldsToValidate.push(sFieldId);
  192. }
  193. }
  194. for(var i in aFieldsToValidate)
  195. {
  196. var oField = this.getField(aFieldsToValidate[i]);
  197. // Checking if the field still exists as it could have been from a dynamic subform (Typically with custom fields)
  198. if(oField.length > 0 && oField.hasClass('form_field'))
  199. {
  200. var oRes = oField.triggerHandler('validate', oData);
  201. if (!oRes.is_valid)
  202. {
  203. this.options.is_valid = false;
  204. }
  205. }
  206. }
  207. return this.options.is_valid;
  208. },
  209. // Debug helper
  210. showOptions: function()
  211. {
  212. return this.options;
  213. },
  214. _loadCssFile: function(url)
  215. {
  216. if (!$('link[href="' + url + '"]').length)
  217. $('<link href="' + url + '" rel="stylesheet">').appendTo('head');
  218. },
  219. _loadJsFile: function(url)
  220. {
  221. if (!$('script[src="' + url + '"]').length)
  222. $.getScript(url);
  223. },
  224. // Place a field for which no container exists
  225. _addField: function(sFieldId)
  226. {
  227. $('<div ' + this.options.field_identifier_attr + '="' + sFieldId + '" data-form-path="' + this.options.form_path + '"></div>').appendTo(this.element);
  228. },
  229. _prepareField: function(sFieldId)
  230. {
  231. var oField = this.options.fields_list[sFieldId];
  232. if(this.getField(oField.id).length === 1)
  233. {
  234. // We replace the node instead of just replacing the inner html so the previous widget is automatically destroyed.
  235. this.getField(oField.id).replaceWith(
  236. $('<div ' + this.options.field_identifier_attr + '="' + oField.id + '" data-form-path="' + this.options.form_path + '"></div>')
  237. );
  238. }
  239. else
  240. {
  241. this._addField(oField.id);
  242. }
  243. var oFieldContainer = this.getField(oField.id);
  244. // HTML
  245. if( (oField.html !== undefined) && (oField.html !== '') )
  246. {
  247. oFieldContainer.html(oField.html);
  248. }
  249. // JS files
  250. if( (oField.js_files !== undefined) && (oField.js_files.length > 0) )
  251. {
  252. for(var i in oField.js_files)
  253. {
  254. this._loadJsFile(oField.js_files[i]);
  255. }
  256. }
  257. // CSS files
  258. if( (oField.css_files !== undefined) && (oField.css_files.length > 0) )
  259. {
  260. for(var i in oField.css_files)
  261. {
  262. this._loadCssFile(oField.css_files[i]);
  263. }
  264. }
  265. // JS inline
  266. if( (oField.js_inline !== undefined) && (oField.js_inline !== '') )
  267. {
  268. this.buildData.script_code += '; '+ oField.js_inline;
  269. }
  270. // CSS inline
  271. if( (oField.css_inline !== undefined) && (oField.css_inline !== '') )
  272. {
  273. this.buildData.style_code += ' '+ oField.css_inline;
  274. }
  275. },
  276. buildForm: function()
  277. {
  278. this.buildData.script_code = '';
  279. this.buildData.style_code = '';
  280. for(var i in this.options.fields_list)
  281. {
  282. var oField = this.options.fields_list[i];
  283. if(oField.id === undefined)
  284. {
  285. console.log('Field set : A field must have at least an id property.');
  286. return false;
  287. }
  288. this._prepareField(oField.id);
  289. }
  290. this.options.script_element.text('$(document).ready(function(){ ' + this.buildData.script_code + ' });');
  291. this.options.style_element.text(this.buildData.style_code);
  292. eval(this.options.script_element.text());
  293. }
  294. });
  295. });