form_handler.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. //iTop Form handler
  2. ;
  3. $(function()
  4. {
  5. // the widget definition, where 'itop' is the namespace,
  6. // 'form_handler' the widget name
  7. $.widget( 'itop.form_handler',
  8. {
  9. // default options
  10. options:
  11. {
  12. formmanager_class: null,
  13. formmanager_data: null,
  14. field_identifier_attr: 'data-field-id', // convention: fields are rendered into a div and are identified by this attribute
  15. fields_list: null,
  16. fields_impacts: {},
  17. touched_fields: [],
  18. submit_btn_selector: null,
  19. cancel_btn_selector: null,
  20. endpoint: null,
  21. is_modal: false,
  22. is_valid: true,
  23. script_element: null,
  24. style_element: null
  25. },
  26. buildData:
  27. {
  28. script_code: '',
  29. style_code: ''
  30. },
  31. // the constructor
  32. _create: function()
  33. {
  34. var me = this;
  35. this.element
  36. .addClass('form_handler');
  37. this.element
  38. .bind('field_change.form_handler', function(event, data){
  39. me._onFieldChange(event, data);
  40. });
  41. // Creating DOM elements if not using user's specifics
  42. if(this.options.script_element === null)
  43. {
  44. this.options.script_element = $('<script type="text/javascript"></script>');
  45. this.element.after(this.options.script_element);
  46. }
  47. if(this.options.style_element === null)
  48. {
  49. this.options.style_element = $('<style></style>');
  50. this.element.before(this.options.style_element);
  51. }
  52. // Building the form
  53. if(this.options.fields_list !== null)
  54. {
  55. this.buildForm();
  56. }
  57. // Binding buttons
  58. if(this.options.submit_btn_selector !== null)
  59. {
  60. this.options.submit_btn_selector.off('click').on('click', this._onSubmitClick());
  61. }
  62. if(this.options.cancel_btn_selector !== null)
  63. {
  64. this.options.cancel_btn_selector.off('click').on('click', this._onCancelClick());
  65. }
  66. },
  67. // called when created, and later when changing options
  68. _refresh: function()
  69. {
  70. },
  71. // events bound via _bind are removed automatically
  72. // revert other modifications here
  73. _destroy: function()
  74. {
  75. this.element
  76. .removeClass('form_handler');
  77. },
  78. // _setOptions is called with a hash of all options that are changing
  79. // always refresh when changing options
  80. _setOptions: function()
  81. {
  82. this._superApply(arguments);
  83. },
  84. // _setOption is called for each individual option that is changing
  85. _setOption: function( key, value )
  86. {
  87. this._super( key, value );
  88. },
  89. _getCurrentValues: function()
  90. {
  91. var result = {};
  92. for(var i in this.options.fields_list)
  93. {
  94. var field = this.options.fields_list[i];
  95. if(this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').hasClass('form_field'))
  96. {
  97. $.extend(true, result, this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').trigger('get_current_value'));
  98. }
  99. else
  100. {
  101. console.log('Form handler : Cannot retrieve current value from field [' + this.options.field_identifier_attr + '="'+field.id+'"] as it seems to have no itop.form_field widget attached.');
  102. }
  103. }
  104. return result;
  105. },
  106. _getRequestedFields: function(sourceFieldName)
  107. {
  108. var fieldsName = [];
  109. if(this.options.fields_impacts[sourceFieldName] !== undefined)
  110. {
  111. for(var i in this.options.fields_impacts[sourceFieldName])
  112. {
  113. fieldsName.push(this.options.fields_impacts[sourceFieldName][i]);
  114. }
  115. }
  116. return fieldsName;
  117. },
  118. _onFieldChange: function(event, data)
  119. {
  120. var me = this;
  121. // Data checks
  122. if(this.options.endpoint === null)
  123. {
  124. console.log('Form handler : An endpoint must be defined.');
  125. return false;
  126. }
  127. if(this.options.formmanager_class === null)
  128. {
  129. console.log('Form handler : Form manager class must be defined.');
  130. return false;
  131. }
  132. if(this.options.formmanager_data === null)
  133. {
  134. console.log('Form handler : Form manager data must be defined.');
  135. return false;
  136. }
  137. // Set field as touched so we know that we have to do checks on it later
  138. if(this.options.touched_fields.indexOf(data.name) < 0)
  139. {
  140. this.options.touched_fields.push(data.name);
  141. }
  142. var requestedFields = this._getRequestedFields(data.name);
  143. if(requestedFields.length > 0)
  144. {
  145. this._disableFormBeforeLoading();
  146. $.post(
  147. this.options.endpoint,
  148. {
  149. operation: 'update',
  150. formmanager_class: this.options.formmanager_class,
  151. formmanager_data: JSON.stringify(this.options.formmanager_data),
  152. current_values: this._getCurrentValues(),
  153. requested_fields: requestedFields
  154. },
  155. this._onUpdateSuccess(data)
  156. )
  157. .fail(this._onUpdateFailure(data))
  158. .always(this._onUpdateAlways());
  159. }
  160. else
  161. {
  162. // Check self NOW as they are no ajax call
  163. this.element.find('[' + this.options.field_identifier_attr + '="' + data.name + '"]').trigger('validate');
  164. }
  165. },
  166. // Intended for overloading in derived classes
  167. _onSubmitClick: function()
  168. {
  169. },
  170. // Intended for overloading in derived classes
  171. _onCancelClick: function()
  172. {
  173. },
  174. // Intended for overloading in derived classes
  175. _onUpdateSuccess: function(data)
  176. {
  177. if(data.form.updated_fields !== undefined)
  178. {
  179. this.buildData.script_code = '';
  180. this.buildData.style_code = '';
  181. for (var i in data.form.updated_fields)
  182. {
  183. var updated_field = data.form.updated_field[i];
  184. this.options.fields_list[updated_field.id] = updated_field;
  185. this._prepareField(updated_field.id);
  186. }
  187. // Adding code to the dom
  188. this.options.script_element.append('\n\n// Appended by update at ' + Date() + '\n' + this.buildData.script_code);
  189. this.options.style_element.append('\n\n// Appended by update at ' + Date() + '\n' + this.buildData.style_code);
  190. // Evaluating script code as adding it to dom did not executed it (only script from update !)
  191. eval(this.buildData.script_code);
  192. }
  193. },
  194. // Intended for overloading in derived classes
  195. _onUpdateFailure: function(data)
  196. {
  197. },
  198. // Intended for overloading in derived classes
  199. _onUpdateAlways: function()
  200. {
  201. // Check all touched AFTER ajax is complete, otherwise the renderer will redraw the field in the mean time.
  202. for(var i in this.options.touched_fields)
  203. {
  204. this.element.find('[' + this.options.field_identifier_attr + '="' + this.options.touched_fields[i] + '"]').trigger('validate');
  205. }
  206. this._enableFormAfterLoading();
  207. },
  208. // Intended for overloading in derived classes
  209. _disableFormBeforeLoading: function()
  210. {
  211. },
  212. // Intended for overloading in derived classes
  213. _enableFormAfterLoading: function()
  214. {
  215. },
  216. _loadCssFile: function(url)
  217. {
  218. if (!$('link[href="'+url+'"]').length)
  219. $('<link href="'+url+'" rel="stylesheet">').appendTo('head');
  220. },
  221. _loadJsFile: function(url)
  222. {
  223. if (!$('script[src="'+url+'"]').length)
  224. $.getScript(url);
  225. },
  226. // Place a field for which no container exists
  227. _addField: function(field_id)
  228. {
  229. $('<div ' + this.options.field_identifier_attr + '="'+field_id+'"></div>').appendTo(this.element);
  230. },
  231. _prepareField: function(field_id)
  232. {
  233. var field = this.options.fields_list[field_id];
  234. if(this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').length === 1)
  235. {
  236. // We replace the node instead of just replacing the inner html so the previous widget is automatically destroyed.
  237. this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').replaceWith( $('<div ' + this.options.field_identifier_attr + '="'+field.id+'"></div>') );
  238. }
  239. else
  240. {
  241. this._addField(field.id);
  242. }
  243. var field_container = this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]');
  244. // HTML
  245. if( (field.html !== undefined) && (field.html !== '') )
  246. {
  247. field_container.html(field.html);
  248. }
  249. // JS files
  250. if( (field.js_files !== undefined) && (field.js_files.length > 0) )
  251. {
  252. for(var j in field.js_files)
  253. {
  254. this._loadJsFile(field.js_files[i]);
  255. }
  256. }
  257. // CSS files
  258. if( (field.css_files !== undefined) && (field.css_files.length > 0) )
  259. {
  260. for(var j in field.css_files)
  261. {
  262. this._loadCssFile(field.css_files[i]);
  263. }
  264. }
  265. // JS inline
  266. if( (field.js_inline !== undefined) && (field.js_inline !== '') )
  267. {
  268. this.buildData.script_code += '; '+ field.js_inline;
  269. }
  270. // CSS inline
  271. if( (field.css_inline !== undefined) && (field.css_inline !== '') )
  272. {
  273. this.buildData.style_code += ' '+ field.css_inline;
  274. }
  275. // JS widget itop.form_field
  276. if (field.validators != undefined)
  277. {
  278. this.buildData.script_code += '; $("[' + this.options.field_identifier_attr + '=\'' + field.id + '\']").trigger(\'set_validators\', ' + JSON.stringify(field.validators) + ');';
  279. }
  280. },
  281. showOptions: function() // Debug helper
  282. {
  283. console.log(this.options);
  284. },
  285. buildForm: function()
  286. {
  287. this.buildData.script_code = '';
  288. this.buildData.style_code = '';
  289. for(var i in this.options.fields_list)
  290. {
  291. var field = this.options.fields_list[i];
  292. if(field.id === undefined)
  293. {
  294. console.log('Form handler : An field must have at least an id property.');
  295. return false;
  296. }
  297. this._prepareField(field.id);
  298. }
  299. this.options.script_element.text('$(document).ready(function(){ '+this.buildData.script_code+' });');
  300. this.options.style_element.text(this.buildData.style_code);
  301. }
  302. });
  303. });