diff --git a/app/client/src/js/_components/_ui.form.datetime.js b/app/client/src/js/_components/_ui.form.datetime.js new file mode 100644 index 0000000..e4037ae --- /dev/null +++ b/app/client/src/js/_components/_ui.form.datetime.js @@ -0,0 +1,86 @@ +import $ from 'jquery'; + +import Events from '../_events'; + +import 'bootstrap-datepicker/dist/js/bootstrap-datepicker.js'; +import 'bootstrap-timepicker/js/bootstrap-timepicker.js'; + +const DatetimeUI = (($) => { + // Constants + const W = window; + const D = document; + const $Body = $('body'); + + const NAME = 'jsDatetimeUI'; + const DATA_KEY = NAME; + + const datepickerOptions = { + autoclose: true, + startDate: 0, + //todayBtn: true, + todayHighlight: true, + clearBtn: true, + }; + + class DatetimeUI { + constructor(element) { + const ui = this; + const $element = $(element); + + $element.data(DATA_KEY, this); + ui._element = element; + + // datepicker + if ($element.hasClass('date')) { + const defaultDate = ($element.attr('name').toLowerCase().indexOf('end') !== -1) ? + '+4d' : + '+3d'; + + $element.attr('readonly', 'true'); + $element.datepicker($.extend(datepickerOptions, { + defaultViewDate: defaultDate + })); + } else + + // timepicker + if ($element.hasClass('time')) { + $element.attr('readonly', 'true'); + $element.timepicker(); + } + } + + static dispose() { + console.log(`Destroying: ${NAME}`); + } + + static _jQueryInterface() { + return this.each(function() { + // attach functionality to element + const $elementement = $(this); + let data = $elementement.data(DATA_KEY); + + if (!data) { + data = new DatetimeUI(this); + $elementement.data(DATA_KEY, data); + } + }); + } + } + + // jQuery interface + $.fn[NAME] = DatetimeUI._jQueryInterface; + $.fn[NAME].Constructor = DatetimeUI; + $.fn[NAME].noConflict = function() { + $.fn[NAME] = JQUERY_NO_CONFLICT; + return DatetimeUI._jQueryInterface; + }; + + // auto-apply + $(window).on(`${Events.AJAX} ${Events.LOADED}`, () => { + $('input.date, input.time').jsDatetimeUI(); + }); + + return DatetimeUI; +})($); + +export default DatetimeUI; diff --git a/app/client/src/js/_components/_ui.form.jqte.js b/app/client/src/js/_components/_ui.form.jqte.js new file mode 100644 index 0000000..ad023f9 --- /dev/null +++ b/app/client/src/js/_components/_ui.form.jqte.js @@ -0,0 +1,86 @@ +"use strict"; + +import $ from 'jquery'; + +import Events from '../_events'; +import Spinner from '../_components/_ui.spinner'; +import FormValidate from './_ui.form.validate'; + +import '../../thirdparty/jquery-te/jquery-te.js'; + +const JqteUI = (($) => { + + const NAME = 'jsJqteUI'; + const DATA_KEY = NAME; + + const jqteOptions = { + color: false, + fsize: false, + funit: 'em', + format: false, + rule: false, + source: false, + sub: false, + sup: false, + }; + + class JqteUI { + + constructor(element) { + const ui = this; + const $element = $(element); + const $jqteFields = $element.find('textarea.jqte-field'); + const validationUI = $element.parents('form').data('jsFormValidate'); + + $element.data(DATA_KEY, this); + ui._element = element; + $element.jqte(jqteOptions); + + // dynamic error control + $element.parents('.jqte').find('.jqte_editor').on('change', (e) => { + const $field = $(e.target); + const $container = $field.closest('.field'); + + if (!$field.text().length && $container.hasClass('required')) { + validationUI.setError($container); + } else { + validationUI.removeError($container); + } + }); + } + + static dispose() { + console.log(`Destroying: ${NAME}`); + } + + static _jQueryInterface() { + return this.each(function() { + // attach functionality to element + const $element = $(this); + let data = $element.data(DATA_KEY); + + if (!data) { + data = new JqteUI(this); + $element.data(DATA_KEY, data); + } + }); + } + } + + // jQuery interface + $.fn[NAME] = JqteUI._jQueryInterface; + $.fn[NAME].Constructor = JqteUI; + $.fn[NAME].noConflict = function() { + $.fn[NAME] = JQUERY_NO_CONFLICT; + return JqteUI._jQueryInterface; + }; + + // auto-apply + $(window).on(`${Events.AJAX} ${Events.LOADED}`, () => { + $('textarea.jqte-field').jsJqteUI(); + }); + + return JqteUI; +})($); + +export default JqteUI; diff --git a/app/client/src/js/_components/_ui.form.stepped.js b/app/client/src/js/_components/_ui.form.stepped.js new file mode 100644 index 0000000..6ecdd6c --- /dev/null +++ b/app/client/src/js/_components/_ui.form.stepped.js @@ -0,0 +1,154 @@ +import $ from 'jquery'; +import Events from '../_events'; +import LANG from '../lang/_en'; +import FormValidate from './_ui.form.validate'; + +const SteppedForm = (($) => { + // Constants + const NAME = 'jsSteppedForm'; + const DATA_KEY = NAME; + + class SteppedForm { + + constructor(element) { + const ui = this; + const $element = $(element); + $element.data(DATA_KEY, this); + + if (!$element.find('.steps-counter').length) { + $element.prepend(LANG['en'][NAME]['STEPCOUNTER']); + } + + if (!$element.find('.steps-buttons').length) { + $element.append(LANG['en'][NAME]['STEPBUTTONS']); + } + + ui._currentStepCounter = $element.find('.steps-counter .current-step'); + ui._totalStepsCounter = $element.find('.steps-counter .total-steps'); + + ui._steps = $element.find('.step'); + ui._stepNext = $element.find('.step-next'); + ui._stepPrev = $element.find('.step-prev'); + ui._actions = $element.children('.btn-toolbar,.form-actions'); + + ui._element = element; + ui._currentStep = 1; + ui._totalSteps = ui._steps.length; + ui._stepsOrder = []; + + ui._totalStepsCounter.text(ui._totalSteps); + + ui.step('.step[data-step="' + ui._currentStep + '"]'); + + ui._stepNext.on('click', (e) => { + e.preventDefault(); + ui.next(); + }); + + ui._stepPrev.on('click', (e) => { + e.preventDefault(); + ui.prev(); + }); + + $element.find('.step-toggle').on('click', (e) => { + const $el = $(e.currentTarget); + + e.preventDefault(); + ui.step($el.data('target')); + }); + + $element.addClass(`${NAME}-active`); + $element.trigger(Events.FORM_INIT_STEPPED); + } + + // Public methods + dispose() { + const ui = this; + const $element = $(ui._element); + + $element.removeClass(`${NAME}-active`); + $.removeData(ui._element, DATA_KEY); + ui._element = null; + } + + next() { + const ui = this; + + if (ui._currentStep >= ui._totalSteps) { + return; + } + + ui._currentStep++; + ui.step('.step[data-step="' + ui._currentStep + '"]'); + } + + prev() { + const ui = this; + + if (ui._currentStep <= 1) { + return; + } + + ui._currentStep--; + ui.step(ui._stepsOrder[ui._currentStep]); + } + + step(target) { + const ui = this; + const $element = $(ui._element); + const $target = $element.find(target); + + if (parseInt($target.data('step')) <= '1') { + ui._stepPrev.hide(); + } else { + ui._stepPrev.show(); + } + + if (parseInt($target.data('step')) >= ui._totalSteps) { + ui._stepNext.hide(); + ui._actions.show(); + } else { + ui._stepNext.show(); + ui._actions.hide(); + } + + ui._currentStep = parseInt($target.data('step')); + ui._stepsOrder[ui._currentStep] = $target; + + ui._steps.removeClass('active'); + $target.addClass('active'); + + ui._currentStepCounter.text(ui._currentStep); + } + + static _jQueryInterface() { + return this.each(function() { + // attach functionality to element + const $element = $(this); + let data = $element.data(DATA_KEY); + + if (!data) { + data = new SteppedForm(this); + $element.data(DATA_KEY, data); + } + }); + } + } + + // jQuery interface + $.fn[NAME] = SteppedForm._jQueryInterface; + $.fn[NAME].Constructor = SteppedForm; + $.fn[NAME].noConflict = function() { + $.fn[NAME] = JQUERY_NO_CONFLICT; + return SteppedForm._jQueryInterface; + }; + + // auto-apply + $(window).on(`${Events.AJAX} ${Events.LOADED}`, () => { + $('.form-stepped').jsSteppedForm(); + }); + + return SteppedForm; +})($); + +export default SteppedForm; diff --git a/app/client/src/js/_components/_ui.form.validate.js b/app/client/src/js/_components/_ui.form.validate.js new file mode 100644 index 0000000..dd239cc --- /dev/null +++ b/app/client/src/js/_components/_ui.form.validate.js @@ -0,0 +1,152 @@ +import $ from 'jquery'; +import Events from "../_events"; + +const FormValidate = (($) => { + // Constants + const NAME = 'jsFormValidate'; + const DATA_KEY = NAME; + const $Html = $('html, body'); + + class FormValidate { + + constructor(element) { + const ui = this; + const $element = $(element); + const $fields = $element.find('input,textarea,select'); + + ui._element = element; + $element.data(DATA_KEY, this); + + ui._fields = $fields; + ui._stepped_form = $element.data('jsSteppedForm'); + $element.on(Events.FORM_INIT_STEPPED, () => { + ui._stepped_form = $element.data('jsSteppedForm'); + }); + + // prevent browsers checks (will do it using JS) + $element.attr('novalidate', 'novalidate'); + $fields.each((i, el) => { + el.required = false; + }); + + $fields.on('change', (e) => { + ui.validateField($(e.target), false); + }); + + // check form + $element.on('submit', (e) => { + ui.validateForm($element, true, () => { + e.preventDefault(); + + // switch to step + if (ui._stepped_form) { + const $el = $element.find('.error').first(); + + if ($el.length) { + ui._stepped_form.step($el.parents('.step')); + } + } + + $element.trigger(Events.FORM_VALIDATION_FAILED); + }); + }); + + $element.addClass(`${NAME}-active`); + $element.trigger(Events.FORM_INIT_VALIDATE); + } + + // Public methods + dispose() { + const $element = $(this._element); + + $element.removeClass(`${NAME}-active`); + $.removeData(this._element, DATA_KEY); + this._element = null; + } + + validateForm($form, scrollTo = true, badCallback = false) { + console.log('Checking the form ...'); + const ui = this; + + ui._fields.each(function(i, el) { + const $el = $(el); + + if (!ui.validateField($el)) { + if (badCallback) { + badCallback(); + } + return false; + } + }); + } + + validateField($el, scrollTo = true) { + const $field = $el.closest('.field'); + + if (!$el[0].checkValidity() || + ($el.hasClass('required') && !$el.val().trim().length) + ) { + this.setError($field, scrollTo); + return false; + } else { + this.removeError($field); + } + + return true; + } + + setError($field, scrollTo = true) { + const pos = $field.offset().top; + + $field.addClass('error'); + + if (scrollTo) { + $field.focus(); + $Html.scrollTop(pos - 100); + } + } + + removeError($field) { + $field.removeClass('error'); + } + + static _jQueryInterface() { + return this.each(function() { + // attach functionality to element + const $element = $(this); + let data = $element.data(DATA_KEY); + + if (!data) { + data = new FormValidate(this); + $element.data(DATA_KEY, data); + } + }); + } + } + + // jQuery interface + $.fn[NAME] = FormValidate._jQueryInterface; + $.fn[NAME].Constructor = FormValidate; + $.fn[NAME].noConflict = function() { + $.fn[NAME] = JQUERY_NO_CONFLICT; + return FormValidate._jQueryInterface; + }; + + // auto-apply + $(window).on(`${Events.AJAX} ${Events.LOADED}`, () => { + $('form').each((i, el) => { + const $el = $(el); + + // skip some forms + if ($el.hasClass('no-validation')) { + return true; + } + + $el.jsFormValidate(); + }); + }); + + return FormValidate; +})($); + +export default FormValidate; diff --git a/app/client/src/js/lang/_en.js b/app/client/src/js/lang/_en.js new file mode 100644 index 0000000..7b00df7 --- /dev/null +++ b/app/client/src/js/lang/_en.js @@ -0,0 +1,15 @@ +/** + * Add your global events here + */ + +module.exports = { + en: { + jsSteppedForm: { + STEPCOUNTER: '
Step of
', + STEPBUTTONS: '
' + + ' Prev' + + ' Next ' + + '
', + }, + }, +}; diff --git a/app/client/src/scss/_components/_ui.form.stepped.scss b/app/client/src/scss/_components/_ui.form.stepped.scss new file mode 100644 index 0000000..1330ef0 --- /dev/null +++ b/app/client/src/scss/_components/_ui.form.stepped.scss @@ -0,0 +1,9 @@ +.form-stepped { + .step { + display: none; + + &.active { + display: block; + } + } +}