jquery.webForms.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. /**
  2. * Copyright (c) UNA, Inc - https://una.io
  3. * MIT License - https://opensource.org/licenses/MIT
  4. *
  5. * @defgroup UnaCore UNA Core
  6. * @{
  7. */
  8. (function($) {
  9. $.fn.addWebForms = function(onLoad) {
  10. if ('undefined' === typeof(glJsLoadOnaddWebForms) || !glJsLoadOnaddWebForms.length) {
  11. $.fn.processWebForms.apply(this);
  12. if(typeof onLoad == 'function')
  13. onLoad();
  14. }
  15. else {
  16. var $this = this;
  17. bx_get_scripts(glJsLoadOnaddWebForms, function () {
  18. $.fn.processWebForms.apply($this);
  19. if(typeof onLoad == 'function')
  20. onLoad();
  21. });
  22. }
  23. return this;
  24. }
  25. $.fn.processWebForms = function() {
  26. // move to the first error
  27. var aErrors = $('.bx-form-warn:visible', this);
  28. if(aErrors.length > 0)
  29. $([document.documentElement, document.body]).animate({
  30. scrollTop: aErrors.first().closest('.bx-form-element-wrapper').offset().top - 50
  31. }, 200);
  32. // switchers
  33. $('.bx-switcher-cont', this).each(function() {
  34. var eSwitcher = $(this);
  35. var eInput = $(this).find('input');
  36. if (eSwitcher.hasClass('bx-form-switcher-processed'))
  37. return;
  38. eSwitcher.addClass('bx-form-switcher-processed');
  39. // the visible control is a button with role=switch: its aria-checked follows the on/off class
  40. var fSyncState = function() {
  41. eSwitcher.find('[role="switch"]').attr('aria-checked', eSwitcher.hasClass('on') ? 'true' : 'false');
  42. };
  43. fSyncState();
  44. eSwitcher.on('click', function() {
  45. var $this = $(this);
  46. if ($this.hasClass('on')) {
  47. $this.find('input').prop('checked', false).trigger('change');
  48. $this.removeClass('on').addClass('off');
  49. } else {
  50. $this.removeClass('off').addClass('on');
  51. $this.find('input').prop('checked', true).trigger('change');
  52. }
  53. fSyncState();
  54. return false;
  55. });
  56. eInput.on('enable', function () {
  57. if (!$(this).prop('checked'))
  58. $(this).prop('checked', true).trigger('change');
  59. if (!eSwitcher.hasClass('on'))
  60. eSwitcher.removeClass('off').addClass('on');
  61. fSyncState();
  62. });
  63. eInput.on('disable', function () {
  64. if ($(this).prop('checked'))
  65. $(this).prop('checked', false).trigger('change');
  66. if (!eSwitcher.hasClass('off'))
  67. eSwitcher.removeClass('on').addClass('off');
  68. fSyncState();
  69. });
  70. });
  71. // collapsable headers
  72. $('.bx-form-collapsable', this).each(function() {
  73. var eFormSection = $(this);
  74. if (eFormSection.hasClass('bx-form-js-processed'))
  75. return;
  76. eFormSection.addClass('bx-form-js-processed');
  77. var eToggle = $('.bx-form-section-title:first', eFormSection).find('a, button').first(); // the caption control, a button now (anchors in older templates)
  78. var fSyncExpanded = function() {
  79. eToggle.attr('aria-expanded', eFormSection.hasClass('bx-form-collapsed') ? 'false' : 'true');
  80. };
  81. fSyncExpanded();
  82. var fCallback = function() {
  83. if (eFormSection.hasClass('bx-form-collapsed')) {
  84. // show
  85. eFormSection.removeClass('bx-form-collapsed');
  86. if (eFormSection.hasClass('bx-form-section-hidden')) {
  87. $('.bx-form-section-content:first', eFormSection).hide();
  88. eFormSection.removeClass('bx-form-section-hidden');
  89. }
  90. $('.bx-form-section-content:first', eFormSection).slideDown(function () {
  91. eFormSection.removeClass('bx-form-section-hidden');
  92. $(this).addWebForms();
  93. $(this).find('.CodeMirror').each(function() {
  94. this.CodeMirror.refresh();
  95. });
  96. //--- fire custom event 'bx_show'
  97. eFormSection.trigger('bx_show');
  98. });
  99. } else {
  100. // hide
  101. eFormSection.addClass('bx-form-collapsed');
  102. $('.bx-form-section-content:first', eFormSection).slideUp(function () {
  103. eFormSection.addClass('bx-form-collapsed bx-form-section-hidden');
  104. //--- fire custom event 'bx_hide'
  105. eFormSection.trigger('bx_hide');
  106. });
  107. }
  108. };
  109. $('.bx-form-section-title:first', eFormSection).click(function() {
  110. // an accordion: a subtle open or close (interface sounds, see bx_sound)
  111. if(typeof(bx_sound) == 'function')
  112. bx_sound(eFormSection.hasClass('bx-form-collapsed') ? 'open' : 'close', {emphasis: 'subtle'});
  113. fCallback();
  114. fSyncExpanded();
  115. });
  116. });
  117. $("select", this).each(function () {
  118. // Labels selector
  119. var oInput = $(this);
  120. if (oInput.hasClass('bx-form-select-labels')) {
  121. $(".bx-form-select-labels").select2ToTree();
  122. }
  123. if(oInput.hasClass('bx-form-input-rgb-list')) {
  124. oInput.bind('change', function() {
  125. oInput.removeClassWild('bg-*').addClass('bg-' + $(this).val());
  126. });
  127. oInput.removeClassWild('bg-*').addClass('bg-' + $(this).val());
  128. }
  129. });
  130. $(".bx-form-input-wrapper-password", this).each(function() {
  131. var sClassInited = "bx-inited";
  132. if($(this).hasClass(sClassInited))
  133. return;
  134. var sClassFocus = "bx-form-input-focus";
  135. $(this).find("input").bind("focus", function() {
  136. $(this).parents(".bx-form-input-wrapper-password:first").addClass(sClassFocus);
  137. }).bind("blur", function() {
  138. $(this).parents(".bx-form-input-wrapper-password:first").removeClass(sClassFocus);
  139. });
  140. $(this).find(".password-show, .password-hide").on("click", function () {
  141. var oLink = $(this);
  142. var oFld = oLink.parents(".bx-form-input-wrapper-password:first").find("input");
  143. if (oLink.hasClass('password-show')) {
  144. oLink.attr('title', _t('_sys_form_input_password_hide')).attr('aria-label', _t('_sys_form_input_password_hide'));
  145. oFld.attr("type", "text");
  146. }
  147. else {
  148. oLink.attr('title', _t('_sys_form_input_password_show')).attr('aria-label', _t('_sys_form_input_password_show'));
  149. oFld.attr("type", "password");
  150. }
  151. oLink.toggleClass('password-show password-hide');
  152. oLink.find('i').toggleClass('eye eye-slash');
  153. });
  154. $(this).addClass(sClassInited);
  155. });
  156. $("input", this).each(function() {
  157. var oInput = $(this);
  158. var onCreateRange = function (event, ui) {
  159. var eInput = $(this).parent().find('input');
  160. var iSliderWidth = eInput.innerWidth() - 160;
  161. $(this).css('width', iSliderWidth + 'px').css('top', (eInput.innerHeight() - $(this).outerHeight())/2 + 1 + 'px');
  162. eInput.css('padding-right', (iSliderWidth + parseFloat($(this).css('margin-right')) + 10) + 'px');
  163. };
  164. // DoubleRange
  165. if (this.getAttribute("type") == 'doublerange') {
  166. var cur = $(this);
  167. if(!cur.is(':visible') || cur.hasClass('bx-form-doublerange-processed'))
  168. return;
  169. cur.addClass('bx-form-doublerange-processed');
  170. var $slider = $('<div class="bx-def-margin-right"></div>').insertAfter(cur);
  171. var iMin = cur.attr("min") ? parseFloat(cur.attr("min"), 10) : 0;
  172. var iMax = cur.attr("max") ? parseFloat(cur.attr("max"), 10) : 100;
  173. var sRangeDv = cur.attr("range-divider") ? cur.attr("range-divider") : '-';
  174. var funcGetValues = function (e) {
  175. var values = e.val().split(sRangeDv, 2); // get values
  176. if (typeof(values[0]) != 'undefined' && values[0].length)
  177. values[0] = parseFloat(values[0]);
  178. else
  179. values[0] = iMin;
  180. if (typeof(values[1]) != 'undefined' && values[1].length)
  181. values[1] = parseFloat(values[1]);
  182. else
  183. values[1] = iMax;
  184. return values;
  185. };
  186. var onChange = function(e, ui) {
  187. values = ui.values;
  188. cur.val( values[0] + sRangeDv + values[1] );
  189. };
  190. $slider.slider({
  191. range: true,
  192. min: iMin,
  193. max: iMax,
  194. step: parseFloat(cur.attr("step")) ? parseFloat(cur.attr("step")) : 1,
  195. values: funcGetValues(cur),
  196. change: onChange,
  197. slide: onChange,
  198. create: onCreateRange
  199. });
  200. cur.bind('change', function () {
  201. var values = funcGetValues($(this));
  202. $slider.slider("option", "values", values);
  203. });
  204. }
  205. // Single range or slider
  206. else if (this.getAttribute("type") == 'slider') {
  207. var cur = $(this);
  208. if(!cur.is(':visible') || cur.hasClass('bx-form-range-processed'))
  209. return;
  210. cur.addClass('bx-form-range-processed');
  211. var $slider = $('<div class="bx-def-margin-right"></div>').insertAfter(cur)
  212. $slider.css('width', ($slider.parent().innerWidth() - cur.outerWidth() - 50) + 'px');
  213. var iMin = cur.attr("min") ? parseFloat(cur.attr("min"), 10) : 0;
  214. var iMax = cur.attr("max") ? parseFloat(cur.attr("max"), 10) : 100;
  215. var onChange = function(e, ui) {
  216. cur.val( ui.value );
  217. };
  218. $slider.slider({
  219. min: iMin,
  220. max: iMax,
  221. step: parseFloat(cur.attr("step")) ? parseFloat(cur.attr("step")) : 1,
  222. value: cur.val(),
  223. change: onChange,
  224. slide: onChange,
  225. create: onCreateRange
  226. });
  227. cur.bind('change', function () {
  228. $slider.slider("option", "value", $(this).val());
  229. });
  230. }
  231. // Date/Time pickers
  232. if (this.getAttribute("type") == "date" || this.getAttribute("type") == "date_calendar" || this.getAttribute("type") == "datetime" || this.getAttribute("type") == "date_time" || this.getAttribute("type") == "datepicker" || this.getAttribute("type") == "dateselect") {
  233. if ($(this).hasClass('bx-form-datepicker-processed'))
  234. return;
  235. $(this).addClass('bx-form-datepicker-processed');
  236. var sYearMin = '1900';
  237. var sYearMax = '2100';
  238. var m;
  239. if ($(this).attr('min') && (m = $(this).attr('min').match(/^(\d{4})/)))
  240. sYearMin = m[1];
  241. if ($(this).attr('max') && (m = $(this).attr('max').match(/^(\d{4})/)))
  242. sYearMax = m[1];
  243. var onBeforeShow = function(oInput, oInstance) {
  244. $(oInstance.dpDiv).addClass('bx-form-datepicker-modal');
  245. };
  246. var parseDateUtc = function (sDate, sFormat) {
  247. let oDate = moment.utc(sDate);
  248. return new Date(
  249. oDate.year(),
  250. oDate.month(),
  251. oDate.date(),
  252. oDate.hour(),
  253. oDate.minute(),
  254. oDate.second()
  255. );
  256. }
  257. var formatDateUtc = function (oDate, sFormat) {
  258. return moment.utc([
  259. oDate.getFullYear(),
  260. oDate.getMonth(),
  261. oDate.getDate(),
  262. oDate.getHours(),
  263. oDate.getMinutes(),
  264. oDate.getSeconds()
  265. ]).format(sFormat);
  266. }
  267. var bDatePicker = false;
  268. if (this.getAttribute("type") == "date" || this.getAttribute("type") == "date_calendar" || this.getAttribute("type") == "datepicker") { // Date picker
  269. flatpickr(this, {
  270. locale: sLang,
  271. disableMobile: true,
  272. altInput: true,
  273. altFormat: this.getAttribute("data-frmt-date") ? this.getAttribute("data-frmt-date") : "F j, Y",
  274. dateFormat: "YYYY-MM-DD",
  275. minDate: sYearMin,
  276. maxDate: sYearMax,
  277. onOpen: onBeforeShow,
  278. parseDate: parseDateUtc,
  279. formatDate: formatDateUtc,
  280. });
  281. bDatePicker = true;
  282. } else if(this.getAttribute("type") == "datetime" || this.getAttribute("type") == "date_time") { // DateTime picker
  283. var oPickerOptions = {
  284. locale: sLang,
  285. disableMobile: true,
  286. enableTime: true,
  287. altInput: true,
  288. altFormat: this.getAttribute("data-frmt-date") ? this.getAttribute("data-frmt-datetime") : "D MMM YYYY h:mma",
  289. dateFormat: "YYYY-MM-DD\\THH:mm:ssZ",
  290. time_24hr: null === this.getAttribute("data-frmt-24h") || 'on' == this.getAttribute("data-frmt-24h") ? true : false,
  291. minDate: sYearMin,
  292. maxDate: sYearMax,
  293. onOpen: onBeforeShow,
  294. parseDate: function (sDate, sFormat) {
  295. let oDate = moment.utc(sDate);
  296. return oDate.toDate();
  297. },
  298. formatDate: function (oDate, sFormat, locale) {
  299. let o;
  300. if (sFormat.endsWith('Z'))
  301. o = moment.utc(oDate);
  302. else
  303. o = moment(oDate);
  304. return o.format(sFormat);
  305. }
  306. };
  307. if (1 == $(this).attr('data-utc')) {
  308. oPickerOptions.parseDate = parseDateUtc;
  309. oPickerOptions.formatDate = formatDateUtc;
  310. }
  311. flatpickr(this, oPickerOptions);
  312. bDatePicker = true;
  313. } else if(this.getAttribute("type") == "dateselect") { // DateTime selector
  314. moment.locale(sLang);
  315. $(this).combodate({
  316. minYear: sYearMin,
  317. maxYear: sYearMax,
  318. format: 'YYYY-MM-DD',
  319. template: 'DD MMMM YYYY',
  320. firstItem: 'none',
  321. 'smartDays': true,
  322. customClass: 'bx-def-font-inputs bx-form-input-select block w-full py-2 px-3 border border-gray-300 dark:border-gray-700 rounded-md shadow-sm bg-white dark:bg-gray-900 placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-2 focus:text-gray-900 dark:focus:text-gray-100 focus:ring-blue-500 focus:border-opacity-70 focus:ring-opacity-20 focus:border-blue-500 text-sm text-gray-700 dark:text-gray-300 appearance-none',
  323. });
  324. }
  325. if(bDatePicker) {
  326. // flatpickr's altInput is the field the user actually sees and focuses, but it is a bare clone:
  327. // it copies class/placeholder/disabled/required/tabindex and no accessible name, so carry the
  328. // labelling of the original (now hidden) input over to it.
  329. var oAltInput = this._flatpickr ? this._flatpickr.altInput : null;
  330. if (oAltInput) {
  331. var aLabelAttrs = ['aria-label', 'aria-labelledby', 'aria-describedby', 'title'];
  332. for (var iLabelAttr = 0; iLabelAttr < aLabelAttrs.length; iLabelAttr++) {
  333. var sLabelValue = this.getAttribute(aLabelAttrs[iLabelAttr]);
  334. if (sLabelValue)
  335. oAltInput.setAttribute(aLabelAttrs[iLabelAttr], sLabelValue);
  336. }
  337. }
  338. if (typeof window.glOnColorSchemeChange === 'undefined')
  339. window.glOnColorSchemeChange = [];
  340. window.glOnColorSchemeChange.push(function(iCode) {
  341. var sNameL = 'flatpickr.min.css';
  342. var sNameD = 'themes/dark.css';
  343. var oStylesheet = null;
  344. switch(iCode) {
  345. case 1: //--- light
  346. oStylesheet = document.head.querySelector("link[href*='" + sNameD + "']");
  347. if(oStylesheet)
  348. oStylesheet.href = oStylesheet.href.replace(sNameD, sNameL);
  349. else
  350. bx_get_style(sUrlRoot + 'plugins_public/flatpickr/dist/' + sNameL);
  351. break;
  352. case 2: //--- dark
  353. oStylesheet = document.head.querySelector("link[href*='" + sNameL + "']");
  354. if(oStylesheet)
  355. oStylesheet.href = oStylesheet.href.replace(sNameL, sNameD);
  356. else
  357. bx_get_style(sUrlRoot + 'plugins_public/flatpickr/dist/' + sNameD);
  358. break;
  359. }
  360. });
  361. }
  362. if (window.navigator.appVersion.search(/Chrome\/(.*?) /) == -1 || parseInt(window.navigator.appVersion.match(/Chrome\/(\d+)\./)[1], 10) < 24)
  363. if( this.getAttribute("allow_input") == null)
  364. $(this).attr('readonly', 'readonly');
  365. }
  366. // RGB/RGBA pickers
  367. if(this.getAttribute('type') == 'text' && (oInput.hasClass('bx-form-input-rgb') || oInput.hasClass('bx-form-input-rgba')) && !oInput.hasClass('minicolors-input')) {
  368. oInput.attr('autocomplete', 'off');
  369. oInput.minicolors({
  370. control: oInput.attr('data-control') || 'hue',
  371. format: oInput.attr('data-format') || 'rgb',
  372. letterCase: oInput.attr('data-letterCase') || 'lowercase',
  373. opacity: oInput.hasClass('bx-form-input-rgba'),
  374. position: oInput.attr('data-position') || 'bottom left',
  375. theme: 'bootstrap'
  376. });
  377. }
  378. });
  379. $('form', this).addClass('bx-form-processed');
  380. return this;
  381. };
  382. })(jQuery);
  383. $(document).ready(function() {
  384. $(this).addWebForms();
  385. });
  386. (function($) {
  387. $.fn.removeClassWild = function(mask) {
  388. return this.removeClass(function(index, cls) {
  389. var re = mask.replace(/\*/g, '\\S+');
  390. return (cls.match(new RegExp('\\b' + re + '', 'g')) || []).join(' ');
  391. });
  392. };
  393. })(jQuery);
  394. /** @} */