ui.js 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737
  1. /**
  2. * Save fields to server
  3. *
  4. * @param {boolean} [includeAll] if set to true then export all shown or hidden fields
  5. * @param {function} [callback] Callback for save complete
  6. * @param {boolean} [force] Force save and ignore changes status
  7. * @return {Object[]} Field objects list
  8. */
  9. function saveFields(includeAll, callback, force)
  10. {
  11. if(typeof includeAll === 'function')
  12. {
  13. callback = includeAll;
  14. includeAll = false;
  15. }
  16. var fieldsList = [];
  17. var changedFields = [];
  18. var revertChanges = function()
  19. {
  20. for(var i = 0; i < changedFields.length; ++i)
  21. {
  22. changedFields[i].changed = true;
  23. }
  24. };
  25. var errorsCount = 0;
  26. for(var i = 0; i < fields.length; ++i)
  27. {
  28. var originField = fields[i];
  29. if(originField.changed)
  30. {
  31. originField.changed = false;
  32. changedFields.push(originField);
  33. }
  34. if(!includeAll && originField.show !== '1') continue;
  35. var field = $.extend({}, originField);
  36. if(originField.errors && originField.errors.$length) errorsCount++;
  37. field.order = i + 1;
  38. if(field.id[0] === '_') delete field.id;
  39. if(field.type === 'decimal')
  40. {
  41. field.length = field.integerDigits + ',' + field.decimalDigits;
  42. }
  43. delete field.unsaved;
  44. delete field._options;
  45. delete field.errors;
  46. delete field.changed;
  47. delete field.rules;
  48. delete field.optionClass;
  49. if($.isArray(field.options))
  50. {
  51. var options = {};
  52. for(var j = 0; j < field.options.length; ++j)
  53. {
  54. var option = field.options[j];
  55. if(option.value) options[option.value] = option.text;
  56. }
  57. field.options = options;
  58. }
  59. if($.isArray(field.layoutRules))
  60. {
  61. field.layoutRules = field.layoutRules.join(',');
  62. }
  63. fieldsList.push(field);
  64. }
  65. if(!force && !changedFields.length) return;
  66. if(errorsCount)
  67. {
  68. revertChanges();
  69. $('#editor').addClass('highlight-errors');
  70. setTimeout(function()
  71. {
  72. $('#editor').removeClass('highlight-errors');
  73. }, 5000);
  74. return new $.zui.Messager(window.validateMessages.failSummary.replace('%s', errorsCount),
  75. {
  76. type: 'danger',
  77. icon: 'bell',
  78. placement: 'center',
  79. }).show();
  80. }
  81. var link = createLink('workflow', 'ui', 'module=' + window.flowModule + '&action=' + window.action + '&groupID=' + groupID);
  82. $.post(link, {fields: JSON.stringify(fieldsList)}, function(response)
  83. {
  84. try {response = JSON.parse(response);}
  85. catch(error)
  86. {
  87. response = response ? response : window.unknownError;
  88. new $.zui.Messager(response,
  89. {
  90. type: 'danger',
  91. icon: 'bell',
  92. placement: 'center',
  93. time: 10000
  94. }).show();
  95. revertChanges();
  96. if(callback) callback(false, response);
  97. return;
  98. }
  99. if(response.result == 'fail')
  100. {
  101. if(typeof response.message === 'string') bootbox.alert(response.message);
  102. else if($.isPlainObject(response.message))
  103. {
  104. var errors = {};
  105. if(response.field) errors[response.field] = response.message;
  106. else errors = response.message;
  107. window.updateFieldsErrors(errors);
  108. }
  109. revertChanges();
  110. if(callback) callback(false, response);
  111. }
  112. else
  113. {
  114. $('#saveBtn').popover(
  115. {
  116. trigger: 'manual',
  117. content: response.message,
  118. tipClass: 'popover-success popover-form-result',
  119. placement: 'left'
  120. }).popover('show');
  121. setTimeout(function(){$('#saveBtn').popover('destroy')}, 2000);
  122. if(callback) callback(true, response);
  123. }
  124. });
  125. return true;
  126. }
  127. /**
  128. * Get default options list of specific option type
  129. *
  130. * @param {Object} field field object
  131. * @param {Function} callback callback
  132. * @return {Object} return default options key-value with a object
  133. */
  134. function getDefaultsOfOption(field, callback)
  135. {
  136. if(field.options == 'sql' && field.sql == '') return false;
  137. /* Fetch default options from server, then call callback on ready */
  138. var sql = window.btoa(encodeURI(field.sql));
  139. var control = window.btoa(encodeURI(field.control));
  140. var optionType = field.options;
  141. var type = field.type;
  142. var value = window.btoa(encodeURI(field.defaultValue));
  143. var link = createLink('workflowfield', 'ajaxGetDefaultControl', 'mode=quick&control=' + control + '&optionType=' + optionType + '&type=' + type + '&sql=' + sql + '&sqlVars=&elementName=&default=' + value);
  144. $.getJSON(link, callback);
  145. }
  146. function initDefault(field, isCustomOptions)
  147. {
  148. var $fieldDefaultValue = $('#fieldDefaultValue');
  149. var chosen = $fieldDefaultValue.data('chosen');
  150. var picker = $fieldDefaultValue.data('zui.picker');
  151. if($fieldDefaultValue.find('option[value=ajax_search_more]').length == 1)
  152. {
  153. $fieldDefaultValue.find('option[value=ajax_search_more]').remove();
  154. if(chosen) chosen.destroy();
  155. var control = window.btoa(encodeURI(field.control));
  156. var optionType = field.options;
  157. var type = field.type;
  158. var sql = '';
  159. var url = createLink('workflowfield', 'ajaxGetMoreDefault', 'mode=advanced&control=' + control + '&optionType=' + optionType + '&type=' + type + '&sql=' + sql + '&sqlVars=&search={search}');
  160. initPicker($('#fieldDefaultValue'), {remote: url});
  161. }
  162. else
  163. {
  164. if(picker) picker.destroy();
  165. if(chosen)
  166. {
  167. $fieldDefaultValue.trigger('chosen:updated');
  168. }
  169. else
  170. {
  171. $fieldDefaultValue.chosen({allow_single_deselect: true});
  172. }
  173. }
  174. }
  175. /**
  176. * Get default field properties by control type
  177. *
  178. * @param {String} controlType
  179. * @return {Object}
  180. */
  181. function getDefaultFieldByControl(controlType)
  182. {
  183. var field =
  184. {
  185. type: fieldDefault.type,
  186. optionClass: fieldDefault.optionClass,
  187. integerDigits: fieldDefault.integerDigits,
  188. decimalDigits: fieldDefault.decimalDigits,
  189. };
  190. switch(controlType)
  191. {
  192. case 'label' :
  193. field.type = 'mediumint';
  194. field.optionClass = 'integer';
  195. break;
  196. case 'formula' :
  197. case 'decimal' :
  198. field.type = 'decimal';
  199. field.optionClass = 'decimal';
  200. break;
  201. case 'integer' :
  202. field.type = 'int';
  203. field.optionClass = 'integer';
  204. break;
  205. case 'multi-select' :
  206. case 'checkbox' :
  207. case 'textarea' :
  208. case 'richtext' :
  209. field.type = 'text';
  210. field.optionClass = 'text';
  211. break;
  212. case 'date' :
  213. field.type = 'date';
  214. field.optionClass = 'date';
  215. break;
  216. case 'datetime' :
  217. field.type = 'datetime';
  218. field.optionClass = 'time';
  219. break;
  220. }
  221. return field;
  222. }
  223. function removeOptionError(field)
  224. {
  225. if(field.errors.options)
  226. {
  227. delete field.errors.options;
  228. field.errors.$length --;
  229. $('#field-' + field.id).toggleClass('has-error', !!(field.errors && field.errors.$length))
  230. $('#fieldOptionsMessage').hide();;
  231. }
  232. }
  233. /* https://tc39.github.io/ecma262/#sec-array.prototype.findIndex */
  234. if (!Array.prototype.findIndex)
  235. {
  236. Object.defineProperty(Array.prototype, 'findIndex',
  237. {
  238. value: function(predicate)
  239. {
  240. if(this == null) throw new TypeError('"this" is null or not defined');
  241. var o = Object(this);
  242. var len = o.length >>> 0;
  243. if(typeof predicate !== 'function') throw new TypeError('predicate must be a function');
  244. var thisArg = arguments[1];
  245. var k = 0;
  246. while(k < len)
  247. {
  248. var kValue = o[k];
  249. if(predicate.call(thisArg, kValue, k, o)) return k;
  250. k++;
  251. }
  252. return -1;
  253. }
  254. });
  255. }
  256. $(function()
  257. {
  258. var isForm = uiMode === 'form';
  259. var isView = uiMode === 'view';
  260. var isBatchForm = uiMode === 'batchForm';
  261. var canSetWidth = uiMode === 'browse' || isBatchForm;
  262. var canSetPosition = canSetWidth || uiMode === 'view';
  263. var $previewBox = $('#editorPreview');
  264. var $preview = $(isForm ? '#uiPreview' : isView ? '#uiViewWrapper' : '#uiListHeader');
  265. var $infoPreview = isView ? $('#uiInfoViewPreview') : null;
  266. var $basicPreview = isView ? $('#uiBasicViewPreview') : null;
  267. var $listPreview = canSetWidth ? $('#uiListPreviewItems') : null;
  268. var $filedEditForm = $('#filedEditForm');
  269. var remoteOptions = window.remoteOptions = fieldDefaultOptions || {};
  270. var hasActionsInList;
  271. var activedField;
  272. /* Format options to standart, like [{text: 'Today', value: 'today'}, ...] */
  273. var formatOptions = function(options)
  274. {
  275. if(typeof options === 'string')
  276. {
  277. options = options.split(',');
  278. }
  279. if($.isArray(options) || $.isPlainObject(options))
  280. {
  281. var optionsList = [];
  282. $.each(options, function(value, text)
  283. {
  284. optionsList[text.length ? 'push' : 'unshift']({value: value, text: text});
  285. });
  286. options = optionsList;
  287. }
  288. return options;
  289. };
  290. /* Get keys of a object */
  291. var getObjectKeys = function(obj, filter)
  292. {
  293. var keys = [];
  294. $.each(obj, function(key)
  295. {
  296. if(filter && !filter(key)) return;
  297. keys.push(key);
  298. });
  299. return keys;
  300. };
  301. /* Get field index in list by id or field code */
  302. var getFieldIndex = function(id)
  303. {
  304. id = String(id);
  305. return fields.findIndex(function(field)
  306. {
  307. return String(field.id) === id || String(field.field) === id;
  308. });
  309. };
  310. /* Get field object by id or field code */
  311. var getField = function(id)
  312. {
  313. var index = getFieldIndex(id);
  314. return index > -1 ? fields[index] : null;
  315. };
  316. /* Check whether the field is buildin */
  317. var isBuildInField = function(field)
  318. {
  319. return field.buildin === '1' || defaultFields[field.field];
  320. };
  321. /* Get field select control type, return 'multi', 'single' or false */
  322. var getFieldSelectType = function(field)
  323. {
  324. if(isBuildInField(field) && field.field !== 'status') return false;
  325. if(field.control === 'multi-select' || field.control === 'checkbox') return field.options !== 'prevModule' ? 'multi' : 'text';
  326. else if(field.control === 'select' || field.control === 'radio') return field.options !== 'prevModule' ? 'single' : 'text';
  327. return false;
  328. };
  329. /* Create preview control element for form mode */
  330. var createFieldControl = function(field, onlyHtml)
  331. {
  332. var controlType = field.control;
  333. var html = '';
  334. switch(controlType)
  335. {
  336. case 'label':
  337. html = '<p class="form-control-static"></p>';
  338. break;
  339. case 'textarea':
  340. case 'richtext':
  341. html = '<textarea class="form-control" rows="' + (isBatchForm ? 1 : 3) + '" style="resize:' + (isBatchForm ? 'none' : 'vertical') + '"></textarea>';
  342. break;
  343. case 'select':
  344. html = '<select class="form-control"></select>';
  345. break;
  346. case 'multi-select':
  347. html = '<select class="form-control" multiple></select>';
  348. break;
  349. case 'checkbox':
  350. html = '<div class="checkbox-list"></div>';
  351. break;
  352. case 'radio':
  353. html = '<div class="radio-list"></div>';
  354. break;
  355. case 'file':
  356. html = '<div class="file-input-list"><div class="file-input-empty"><button type="button" class="btn btn-link file-input-btn"><i class="icon icon-plus"></i> ' + window.langAddFile + '</button></div></div>';
  357. break;
  358. default:
  359. html = '<input type="text" class="form-control">';
  360. }
  361. return onlyHtml ? html : $(html);
  362. };
  363. /* Update field preview control for form mode */
  364. var updateFieldControl = function($wrapper, field)
  365. {
  366. if(field.field === 'file') return;
  367. var controlType = field.control;
  368. var readonly = field.readonly;
  369. var defaultValue = field.defaultValue;
  370. switch(controlType)
  371. {
  372. case 'label':
  373. $wrapper.find('.form-control-static').text(defaultValue);
  374. break;
  375. case 'textarea':
  376. case 'richtext':
  377. $wrapper.find('textarea').val(defaultValue);
  378. break;
  379. case 'select':
  380. case 'multi-select':
  381. var $select = $wrapper.find('select');
  382. $select.empty();
  383. defaultValue = ',' + defaultValue + ',';
  384. if(typeof field.options === 'object')
  385. {
  386. $.each(field.options, function(_, option)
  387. {
  388. var $option = $('<option></option>').attr('value', option.value).text(option.text);
  389. if(defaultValue.indexOf(',' + option.value + ',') != -1) $option = $option.prop('selected', 'selected');
  390. $select.append($option);
  391. });
  392. }
  393. else
  394. {
  395. if(field.optionsData)
  396. {
  397. $.each(field.optionsData, function(value, text)
  398. {
  399. var $option = $('<option></option>').attr('value', value).text(text);
  400. if(defaultValue.indexOf(',' + value + ',') != -1) $option = $option.prop('selected', 'selected');
  401. $select.append($option);
  402. });
  403. }
  404. }
  405. if(!$select.data('chosen')) $select.chosen();
  406. else $select.trigger('chosen:updated');
  407. break;
  408. case 'checkbox':
  409. case 'radio':
  410. var $list = $wrapper.find('.' + controlType + '-list');
  411. $list.empty();
  412. var name = 'field-' + controlType + '-' + field.id;
  413. var options = typeof field.options === 'object' ? field.options : [{value: '__example', text: ''}];
  414. $.each(options, function(_, option)
  415. {
  416. var $label = $('<label class="'+ controlType + '-inline"></label>');
  417. var $option = $('<input type="' + controlType + '">').attr({name: name, value: option.value});
  418. $label.append($option).append($('<span></span>').text(option.text));
  419. if(option.value === '__example') $label.find('span').addClass('example-text-holder');
  420. $list.append($label);
  421. });
  422. break;
  423. case 'date':
  424. var $input = $wrapper.find('.form-control');
  425. if(defaultValue == 'currentTime') defaultValue = window.currentDate;
  426. $input.val(defaultValue).attr('readonly', readonly === '1' ? 'readonly' : null);
  427. if(!$input.data('datetimepicker')) $input.datepicker();
  428. break;
  429. case 'datetime':
  430. var $input = $wrapper.find('.form-control');
  431. if(defaultValue == 'currentTime') defaultValue = window.currentTime;
  432. $input.val(defaultValue).attr('readonly', readonly === '1' ? 'readonly' : null);
  433. if(!$input.data('datetimepicker')) $input.datetimepicker();
  434. break;
  435. default:
  436. $wrapper.find('.form-control').val(defaultValue);
  437. }
  438. };
  439. /* Update field detail view item for view mode */
  440. var updateFieldView = function($wrapper, field)
  441. {
  442. var $label = $wrapper.find('.field-label');
  443. $label.text(field.name);
  444. if(field.position !== $wrapper.closest('.view-dropbox').data('position'))
  445. {
  446. var $viewBox = field.position === 'basic' ? $basicPreview : $infoPreview;
  447. var inserted = false;
  448. $viewBox.find('.field-view-item').each(function()
  449. {
  450. var $field = $(this);
  451. if(field.order >= $field.data('order'))
  452. {
  453. $field.after($wrapper);
  454. inserted = true;
  455. return false;
  456. }
  457. });
  458. if(!inserted) $viewBox.append($wrapper);
  459. }
  460. };
  461. /* Update field preview control for list column */
  462. var updateFieldColumn = function($wrapper, field)
  463. {
  464. var $label = $wrapper.find('.field-col-item-label');
  465. $label.text(field.name);
  466. $wrapper.css(
  467. {
  468. width: field.width,
  469. textAlign: field.position
  470. });
  471. };
  472. var createListPreviewItem = function(field)
  473. {
  474. return isBatchForm ? '<div class="field-col-preview-form">' + (field ? createFieldControl(field, true) : '<input type="text" class="form-control" />') + '</div>' : ('<div class="field-col-preview-view"><div class="example-text-holder" data-size="' + (Math.floor(Math.random() * 8) + 2) + '"></div></div>');;
  475. };
  476. /* Update list preview */
  477. var updateListPreview = function()
  478. {
  479. if(!canSetWidth) return;
  480. var $columns = $listPreview.children('.field-col-preview').addClass('expired');
  481. var totalWidth = 8;
  482. $preview.find('.field-col-item').each(function()
  483. {
  484. var $colHeader = $(this);
  485. var colId = $colHeader.data('id');
  486. var colWidth = $colHeader.outerWidth();
  487. var $col = $columns.filter('[data-id="' + colId + '"]');
  488. var field = isBatchForm ? getField(colId) : null;
  489. if(!$col.length)
  490. {
  491. $col = $([
  492. '<div class="field-col-preview">',
  493. createListPreviewItem(field), createListPreviewItem(field), createListPreviewItem(field), createListPreviewItem(field), createListPreviewItem(field),
  494. '</div>'
  495. ].join('')).attr('data-id', colId);
  496. }
  497. $col.css('width', colWidth).removeClass('expired').appendTo($listPreview);
  498. if(isBatchForm) updateFieldControl($col.find('.field-col-preview-form'), field);
  499. totalWidth += colWidth;
  500. });
  501. $listPreview.css('width', totalWidth);
  502. $columns.filter('.expired').remove();
  503. };
  504. /* Update field preview control */
  505. var updateField = function(field, relativeField, relativeAsNext)
  506. {
  507. var id = field.id;
  508. var $field = $('#field-' + id);
  509. if(field.show !== '1')
  510. {
  511. if($field.length) $field.remove();
  512. return;
  513. }
  514. if(!$field.length)
  515. {
  516. var fieldHtmlLines;
  517. if(isForm)
  518. {
  519. let disabled = (field.field != '' && field.field == flowBelong && action == 'create') ? 'disabled' : '';
  520. fieldHtmlLines =
  521. [
  522. '<div data-field="' + (field.field || '') + '" data-id="' + id + '" id="field-' + id + '" class="field-item">',
  523. '<div class="field-item-row">',
  524. '<div class="field-label"></div>',
  525. '<div class="field-control"></div>',
  526. '<div class="field-buttons">',
  527. '<button type="button" class="btn btn-link field-sort-handler" data-type="move"><i class="icon icon-move"></i></button>',
  528. '<button type="button" class="btn btn-link field-delete-btn" ' + disabled + ' data-type="delete"><i class="icon icon-trash"></i></button>',
  529. '<button type="button" class="btn btn-link field-copy-btn" data-type="copy"><i class="icon icon-copy"></i></button><button type="button" class="btn btn-link field-error-btn" data-toggle="tooltip"><i class="icon icon-alert text-danger"></i></button>',
  530. '</div>',
  531. '</div>',
  532. '</div>'
  533. ];
  534. }
  535. else if (isView)
  536. {
  537. fieldHtmlLines =
  538. [
  539. '<div data-field="' + (field.field || '') + '" data-id="' + id + '" id="field-' + id + '" class="field-view-item">',
  540. '<div class="field-item-row">',
  541. '<div class="field-label"></div>',
  542. '<div class="field-value"><div class="example-text-holder"></div></div>',
  543. '<div class="field-buttons">',
  544. '<button type="button" class="btn btn-link field-sort-handler" data-type="move"><i class="icon icon-move"></i></button>',
  545. '<button type="button" class="btn btn-link field-delete-btn" data-type="delete"><i class="icon icon-trash"></i></button>',
  546. '<button type="button" class="btn btn-link field-copy-btn" data-type="copy"><i class="icon icon-copy"></i></button><button type="button" class="btn btn-link field-error-btn" data-toggle="tooltip"><i class="icon icon-alert text-danger"></i></button>',
  547. '</div>',
  548. '</div>',
  549. '</div>'
  550. ];
  551. }
  552. else
  553. {
  554. fieldHtmlLines =
  555. [
  556. '<div data-field="' + field.field + '" data-id="' + id + '" id="field-' + id + '" class="field-col-item">',
  557. '<i class="icon icon-move field-sort-handler"></i>',
  558. '<span class="field-col-item-label field-sort-handler"></span>',
  559. '<i class="icon icon-sort text-primary"></i>',
  560. '<button type="button" class="btn btn-link field-delete-btn" data-type="delete"><i class="icon icon-trash"></i></button>',
  561. '<button type="button" class="btn btn-link field-error-btn" data-toggle="tooltip"><i class="icon icon-alert text-danger"></i></button>',
  562. '</div>'
  563. ];
  564. }
  565. $field = $(fieldHtmlLines.join(''));
  566. if(field.field === 'actions' && !isForm)
  567. {
  568. $field.find('.field-col-item-label').removeClass('field-sort-handler');
  569. }
  570. if(relativeField)
  571. {
  572. var $relativeField = $('#field-' + relativeField.id);
  573. if($relativeField.length) $relativeField[relativeAsNext ? 'before' : 'after']($field);
  574. }
  575. else if(isView)
  576. {
  577. $field.find('.example-text-holder').attr('data-size', Math.floor(Math.random() * 10) + 1);
  578. $field.appendTo(field.position === 'basic' ? $basicPreview : $infoPreview);
  579. }
  580. else $field.appendTo($preview);
  581. $field.find('.field-error-btn').tooltip(
  582. {
  583. placement: 'top',
  584. container: 'body',
  585. tipClass: 'tooltip-danger',
  586. html: true,
  587. title: function()
  588. {
  589. if(field.errors && field.errors.$length)
  590. {
  591. var messages = [];
  592. $.each(field.errors, function(key, message)
  593. {
  594. if(key[0] === '$') return;
  595. messages.push(message);
  596. });
  597. return messages.join('<br>');
  598. }
  599. }
  600. });
  601. }
  602. if(isForm)
  603. {
  604. var $control = $field.find('.field-control');
  605. var controlType = field.control;
  606. if($control.data('type') !== controlType) {
  607. $control.data('type', controlType).empty().append(createFieldControl(field));
  608. }
  609. updateFieldControl($control, field);
  610. }
  611. else if(isView) updateFieldView($field, field);
  612. else updateFieldColumn($field, field);
  613. $field.find('.field-label').text(field.name);
  614. $field.attr('data-order', field.order)
  615. .attr('data-field', field.field)
  616. .data('order', field.order)
  617. .toggleClass('has-error', !!(field.errors && field.errors.$length))
  618. .toggleClass('active', !!(activedField && activedField.id === id))
  619. .toggleClass('field-required', (',' + field.layoutRules + ',').indexOf(',' + window.notEmptyRule + ',') > -1);
  620. updateListPreview(field);
  621. };
  622. /* Update showed field list at left of page */
  623. var updateShowedFields = function()
  624. {
  625. var showedFields = [];
  626. for(var i = 0; i < fields.length; ++i)
  627. {
  628. var field = fields[i];
  629. if(field.show === '0' && !field.unsaved) showedFields.push(field);
  630. }
  631. showedFields.sort(function(field1, field2)
  632. {
  633. return ('' + field1.name).localeCompare(field2.name);
  634. });
  635. var $editorShowedFields = $('#editorShowedFields');
  636. var $fields = $editorShowedFields.find('.btn').addClass('can-remove');
  637. for(var i = 0; i < showedFields.length; ++i)
  638. {
  639. var field = showedFields[i];
  640. var $field = $fields.filter('[data-field="' + field.field + '"]');
  641. if($field.length) $field.removeClass('can-remove').appendTo($editorShowedFields);
  642. else $('<button type="button" class="btn btn-field-control" data-field="' + field.field + '">' + field.name + '</button>').appendTo($editorShowedFields);
  643. }
  644. $fields.filter('.can-remove').remove();
  645. };
  646. /* Sort fields by order property */
  647. var sortFields = function()
  648. {
  649. fields.sort(function(x, y){return x.order * 1 - y.order * 1;});
  650. var order = 0;
  651. $.each(fields, function(index, field)
  652. {
  653. field.order = order;
  654. order = order + 2;
  655. });
  656. };
  657. /* Update all fields */
  658. var updateFields = function(sort)
  659. {
  660. hasActionsInList = false;
  661. if(sort) sortFields();
  662. for(var i = 0; i < fields.length; ++i)
  663. {
  664. var field = fields[i];
  665. if(!field.id) field.id = '_' + $.zui.uuid();
  666. if(sort) field.order = i * 100 + 1;
  667. if($.isPlainObject(field.options))
  668. {
  669. var options = [];
  670. $.each(field.options, function(value, text)
  671. {
  672. options[text.length ? 'push' : 'unshift']({value: value, text: text});
  673. });
  674. field.options = options;
  675. field._options = options;
  676. }
  677. else if($.isArray(field.options))
  678. {
  679. var options = [];
  680. $.each(field.options, function(index, option)
  681. {
  682. if(typeof option === 'string') option = {value: String(index), text: option};
  683. else option = $.extend({value: '', text: ''}, option);
  684. options[option.text.length ? 'push' : 'unshift'](option);
  685. });
  686. field.options = options;
  687. }
  688. if(field.field === 'file') field.control = 'file';
  689. if(typeof field.show === 'number') field.show = String(field.show);
  690. if(field.type === 'decimal')
  691. {
  692. var digits = field.length.split(',');
  693. field.integerDigits = Number.parseInt(digits[0], 10);
  694. field.decimalDigits = Number.parseInt(digits[1], 10);
  695. if(Number.isNaN(field.integerDigits)) field.integerDigits = fieldDefault.integerDigits;
  696. if(Number.isNaN(field.decimalDigits)) field.decimalDigits = fieldDefault.decimalDigits;
  697. }
  698. if(field.name === null) field.name = '';
  699. if(!field.length) field.length = fieldDefault.fieldLength[field.field];
  700. validateField(field);
  701. updateField(field);
  702. if(!isForm && field.field === 'actions') hasActionsInList = true;
  703. }
  704. };
  705. /* Validate field properties */
  706. var validateField = function(field, includeHiddenField)
  707. {
  708. if(!field)
  709. {
  710. var errorsCount;
  711. for(var i = 0; i < fields.length; ++i)
  712. {
  713. if(validateField(fields[i], includeHiddenField)) {
  714. errorsCount++;
  715. }
  716. }
  717. return errorsCount;
  718. }
  719. if(typeof field === 'string')
  720. {
  721. field = getField(field);
  722. if(!field) return;
  723. }
  724. if(!includeHiddenField && field.show !== '1') return;
  725. var errors = field.errors || {};
  726. if(!errors.name)
  727. {
  728. if(typeof field.name !== 'string' || !field.name.length) errors.name = window.validateMessages.nameRequired;
  729. else
  730. {
  731. for(var i = 0; i < fields.length; ++i)
  732. {
  733. var thisField = fields[i];
  734. if(thisField.id !== field.id && thisField.name === field.name)
  735. {
  736. errors.name = window.validateMessages.nameDuplicated;
  737. break;
  738. }
  739. }
  740. }
  741. }
  742. var buildin = field.buildin === '1' || defaultFields[field.field];
  743. var isTypeLengthRequired = field.type === 'char' || field.type === 'varchar';
  744. var isDecimalType = field.type === 'decimal';
  745. var isCustomOptions = typeof field.options === 'object';
  746. if(!buildin)
  747. {
  748. if(!errors.field)
  749. {
  750. if(typeof field.field !== 'string' || !field.field.length) errors.field = window.validateMessages.fieldRequired;
  751. else if(!/^[a-zA-Z]+$/.test(field.field)) errors.field = window.validateMessages.fieldInvalid;
  752. else
  753. {
  754. for(var i = 0; i < fields.length; ++i)
  755. {
  756. var thisField = fields[i];
  757. if(thisField.id !== field.id && thisField.field === field.field)
  758. {
  759. errors.field = window.validateMessages.fieldDuplicated.replace('%s', thisField.name);
  760. break;
  761. }
  762. }
  763. }
  764. }
  765. if(!errors.length && isTypeLengthRequired)
  766. {
  767. if(typeof field.length !== 'string' || !field.length.length) errors.length = window.validateMessages.lengthRequired;
  768. }
  769. if(!errors.defaultValue && typeof field.defaultValue === 'string' && field.defaultValue.length)
  770. {
  771. var fieldSelectType = getFieldSelectType(field);
  772. if(fieldSelectType)
  773. {
  774. var isCustomOption = typeof field.options === 'object';
  775. var optionsKey = field.module + '_' + field.field + '_' + field.options;
  776. var fieldOptions = isCustomOption ? field.options : remoteOptions[optionsKey];
  777. if(fieldOptions)
  778. {
  779. var isMultiSelect = fieldSelectType === 'multi';
  780. var defaultValues = isMultiSelect ? field.defaultValue.split(',') : [field.defaultValue];
  781. var defaultValueError;
  782. for(var i = 0; i < defaultValues.length; ++i)
  783. {
  784. var defaultValue = defaultValues[i];
  785. var optionIndex = fieldOptions.findIndex(function(option)
  786. {
  787. return option.value == defaultValue;
  788. });
  789. if(optionIndex < 0)
  790. {
  791. if(fieldOptions.findIndex(function(option){return option.text === defaultValue;}) > 0) defaultValueError = window.validateMessages.defaultNotOptionKey.replace('%s', defaultValue);
  792. else defaultValueError = window.validateMessages.defaultNotInOptions.replace('%s', defaultValue);
  793. break;
  794. }
  795. }
  796. if(defaultValueError) errors.defaultValue = defaultValueError;
  797. }
  798. else if(!isCustomOption)
  799. {
  800. delete errors.defaultValue;
  801. }
  802. }
  803. }
  804. }
  805. if(!errors.width && canSetWidth && field.width && field.width !== 'auto' && !/^\d+$/.test(field.width))
  806. {
  807. errors.width = window.validateMessages.widthInvalid;
  808. }
  809. errors.$length = getObjectKeys(errors, function(key)
  810. {
  811. return key[0] !== '$';
  812. }).length;
  813. if(activedField && activedField.id === field.id)
  814. {
  815. var $fieldNameMessage = $('#fieldNameMessage');
  816. if(errors.name) $fieldNameMessage.html(errors.name).show();
  817. else $fieldNameMessage.hide();
  818. $('#fieldEditName').toggleClass('has-error', !!errors.name);
  819. if(!buildin)
  820. {
  821. var $fieldFieldMessage = $('#fieldFieldMessage');
  822. if(errors.field) $fieldFieldMessage.html(errors.field).show();
  823. else $fieldFieldMessage.hide();
  824. $('#fieldEditField').toggleClass('has-error', !!errors.field);
  825. var $fieldLengthMessage = $('#fieldLengthMessage');
  826. var lengthError = errors.length || errors.integerDigits || errors.decimalDigits;
  827. if(lengthError) $fieldLengthMessage.html(lengthError).show();
  828. else $fieldLengthMessage.hide();
  829. $('#fieldLength').toggleClass('has-error', !!errors.length);
  830. $('#integerDigits').toggleClass('has-error', !!errors.integerDigits);
  831. $('#decimalDigits').toggleClass('has-error', !!errors.decimalDigits);
  832. $('#fieldEditType').toggleClass('hide-type-length', !isTypeLengthRequired);
  833. $('#decimalDigitsGroup').toggleClass('hidden', !isDecimalType);
  834. var $fieldDefaultValueMessage = $('#fieldDefaultValueMessage');
  835. if(errors.default) $fieldDefaultValueMessage.html(errors.default).show();
  836. else $fieldDefaultValueMessage.hide();
  837. $('#fieldEditDefaultValue').toggleClass('has-error', !!errors.default);
  838. }
  839. if(canSetWidth)
  840. {
  841. var $fieldWidthMessage = $('#fieldWidthMessage');
  842. if(errors.width) $fieldWidthMessage.html(errors.width).show();
  843. else $fieldWidthMessage.hide();
  844. $('#widthEditControl').toggleClass('has-error', !!errors.width);
  845. }
  846. if(isCustomOptions)
  847. {
  848. var $fieldOptionsMessage = $('#fieldOptionsMessage');
  849. if(errors.options) $fieldOptionsMessage.html(errors.options).show();
  850. else $fieldOptionsMessage.hide();
  851. }
  852. }
  853. field.errors = errors;
  854. updateField(field);
  855. return errors.$length;
  856. };
  857. /* Public method to update field errors, usually used for form submit result */
  858. window.updateFieldsErrors = function(errors)
  859. {
  860. var firstField;
  861. var errorsCount = 0;
  862. $.each(errors, function(fieldId, errorsMap)
  863. {
  864. var field = getField(fieldId);
  865. if(field)
  866. {
  867. field.errors = errorsMap;
  868. if(field.errors) field.errors.$length = getObjectKeys(field.errors).length;
  869. updateField(field);
  870. if(!firstField) firstField = field;
  871. errorsCount++;
  872. }
  873. });
  874. if(firstField)
  875. {
  876. activeField(firstField.id, true);
  877. $('#editor').addClass('highlight-errors');
  878. setTimeout(function()
  879. {
  880. $('#editor').removeClass('highlight-errors');
  881. }, 5000);
  882. return new $.zui.Messager(window.validateMessages.failSummary.replace('%s', errorsCount),
  883. {
  884. type: 'danger',
  885. icon: 'bell',
  886. placement: 'center',
  887. }).show();
  888. }
  889. };
  890. /* Edit field properties and update editor at right of page */
  891. var editField = function(field)
  892. {
  893. if(typeof field === 'string') field = getField(field);
  894. var $filedEditTip = $('#filedEditTip');
  895. if(!field)
  896. {
  897. $filedEditTip.show();
  898. $filedEditForm.hide();
  899. return;
  900. }
  901. $filedEditTip.hide();
  902. var isFileOrActions = field.field === 'file' || field.field === 'actions' || field.buildin == '1';
  903. var isQuote = field.role == 'quote';
  904. $filedEditForm.find('#fieldName').val(field.name).attr('readonly', isFileOrActions || isQuote);
  905. var errors = field.errors || {};
  906. var buildin = isBuildInField(field);
  907. var $fieldNameMessage = $('#fieldNameMessage');
  908. if(errors.name) $fieldNameMessage.html(errors.name).show();
  909. else $fieldNameMessage.hide();
  910. $('#fieldEditName').toggleClass('has-error', !!errors.name).toggleClass('required', !isFileOrActions);
  911. $('#fieldControl').val(field.control).attr('disabled', isQuote).change();
  912. if(!buildin && !isFileOrActions)
  913. {
  914. $('#fieldField').val(field.field);
  915. $filedEditForm.find('#fieldField').attr('disabled', typeof field.field != 'undefined' && field.field != '');
  916. var $fieldFieldMessage = $('#fieldFieldMessage');
  917. if(errors.field) $fieldFieldMessage.html(errors.field).show();
  918. else $fieldFieldMessage.hide();
  919. $('#fieldEditField').toggleClass('has-error', !!errors.field);
  920. $('#fieldType').val(field.type).attr('disabled', isQuote);
  921. $('#fieldLength').val(field.length).attr('disabled', isQuote);
  922. var defaultValue = field.defaultValue;
  923. if(field.control == 'datetime' && defaultValue == 'currentTime') defaultValue = window.currentTime;
  924. if(field.control == 'date' && defaultValue == 'currentTime') defaultValue = window.currentDate;
  925. $('#fieldDefaultValue').val(defaultValue);
  926. $('#fieldRules').val((typeof field.layoutRules === 'string' && field.layoutRules.length) ? field.layoutRules.split(',') : field.layoutRules).trigger('chosen:updated');
  927. $('#fieldSql').val(field.sql);
  928. $('#integerDigits').val(field.integerDigits);
  929. $('#decimalDigits').val(field.decimalDigits);
  930. var $fieldLengthMessage = $('#fieldLengthMessage');
  931. if(errors.length) $fieldLengthMessage.html(errors.length).show();
  932. else $fieldLengthMessage.hide();
  933. $('#fieldLength').toggleClass('has-error', !!errors.length);
  934. var $fieldDefaultValueMessage = $('#fieldDefaultValueMessage');
  935. if(errors.default) $fieldDefaultValueMessage.html(errors.default).show();
  936. else $fieldDefaultValueMessage.hide();
  937. $('#fieldEditDefaultValue').toggleClass('has-error', !!errors.default);
  938. }
  939. if(canSetWidth)
  940. {
  941. var $fieldWidthMessage = $('#fieldWidthMessage');
  942. if(errors.width) $fieldWidthMessage.html(errors.width).show();
  943. else $fieldWidthMessage.hide();
  944. $('#widthEditControl').toggleClass('has-error', !!errors.width);
  945. }
  946. if(!buildin || field.field === 'status')
  947. {
  948. var isCustomOptions = typeof field.options === 'object';
  949. $('#fieldOptionType').val(isCustomOptions ? 'custom' : field.options).attr('disabled', isQuote).change();
  950. }
  951. if(canSetWidth) $('#fieldWidth').val(field.width);
  952. if(canSetPosition) $('#fieldPosition').val(field.position);
  953. $('#fieldIsValue').prop('checked', field.isValue === '1');
  954. $filedEditForm.toggleClass('is-status', field.field === 'status').toggleClass('is-file', isFileOrActions).toggleClass('is-buildin', !!buildin).show();
  955. };
  956. /* Unactive field in preview */
  957. var unactiveField = function()
  958. {
  959. if(activedField)
  960. {
  961. var oldActiveField = activedField;
  962. activedField = null;
  963. updateField(oldActiveField);
  964. editField();
  965. }
  966. };
  967. /* Active field in preview */
  968. var activeField = function(id, force)
  969. {
  970. var oldActiveField = activedField;
  971. var field = typeof id === 'object' ? id : getField(id);
  972. if(field && field.show === '1' && (force || !oldActiveField || oldActiveField.id !== field.id))
  973. {
  974. activedField = field;
  975. if(oldActiveField) updateField(oldActiveField);
  976. updateField(field);
  977. editField(field);
  978. var $field = $('#field-' + field.id);
  979. if($field.length && $field[0].scrollIntoViewIfNeeded)
  980. {
  981. $field[0].scrollIntoViewIfNeeded();
  982. }
  983. }
  984. };
  985. /* Delete field in preview */
  986. var deleteField = function(id)
  987. {
  988. var field = getField(id);
  989. if(field)
  990. {
  991. field.show = '0';
  992. field.changed = true;
  993. updateField(field);
  994. updateShowedFields();
  995. if(activedField && activedField.id === field.id) unactiveField();
  996. updateListPreview();
  997. }
  998. };
  999. /* Clone field in preview */
  1000. var cloneField = function(id)
  1001. {
  1002. var fieldIndex = getFieldIndex(id);
  1003. if(fieldIndex > -1)
  1004. {
  1005. var field = fields[fieldIndex];
  1006. var copyField = $.extend({}, field, {id: '_' + $.zui.uuid(), order: field.order + 1, field: '', name: '', changed: true, unsaved: true, buildin: '0', role: 'custom', errors: {$length: 0}});
  1007. fields.splice(fieldIndex + 1, 0, copyField);
  1008. updateField(copyField, field);
  1009. validateField(copyField);
  1010. activeField(copyField.id);
  1011. }
  1012. };
  1013. /* Update fields preview controls and showed list on left */
  1014. updateFields(true);
  1015. updateShowedFields();
  1016. /* Bind events on preview element */
  1017. $preview.on('click', '.field-buttons>.btn,.field-delete-btn', function(e)
  1018. {
  1019. var $this = $(this);
  1020. var btnType = $this.data('type');
  1021. var id = $this.closest('.field-item,.field-col-item,.field-view-item').data('id');
  1022. if(btnType === 'delete') deleteField(id);
  1023. else if(btnType === 'copy') cloneField(id);
  1024. e.stopPropagation();
  1025. }).on('click', '.field-item,.field-col-item,.field-view-item', function()
  1026. {
  1027. activeField($(this).data('id'));
  1028. });
  1029. /* Make preview control sortable */
  1030. $preview.sortable(
  1031. {
  1032. selector: '.field-item,.field-col-item,.field-view-item',
  1033. trigger: '.field-sort-handler',
  1034. containerSelector: isView ? '#uiInfoViewPreview, #uiBasicViewPreview' : false,
  1035. start: function(){$('#editorPreview').addClass('editor-sorting');},
  1036. stopPropagation: false,
  1037. before: function(e)
  1038. {
  1039. /* Fix backyard task #11550 https://back.zcorp.cc/pms/task-view-11550.html */
  1040. e.event.preventDefault = function(){};
  1041. },
  1042. finish: function(e)
  1043. {
  1044. $('#editorPreview').removeClass('editor-sorting');
  1045. if(e.changed)
  1046. {
  1047. for(var i = 0; i < e.list.length; ++i)
  1048. {
  1049. var $item = e.list[i].item;
  1050. var field = getField($item.data('id'));
  1051. field.order = i * 100 + 1;
  1052. field.changed = true;
  1053. }
  1054. sortFields();
  1055. if(isView)
  1056. {
  1057. var field = getField(e.element.data('id'));
  1058. var newPosition = e.element.closest('.view-dropbox').data('position');
  1059. if(newPosition !== field.position)
  1060. {
  1061. field.position = newPosition;
  1062. field.changed = true;
  1063. updateField(field);
  1064. }
  1065. }
  1066. updateListPreview();
  1067. }
  1068. }
  1069. });
  1070. /* Update field type tip */
  1071. var updateFieldTypeTip = function(field)
  1072. {
  1073. var tip = '';
  1074. switch(field.type)
  1075. {
  1076. case 'tinyint':
  1077. case 'smallint':
  1078. case 'mediumint':
  1079. case 'int':
  1080. var max = fieldMax[field.type];
  1081. var min = fieldMin[field.type];
  1082. tip = window.tips.number.replace(/MAX/, max).replace(/MIN/, min);
  1083. $('#fieldDefaultValue').attr({min: min, max: max});
  1084. break;
  1085. case 'decimal':
  1086. var integerDigits = field.integerDigits;
  1087. var decimalDigits = field.decimalDigits;
  1088. integerDigits = integerDigits ? parseInt(integerDigits) : 0;
  1089. integerDigits = integerDigits > fieldMax.integerDigits ? fieldMax.integerDigits : integerDigits;
  1090. integerDigits = integerDigits < fieldMin.integerDigits ? fieldDefault.integerDigits : integerDigits;
  1091. integerDigits = parseInt(integerDigits);
  1092. decimalDigits = decimalDigits ? parseInt(decimalDigits) : 0;
  1093. decimalDigits = decimalDigits > fieldMax.decimalDigits ? fieldMax.decimalDigits : decimalDigits;
  1094. decimalDigits = decimalDigits < fieldMin.integerDigits ? fieldDefault.decimalDigits : decimalDigits;
  1095. decimalDigits = parseInt(decimalDigits);
  1096. var max = '.'.padStart(integerDigits + 1, 9).padEnd(integerDigits + decimalDigits + 1, 9);
  1097. var min = '-' + max;
  1098. tip = window.tips.number.replace(/MAX/, max).replace(/MIN/, min);
  1099. $('#fieldDefaultValue').attr({min: min, max: max});
  1100. break;
  1101. case 'char':
  1102. var length = field.length;
  1103. length = length ? parseInt(length) : 0;
  1104. length = length > fieldMax.charLength ? fieldMax.charLength : length;
  1105. length = length < fieldMin.charLength ? fieldDefault.charLength : length;
  1106. tip = window.tips.string.replace(/LENGTH/, length);
  1107. break;
  1108. case 'varchar':
  1109. var length = field.length;
  1110. length = length ? parseInt(length) : 0;
  1111. length = length > fieldMax.varcharLength ? fieldMax.varcharLength : length;
  1112. length = length < fieldMin.varcharLength ? fieldDefault.varcharLength : length;
  1113. tip = window.tips.string.replace(/LENGTH/, length);
  1114. break;
  1115. }
  1116. $('#fieldTypeTip').toggleClass('hidden', !tip).html(tip);
  1117. };
  1118. /* Bind events on properties editor */
  1119. var $fieldEditOptionsList = $('#fieldEditOptionsList');
  1120. $filedEditForm.on('change', function(e)
  1121. {
  1122. var field = activedField;
  1123. if(!field) return;
  1124. var $control = $(e.target);
  1125. if($control.closest('.field-option-item').length)
  1126. {
  1127. $control = $('#fieldControl');
  1128. }
  1129. var controlName = $control.attr('name');
  1130. if(!controlName) return;
  1131. var oldValue = field[controlName];
  1132. var controlValue = $control.val();
  1133. if($control.is(':checkbox')) controlValue = $control.prop('checked') ? controlValue : '';
  1134. var optionsKey = field.module + '_' + field.field + '_' + field.options;
  1135. if(controlName === 'length' || controlName === 'integerDigits' || controlName === 'decimalDigits')
  1136. {
  1137. var numValue = Number.parseInt(controlValue, 10);
  1138. var valueName = controlName === 'length' ? (field.type + 'Length') : controlName;
  1139. if(Number.isNaN(numValue)) controlValue = Number.parseInt(fieldDefault[valueName], 10);
  1140. else
  1141. {
  1142. var min = Number.parseInt(fieldMin[valueName], 10);
  1143. var max = Number.parseInt(fieldMax[valueName], 10);
  1144. var fixValue = Math.floor(Math.max(min, Math.min(max, numValue))) + '';
  1145. if(fixValue !== controlValue)
  1146. {
  1147. $control.val(fixValue);
  1148. controlValue = fixValue + '';
  1149. }
  1150. }
  1151. }
  1152. else if(controlName === 'defaultValue')
  1153. {
  1154. if(field.control === 'integer' || field.control === 'decimal')
  1155. {
  1156. var $fieldDefaultValue = $('#fieldDefaultValue');
  1157. controlValue = Math.min(Math.max(Number.parseInt(controlValue, 10), Number.parseInt($fieldDefaultValue.attr('min'), 10)), Number.parseInt($fieldDefaultValue.attr('max'), 10)) + '';
  1158. $fieldDefaultValue.val(controlValue);
  1159. }
  1160. if(controlValue)
  1161. {
  1162. var fieldOptions = remoteOptions[optionsKey];
  1163. if(fieldOptions)
  1164. {
  1165. var optionIndex = fieldOptions.findIndex(function(option)
  1166. {
  1167. return option.value == controlValue;
  1168. });
  1169. if(optionIndex < 0)
  1170. {
  1171. var controlText = $control.text();
  1172. fieldOptions.push({value: controlValue, text: controlText});
  1173. }
  1174. }
  1175. }
  1176. }
  1177. if(controlName === 'optionType')
  1178. {
  1179. oldValue = typeof field.options === 'object' ? 'custom' : String(field.options);
  1180. if(controlValue === 'custom')
  1181. {
  1182. field.options = field._options || [];
  1183. }
  1184. else
  1185. {
  1186. if(typeof field.options === 'object') field._options = field.options;
  1187. field.options = controlValue;
  1188. }
  1189. }
  1190. else field[controlName] = controlValue;
  1191. if(controlName === 'control' || controlName === 'optionType')
  1192. {
  1193. var optionSelectType = getFieldSelectType(field);
  1194. $filedEditForm.find('#fieldEditOptionType').toggleClass('hide', !optionSelectType);
  1195. var isCustomOptions = optionSelectType && typeof field.options === 'object';
  1196. $('#fieldEditOptions').toggleClass('hide', !isCustomOptions);
  1197. $('#fieldEditSql').toggleClass('hide', isCustomOptions || !optionSelectType || field.options !== 'sql');
  1198. var $optionsList = $fieldEditOptionsList.empty();
  1199. var $fieldDefaultValue = $('#fieldDefaultValue');
  1200. var isMultiSelect = optionSelectType === 'multi';
  1201. if(oldValue !== controlValue) field.defaultValue = '';
  1202. if($('#dateCustom').length && field.control !== 'datetime' && field.control !== 'date')
  1203. {
  1204. $('#dateCustom').after($fieldDefaultValue);
  1205. $('#dateCustom').remove();
  1206. }
  1207. if(optionSelectType && optionSelectType !== 'text')
  1208. {
  1209. var picker = $fieldDefaultValue.data('zui.picker');
  1210. if(picker) picker.destroy();
  1211. if(!$fieldDefaultValue.is('select'))
  1212. {
  1213. $fieldDefaultValue.replaceWith($('<select id="fieldDefaultValue" name="defaultValue" class="form-control"><option value=""></option></select>').attr('multiple', isMultiSelect ? 'multiple' : null));
  1214. $fieldDefaultValue = $('#fieldDefaultValue');
  1215. }
  1216. else if(isMultiSelect !== !!$fieldDefaultValue.attr('multiple'))
  1217. {
  1218. var chosen = $fieldDefaultValue.data('chosen');
  1219. if(chosen) chosen.destroy();
  1220. $fieldDefaultValue.attr('multiple', isMultiSelect ? 'multiple' : null);
  1221. }
  1222. $fieldDefaultValue.empty();
  1223. if(isCustomOptions)
  1224. {
  1225. var hasEmptyOption;
  1226. if(!field.options.length) field.options.push({value: '', text: ''});
  1227. $.each(field.options, function(_, option)
  1228. {
  1229. var $inputGroup = $('<div class="input-group field-option-item"></div>');
  1230. $inputGroup.append('<span class="input-group-addon">' + window.keyValueList.key + '</span>');
  1231. $inputGroup.append($('<input name="optionValue" class="form-control w-50px" placeholder="' + window.langOptionCode + '" />').val(option.value));
  1232. $inputGroup.append('<span class="input-group-addon">' + window.keyValueList.value + '</span>');
  1233. $inputGroup.append($('<input name="optionText" class="form-control" />').val(option.text));
  1234. $inputGroup.append('<span class="input-group-addon" style="padding: 4px 2px"><i class="icon icon-plus" data-type="add"></i><i class="icon icon-minus" data-type="delete"></i></span>');
  1235. $optionsList.append($inputGroup);
  1236. $fieldDefaultValue.append($('<option>').attr('value', option.value).text(option.text));
  1237. if(!option.value.length) hasEmptyOption = true;
  1238. });
  1239. if(!hasEmptyOption) $fieldDefaultValue.prepend('<option value=""></option>');
  1240. }
  1241. else
  1242. {
  1243. var options = remoteOptions[optionsKey];
  1244. var hasEmptyOption;
  1245. if(options)
  1246. {
  1247. $.each(options, function(_, option)
  1248. {
  1249. if(!option.value.length) hasEmptyOption = true;
  1250. $fieldDefaultValue.append($('<option>').attr('value', option.value).text(option.text));
  1251. });
  1252. }
  1253. if(!hasEmptyOption) $fieldDefaultValue.prepend('<option value=""></option>');
  1254. }
  1255. var fieldValue = (typeof field.defaultValue === 'string' && field.defaultValue.length) ? field.defaultValue.split(',') : field.defaultValue;
  1256. $fieldDefaultValue.val(fieldValue);
  1257. initDefault(field, isCustomOptions);
  1258. }
  1259. else
  1260. {
  1261. if(!$fieldDefaultValue.is('input'))
  1262. {
  1263. var chosen = $fieldDefaultValue.data('chosen');
  1264. var picker = $fieldDefaultValue.data('zui.picker');
  1265. if(chosen) chosen.destroy();
  1266. if(picker) picker.destroy();
  1267. $fieldDefaultValue.replaceWith('<input type="text" id="fieldDefaultValue" name="defaultValue" class="form-control" />');
  1268. $fieldDefaultValue = $('#fieldDefaultValue').val(field.defaultValue);
  1269. }
  1270. var dateType = (field.control === 'datetime' || field.control === 'date') ? field.control : false;
  1271. if($fieldDefaultValue.data('dateType') !== dateType)
  1272. {
  1273. $fieldDefaultValue.data('dateType', dateType);
  1274. $fieldDefaultValue.attr('autocomplete', 'off');
  1275. var datepicker = $fieldDefaultValue.data('datetimepicker');
  1276. if(datepicker) datepicker.remove();
  1277. $fieldDefaultValue.val(field.defaultValue);
  1278. if(dateType) $fieldDefaultValue[dateType + 'picker']();
  1279. }
  1280. if(dateType == 'date' || dateType == 'datetime')
  1281. {
  1282. window.dateType = dateType;
  1283. fieldDefaultValue = field.defaultValue;
  1284. if($('#dateCustom').length === 0)
  1285. {
  1286. checked = field.defaultValue != 'currentTime' ? 'checked' : '';
  1287. $fieldDefaultValue.before("<div class='input-group' id='dateCustom'><span class='input-group-addon'><div class='checkbox-primary'><input type='checkbox' id='custom' value='1' " + checked + "/><label>" + window.custom + "</label></div></span></div>");
  1288. $('#dateCustom').prepend($fieldDefaultValue);
  1289. $('#custom').change();
  1290. }
  1291. else
  1292. {
  1293. $('#custom').prop('checked', field.defaultValue != 'currentTime').change();
  1294. }
  1295. $fieldDefaultValue = $('#fieldDefaultValue');
  1296. $fieldDefaultValue.val(field.defaultValue);
  1297. }
  1298. if(!dateType)
  1299. {
  1300. var isNumber = field.control === 'integer' || field.control === 'decimal';
  1301. $fieldDefaultValue.attr({type: isNumber ? 'number' : 'text', min: null, max: null});
  1302. }
  1303. }
  1304. $fieldDefaultValue.attr('placeholder', isMultiSelect ? window.multiDefaultPlaceholder : '');
  1305. if(controlName === 'optionType' && optionSelectType && !isCustomOptions)
  1306. {
  1307. getDefaultsOfOption(field, function(options)
  1308. {
  1309. optionsKey = field.module + '_' + field.field + '_' + field.options;
  1310. activedField.optionsData = options;
  1311. remoteOptions[optionsKey] = formatOptions(options);
  1312. validateField(field);
  1313. $('#fieldControl').change();
  1314. });
  1315. }
  1316. else if(controlName === 'control')
  1317. {
  1318. $filedEditForm.attr('data-control', controlValue);
  1319. if(controlValue !== 'file')
  1320. {
  1321. var defaultProps = getDefaultFieldByControl(controlValue);
  1322. var $hiddenOptions = $('#fieldType option').show().not('.' + defaultProps.optionClass).hide();
  1323. if($hiddenOptions.filter('[value="' + field.type + '"]').length)
  1324. {
  1325. $('#fieldType').val(defaultProps.type).change();
  1326. }
  1327. }
  1328. }
  1329. }
  1330. else if(controlName === 'type')
  1331. {
  1332. var typeHasLength = field.type === 'char' || field.type === 'varchar';
  1333. $('#fieldEditType').toggleClass('hide-type-length', !typeHasLength);
  1334. if(typeHasLength)
  1335. {
  1336. var valueName = field.type + 'Length';
  1337. var min = Number.parseInt(fieldMin[valueName], 10);
  1338. var max = Number.parseInt(fieldMax[valueName], 10);
  1339. var numValue = Number.parseInt(field.length, 10);
  1340. var fixValue = Math.floor(Math.max(min, Math.min(max, numValue))) + '';
  1341. $('#fieldLength').attr({min: min, max: max}).val(fixValue);
  1342. }
  1343. $('#decimalDigitsGroup').toggleClass('hidden', field.type !== 'decimal');
  1344. }
  1345. if(oldValue !== controlValue)
  1346. {
  1347. field.changed = true;
  1348. if(field.errors)
  1349. {
  1350. if(field.errors && field.errors[controlName]) delete field.errors[controlName];
  1351. if(controlName === 'control' || controlName === 'optionType') delete field.errors.defaultValue;
  1352. if(controlName === 'type') delete field.errors.length;
  1353. if(controlName === 'integerDigits') delete field.errors.integerDigits;
  1354. if(controlName === 'decimalDigits')
  1355. {
  1356. delete field.errors.decimalDigits;
  1357. delete field.errors.integerDigits;
  1358. }
  1359. }
  1360. }
  1361. validateField(field);
  1362. updateField(field);
  1363. updateFieldTypeTip(field);
  1364. });
  1365. /* Synchronize field options after use change it */
  1366. var syncFieldOptions = function()
  1367. {
  1368. if(!activedField || typeof activedField.options !== 'object') return;
  1369. var options = [];
  1370. $fieldEditOptionsList.find('.field-option-item').each(function()
  1371. {
  1372. var $item = $(this);
  1373. var value = $item.find('[name="optionValue"]').val();
  1374. var text = $item.find('[name="optionText"]').val();
  1375. options.push(
  1376. {
  1377. value: value,
  1378. text: text,
  1379. });
  1380. });
  1381. activedField.options = options;
  1382. activedField._options = options;
  1383. activedField.changed = true;
  1384. delete activedField.errors.defaultValue;
  1385. validateField(activedField);
  1386. $('#fieldControl').change();
  1387. };
  1388. $fieldEditOptionsList.on('focus', 'input', function()
  1389. {
  1390. $(this).closest('.field-option-item').addClass('active');
  1391. }).on('blur', 'input', function()
  1392. {
  1393. $(this).closest('.field-option-item').removeClass('active');
  1394. }).on('click', '.icon', function()
  1395. {
  1396. var $this = $(this);
  1397. var type = $this.data('type');
  1398. var $item = $this.closest('.field-option-item');
  1399. if(type === 'add')
  1400. {
  1401. var $newItem = $item.clone();
  1402. $newItem.find('[name="optionValue"],[name="optionText"]').val('');
  1403. $item.after($newItem);
  1404. }
  1405. else
  1406. {
  1407. if($fieldEditOptionsList.find('.field-option-item').length === 1)
  1408. {
  1409. $item.find('[name="optionValue"],[name="optionText"]').val('');
  1410. }
  1411. else $item.remove();
  1412. }
  1413. syncFieldOptions();
  1414. }).on('change', syncFieldOptions);
  1415. /* Support to add fields to preview section with drag-and-drop */
  1416. $('#editorControls').droppable(
  1417. {
  1418. container: 'body',
  1419. selector: '.btn-field-control',
  1420. target: '.field-item,#uiPreview,.field-col-item' + (hasActionsInList ? '' : ',#uiListHeader,#uiInfoViewPreview,#uiBasicViewPreview,.field-view-item'),
  1421. stopPropagation: true,
  1422. nested: true,
  1423. start: function()
  1424. {
  1425. $('input').blur();
  1426. $('#editor').addClass('editor-dragging');
  1427. },
  1428. always: function()
  1429. {
  1430. $('#editor').removeClass('editor-dragging');
  1431. },
  1432. drag: function(e)
  1433. {
  1434. $previewBox.find('.drop-hover').removeClass('drop-hover');
  1435. if(e.isIn) $(e.target).closest('#uiListTable,#uiPreviewWrapper,.view-panel').addClass('drop-hover');
  1436. },
  1437. drop: function(e)
  1438. {
  1439. var $element = e.element;
  1440. var field = $element.data('field');
  1441. var nextField;
  1442. var getOrder = function()
  1443. {
  1444. var $target = e.target;
  1445. nextField = $target.is('#uiPreview,#uiListHeader,#uiInfoViewPreview,#uiBasicViewPreview') ? null : getField($target.data('id'));
  1446. if(nextField) return nextField.order - 1;
  1447. var lastField = getField($preview.find('.field-item,.field-col-item,.field-view-item').last().data('id'));
  1448. return lastField ? lastField.order + 1 : fields.length * 100 + 2;
  1449. }
  1450. if(field)
  1451. {
  1452. field = getField(field);
  1453. if(field)
  1454. {
  1455. field.show = '1';
  1456. field.order = getOrder();
  1457. field.changed = true;
  1458. if(isView)
  1459. {
  1460. field.position = e.target.closest('.view-dropbox').data('position');
  1461. }
  1462. }
  1463. }
  1464. else
  1465. {
  1466. var controlType = $element.data('type');
  1467. if(controlType)
  1468. {
  1469. field =
  1470. $.extend({
  1471. id : '_' + $.zui.uuid(),
  1472. control : controlType,
  1473. name : window.controlTypeList[controlType],
  1474. order : getOrder(),
  1475. buildin : '0',
  1476. layoutRules : '',
  1477. defaultValue : '',
  1478. mobileShow : '0',
  1479. module : window.flowModule,
  1480. options : [{value: '', text: ''}],
  1481. position : isView ? e.target.closest('.view-dropbox').data('position') : 'left',
  1482. readonly : '0',
  1483. rules : '',
  1484. show : '1',
  1485. summary : '',
  1486. length : '255',
  1487. width : 'auto',
  1488. changed : true,
  1489. isValue : '0',
  1490. unsaved : true
  1491. }, getDefaultFieldByControl(controlType));
  1492. fields.push(field);
  1493. }
  1494. }
  1495. if(field && typeof field === 'object')
  1496. {
  1497. if(nextField) nextField.changed = true;
  1498. field.changed = true;
  1499. sortFields();
  1500. updateField(field, nextField, true);
  1501. updateShowedFields();
  1502. activeField(field);
  1503. }
  1504. }
  1505. });
  1506. /* Limit input content to prevent mistakes */
  1507. $('#fieldLength').on('change', function()
  1508. {
  1509. var $input = $(this);
  1510. $input.val($input.val().replace(/[^\d,]/g, ''));
  1511. });
  1512. $('#fieldField').on('change', function()
  1513. {
  1514. var $input = $(this);
  1515. $input.val($input.val().replace(/[^a-zA-Z]/g, ''));
  1516. });
  1517. $('#fieldEditOptionsList').on('change', '[name="optionValue"]', function()
  1518. {
  1519. var $input = $(this);
  1520. $input.val($input.val().replace(/[^a-zA-Z\d]/g, ''));
  1521. });
  1522. $('#fieldEditOptionsList').on('change', '[name="optionText"]', function(){removeOptionError(activedField);});
  1523. $('#fieldEditOptionsList').on('click', '.icon-minus', function(){removeOptionError(activedField);});
  1524. /* Bind event to make list preview auto scroll with header */
  1525. $preview.on('scroll', function()
  1526. {
  1527. $('#uiListBody').scrollLeft($preview.scrollLeft());
  1528. });
  1529. /* Bind event to unactive field on use click top menu */
  1530. $('#editorNav').on('click', unactiveField);
  1531. $('#saveBtn').on('click', saveFields.bind(null, false, function(result)
  1532. {
  1533. if(result) setTimeout(function(){location.href = location.href;}, 1200);
  1534. },
  1535. true
  1536. ));
  1537. $('a.btn.release').on('click', function()
  1538. {
  1539. if(saveFields(function(result)
  1540. {
  1541. if(result) $('a.btn.release').click();
  1542. })) return false;
  1543. });
  1544. $(document).on('change', '#custom', function()
  1545. {
  1546. var $fieldDefaultValue = $('#fieldDefaultValue');
  1547. if(!$(this).prop('checked'))
  1548. {
  1549. $fieldDefaultValue.replaceWith($('<select id="fieldDefaultValue" name="defaultValue" class="form-control"><option value=""></option><option value="currentTime">' + window.currentTimeLang + '</option></select>'));
  1550. $fieldDefaultValue = $('#fieldDefaultValue');
  1551. }
  1552. else
  1553. {
  1554. $fieldDefaultValue.replaceWith($('<input type="text" id="fieldDefaultValue" name="defaultValue" class="form-control"/>'));
  1555. $fieldDefaultValue = $('#fieldDefaultValue');
  1556. if(window.dateType) $fieldDefaultValue[window.dateType + 'picker']();
  1557. }
  1558. $fieldDefaultValue.data('dateType', window.dateType).attr('autocomplete', 'off');
  1559. });
  1560. $(document).on('hidden.zui.modal', '#triggerModal', function()
  1561. {
  1562. location.reload();
  1563. });
  1564. $('#editorPages > .btn, .prevStep').on('click', function()
  1565. {
  1566. var url = $(this).attr('href');
  1567. if(saveFields(function(result)
  1568. {
  1569. if(result) setTimeout(function(){location.href = url;}, 1200);
  1570. })) return false;
  1571. });
  1572. $('.release.btn').on('click', function(){$('#confirmReleaseModal .close').click();});
  1573. /* Show alert before user leave page without save changes */
  1574. $(window).on('beforeunload', function(e)
  1575. {
  1576. var hasChanges;
  1577. for(var i = 0; i < fields.length; ++i)
  1578. {
  1579. var field = fields[i];
  1580. if(field.changed)
  1581. {
  1582. hasChanges = true;
  1583. break;
  1584. }
  1585. }
  1586. if(hasChanges)
  1587. {
  1588. e = e || window.event;
  1589. // IE8, Firefox 4
  1590. if(e) e.returnValue = window.leavePageTip;
  1591. // Chrome, Safari, Firefox 4+, Opera 12+ , IE 9+
  1592. return window.leavePageTip;
  1593. }
  1594. });
  1595. setAutoHeight();
  1596. $(window).on('resize', setAutoHeight);
  1597. /* Clear default error tips when input default. */
  1598. $('#fieldEditDefaultValue').on('input', function(e){
  1599. var field = activedField;
  1600. if(field.errors.default)
  1601. {
  1602. $('#fieldDefaultValueMessage').hide();
  1603. $('#fieldEditDefaultValue').toggleClass('has-error',false);
  1604. delete field.errors.default;
  1605. field.errors.$length --;
  1606. $('#field-' + field.id).toggleClass('has-error', !!(field.errors && field.errors.$length))
  1607. }
  1608. });
  1609. });