Core.UI = Core.UI || {}; Core.UI.Bind = class extends Core.Singleton { /** * Get class name. * @return {string} */ static getClassName() { return 'Core.UI.Bind'; } /** * Add a method that is called anytime a bind is called. Used to add functionality * to other projects. * @param {callable} method */ static addBindMethod(method) { if (!this.bindMethods) { this.bindMethods = []; } this.bindMethods.push(method); } /** * @param {string} id * @param {object} parameters * @param {jQuery} parameters.$container * @param {jQuery} parameters.$popup * @param {object} parameters.buttons * @param {object} parameters.events * @param {object} parameters.instance */ init(id, parameters) { this.id = id; this.parameters = this.parameters || {}; if (!parameters.instance) { parameters.instance = Page.activePage; } for (const name in parameters) { if (parameters[name]) { this.parameters[name] = parameters[name]; } } this.bindElements(); this.bindButtons(); this.bindEvents(); if (Core.UI.Bind.bindMethods && !parameters.skipExtraMethods) { for (let i = 0; i < Core.UI.Bind.bindMethods.length; i++) { parameters.skipExtraMethods = true; Core.UI.Bind.bindMethods[i](this.parameters.$container, parameters); } } return true; } /** * Reinit container bind, if applicable. * @param {string} id * @param {object} parameters */ reinit(id, parameters) { return this.init(id, parameters); } /** * Execute event. * @param {object} e * @param {jQuery} $element * @returns */ executeEvent(e, $element) { if (!e || e.isDefaultPrevented()) { return; } if (!this.parameters.events[e.type]) { return; } let filtered = this.parameters.events[e.type].filter($element); if (filtered === null) { console.error('Filter returned null, missing return key?'); } if (!filtered) { return; } this.parameters.events[e.type].action($element); e.preventDefault(); e.stopPropagation(); } /** * Bind misc events. */ bindEvents() { if (!this.parameters.events) { return; } let me = this; this.parameters.$container .off( Object.keys(this.parameters.events).join(' '), '*', function (e) { me.executeEvent(e, $(this)); } ) .on( Object.keys(this.parameters.events).join(' '), '*', function (e) { me.executeEvent(e, $(this)); } ); } /** * Execute button click. * @param {jQuery} $button * @param {string} className */ executeButtonClick($button, className) { let data = {}; if (this.parameters.$popup) { let $form = this.parameters.$popup.find('form'); if ($form.exists()) { for (let name in $form.form('get values')) { data[name] = $form.form('get value', name); } } } for (let name in $button.data()) { if (name == 'modulePopup' || name == 'module_popup') { continue; } data[_.snakeCase(name)] = $button.data(name); } delete data.parameters; this.parameters.buttons[className].call($button, data, $button); if (this.parameters.$popup) { $('body').trigger('click'); } } /** * Bind container actions. */ bindButtons() { let me = this; this.parameters.$container .off( 'click mouseover', '.action.button' ) .on( 'click mouseover', '.action.button', function (e) { let $button = $(this); let on = 'hover'; if ($button.data('on')) { on = $button.data('on'); } if ($button.hasClass('inactive')) { on = 'hover'; } if (on === 'click' && e.type == 'click') { for (let className in me.parameters.buttons) { if ($button.hasClass(className)) { e.preventDefault(); e.stopPropagation(); me.executeButtonClick($button, className); $('body').popup('hide all'); return; } } } if ($button.data('html')) { if ($button.popup('exists')) { return; } $button.popup({ on: $button.data('popup-on') || on, offset: 5, onShow: function () { if ($button.hasClass('inactive')) { return true; } let $popup = $(this); console.log(Core.Singleton.references); console.log(me.parameters); Core.UI.Bind.get( $popup, Object.assign( me.parameters, { $container: $popup, $popup: $popup } ) ); if (Page.keys.control) { //$popup.find('.action.button').first().trigger('click'); //return false; } return true; }, onVisible: function () { if ($button.hasClass('inactive')) { return true; } let $popup = $(this); let $form = $popup.find('form'); if ($form.isVisible()) { $form.rebind('submit', function (e) { e.preventDefault(); $popup.find('.action.button').first().trigger('click'); return false; }); } }, onHidden: function () { $(this).popup('destroy'); } }); if ((on === 'hover' || $button.data('popup-on') === 'hover') && e.type === 'mouseover') { $button.popup('show'); } else if (on === 'click' && e.type === 'click') { $button.popup('show'); } } } ); } /** * Bind form input changes. */ bindInputWatches() { let me = this; this.parameters.$container.find('input, textarea').each(function () { let $input = $(this); $input.data( 'old-val', ($input.isCheckbox() ? $input.isChecked() * 1 : $input.val()) ); $input .off('focus blur') .on( 'focus blur', function(e) { let $input = $(this); if (e.type == 'focus') { if (typeof $input.data('value') !== 'undefined') { $input.val($input.data('value')); } } else if (e.type == 'blur' && typeof $input.data('value') !== 'undefined') { if (typeof $input.data('new-val') !== 'undefined') { setTimeout( () => $input.val($input.data('new-val')), 100 ); } else if (typeof $input.data('old-val') !== 'undefined') { setTimeout( () => $input.val($input.data('old-val')), 100 ); } } } ); }); this.parameters.$container .off( 'change', 'input, textarea' ) .on( 'change', 'input, textarea', function (e, postSave) { e.stopPropagation(); let $input = $(this); if (!$input.data('uri')) { if ($input.isCheckbox()) { me.toggleFields(); } return; } let data = { id: $input.data('id'), fields: {}, attributes: {}, error_return_code: 2, configuration: Page.getParameters() }; for (let name in $input.data()) { if (name == 'modulePopup' || name == 'module_popup') { continue; } if ($.isFunction($input.data(name))) { continue; } data.attributes[_.snakeCase(name)] = $input.data(name); } data.fields[$input.attr('name')] = ($input.isCheckbox() ? $input.isChecked() * 1 : $input.val()); $input.data('new-val', data.fields[$input.attr('name')]); if ($input.isCheckbox() && $input.data('values')) { data.fields[$input.attr('name')] = $input.data('values')[$input.isChecked() * 1]; } $input.data('value', $input.val()); if ($input.data('call-before-change')) { let call = $input.data('call-before-change'); if ($.isFunction(call)) { call($input); } else { eval(call); } } let savingToast = $('body').toast({ displayTime: 0, message: 'Saving...', closeOnClick: false, transition: { showMethod: 'fade', showDuration: 3000, } }); Core.API.call({ url: `PUT ${$input.data('uri')}`, data: data, callback: (data) => { try { savingToast.toast('close'); } catch (err) { savingToast.toast({ displayTime: 100 }); } if (data.return == 2) { if (!$input.isCheckbox()) { $input[0].select(); if ($input.attr('type') != 'number' && $input.attr('type') != 'hidden') { $input.selectRange(0, 99999); } } document.execCommand('copy'); $('body').toast({ displayTime: 10000, showProgress: 'top', classProgress: 'red', className: { toast: 'ui message' }, closeIcon: true, title: 'Failed to ' + _.startCase($input.attr('name')).toLowerCase() + '.', message: (data.message ? data.message + '
(What you tried to save was copied to the clipboard.)' : ''), closeOnClick: false, class: 'red' }); if ($input.isCheckbox()) { if ($input.data('old-val') == 1) { $input.parent().checkbox('set checked'); } else { $input.parent().checkbox('set unchecked'); } $input.data('new-val', ($input.isCheckbox() ? $input.isChecked() * 1 : $input.val())); } else { $input.val($input.data('old-val')); $input.data('new-val', $input.data('old-val')); if ($input.parent().parent().hasClass('calendar')) { $input.parent().parent().calendar('set date', $input.data('old-val'), false, false); } if ($input.parent().hasClass('dropdown')) { $input.parent().dropdown('set selected', $input.data('old-val')); } } } else { $input.data('old-val', ($input.isCheckbox() ? $input.isChecked() * 1 : $input.val())); if ($input.isCheckbox()) { me.toggleFields(); } } if ($.isFunction(postSave)) { postSave(); } if ($input.data('call-after-change')) { let call = $input.data('call-after-change'); if ($.isFunction(call)) { call($input, data); } else { eval(call); } } if ($input.data('refresh-on-change')) { new RefreshOnChange($input.data('refresh-on-change')).process(); } }, $context: me.parameters.$container, fadeParameters: { hide: true } }); } ); } /** * Toggle fields on checkbox change. */ toggleFields() { let forms = {}; this.parameters.$container.find('form').each(function () { let $form = $(this); let classes = []; $form[0].classList.forEach(function (className) { classes.push(className); }); classes.splice(0, 2); forms[classes[0]] = $form; }); for (let formName in forms) { let $form = forms[formName]; for (let fieldName in $form.form('get values')) { let $input = null; try { $input = $form.form('get field', fieldName); } catch (e) { continue; } if ($input.prop('type') == 'checkbox') { let current = Boolean($input.is(':checked')); if ($input.data('toggle-field')) { if ($input.data('toggle-direction') == 'inverted') { if (current) { $form.form().find($input.data('toggle-field')).slideUp(); } else { $form.form().find($input.data('toggle-field')).slideDown(); } } else { if (current) { $form.form().find($input.data('toggle-field')).slideDown(); } else { $form.form().find($input.data('toggle-field')).slideUp(); } } } } } } } /** * Bind all the basic UI elements. */ bindElements() { if (Core.UI.LockWidget) { Core.UI.LockWidget.init(this.parameters.$container); } this.parameters.$container.find('.ui.accordion').accordion(); this.parameters.$container.find('.ui.checkbox').checkbox(); this.parameters.$container.find('.ui.slider').each(function() { let $slider = $(this); let saveTimer = null; $slider.slider({ min: $slider.data('min'), max: $slider.data('max'), start: $slider.data('start'), end: $slider.data('end'), step: $slider.data('step'), showLabelTicks: true, onChange: function() { if (saveTimer) { clearTimeout(saveTimer); } saveTimer = setTimeout( () => { $slider.parent().find(`input[name="${$slider.attr('id')}"]`).trigger('change'); saveTimer = null; }, 500 ); }, onMove: function() { } }); }); this.parameters.$container.find('.ui.dropdown').each(function () { let $dropdown = $(this); let parameters = { fullTextSearch: true, ignoreSearchCase: true, ignoreDiacritics: true, on: $dropdown.data('on') || 'click', action: $dropdown.data('action') || 'activate' }; if ($dropdown.data('allow-additions')) { parameters.allowAdditions = true; parameters.hideAdditions = false; parameters.forceSelection = false; } $dropdown.dropdown(parameters); }); if ($.timeago) { $.timeago.settings.allowFuture = true; this.parameters.$container.find('time.timeago').timeago(); } this.parameters.$container.find('input.calendar').each(function () { let $input = $(this); if ($input.parent().hasClass('field')) { $input.replaceWith(`
${$input[0].outerHTML}
`); } }); this.parameters.$container.find('.ui.calendar').each(function () { let $calendar = $(this); $calendar.calendar({ type: ($calendar.data('type') ? $calendar.data('type') : 'date'), on: ($calendar.data('on') ? $calendar.data('on') : 'click'), today: true, selectAdjacentDays: true, onSelect: (date) => { $calendar.find('input').val(date).trigger('change'); }, minTimeGap: $calendar.data('min-time-gap') }); }); this.parameters.$container.find('.info-popup').popup({ variation: 'very wide', lastResort: 'right center' }); if ($.howToMessage) { this.parameters.$container.find('.how-to.message').howToMessage(); } this.parameters.$container.find('.image-popup').each(function () { let $element = $(this); let size = $element.data('size') || 'medium'; $element.popup({ html: ``, onVisible: () => $element.popup('reposition') }); }); this.parameters.$container.find('.view-image').each(function () { let $image = $(this); $image.addClass('pointer-on-hover'); $image.off('click'); // @todo rebind isn't removing old bindings. $image.rebind('click', () => { let w = window.open(''); let img = new Image(); img.src = $image.attr('src'); w.document.write(img.outerHTML); }); }); this.parameters.$container.find('.ui.text[data-animation]').each(function() { let $text = $(this); $text .css('display', 'inline-block') .transition('set looping') .transition($text.data('animation')); }); this.parameters.$container.find('form').rebind('submit', function (e) { e.preventDefault(); e.stopPropagation(); return false; }); let me = this; this.parameters.$container.find('.ui.tabular.menu').each(function () { Core.UI.TabMenu.get( $(this).data('name'), { instance: me.parameters.instance } ); }); this.bindInputWatches(); this.toggleFields(); if ($.fn.inputPopup) { this.parameters.$container.find('.input-popup').inputPopup(); } this.parameters.$container.find('*[data-page-link]').each(function () { let $element = $(this); $element.rebind($element.data('on') || 'click', function () { if (Page.keys.control || Boolean($element.data('new-page'))) { let url = window.location.href.split('?')[0]; let parameters = $.extend( $element.data('parameters'), { 'page': $element.data('page-link') } ); window.open( `${url}?${$.param(parameters)}` ); } else { eval($element.data('page-link')).open($element.data('parameters')); } }); }); this.parameters.$container.find('*[data-modal-name]').each(function () { let $element = $(this); $element.rebind($element.data('on') || 'click', function () { if (!Page.keys.control && Boolean($element.data('control-click'))) { return; } let object = eval($element.data('modal-name')); new object($element.data('parameters')); }); }); this.parameters.$container.find('i.link.toggle-password').each(function() { let $element = $(this); let $input = $element.parent('.ui.input').find('input'); $element.off('click'); // @todo this shouldn't be needed $element.rebind('click', function () { if ($input.attr('type') == 'password' && $input.val()) { $input.attr('type', 'text'); } else { $input.attr('type', 'password'); } }); }); // this.parameters.$container.find('*[href]').each(function () { // let $link = $(this); // $link.rebind('click', function () { // window.open($link.attr('href')); // }); // }); } };