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', 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', function(event){ me._onSubmitClick(event); });
  61. }
  62. if(this.options.cancel_btn_selector !== null)
  63. {
  64. this.options.cancel_btn_selector.off('click').on('click', function(event){ me._onCancelClick(event); });
  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+'"]').triggerHandler('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. function(data){
  156. me._onUpdateSuccess(data);
  157. }
  158. )
  159. .fail(function(data){ me._onUpdateFailure(data); })
  160. .always(function(data){ me._onUpdateAlways(data); });
  161. }
  162. else
  163. {
  164. // Check self NOW as they are no ajax call
  165. this.element.find('[' + this.options.field_identifier_attr + '="' + data.name + '"]').trigger('validate');
  166. }
  167. },
  168. // Intended for overloading in derived classes
  169. _onSubmitClick: function(event)
  170. {
  171. },
  172. // Intended for overloading in derived classes
  173. _onCancelClick: function(event)
  174. {
  175. },
  176. // Intended for overloading in derived classes
  177. _onUpdateSuccess: function(data)
  178. {
  179. if(data.form.updated_fields !== undefined)
  180. {
  181. this.buildData.script_code = '';
  182. this.buildData.style_code = '';
  183. for (var i in data.form.updated_fields)
  184. {
  185. var updated_field = data.form.updated_fields[i];
  186. this.options.fields_list[updated_field.id] = updated_field;
  187. this._prepareField(updated_field.id);
  188. }
  189. // Adding code to the dom
  190. this.options.script_element.append('\n\n// Appended by update at ' + Date() + '\n' + this.buildData.script_code);
  191. this.options.style_element.append('\n\n// Appended by update at ' + Date() + '\n' + this.buildData.style_code);
  192. // Evaluating script code as adding it to dom did not executed it (only script from update !)
  193. eval(this.buildData.script_code);
  194. }
  195. },
  196. // Intended for overloading in derived classes
  197. _onUpdateFailure: function(data)
  198. {
  199. },
  200. // Intended for overloading in derived classes
  201. _onUpdateAlways: function(data)
  202. {
  203. // Check all touched AFTER ajax is complete, otherwise the renderer will redraw the field in the mean time.
  204. for(var i in this.options.touched_fields)
  205. {
  206. this.element.find('[' + this.options.field_identifier_attr + '="' + this.options.touched_fields[i] + '"]').trigger('validate');
  207. }
  208. this._enableFormAfterLoading();
  209. },
  210. // Intended for overloading in derived classes
  211. _disableFormBeforeLoading: function()
  212. {
  213. },
  214. // Intended for overloading in derived classes
  215. _enableFormAfterLoading: function()
  216. {
  217. },
  218. _loadCssFile: function(url)
  219. {
  220. if (!$('link[href="'+url+'"]').length)
  221. $('<link href="'+url+'" rel="stylesheet">').appendTo('head');
  222. },
  223. _loadJsFile: function(url)
  224. {
  225. if (!$('script[src="'+url+'"]').length)
  226. $.getScript(url);
  227. },
  228. // Place a field for which no container exists
  229. _addField: function(field_id)
  230. {
  231. $('<div ' + this.options.field_identifier_attr + '="'+field_id+'"></div>').appendTo(this.element);
  232. },
  233. _prepareField: function(field_id)
  234. {
  235. var field = this.options.fields_list[field_id];
  236. if(this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').length === 1)
  237. {
  238. // We replace the node instead of just replacing the inner html so the previous widget is automatically destroyed.
  239. this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').replaceWith( $('<div ' + this.options.field_identifier_attr + '="'+field.id+'"></div>') );
  240. }
  241. else
  242. {
  243. this._addField(field.id);
  244. }
  245. var field_container = this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]');
  246. // HTML
  247. if( (field.html !== undefined) && (field.html !== '') )
  248. {
  249. field_container.html(field.html);
  250. }
  251. // JS files
  252. if( (field.js_files !== undefined) && (field.js_files.length > 0) )
  253. {
  254. for(var j in field.js_files)
  255. {
  256. this._loadJsFile(field.js_files[i]);
  257. }
  258. }
  259. // CSS files
  260. if( (field.css_files !== undefined) && (field.css_files.length > 0) )
  261. {
  262. for(var j in field.css_files)
  263. {
  264. this._loadCssFile(field.css_files[i]);
  265. }
  266. }
  267. // JS inline
  268. if( (field.js_inline !== undefined) && (field.js_inline !== '') )
  269. {
  270. this.buildData.script_code += '; '+ field.js_inline;
  271. }
  272. // CSS inline
  273. if( (field.css_inline !== undefined) && (field.css_inline !== '') )
  274. {
  275. this.buildData.style_code += ' '+ field.css_inline;
  276. }
  277. },
  278. buildForm: function()
  279. {
  280. this.buildData.script_code = '';
  281. this.buildData.style_code = '';
  282. for(var i in this.options.fields_list)
  283. {
  284. var field = this.options.fields_list[i];
  285. if(field.id === undefined)
  286. {
  287. console.log('Form handler : An field must have at least an id property.');
  288. return false;
  289. }
  290. this._prepareField(field.id);
  291. }
  292. this.options.script_element.text('$(document).ready(function(){ '+this.buildData.script_code+' });');
  293. this.options.style_element.text(this.buildData.style_code);
  294. eval(this.options.script_element.text());
  295. },
  296. showOptions: function() // Debug helper
  297. {
  298. console.log(this.options);
  299. }
  300. });
  301. });