Файловый менеджер - Редактировать - /home/lmsyaran/public_html/joomla5/media/com_fabrik/js/form.js
Назад
/** * Form * * @copyright: Copyright (C) 2005-2013, fabrikar.com - All rights reserved. * @license: GNU/GPL http://www.gnu.org/copyleft/gpl.html */ define(['jquery', 'fab/encoder', 'fab/fabrik', 'lib/debounce/jquery.ba-throttle-debounce'], function (jQuery, Encoder, Fabrik, Debounce) { var FbForm = new Class({ Implements: [Options, Events], options: { 'rowid' : '', 'admin' : false, 'ajax' : false, 'primaryKey' : null, 'error' : '', 'submitOnEnter' : false, 'updatedMsg' : 'Form saved', 'pages' : [], 'start_page' : 0, 'multipage_save': 0, 'ajaxValidation': false, 'showLoader' : false, 'customJsAction': '', 'plugins' : {}, 'ajaxmethod' : 'post', 'inlineMessage' : true, 'print' : false, 'toggleSubmit' : false, 'toggleSubmitTip': 'must validate', 'mustValidate' : false, 'lang' : false, 'debounceDelay' : 500, 'images' : { 'alert' : '', 'action_check': '', 'ajax_loader' : '' } }, initialize: function (id, options) { // $$$ hugh - seems options.rowid can be null in certain corner cases, so defend against that if (typeOf(options.rowid) === 'null') { options.rowid = ''; } this.id = id; //set this to false in window.fireEvents to stop current action (e.g. stop form submission) this.result = true; this.setOptions(options); this.options.pages = $H(this.options.pages); this.subGroups = $H({}); this.currentPage = this.options.start_page; this.formElements = $H({}); this.hasErrors = $H({}); this.mustValidateEls = $H({}); this.toggleSubmitTipAdded = false; this.elements = this.formElements; this.duplicatedGroups = $H({}); this.addingOrDeletingGroup = false; this.addedGroups = []; this.watchRepeatNumsDone = false; this.fx = {}; this.fx.elements = []; this.fx.hidden = []; this.fx.validations = {}; this.setUpAll(); this._setMozBoxWidths(); if (this.options.editable) { (function () { this.duplicateGroupsToMin(); }.bind(this)).delay(1000); } // Delegated element events this.events = {}; this.submitBroker = new FbFormSubmit(); this.scrollTips(); Fabrik.fireEvent('fabrik.form.loaded', [this]); }, _setMozBoxWidths: function () { if (Browser.firefox && this.getForm()) { //as firefox treats display:-moz-box as display:-moz-box-inline we have to programatically set their widths this.getForm().getElements('.fabrikElementContainer > .displayBox').each(function (b) { var computed = b.getParent().getComputedSize(); //remove margins/paddings from width var x = b.getParent().getSize().x - (computed.computedLeft + computed.computedRight); var w = b.getParent().getSize().x === 0 ? 400 : x; b.setStyle('width', w + 'px'); var e = b.getElement('.fabrikElement'); if (typeOf(e) !== 'null') { x = 0; b.getChildren().each(function (c) { if (c !== e) { x += c.getSize().x; } }); e.setStyle('width', w - x - 10 + 'px'); } }); } }, setUpAll: function () { this.setUp(); // add a wrapper if we're going to be using the tooltip, as can't do tooltip on disabled elements if (this.options.ajaxValidation && this.options.toggleSubmit && this.options.toggleSubmitTip !== '') { var submit = this._getButton('Submit'); if (typeOf(submit) !== 'null') { jQuery(submit).wrap('<div data-bs-toggle="tooltip" title="' + Joomla.JText._('COM_FABRIK_MUST_VALIDATE') + '" class="fabrikSubmitWrapper" style="display: inline-block"></div>div>'); } } this.winScroller = new Fx.Scroll(window); if (this.form) { if (this.options.ajax || this.options.submitOnEnter === false) { this.stopEnterSubmitting(); } this.watchAddOptions(); } if (this.options.editable) { $H(this.options.hiddenGroup).each(function (v, k) { if (v === true && typeOf(document.id('group' + k)) !== 'null') { var subGroup = document.id('group' + k).getElement('.fabrikSubGroup'); this.subGroups.set(k, subGroup.cloneWithIds()); this.hideLastGroup(k, subGroup); } }.bind(this)); this.setupSortable(); } // get an int from which to start incrementing for each repeated group id // don't ever decrease this value when deleting a group as it will cause all sorts of // reference chaos with cascading dropdowns etc. this.repeatGroupMarkers = $H({}); if (this.form) { this.form.getElements('.fabrikGroup').each(function (group) { var id = group.id.replace('group', ''); var c = group.getElements('.fabrikSubGroup').length; //if no joined repeating data then c should be 0 and not 1 if (c === 1) { if (group.getElement('.fabrikSubGroupElements').getStyle('display') === 'none') { c = 0; } } this.repeatGroupMarkers.set(id, c); }.bind(this)); this.watchGoBackButton(); } this.watchPrintButton(); this.watchPdfButton(); this.watchTabs(); this.watchRepeatNums(); }, watchRepeatNums: function () { Fabrik.addEvent('fabrik.form.elements.added', function (form) { if (form.id === this.id && !this.watchRepeatNumsDone) { Object.each(this.options.numRepeatEls, function (name, key) { if (name !== '') { var el = this.formElements.get(name); if (el) { el.addNewEventAux(el.getChangeEvent(), function(event) { var v = el.getValue(); this.options.minRepeat[key] = v.toInt(); this.options.maxRepeat[key] = v.toInt(); this.duplicateGroupsToMin(); }.bind(this, el, key)); } } }.bind(form)); this.watchRepeatNumsDone = true; } }.bind(this)); }, /** * Print button action - either open up the print preview window - or print if already opened */ watchPrintButton: function () { if (this.form) { this.form.getElements('a[data-fabrik-print]').addEvent('click', function (e) { e.stop(); if (this.options.print) { window.print(); } else { // Build URL as we could have changed the rowid via ajax pagination var url = jQuery(e.target).prop('href'); url = url.replace(/&rowid=\d+/, '&rowid=' + this.options.rowid); if (this.options.lang !== false) { if (url.test(/\?/)) { url += '&lang=' + this.options.lang; } else { url += '?lang=' + this.options.lang; } } window.open( url, 'win2', 'status=no,toolbar=no,scrollbars=yes,titlebar=no,menubar=no,resizable=yes,width=400,height=350,directories=no,location=no;' ); } }.bind(this)); } }, /** * PDF button action. */ watchPdfButton: function () { if (this.form) { this.form.getElements('*[data-role="open-form-pdf"]').addEvent('click', function (e) { e.stop(); // Build URL as we could have changed the rowid via ajax pagination. // @FIXME for SEF var url = e.event.currentTarget.href.replace(/(rowid=\d*)/, 'rowid=' + this.options.rowid); if (this.options.lang !== false) { if (url.test(/\?/)) { url += '&lang=' + this.options.lang; } else { url += '?lang=' + this.options.lang; } } window.location = url; }.bind(this)); } }, /** * Go back button in ajax pop up window should close the window */ watchGoBackButton: function () { if (this.options.ajax) { var goback = this._getButton('Goback'); if (typeOf(goback) === 'null') { return; } goback.addEvent('click', function (e) { e.stop(); if (Fabrik.Windows[this.options.fabrik_window_id]) { Fabrik.Windows[this.options.fabrik_window_id].close(); } else { // $$$ hugh - http://fabrikar.com/forums/showthread.php?p=166140#post166140 window.history.back(); } }.bind(this)); } }, watchAddOptions: function () { this.fx.addOptions = []; this.getForm().getElements('.addoption').each(function (d) { var a = d.getParent('.fabrikElementContainer').getElement('.toggle-addoption'); var mySlider = new Fx.Slide(d, { duration: 500 }); mySlider.hide(); a.addEvent('click', function (e) { e.stop(); mySlider.toggle(); }); }); }, setUp: function () { this.form = this.getForm(); this.watchGroupButtons(); // Submit can appear in confirmation plugin even when readonly this.watchSubmit(); this.createPages(); this.watchClearSession(); }, getForm: function () { if (typeOf(this.form) === 'null') { this.form = document.id(this.getBlock()); } return this.form; }, getBlock: function () { if (typeOf(this.block) === 'null') { this.block = this.options.editable === true ? 'form_' + this.id : 'details_' + this.id; if (this.options.rowid !== '') { this.block += '_' + this.options.rowid; } } return this.block; }, /** * Attach an effect to an elements * * @param {string} id Element or group to apply the fx TO, triggered from another element * @param {string} method JS event which triggers the effect (click,change etc.) * * @return {*} false if no element found or element fx */ addElementFX: function (id, method) { var c, k, fxdiv; id = id.replace('fabrik_trigger_', ''); // Paul - add sanity checking and error reporting if (id.slice(0, 6) === 'group_') { id = id.slice(6, id.length); k = id; c = document.id(id); if (!c) { fconsole('Fabrik form::addElementFX: Group "' + id + '" does not exist.'); return false; } } else if (id.slice(0, 8) === 'element_') { id = id.slice(8, id.length); k = 'element' + id; c = document.id(id); if (!c) { fconsole('Fabrik form::addElementFX: Element "' + id + '" does not exist.'); return false; } c = c.getParent('.fabrikElementContainer'); if (!c) { fconsole('Fabrik form::addElementFX: Element "' + id + '.fabrikElementContainer" does not exist.'); return false; } } else { fconsole('Fabrik form::addElementFX: Not an element or group: ' + id); return false; } if (c) { // c will be the <li> element - you can't apply fx's to this as it makes the // DOM squiffy with multi column rows, so get the li's content and put it // inside a div which is injected into c // apply fx to div rather than li - damn I'm good var tag = (c).get('tag'); if (tag === 'li' || tag === 'td') { fxdiv = new Element('div', {'style': 'width:100%'}).adopt(c.getChildren()); c.empty(); fxdiv.inject(c); } else { fxdiv = c; } var opts = { duration : 800, transition: Fx.Transitions.Sine.easeInOut }; if (typeOf(this.fx.elements[k]) === 'null') { this.fx.elements[k] = {}; } this.fx.elements[k].css = new Fx.Morph(fxdiv, opts); if (typeOf(fxdiv) !== 'null' && (method === 'slide in' || method === 'slide out' || method === 'slide toggle')) { this.fx.elements[k].slide = new Fx.Slide(fxdiv, opts); } return this.fx.elements[k]; } return false; }, /** * An element state has changed, so lets run any associated effects * * @param {string} id Element id to run the effect on * @param {string} method Method to run * @param {object} elementModel The element JS object which is calling the fx, this is used to work ok which * repeat group the fx is applied on */ doElementFX: function (id, method, elementModel) { var k, groupfx, fx, fxElement; // Could be the source element is in a repeat group but the target is not. var target = this.formElements.get(id.replace('fabrik_trigger_element_', '')), targetInRepeat = true; if (target) { targetInRepeat = target.options.inRepeatGroup; } if (id.slice(0, 21) === 'fabrik_trigger_group_') { groupfx = true; } else { groupfx = false; } // Update the element id that we will apply the fx to to be that of the calling elementModels group // (if in a repeat group) if (elementModel && targetInRepeat && !groupfx) { if (elementModel.options.inRepeatGroup) { var bits = id.split('_'); bits[bits.length - 1] = elementModel.options.repeatCounter; id = bits.join('_'); } } // Create the fx key id = id.replace('fabrik_trigger_', ''); if (id.slice(0, 6) === 'group_') { id = id.slice(6, id.length); // weird fix? if (id.slice(0, 6) === 'group_') { id = id.slice(6, id.length); } k = id; } else { id = id.slice(8, id.length); k = 'element' + id; } // Get the stored fx fx = this.fx.elements[k]; if (!fx) { // A group was duplicated but no element FX added, lets try to add it now fx = this.addElementFX('element_' + id, method); // If it wasn't added then lets get out of here if (!fx) { return; } } // Seems dropdown element fx.css.element is already the container if (groupfx || fx.css.element.hasClass('fabrikElementContainer')) { fxElement = fx.css.element; } else { fxElement = fx.css.element.getParent('.fabrikElementContainer'); } // For repeat groups rendered as tables we cant apply fx on td so get child if (fxElement.get('tag') === 'td') { fxElement = fxElement.getChildren()[0]; } switch (method) { case 'show': fxElement.fade('show').removeClass('fabrikHide'); if (groupfx) { // strange fix for ie8 // http://fabrik.unfuddle.com/projects/17220/tickets/by_number/703?cycle=true document.id(id).getElements('.fabrikinput').setStyle('opacity', '1'); this.showGroupTab(id); // if it was hidden by group's "Show Group" setting ("Yes, but hidden"), need to show() fxElement.show(); } break; case 'hide': fxElement.fade('hide').addClass('fabrikHide'); if (groupfx) { this.hideGroupTab(id); } break; case 'fadein': fxElement.removeClass('fabrikHide'); if (fx.css.lastMethod !== 'fadein') { fx.css.element.show(); fx.css.start({'opacity': [0, 1]}); } if (groupfx) { this.showGroupTab(id); fxElement.show(); } break; case 'fadeout': if (fx.css.lastMethod !== 'fadeout') { fx.css.start({'opacity': [1, 0]}).chain(function () { fx.css.element.hide(); fxElement.addClass('fabrikHide'); }); } if (groupfx) { this.hideGroupTab(id); } break; case 'slide in': fx.slide.slideIn(); break; case 'slide out': fx.slide.slideOut(); fxElement.removeClass('fabrikHide'); break; case 'slide toggle': fx.slide.toggle(); break; case 'clear': this.formElements.get(id).clear(); break; case 'disable': if (!groupfx) { jQuery('#' + id).prop('disabled', true); } break; case 'enable': if (!groupfx) { jQuery('#' + id).prop('disabled', false); } break; case 'readonly': if (!groupfx) { // can't "readonly" a select, so disable all but selected option instead if (jQuery('#' + id).prop('tagName') === 'SELECT') { jQuery('#' + id + ' option:not(:selected)').attr('disabled', true); } else { jQuery('#' + id).prop('readonly', true); } } break; case 'notreadonly': if (!groupfx) { if (jQuery('#' + id).prop('tagName') === 'SELECT') { jQuery('#' + id + ' option').attr('disabled', false); } else { jQuery('#' + id).prop('readonly', false); } } break; } fx.lastMethod = method; Fabrik.fireEvent('fabrik.form.doelementfx', [this, method, id, groupfx]); }, /** * Get a group's tab, if it exists * * These tab functions are currently just helpers for user scripts * * @param {string} groupId group ID * * @return tab | false */ getGroupTab: function (groupId) { if (!groupId.test(/^group/)) { groupId = 'group' + groupId; } var tab_button = document.getElementById(groupId+'_tab'); return tab_button == null ? false : tab_button; }, /** * Hide a group's tab, if it exists * * @param {string} groupId */ hideGroupTab: function (groupId) { var tab = this.getGroupTab(groupId); if (tab !== false) { jQuery(tab).hide(); if (tab.hasClass('active')) { if (tab.getPrevious()) { jQuery(tab.getPrevious().getFirst()).tab('show'); } else if (tab.getNext()) { jQuery(tab.getNext().getFirst()).tab('show'); } } } }, /** * Hide a group's tab, if it exists * * @param {string} groupId */ selectGroupTab: function (groupId) { var tab = this.getGroupTab(groupId); if (tab !== false) { if (!tab.hasClass('active')) { jQuery(tab.getFirst()).tab('show'); } } }, /** * Hide a group's tab, if it exists * * @param {string} groupId */ showGroupTab: function (groupId) { var tab = this.getGroupTab(groupId); if (tab !== false) { jQuery(tab).show(); } }, /** * Convenience for custom code that needs to fire when a tab is changed */ watchTabs: function () { var self = this; jQuery(this.form).on('click', '*[data-role=fabrik_tab]', function (event) { var groupId = event.target.id.match(/group(\d+)_tab/); if (groupId.length > 1) { groupId = groupId[1]; } Fabrik.fireEvent('fabrik.form.tab.click', [self, groupId, event], 500); }); }, /** * If a user has previously started a multi-page form, then we will have a .clearSession * button which resets the form and submits it using the removeSession task. */ watchClearSession: function () { if (this.options.multipage_save === 0) { return; } var self = this, form = jQuery(this.form); form.find('.clearSession').on('click', function (e) { e.preventDefault(); form.find('input[name=task]').val('removeSession'); self.clearForm(); self.form.submit(); }); }, createPages: function () { var submit, p, firstGroup, tabDiv; if (this.isMultiPage()) { // Wrap each page in its own div this.options.pages.each(function (page, i) { p = jQuery(document.createElement('div')); p.attr({ 'class': 'page', 'id' : 'page_' + i }); firstGroup = jQuery('#group' + page[0]); // Paul - Don't use pages if this is a bootstrap_tab form tabDiv = firstGroup.closest('div'); if (tabDiv.hasClass('tab-pane')) { return; } p.insertBefore(firstGroup); page.each(function (group) { p.append(jQuery('#group' + group)); }); }); submit = this._getButton('Submit'); if (submit && this.options.rowid === '') { submit.disabled = 'disabled'; submit.setStyle('opacity', 0.5); } var self = this; jQuery(this.form).on('click', '.fabrikPagePrevious', function (e) { self._doPageNav(e, -1); }); jQuery(this.form).on('click', '.fabrikPageNext', function (e) { self._doPageNav(e, 1); }); this.setPageButtons(); this.hideOtherPages(); } }, isMultiPage: function () { return this.options.pages.getKeys().length > 1; }, /** * Move forward/backwards in multi-page form * * @param {event} e * @param {int} dir 1/-1 */ _doPageNav: function (e, dir) { var self = this, url, d; if (this.options.editable) { if (typeOf(this.form.getElement('.fabrikMainError')) !== 'null') { this.form.getElement('.fabrikMainError').addClass('fabrikHide'); } // If tip shown at bottom of long page and next page shorter we need to move the tip to // the top of the page to avoid large space appearing at the bottom of the page. jQuery('.tool-tip').css('top', 0); // Don't prepend with Fabrik.liveSite, as it can create cross origin browser errors if // you are on www and livesite is not on www. url = 'index.php?option=com_fabrik&format=raw&task=form.ajax_validate&form_id=' + this.id; if (this.options.lang !== false) { url += '&lang=' + this.options.lang; } Fabrik.loader.start(this.getBlock(), Joomla.JText._('COM_FABRIK_VALIDATING')); this.clearErrors(); d = jQuery.extend({}, this.getFormData(), { task: 'form.ajax_validate', fabrik_ajax: '1', format : 'raw' }); d = this._prepareRepeatsForAjax(d); jQuery.ajax({ 'url' : url, method: this.options.ajaxmethod, data : d, }).done(function (r) { Fabrik.loader.stop(self.getBlock()); r = JSON.parse(r); // Don't show validation errors if we are going back a page if (dir === -1 || self._showGroupError(r, d) === false) { self.changePage(dir); self.saveGroupsToDb(); } jQuery('html, body').animate({ scrollTop: jQuery(self.form).offset().top }, 300); }); } else { this.changePage(dir); } e.preventDefault(); }, /** * On a multi-page form save the group data */ saveGroupsToDb: function () { var self = this, orig, origProcess, url, data, format = this.form.querySelector('input[name=format]'), task = this.form.querySelector('input[name=task]'), block = this.getBlock(); if (this.options.multipage_save === 0) { return; } Fabrik.fireEvent('fabrik.form.groups.save.start', [this]); if (this.result === false) { this.result = true; return; } orig = format.value; origProcess = task.value; this.form.querySelector('input[name=format]').value = 'raw'; this.form.querySelector('input[name=task]').value = 'form.savepage'; url = 'index.php?option=com_fabrik&format=raw&page=' + this.currentPage; if (this.options.lang !== false) { url += '&lang=' + this.options.lang; } Fabrik.loader.start(block, 'saving page'); data = this.getFormData(); data.fabrik_ajax = 1; jQuery.ajax({ url : url, method : this.options.ajaxmethod, data : data, }).done(function (r) { Fabrik.fireEvent('fabrik.form.groups.save.completed', [self]); if (self.result === false) { self.result = true; return; } format.value = orig; task.value = origProcess; if (self.options.ajax) { Fabrik.fireEvent('fabrik.form.groups.save.end', [self, r]); } Fabrik.loader.stop(block); }); }, changePage: function (dir) { this.changePageDir = dir; Fabrik.fireEvent('fabrik.form.page.change', [this, dir]); if (this.result === false) { this.result = true; return; } this.currentPage = this.currentPage.toInt(); if (this.currentPage + dir >= 0 && this.currentPage + dir < this.options.pages.getKeys().length) { this.currentPage = this.currentPage + dir; if (!this.pageGroupsVisible()) { this.changePage(dir); } } this.setPageButtons(); jQuery('#page_' + this.currentPage).css('display', ''); this._setMozBoxWidths(); this.hideOtherPages(); Fabrik.fireEvent('fabrik.form.page.chage.end', [this, dir]); Fabrik.fireEvent('fabrik.form.page.change.end', [this, dir]); if (this.result === false) { this.result = true; return; } }, pageGroupsVisible: function () { var visible = false; this.options.pages.get(this.currentPage).each(function (gid) { var group = jQuery('#group' + gid); if (group.length > 0) { if (group.css('display') !== 'none') { visible = true; } } }); return visible; }, /** * Hide all groups except those in the active page */ hideOtherPages: function () { var page, currentPage = parseInt(this.currentPage, 10); this.options.pages.each(function (gids, i) { if (parseInt(i, 10) !== currentPage) { page = jQuery('#page_' + i); page.hide(); } }); }, setPageButtons: function () { var submit = this._getButton('Submit'); var prevs = this.form.getElements('.fabrikPagePrevious'); var nexts = this.form.getElements('.fabrikPageNext'); nexts.each(function (next) { if (this.currentPage === this.options.pages.getKeys().length - 1) { if (typeOf(submit) !== 'null') { submit.disabled = ''; submit.setStyle('opacity', 1); } next.disabled = 'disabled'; next.setStyle('opacity', 0.5); } else { if (typeOf(submit) !== 'null' && (this.options.rowid === '' || this.options.rowid.toString() === '0')) { submit.disabled = 'disabled'; submit.setStyle('opacity', 0.5); } next.disabled = ''; next.setStyle('opacity', 1); } }.bind(this)); prevs.each(function (prev) { if (this.currentPage === 0) { prev.disabled = 'disabled'; prev.setStyle('opacity', 0.5); } else { prev.disabled = ''; prev.setStyle('opacity', 1); } }.bind(this)); }, destroyElements: function () { this.formElements.each(function (el) { el.destroy(); }); }, /** * Add elements into the form * * @param Hash a Elements to add. */ addElements: function (a) { /* * Store the newly added elements so we can call attachedToForm only on new elements. * Avoids issue with cdd in repeat groups resetting themselves when you add a new group */ var added = [], i = 0; a = $H(a); a.each(function (elements, gid) { elements.each(function (el) { if (typeOf(el) === 'array') { if (typeOf(document.id(el[1])) === 'null') { /* Some elements may not exist if this is a new record, specifically the lockrow element */ if (document.getElements('input[name=rowid]')[0].value != "" && el[0] != 'FbLockrow') { fconsole('Fabrik form::addElements: Cannot add element "' + el[1] + '" because it does not exist in HTML.'); } return; } try { var oEl = new window[el[0]](el[1], el[2]); } catch (err) { fconsole('Fabrik form::addElements: Cannot add element "' + el[1] + '" of type "' + el[0] + '" because: ' + err.message); return; } added.push(this.addElement(oEl, el[1], gid)); } else if (typeOf(el) === 'object') { if (typeOf(document.id(el.options.element)) === 'null') { fconsole('Fabrik form::addElements: Cannot add element "' + el.options.element + '" because it does not exist in HTML.'); return; } added.push(this.addElement(el, el.options.element, gid)); } else if (typeOf(el) !== 'null') { fconsole('Fabrik form::addElements: Cannot add unknown element: ' + el); } else { fconsole('Fabrik form::addElements: Cannot add null element.'); } }.bind(this)); }.bind(this)); // $$$ hugh - moved attachedToForm calls out of addElement to separate loop, to fix forward reference issue, // i.e. calc element adding events to other elements which come after itself, which won't be in formElements // yet if we do it in the previous loop ('cos the previous loop is where elements get added to formElements) for (i = 0; i < added.length; i++) { if (typeOf(added[i]) !== 'null') { try { added[i].attachedToForm(); } catch (err) { fconsole(added[i].options.element + ' attach to form:' + err); } } } Fabrik.fireEvent('fabrik.form.elements.added', [this]); }, addElement: function (oEl, elId, gid) { elId = oEl.getFormElementsKey(elId); elId = elId.replace('[]', ''); var ro = elId.substring(elId.length - 3, elId.length) === '_ro'; oEl.form = this; oEl.groupid = gid; this.formElements.set(elId, oEl); Fabrik.fireEvent('fabrik.form.element.added', [this, elId, oEl]); if (ro) { elId = elId.substr(0, elId.length - 3); this.formElements.set(elId, oEl); } this.submitBroker.addElement(elId, oEl); return oEl; }, /** * Dispatch an event to an element * * @param string elementType Deprecated * @param string elementId Element key to look up in this.formElements * @param string action Event change/click etc. * @param mixed js String or function */ dispatchEvent: function (elementType, elementId, action, js) { if (typeOf(js) === 'string') { js = Encoder.htmlDecode(js); } var el = this.formElements.get(elementId); if (!el) { // E.g. db join rendered as chx var els = Object.each(this.formElements, function (e) { if (elementId === e.baseElementId) { el = e; } }); } if (!el) { fconsole('Fabrik form::dispatchEvent: Cannot find element to add ' + action + ' event to: ' + elementId); } else if (js !== '') { el.addNewEvent(action, js); } else if (Fabrik.debug) { fconsole('Fabrik form::dispatchEvent: Javascript empty for ' + action + ' event on: ' + elementId); } }, action: function (task, el) { var oEl = this.formElements.get(el); Browser.exec('oEl.' + task + '()'); }, triggerEvents: function (el) { this.formElements.get(el).fireEvents(arguments[1]); }, /** * If Ajax validations are turned on the watch the elements and their sub-elements * * @param {string} id Element id to observe * @param {string} triggerEvent Event type to add */ watchValidation: function (id, triggerEvent) { var self = this, el = jQuery('#' + id); if (this.options.ajaxValidation === false) { return; } if (el.length === 0) { fconsole('Fabrik form::watchValidation: Could not add ' + triggerEvent + ' event because element "' + id + '" does not exist.'); return; } el = this.formElements.get(id); el.addAjaxValidation(); }, /** * as well as being called from watchValidation can be called from other * element js actions, e.g. date picker closing * * @param {event} e the event * @param {bool} subEl has sub elements * @param {string} replacetxt additional text on the value field, like _time */ doElementValidation: function (e, subEl, replacetxt) { var id; if (this.options.ajaxValidation === false) { return; } replacetxt = typeOf(replacetxt) === 'null' ? '_time' : replacetxt; if (typeOf(e) === 'event' || typeOf(e) === 'object' || typeOf(e) === 'domevent') { // type object in id = e.target.id; // for elements with subelements e.g. checkboxes radiobuttons if (subEl === true) { id = document.id(e.target).getParent('.fabrikSubElementContainer').id; } } else { // hack for closing date picker where it seems the event object isn't // available id = e; } if (typeOf(document.id(id)) === 'null') { return; } if (document.id(id).getProperty('readonly') === true || document.id(id).getProperty('readonly') === 'readonly') { // stops date element being validated // return; } var el = this.formElements.get(id); if (!el) { //silly catch for date elements you cant do the usual method of setting the id in the //fabrikSubElementContainer as its required to be on the date element for the calendar to work id = id.replace(replacetxt, ''); el = this.formElements.get(id); if (!el) { return; } } if (!el.shouldAjaxValidate()) { return; } Fabrik.fireEvent('fabrik.form.element.validation.start', [this, el, e]); if (this.result === false) { this.result = true; return; } el.setErrorMessage(Joomla.JText._('COM_FABRIK_VALIDATING'), 'fabrikValidating'); var d = $H(this.getFormData()); d.set('task', 'form.ajax_validate'); d.set('fabrik_ajax', '1'); d.set('format', 'raw'); if (this.options.lang !== false) { d.set('lang', this.options.lang); } d = this._prepareRepeatsForAjax(d); // $$$ hugh - nasty hack, because validate() in form model will always use _0 for // repeated id's var origid = id; if (el.origId) { origid = el.origId + '_0'; } el.options.repeatCounter = el.options.repeatCounter ? el.options.repeatCounter : 0; var myAjax = new Request({ url : '', method : this.options.ajaxmethod, data : d, onComplete: function (e) { this._completeValidaton(e, id, origid); }.bind(this) }).send(); }, /** * Run once a validation is completed * @param {string} r * @param {string} id * @param {string} origid * @private */ _completeValidaton: function (r, id, origid) { r = JSON.parse(r); if (typeOf(r) === 'null') { this._showElementError(['Oups'], id); this.result = true; return; } this.formElements.each(function (el, key) { el.afterAjaxValidation(); }); Fabrik.fireEvent('fabrik.form.element.validation.complete', [this, r, id, origid]); if (this.result === false) { this.result = true; return; } var el = this.formElements.get(id); if ((typeOf(r.modified[origid]) !== 'null')) { if (el.options.inRepeatGroup) { el.update(r.modified[origid][el.options.repeatCounter]); } else { el.update(r.modified[origid]); } } if (typeOf(r.errors[origid]) !== 'null') { this._showElementError(r.errors[origid][el.options.repeatCounter], id); } else { this._showElementError([], id); } if (this.options.toggleSubmit) { if (this.options.mustValidate) { if (!this.hasErrors.has(id) || !this.hasErrors.get(id)) { this.mustValidateEls[id] = false; } if (!this.mustValidateEls.hasValue(true)) { this.toggleSubmit(true); } } else { this.toggleSubmit(this.hasErrors.getKeys().length === 0); } } }, _prepareRepeatsForAjax: function (d) { this.getForm(); //ensure we are dealing with a simple object if (typeOf(d) === 'hash') { d = d.getClean(); } //data should be keyed on the data stored in the elements name between []'s which is the group id this.form.getElements('input[name^=fabrik_repeat_group]').each( function (e) { // $$$ hugh - had a client with a table called fabrik_repeat_group, which was hosing up here, // so added a test to narrow the element name down a bit! if (e.id.test(/fabrik_repeat_group_\d+_counter/)) { var c = e.name.match(/\[(.*)\]/)[1]; d['fabrik_repeat_group[' + c + ']'] = e.get('value'); } } ); return d; }, _showGroupError: function (r, d) { var tmperr; var gids = Array.mfrom(this.options.pages.get(this.currentPage.toInt())); var err = false; $H(d).each(function (v, k) { k = k.replace(/\[(.*)\]/, '').replace(/%5B(.*)%5D/, '');// for dropdown validations if (this.formElements.has(k)) { var el = this.formElements.get(k); if (gids.contains(el.groupid.toInt())) { if (r.errors[k]) { if (el.options.inRepeatGroup) { r.errors[k].each(function (v2, k2) { var k3 = k.replace(/_(\d+)$/, '_' + k2); var rEl = this.formElements.get(k3); var msg = ''; if (typeOf(v2) !== 'null') { msg = v2.flatten().join('<br />'); } if (msg !== '') { tmperr = this._showElementError(v2, k3); if (err === false) { err = tmperr; } } else { rEl.setErrorMessage('', ''); } }.bind(this)); } else { // prepare error so that it only triggers for real errors and not success // msgs var msg = ''; if (typeOf(r.errors[k]) !== 'null') { msg = r.errors[k].flatten().join('<br />'); } if (msg !== '') { tmperr = this._showElementError(r.errors[k], k); if (err === false) { err = tmperr; } } else { el.setErrorMessage('', ''); } } } if (r.modified[k]) { if (el) { el.update(r.modified[k]); } } } } }.bind(this)); return err; }, /** * Show element error * @param {array} r * @param {string} id * @returns {boolean} * @private */ _showElementError: function (r, id) { // r should be the errors for the specific element, down to its repeat group // id. var msg = ''; if (typeOf(r) !== 'null') { msg = r.flatten().join('<br />'); } var classname = (msg === '') ? 'fabrikSuccess' : 'fabrikError'; if (msg === '') { delete this.hasErrors[id]; msg = Joomla.JText._('COM_FABRIK_SUCCESS'); } else { this.hasErrors.set(id, true); } msg = '<span> ' + msg + '</span>'; this.formElements.get(id).setErrorMessage(msg, classname); return (classname === 'fabrikSuccess') ? false : true; }, updateMainError: function () { var myfx, activeValidations; if (typeOf(this.form.getElement('.fabrikMainError')) !== 'null') { this.form.getElement('.fabrikMainError').set('html', this.options.error); } activeValidations = this.form.getElements('.fabrikError').filter( function (e, index) { return !e.hasClass('fabrikMainError'); }); if (activeValidations.length > 0 && this.form.getElement('.fabrikMainError').hasClass('fabrikHide')) { this.showMainError(this.options.error); } if (activeValidations.length === 0) { this.hideMainError(); } }, hideMainError: function () { if (typeOf(this.form.getElement('.fabrikMainError')) !== 'null') { var mainEr = this.form.getElement('.fabrikMainError'); myfx = new Fx.Tween(mainEr, { property : 'opacity', duration : 500, onComplete: function () { mainEr.addClass('fabrikHide'); } }).start(1, 0); } }, showMainError: function (msg) { // If we are in j3 and ajax validations are on - don't show main error as it makes the form 'jumpy' if (Fabrik.bootstrapped && this.options.ajaxValidation) { return; } if (typeOf(this.form.getElement('.fabrikMainError')) !== 'null') { var mainEr = this.form.getElement('.fabrikMainError'); mainEr.set('html', msg); mainEr.removeClass('fabrikHide'); myfx = new Fx.Tween(mainEr, { property: 'opacity', duration: 500 }).start(0, 1); } }, /** @since 3.0 get a form button name */ _getButton: function (name) { if (!this.getForm()) { return; } var b = this.form.getElement('input[type=button][name=' + name + ']'); if (!b) { b = this.form.getElement('input[type=submit][name=' + name + ']'); } if (!b) { b = this.form.getElement('button[type=button][name=' + name + ']'); } if (!b) { b = this.form.getElement('button[type=submit][name=' + name + ']'); } return b; }, watchSubmit: function () { var submit = this._getButton('Submit'); var apply = this._getButton('apply'); if (!submit && !apply) { // look for a button element set to submit if (this.form && this.form.getElement('button[type=submit]')) { submit = this.form.getElement('button[type=submit]'); } else { return; } } var del = this._getButton('delete'), copy = this._getButton('Copy'); if (del) { del.addEvent('click', function (e) { if (window.confirm(Joomla.JText._('COM_FABRIK_CONFIRM_DELETE_1'))) { var res = Fabrik.fireEvent('fabrik.form.delete', [this, this.options.rowid]).eventResults; if (typeOf(res) === 'null' || res.length === 0 || !res.contains(false)) { // Task value is the same for front and admin this.form.getElement('input[name=task]').value = 'form.delete'; this.doSubmit(e, del); } else { e.stop(); return false; } } else { return false; } }.bind(this)); } var submits = this.form.getElements('button[type=submit]').combine([apply, submit, copy]); submits.each(function (btn) { if (typeOf(btn) !== 'null') { btn.addEvent('click', function (e) { this.doSubmit(e, btn); }.bind(this)); } }.bind(this)); this.form.addEvent('submit', function (e) { this.doSubmit(e); }.bind(this)); }, mockSubmit: function (btnName) { btnName = typeof btnName !== 'undefined' ? btnName : 'Submit'; var btn = this._getButton(btnName); if (!btn) { btn = new Element('button', {'name': btnName, 'type': 'submit'}); } this.doSubmit(new Event.Mock(btn, 'click'), btn); }, doSubmit: function (e, btn) { if (this.submitBroker.enabled()) { e.stop(); return false; } this.toggleSubmit(false); this.submitBroker.submit(function () { if (this.options.showLoader) { Fabrik.loader.start(this.getBlock(), Joomla.JText._('COM_FABRIK_LOADING')); } Fabrik.fireEvent('fabrik.form.submit.start', [this, e, btn]); if (this.result === false) { this.result = true; e.stop(); Fabrik.loader.stop(this.getBlock()); // Update global status error this.updateMainError(); this.toggleSubmit(true); // Return otherwise ajax upload may still occur. return; } // Insert a hidden element so we can reload the last page if validation fails if (this.options.pages.getKeys().length > 1) { this.form.adopt(new Element('input', { 'name' : 'currentPage', 'value': this.currentPage.toInt(), 'type' : 'hidden' })); } hiddenElements = []; // insert hidden element of hidden elements (!) used by validation code for "skip if hidden" option jQuery.each(this.formElements, function (id, el) { if (el.element && jQuery(el.element).closest('.fabrikHide').length !== 0) { hiddenElements.push(id); } }); this.form.adopt(new Element('input', { 'name' : 'hiddenElements', 'value': JSON.stringify(hiddenElements), 'type' : 'hidden' })); if (this.options.ajax) { // Do ajax val only if onSubmit val ok if (this.form) { // if showLoader is enabled (for non AJAX submits) the loader will already have been shown up there ^^ if (!this.options.showLoader) { Fabrik.loader.start(this.getBlock(), Joomla.JText._('COM_FABRIK_LOADING')); } // Get all values from the form var data = $H(this.getFormData()); data = this._prepareRepeatsForAjax(data); data[btn.name] = btn.value; if (btn.name === 'Copy') { data.Copy = 1; e.stop(); } data.fabrik_ajax = '1'; data.format = 'raw'; // if HTML 5, use FormData so we can do uploads from popups via AJAX // poop, doesn't work in Edge or Safari, punt till they implement FormData correctly if (false && window.FormData) { fd = new FormData(this.form); jQuery.each(data, function(k, v) { if (fd.has(k)) { if (typeOf(fd.get(k)) !== 'object') { fd.set(k, v); } } else { fd.set(k, v); } }); data = fd; var self = this; jQuery.ajax({ 'url': this.form.action, 'data': data, 'method': this.options.ajaxmethod, 'processData': false, 'contentType': false }) .fail(function (text, error) { fconsole(text + ': ' + error); self.showMainError(error); Fabrik.loader.stop(self.getBlock(), 'Error in returned JSON'); self.toggleSubmit(true); }) .done(function (json, txt) { json = JSON.parse(json); self.toggleSubmit(true); if (typeOf(json) === 'null') { // Stop spinner Fabrik.loader.stop(self.getBlock(), 'Error in returned JSON'); fconsole('error in returned json', json, txt); return; } // Process errors if there are some jQuery(self.form.getElements('[data-role=fabrik_tab]')).removeClass('fabrikErrorGroup') var errfound = false; if (json.errors !== undefined) { // For every element of the form update error message $H(json.errors).each(function (errors, key) { if (self.formElements.has(key) && errors.flatten().length > 0) { errfound = true; if (self.formElements[key].options.inRepeatGroup) { for (e = 0; e < errors.length; e++) { if (errors[e].flatten().length > 0) { var this_key = key.replace(/(_\d+)$/, '_' + e); self._showElementError(errors[e], this_key); } } } else { self._showElementError(errors, key); } } }); } // Update global status error self.updateMainError(); if (errfound === false) { var clear_form = false; if (self.options.rowid === '' && btn.name !== 'apply') { // We're submitting a new form - so always clear clear_form = true; } Fabrik.loader.stop(self.getBlock()); var savedMsg = (typeOf(json.msg) !== 'null' && json.msg !== undefined && json.msg !== '') ? json.msg : Joomla.JText._('COM_FABRIK_FORM_SAVED'); if (json.baseRedirect !== true) { clear_form = json.reset_form; if (json.url !== undefined) { if (json.redirect_how === 'popup') { var width = json.width ? json.width : 400; var height = json.height ? json.height : 400; var x_offset = json.x_offset ? json.x_offset : 0; var y_offset = json.y_offset ? json.y_offset : 0; var title = json.title ? json.title : ''; Fabrik.getWindow({ 'id' : 'redirect', 'type' : 'redirect', contentURL: json.url, caller : self.getBlock(), 'height' : height, 'width' : width, 'offset_x': x_offset, 'offset_y': y_offset, 'title' : title }); } else { if (json.redirect_how === 'samepage') { window.open(json.url, '_self'); } else if (json.redirect_how === 'newpage') { window.open(json.url, '_blank'); } } } else { if (!json.suppressMsg) { alert(savedMsg); } } } else { clear_form = json.reset_form !== undefined ? json.reset_form : clear_form; if (!json.suppressMsg) { alert(savedMsg); } } // Query the list to get the updated data Fabrik.fireEvent('fabrik.form.submitted', [self, json]); if (btn.name !== 'apply') { if (clear_form) { self.clearForm(); } // If the form was loaded in a Fabrik.Window close the window. if (Fabrik.Windows[self.options.fabrik_window_id]) { Fabrik.Windows[self.options.fabrik_window_id].close(); } } } else { Fabrik.fireEvent('fabrik.form.submit.failed', [self, json]); // Stop spinner Fabrik.loader.stop(self.getBlock(), Joomla.JText._('COM_FABRIK_VALIDATION_ERROR')); } }); } else { var myajax = new Request.JSON({ 'url': this.form.action, 'data': data, 'method': this.options.ajaxmethod, onError: function (text, error) { fconsole(text + ': ' + error); this.showMainError(error); Fabrik.loader.stop(this.getBlock(), 'Error in returned JSON'); this.toggleSubmit(true); }.bind(this), onFailure: function (xhr) { fconsole(xhr); Fabrik.loader.stop(this.getBlock(), 'Ajax failure'); this.toggleSubmit(true); }.bind(this), onComplete: function (json, txt) { this.toggleSubmit(true); if (typeOf(json) === 'null') { // Stop spinner Fabrik.loader.stop(this.getBlock(), 'Error in returned JSON'); fconsole('error in returned json', json, txt); return; } // Process errors if there are some jQuery(this.form.getElements('[data-role=fabrik_tab]')).removeClass('fabrikErrorGroup') var errfound = false; if (json.errors !== undefined) { // For every element of the form update error message $H(json.errors).each(function (errors, key) { if (errors.flatten().length > 0) { /* * might not be an element error - could be a custom plugin error - so * flag an error found, even if we don't match it to an element. */ errfound = true; if (this.formElements.has(key)) { if (this.formElements[key].options.inRepeatGroup) { for (e = 0; e < errors.length; e++) { if (errors[e].flatten().length > 0) { var this_key = key.replace(/(_\d+)$/, '_' + e); this._showElementError(errors[e], this_key); } } } else { this._showElementError(errors, key); } } } }.bind(this)); } // Update global status error this.updateMainError(); if (errfound === false) { var clear_form = false; if (this.options.rowid === '' && btn.name !== 'apply') { // We're submitting a new form - so always clear clear_form = true; } Fabrik.loader.stop(this.getBlock()); var savedMsg = (typeOf(json.msg) !== 'null' && json.msg !== undefined && json.msg !== '') ? json.msg : Joomla.JText._('COM_FABRIK_FORM_SAVED'); if (json.baseRedirect !== true) { clear_form = json.reset_form; if (json.url !== undefined) { if (json.redirect_how === 'popup') { var width = json.width ? json.width : 400; var height = json.height ? json.height : 400; var x_offset = json.x_offset ? json.x_offset : 0; var y_offset = json.y_offset ? json.y_offset : 0; var title = json.title ? json.title : ''; Fabrik.getWindow({ 'id': 'redirect', 'type': 'redirect', contentURL: json.url, caller: this.getBlock(), 'height': height, 'width': width, 'offset_x': x_offset, 'offset_y': y_offset, 'title': title }); } else { if (json.redirect_how === 'samepage') { window.open(json.url, '_self'); } else if (json.redirect_how === 'newpage') { window.open(json.url, '_blank'); } } } else { if (!json.suppressMsg) { alert(savedMsg); } } } else { clear_form = json.reset_form !== undefined ? json.reset_form : clear_form; if (!json.suppressMsg) { alert(savedMsg); } } // Query the list to get the updated data Fabrik.fireEvent('fabrik.form.submitted', [this, json]); if (btn.name !== 'apply') { if (clear_form) { this.clearForm(); } // If the form was loaded in a Fabrik.Window close the window. if (Fabrik.Windows[this.options.fabrik_window_id]) { Fabrik.Windows[this.options.fabrik_window_id].close(); } } Fabrik.fireEvent('fabrik.form.submitted.end', [this, json]); } else { Fabrik.fireEvent('fabrik.form.submit.failed', [this, json]); // Stop spinner Fabrik.loader.stop(this.getBlock(), Joomla.JText._('COM_FABRIK_VALIDATION_ERROR')); } }.bind(this) }).send(); } } } Fabrik.fireEvent('fabrik.form.submit.end', [this]); if (this.result === false) { this.result = true; e.stop(); // Update global status error this.updateMainError(); } else { // Enables the list to clean up the form and custom events if (this.options.ajax) { e.stop(); Fabrik.fireEvent('fabrik.form.ajax.submit.end', [this]); } else { // Inject submit button name/value. if (typeOf(btn) !== 'null') { new Element('input', {type: 'hidden', name: btn.name, value: btn.value}).inject(this.form); this.form.submit(); } else { // Regular button pressed which seems to be triggering form.submit() method. e.stop(); } } } }.bind(this)); e.stop(); }, /** * Used to get the querystring data and * for any element overwrite with its own data definition * required for empty select lists which return undefined as their value if no * items available * * @param {bool} submit Should we run the element onsubmit() methods - set to false in calc element */ getFormData: function (submit) { submit = typeOf(submit) !== 'null' ? submit : true; if (submit) { this.formElements.each(function (el, key) { el.onsubmit(); }); } this.getForm(); var s = this.form.toQueryString(); var h = {}; s = s.split('&'); var arrayCounters = $H({}); s.each(function (p) { p = p.split('='); var k = p[0]; // $$$ rob deal with checkboxes // Ensure [] is not encoded k = decodeURI(k); if (k.substring(k.length - 2) === '[]') { k = k.substring(0, k.length - 2); if (!arrayCounters.has(k)) { // rob for ajax validation on repeat element this is required to be set to 0 arrayCounters.set(k, 0); } else { arrayCounters.set(k, arrayCounters.get(k) + 1); } k = k + '[' + arrayCounters.get(k) + ']'; } h[k] = p[1]; }); // toQueryString() doesn't add in empty data - we need to know that for the // validation on multipages var elKeys = this.formElements.getKeys(); this.formElements.each(function (el, key) { //fileupload data not included in querystring if (el.plugin === 'fabrikfileupload') { h[key] = el.get('value'); } if (typeOf(h[key]) === 'null') { // search for elementname[*] in existing data (search for * as datetime // elements aren't keyed numerically) var found = false; $H(h).each(function (val, dataKey) { dataKey = unescape(dataKey); // 3.0 ajax submission [] are escaped dataKey = dataKey.replace(/\[(.*)\]/, ''); if (dataKey === key) { found = true; } }.bind(this)); if (!found) { h[key] = ''; } } }.bind(this)); return h; }, /* * Used by things like CDD to populate 'data' for the AJAX update, so custom 'where' clauses * can use {placeholders}. Initially tried to use getFormData for this, but because * it adds ALL the query string args from the page, the AJAX call from cascade ended * up trying to submit the form. So, this func only fetches actual form element data. */ getFormElementData: function () { var h = {}; this.formElements.each(function (el, key) { if (el.element) { h[key] = el.getValue(); h[key + '_raw'] = h[key]; } }.bind(this)); return h; }, watchGroupButtons: function () { var self = this; jQuery(this.form).on('click', '.deleteGroup', Debounce(this.options.debounceDelay, true, function (e, target) { e.preventDefault(); if (!self.addingOrDeletingGroup) { self.addingOrDeletingGroup = true; var group = e.target.getParent('.fabrikGroup'), subGroup = e.target.getParent('.fabrikSubGroup'); self.deleteGroup(e, group, subGroup); self.addingOrDeletingGroup = false; } })); jQuery(this.form).on('click', '.addGroup', Debounce(this.options.debounceDelay, true, function (e, target) { e.preventDefault(); if (!self.addingOrDeletingGroup) { self.addingOrDeletingGroup = true; self.duplicateGroup(e, true); self.addingOrDeletingGroup = false; } })); if (this.form) { this.form.addEvent('click:relay(.fabrikSubGroup)', function (e, subGroup) { var r = subGroup.getElement('.fabrikGroupRepeater'); if (r) { subGroup.addEvent('mouseenter', function (e) { r.fade(1); }); subGroup.addEvent('mouseleave', function (e) { r.fade(0.2); }); } }.bind(this)); } }, /** * not currently used in our code, provided as a helper function for custom JS * * @param groupId * @returns {boolean} */ mockDuplicateGroup: function(groupId) { var add_btn = this.form.getElement('#group' + groupId + ' .addGroup'); if (typeOf(add_btn) !== 'null') { var add_e = new Event.Mock(add_btn, 'click'); this.duplicateGroup(add_e, false); return true; } return false; }, renumberRepeatGroup: function(el, groupId, newRepeatNum, doDelete) { var input = jQuery(el).find('.fabrikinput'); if (input) { var nameMap = {}; var newMap = {}; var elId = input[0].id; var element = this.formElements.get(elId); if (element) { var repeatNum = elId.split('_').getLast(); console.log('renumbering: ' + repeatNum + ' => ' + newRepeatNum); element.update(newRepeatNum + 1); this.formElements.each(function (e, k) { if (e.groupid === groupId && k.split('_').getLast() === repeatNum) { nameMap[k] = e.setName(newRepeatNum); } }.bind(this)); $H(nameMap).each(function (newKey, oldKey) { if (oldKey !== newKey) { newMap[newKey] = this.formElements[oldKey]; if (doDelete === true) { delete this.formElements[oldKey]; } } }.bind(this)); $H(newMap).each(function (data, newKey) { this.formElements[newKey] = data; }.bind(this)); } } }, renumberSortable: function (groupId) { if (typeOf(this.options.group_repeat_sortable[groupId]) === 'null' || !this.options.group_repeat_sortable[groupId]) { return; } var orderElName = this.options.group_repeat_order_element[groupId]; var group = this.form.getElement('#group' + groupId); var tbody = jQuery(group).find('tbody'); var tdEls = jQuery(tbody).find('.fabrikRepeatGroup___' + orderElName); var i = 1; tdEls.each(function (k, el) { var input = jQuery(el).find('.fabrikinput'); if (input) { var elId = input[0].id; var element = this.formElements.get(elId); if (element) { element.update(i); } } i++; }.bind(this)); }, reorderSortable: function (groupId) { if (typeOf(this.options.group_repeat_sortable[groupId]) === 'null' || !this.options.group_repeat_sortable[groupId]) { return; } var orderElName = this.options.group_repeat_order_element[groupId]; var nameMap = {}; var newMap = {}; var group = this.form.getElement('#group' + groupId); var tbody = jQuery(group).find('tbody'); var tdEls = jQuery(tbody).find('.fabrikRepeatGroup___' + orderElName); var to = 0; var save = false; var dir = false; var started = false; var ended = false; var start = false; var end = false; var lastFrom = -1; tdEls.each(function (k, el) { if (!ended) { var input = jQuery(el).find('.fabrikinput'); if (input) { var elId = input[0].id; var element = this.formElements.get(elId); if (element) { var from = elId.split('_').getLast().toInt(); if (!started) { var gap = (from - lastFrom); if (gap === 2) { started = true; dir = 'down'; start = to; } else if (gap > 2) { started = true; dir = 'up'; start = to; end = from; save = from; ended = true; } } else { if (dir === 'down') { if (from === start) { end = to; save = to; ended = true; } } } lastFrom = from; to++; } } } }.bind(this)) if (dir === 'up') { var el; el = tdEls[end]; this.renumberRepeatGroup(el, groupId, 9999, false); for (var i = end - 1; i >= start; i--) { el = tdEls[i]; this.renumberRepeatGroup(el, groupId, i, false); } el = tdEls[end]; this.renumberRepeatGroup(el, groupId, end, true); } else { var el; el = tdEls[end]; this.renumberRepeatGroup(el, groupId, 9999, false); for (var i = start; i < end; i++) { el = tdEls[i]; this.renumberRepeatGroup(el, groupId, i, false); } el = tdEls[end]; this.renumberRepeatGroup(el, groupId, end, true); } $H(nameMap).each(function (newKey, oldKey) { if (oldKey !== newKey) { newMap[newKey] = this.formElements[oldKey]; } }.bind(this)); $H(newMap).each(function (newKey, data) { this.formElements[newKey] = data; }.bind(this)); }, setupSortable: function () { if (!this.form) { return; } Object.each(this.options.group_repeats, function (canRepeat, groupId) { if (canRepeat.toInt() !== 1) { return; } if (typeOf(this.options.group_repeat_sortable[groupId]) !== 'null' && this.options.group_repeat_tablesort[groupId]) { var group = this.form.getElement('#group' + groupId); if (group) { var cellFilters = []; group.getElements('th.fabrikElementContainer').each(function (e, x) { if (e.hasClass('fabrikHide')) { cellFilters.push('fabrikHide'); } else { cellFilters.push(''); } }); jQuery('#group' + groupId + ' table').tablesorter({ theme: 'blue', widthFixed: true, widgets: ["filter"], cssInfoBlock: "tablesorter-no-sort", ignoreCase: true, widgetOptions: { filter_ignoreCase: true, filter_matchType: {'input': 'match', 'select': 'match'}, filter_saveFilters: true, filter_liveSearch: true, filter_cellFilter: cellFilters } }); } } if (typeOf(this.options.group_repeat_sortable[groupId]) !== 'null' && this.options.group_repeat_sortable[groupId]) { Fabrik.addEvent('fabrik.form.elements.added', function (form) { this.renumberSortable(groupId); }.bind(this)); jQuery('#group' + groupId + ' table tbody').sortable({ scroll: true, scrollSensitivity: 100, stop: function (event, ui) { var group = ui.item[0].closest('.fabrikGroup'); var groupId = group.id.replace('group', ''); this.reorderSortable(groupId); }.bind(this) }); } }.bind(this)); }, /** * When editing a new form and when min groups set we need to duplicate each group * by the min repeat value. */ duplicateGroupsToMin: function () { if (!this.form) { return; } Fabrik.fireEvent('fabrik.form.group.duplicate.min', [this]); Object.each(this.options.group_repeats, function (canRepeat, groupId) { if (typeOf(this.options.minRepeat[groupId]) === 'null') { return; } if (canRepeat.toInt() !== 1) { return; } var repeat_counter = this.form.getElement('#fabrik_repeat_group_' + groupId + '_counter'), repeat_added = this.form.getElement('#fabrik_repeat_group_' + groupId + '_added').value, repeat_rows, repeat_real, add_btn, deleteButton, i, repeat_id_0, deleteEvent; if (typeOf(repeat_counter) === 'null') { return; } repeat_rows = repeat_real = repeat_counter.value.toInt(); // figure out if the first group should be hidden (min repeat is 0) if (repeat_rows === 1) { repeat_id_0 = this.form.getElement('#' + this.options.group_pk_ids[groupId] + '_0'); // repeat_added means they added a first group, and we've failed validation, so show it if (repeat_added !== '1' && typeOf(repeat_id_0) !== 'null' && repeat_id_0.value === '') { repeat_real = 0; } } var min = this.options.minRepeat[groupId].toInt(); var max = this.options.maxRepeat[groupId].toInt(); var group = this.form.getElement('#group' + groupId); var subGroup; /** * $$$ hugh - added ability to override min count * http://fabrikar.com/forums/index.php?threads/how-to-initially-show-repeat-group.32911/#post-170147 * $$$ hugh - trying out min of 0 for Troester * http://fabrikar.com/forums/index.php?threads/how-to-start-a-new-record-with-empty-repeat-group.34666/#post-175408 * $$$ paul - fixing min of 0 for Jaanus * http://fabrikar.com/forums/index.php?threads/couple-issues-with-protostar-template.35917/ **/ if (min === 0 && repeat_real === 0) { // Create mock event deleteButton = this.form.getElement('#group' + groupId + ' .deleteGroup'); deleteEvent = typeOf(deleteButton) !== 'null' ? new Event.Mock(deleteButton, 'click') : false; subGroup = group.getElement('.fabrikSubGroup'); // Remove only group this.deleteGroup(deleteEvent, group, subGroup); } else if (repeat_rows < min) { // Create mock event add_btn = this.form.getElement('#group' + groupId + ' .addGroup'); if (typeOf(add_btn) !== 'null') { var add_e = new Event.Mock(add_btn, 'click'); // Duplicate group for (i = repeat_rows; i < min; i++) { this.duplicateGroup(add_e, false); } } } else if (max > 0 && repeat_rows > max) { // Delete groups for (i = repeat_rows; i > max; i--) { var b = jQuery(this.form.getElements('#group' + groupId + ' .deleteGroup')).last()[0]; var del_btn = jQuery(b).find('[data-role=fabrik_delete_group]')[0]; subGroup = jQuery(group.getElements('.fabrikSubGroup')).last()[0]; if (typeOf(del_btn) !== 'null') { var del_e = new Event.Mock(del_btn, 'click'); this.deleteGroup(del_e, group, subGroup); } } } this.setRepeatGroupIntro(group, groupId); }.bind(this)); }, /** * Delete an repeating group * * @param e * @param group */ deleteGroup: function (e, group, subGroup) { Fabrik.fireEvent('fabrik.form.group.delete', [this, e, group]); if (this.result === false) { this.result = true; return; } if (e) { e.preventDefault(); } // Find which repeat group was deleted var delIndex = 0; // if clicked exactly on icon, e.target will be icon, not surrounding link, so need find with addBack var target = jQuery(e.target).find('[data-role=fabrik_delete_group]').addBack('[data-role=fabrik_delete_group]')[0]; group.getElements('.deleteGroup').each(function (b, x) { if (jQuery(b).find('[data-role=fabrik_delete_group]')[0] === target) { delIndex = x; } }.bind(this)); var i = group.id.replace('group', ''); var repeats = document.id('fabrik_repeat_group_' + i + '_counter').get('value').toInt(); if (repeats <= this.options.minRepeat[i] && this.options.minRepeat[i] !== 0) { if (this.options.minMaxErrMsg[i] !== '') { var errorMessage = this.options.minMaxErrMsg[i]; errorMessage = errorMessage.replace(/\{min\}/, this.options.minRepeat[i]); errorMessage = errorMessage.replace(/\{max\}/, this.options.maxRepeat[i]); alert(errorMessage); } return; } delete this.duplicatedGroups.i; if (document.id('fabrik_repeat_group_' + i + '_counter').value === '0') { return; } var subgroups = group.getElements('.fabrikSubGroup'); this.subGroups.set(i, subGroup.clone()); if (subgroups.length <= 1) { this.hideLastGroup(i, subGroup); this.formElements.each(function (e, k) { if (e.groupid === i && typeOf(e.element) !== 'null') { this.removeMustValidate(e); } }.bind(this)); document.id('fabrik_repeat_group_' + i + '_added').value = '0'; Fabrik.fireEvent('fabrik.form.group.delete.end', [this, e, i, delIndex]); } else { var toel = subGroup.getPrevious(); /* var myFx = new Fx.Tween(subGroup, { 'property': 'opacity', duration : 300, onComplete: function () { */ if (subgroups.length > 1) { subGroup.dispose(); } this.formElements.each(function (e, k) { if (typeOf(e.element) !== 'null') { if (typeOf(document.id(e.element.id)) === 'null') { e.decloned(i); delete this.formElements[k]; } } }.bind(this)); // Minus the removed group subgroups = group.getElements('.fabrikSubGroup'); var nameMap = {}; this.formElements.each(function (e, k) { if (e.groupid === i) { nameMap[k] = e.decreaseName(delIndex); } }.bind(this)); // ensure that formElements' keys are the same as their object's ids // otherwise delete first group, add 2 groups - ids/names in last // added group are not updated $H(nameMap).each(function (newKey, oldKey) { if (oldKey !== newKey) { this.formElements[newKey] = this.formElements[oldKey]; delete this.formElements[oldKey]; } }.bind(this)); Fabrik.fireEvent('fabrik.form.group.delete.end', [this, e, i, delIndex]); /* }.bind(this) }).start(1, 0); */ if (toel) { // Only scroll the window if the previous element is not visible var win_scroll = document.id(window).getScroll().y; var obj = toel.getCoordinates(); // If the top of the previous repeat goes above the top of the visible // window, // scroll down just enough to show it. if (obj.top < win_scroll) { var new_win_scroll = obj.top; this.winScroller.start(0, new_win_scroll); } } } // Update the hidden field containing number of repeat groups document.id('fabrik_repeat_group_' + i + '_counter').value = document.id('fabrik_repeat_group_' + i + '_counter').get('value').toInt() - 1; // $$$ hugh - no, mustn't decrement this! See comment in setupAll this.repeatGroupMarkers.set(i, this.repeatGroupMarkers.get(i) - 1); this.renumberSortable(i); this.setRepeatGroupIntro(group, i); }, hideLastGroup: function (groupId, subGroup) { var msg = this.options.noDataMsg[groupId]; if (msg === '') { msg = Joomla.JText._('COM_FABRIK_NO_REPEAT_GROUP_DATA'); } var sge = subGroup.getElement('.fabrikSubGroupElements'); var notice = new Element( 'div', {'class': 'fabrikNotice alert'} ).appendText(msg); if (typeOf(sge) === 'null') { sge = subGroup; var add = sge.getElement('.addGroup'); if (typeOf(add) !== 'null') { var lastth = sge.getParent('table').getElements('*[data-role="fabrik-group-repeaters"]').getLast(); if (!lastth) { // for old custom templates that don't have the data-role, fall back to just grabbing last th lastth = sge.getParent('table').getElements('thead th').getLast(); } add.inject(lastth); } } sge.setStyle('display', 'none'); notice.inject(sge, 'after'); }, isFirstRepeatSubGroup: function (group) { var subgroups = group.getElements('.fabrikSubGroup'); return subgroups.length === 1 && group.getElement('.fabrikNotice'); }, getSubGroupToClone: function (groupId) { var group = document.id('group' + groupId); var subgroup = group.getElement('.fabrikSubGroup'); if (!subgroup) { subgroup = this.subGroups.get(groupId); } var clone = null; var found = false; if (this.duplicatedGroups.has(groupId)) { found = true; } if (!found) { clone = subgroup.cloneNode(true); this.duplicatedGroups.set(groupId, clone); } else { if (!subgroup) { clone = this.duplicatedGroups.get(groupId); } else { clone = subgroup.cloneNode(true); } } return clone; }, repeatGetChecked: function (group) { // /stupid fix for radio buttons loosing their checked value var tocheck = []; group.getElements('.fabrikinput').each(function (i) { if (i.type === 'radio' && i.getProperty('checked')) { tocheck.push(i); } }); return tocheck; }, /** * Duplicates the groups sub group and places it at the end of the group * * @param event e Click event * @param bool scroll Scroll to group if offscreen */ duplicateGroup: function (e, scroll) { scroll = typeof scroll !== 'undefined' ? scroll : true; var subElementContainer, container; Fabrik.fireEvent('fabrik.form.group.duplicate', [this, e]); if (this.result === false) { this.result = true; return; } if (e) { e.preventDefault(); } var i = e.target.getParent('.fabrikGroup').id.replace('group', ''); var group_id = i.toInt(); var group = document.id('group' + i); var c = this.repeatGroupMarkers.get(i); var repeats = document.id('fabrik_repeat_group_' + i + '_counter').get('value').toInt(); if (repeats >= this.options.maxRepeat[i] && this.options.maxRepeat[i] !== 0) { if (this.options.minMaxErrMsg[i] !== '') { var errorMessage = this.options.minMaxErrMsg[i]; errorMessage = errorMessage.replace(/\{min\}/, this.options.minRepeat[i]); errorMessage = errorMessage.replace(/\{max\}/, this.options.maxRepeat[i]); window.alert(errorMessage); } return; } document.id('fabrik_repeat_group_' + i + '_counter').value = repeats + 1; if (this.isFirstRepeatSubGroup(group)) { var subgroups = group.getElements('.fabrikSubGroup'); // user has removed all repeat groups and now wants to add it back in // remove the 'no groups' notice var sub = subgroups[0].getElement('.fabrikSubGroupElements'); if (typeOf(sub) === 'null') { group.getElement('.fabrikNotice').dispose(); sub = subgroups[0]; // Table group var add = group.getElement('.addGroup'); add.inject(sub.getElement('div.fabrikGroupRepeater'),'top'); sub.setStyle('display', ''); } else { subgroups[0].getElement('.fabrikNotice').dispose(); subgroups[0].getElement('.fabrikSubGroupElements').show(); } this.repeatGroupMarkers.set(i, this.repeatGroupMarkers.get(i) + 1); document.id('fabrik_repeat_group_' + i + '_added').value = '1'; this.formElements.each(function (e, k) { if (e.groupid === i && typeOf(e.element) !== 'null') { this.addMustValidate(e); } }.bind(this)); Fabrik.fireEvent('fabrik.form.group.duplicate.end', [this, e, i, c]); return; } var clone = this.getSubGroupToClone(i); var tocheck = this.repeatGetChecked(group); // Check for table style group, which may or may not have a tbody in it var groupTable = group.getElement('table.repeatGroupTable'); if (groupTable) { if (groupTable.getElement('tbody')) { groupTable = groupTable.getElement('tbody'); } groupTable.appendChild(clone); } else { group.appendChild(clone); } tocheck.each(function (i) { i.setProperty('checked', true); }); this.subelementCounter = 0; // Remove values and increment ids var newElementControllers = [], hasSubElements = false, inputs = clone.getElements('.fabrikinput'), lastinput = null; this.formElements.each(function (el) { var formElementFound = false; subElementContainer = null; var subElementCounter = -1; inputs.each(function (input) { hasSubElements = el.hasSubElements(); container = input.getParent('.fabrikSubElementContainer'); var testid = (hasSubElements && container) ? container.id : input.id; var cloneName = el.getCloneName(); // Test ===, plus special case for join rendered as auto-complete if (testid === cloneName || testid === cloneName + '-auto-complete') { lastinput = input; formElementFound = true; if (hasSubElements) { subElementCounter++; subElementContainer = input.getParent('.fabrikSubElementContainer'); // Clone the first inputs event to all subelements // $$$ hugh - sanity check in case we have an element which has no input if (document.id(testid).getElement('input')) { input.cloneEvents(document.id(testid).getElement('input')); // Note: Radio's etc. now have their events delegated from the form - so no need to duplicate them // Update labels for sub elements var l = subElementContainer.getParent('.fabrikElementContainer').getElement('label'); if (l) { l.setProperty('for', subElementContainer.id); } } } else { input.cloneEvents(el.element); // Update the element id use el.element.id rather than input.id as // that may contain _1 at end of id var bits = Array.mfrom(el.element.id.split('_')); bits.splice(bits.length - 1, 1, c); input.id = bits.join('_'); // Update labels for non sub elements var l = input.getParent('.fabrikElementContainer').getElement('label'); if (l) { l.setProperty('for', input.id); } } if (typeOf(input.name) !== 'null') { input.name = input.name.replace('[0]', '[' + c + ']'); } } }.bind(this)); if (formElementFound) { if (hasSubElements && typeOf(subElementContainer) !== 'null') { // if we are checking subelements set the container id after they have all // been processed // otherwise if check only works for first subelement and no further // events are cloned // $$$ rob fix for date element var bits = Array.mfrom(el.options.element.split('_')); bits.splice(bits.length - 1, 1, c); subElementContainer.id = bits.join('_'); } var origelid = el.options.element; // clone js element controller, set form to be passed by reference and // not cloned var ignore = el.unclonableProperties(); var newEl = new CloneObject(el, true, ignore); newEl.container = null; newEl.options.repeatCounter = c; // This seems to be wrong, as it'll set origId to the repeat ID with the _X appended. //newEl.origId = origelid; if (hasSubElements && typeOf(subElementContainer) !== 'null') { newEl.element = document.id(subElementContainer); newEl.cloneUpdateIds(subElementContainer.id); newEl.options.element = subElementContainer.id; newEl._getSubElements(); // update the labels newEl.subElements.each(function (subEl) { l = subEl.nextElementSibling; while (l) { if (l.nodeName == "LABEL") { let lBits = l.htmlFor.split('_'); let eBits = newEl.options.element.split('_'); lBits[6] = eBits[6]; l.htmlFor = lBits.join('_'); break; } l = l.nextElementSibling; } }); // Update the id on the fieldset if there is one let fs = subElementContainer.getElement('fieldset'); if (fs) { fs.setAttribute('id', subElementContainer.id); } } else { newEl.cloneUpdateIds(lastinput.id); } //newEl.reset(); newElementControllers.push(newEl); } }.bind(this)); newElementControllers.each(function (newEl) { newEl.cloned(c); // $$$ hugh - moved reset() from end of loop above, otherwise elements with un-cloneable object // like maps end up resetting the wrong map to default values. Needs to run after element has done // whatever it needs to do with un-cloneable object before resetting. // $$$ hugh - adding new option to allow copying of the existing element values when copying // a group, instead of resetting to default value. This means knowing what the group PK element // is, do we don't copy that value. hence new group_pk_ids[] array, which gives us the PK element // name in regular full format, which we need to test against the join string name. //var pk_re = new RegExp('\\[' + this.options.group_pk_ids[group_id] + '\\]'); var pk_re = new RegExp(this.options.group_pk_ids[group_id]); if (!this.options.group_copy_element_values[group_id] || (this.options.group_copy_element_values[group_id] && newEl.element.name && newEl.element.name.test(pk_re))) { // Call reset method that resets both events and value back to default. newEl.reset(); } else { // Call reset method that only resets the events, not the value newEl.resetEvents(); } }.bind(this)); // Update the element container label if( container ) { lastinput.getParent('.fabrikElementContainer').getElement('label').htmlFor = container.id; } var o = {}; o[i] = newElementControllers; this.addElements(o); /** * Only scroll the window if the new element is not visible and 'scroll' arg true * (so for example, we won't scroll if called from duplicateGroupsToMin) */ if (scroll) { var win_size = jQuery(window).height(), win_scroll = document.id(window).getScroll().y, obj = clone.getCoordinates(); /** * If the bottom of the new repeat goes below the bottom of the visible window, * scroll up just enough to show it. */ if (obj.bottom > (win_scroll + win_size)) { var new_win_scroll = obj.bottom - win_size; this.winScroller.start(0, new_win_scroll); } } var myFx = new Fx.Tween(clone, { 'property': 'opacity', duration : 500 }).set(0); clone.fade(1); Fabrik.fireEvent('fabrik.form.group.duplicate.end', [this, e, i, c]); this.setRepeatGroupIntro(group, i); this.renumberSortable(i); this.repeatGroupMarkers.set(i, this.repeatGroupMarkers.get(i) + 1); this.addedGroups.push('group' + i); }, /** * Set the repeat group intro text * @param {string} group group container * @param {string} groupId group ID */ setRepeatGroupIntro: function (group, groupId) { var intro = this.options.group_repeat_intro[groupId], tmpIntro = '', targets = group.getElements('*[data-role="group-repeat-intro"]'); targets.each(function (target, i) { tmpIntro = intro.replace('{i}', i + 1); // poor man's parseMsgForPlaceholder ... ignore elements in joined groups. this.formElements.each(function (el) { if (!el.options.inRepeatGroup) { var re = new RegExp('\{' + el.element.id + '\}'); // might should do a match first, to avoid always calling getValue(), just not sure which is more overhead! tmpIntro = tmpIntro.replace(re, el.getValue()); } }); target.set('html', tmpIntro); }.bind(this)); }, update: function (o) { Fabrik.fireEvent('fabrik.form.update', [this, o.data]); if (this.result === false) { this.result = true; return; } var leaveEmpties = arguments[1] || false; var data = o.data; this.getForm(); if (this.form) { // test for detailed view in module??? var rowidel = this.form.getElement('input[name=rowid]'); if (rowidel && data.rowid) { rowidel.value = data.rowid; } } this.formElements.each(function (el, key) { // if updating from a detailed view with prev/next then data's key is in // _ro format if (typeOf(data[key]) === 'null') { if (key.substring(key.length - 3, key.length) === '_ro') { key = key.substring(0, key.length - 3); } } // this if stopped the form updating empty fields. Element update() // methods // should test for null // variables and convert to their correct values // if (data[key]) { if (typeOf(data[key]) === 'null') { // only update blanks if the form is updating itself // leaveEmpties set to true when this form is called from updateRows if (o.id === this.id && !leaveEmpties) { el.update(''); } } else { el.update(data[key]); } }.bind(this)); }, reset: function () { this.addedGroups.each(function (subgroup) { var group = document.id(subgroup).findClassUp('fabrikGroup'); var i = group.id.replace('group', ''); document.id('fabrik_repeat_group_' + i + '_counter').value = document.id('fabrik_repeat_group_' + i + '_counter').get('value').toInt() - 1; subgroup.remove(); }); this.addedGroups = []; Fabrik.fireEvent('fabrik.form.reset', [this]); if (this.result === false) { this.result = true; return; } this.formElements.each(function (el, key) { el.reset(); }.bind(this)); }, showErrors: function (data) { var d = null; if (data.id === this.id) { // show errors var errors = new Hash(data.errors); if (errors.getKeys().length > 0) { if (typeOf(this.form.getElement('.fabrikMainError')) !== 'null') { this.form.getElement('.fabrikMainError').set('html', this.options.error); this.form.getElement('.fabrikMainError').removeClass('fabrikHide'); } errors.each(function (a, key) { if (typeOf(document.id(key + '_error')) !== 'null') { var e = document.id(key + '_error'); var msg = new Element('span'); for (var x = 0; x < a.length; x++) { for (var y = 0; y < a[x].length; y++) { d = new Element('div').appendText(a[x][y]).inject(e); } } } else { fconsole(key + '_error' + ' not found (form show errors)'); } }); } } }, /** add additional data to an element - e.g database join elements */ appendInfo: function (data) { this.formElements.each(function (el, key) { if (el.appendInfo) { el.appendInfo(data, key); } }.bind(this)); }, clearForm: function () { this.getForm(); if (!this.form) { return; } this.formElements.each(function (el, key) { if (key === this.options.primaryKey) { this.form.getElement('input[name=rowid]').value = ''; } el.update(''); }.bind(this)); this.form.getElements('.fabrikError').empty(); this.form.getElements('.fabrikError').addClass('fabrikHide'); }, /** * Reset errors */ clearErrors: function () { jQuery(this.form).find('.fabrikError').removeClass('fabrikError') .removeClass('error').removeClass('has-error'); this.hideTips(); }, /** * Hide tips */ hideTips: function () { this.elements.each(function(element) { element.removeTipMsg(); }); }, /** * If the form is in a modal and the modal scrolls we should update the * elements tips to keep the tip attached to the element. */ scrollTips: function () { var self = this, top, left, match = jQuery(self.form).closest('.fabrikWindow'), modal = match.find('.itemContent'), currentPos; var pos = function () { var origPos = match.data('origPosition'); if (origPos === undefined) { origPos = match.position(); match.data('origPosition', origPos); } currentPos = match.position(); top = origPos.top - currentPos.top + modal.scrollTop(); left = origPos.left - currentPos.left + modal.scrollLeft(); self.elements.each(function(element) { element.moveTip(top, left); }); }; modal.on('scroll', function () { pos(); }); Fabrik.on('fabrik.window.resized', function (window) { if (match.length > 0 && window === match[0]) { pos(); } }); }, stopEnterSubmitting: function () { var inputs = this.form.getElements('input.fabrikinput'); inputs.each(function (el, i) { el.addEvent('keypress', function (e) { if (e.key === 'enter') { e.stop(); if (inputs[i + 1]) { inputs[i + 1].focus(); } //last one? if (i === inputs.length - 1) { this._getButton('Submit').focus(); } } }.bind(this)); }.bind(this)); }, getSubGroupCounter: function (group_id) { }, addMustValidate: function (el) { if (this.options.ajaxValidation && this.options.toggleSubmit) { this.mustValidateEls.set(el.element.id, el.options.mustValidate); if (el.options.mustValidate) { this.options.mustValidate = true; this.toggleSubmit(false); } } }, removeMustValidate: function (el) { if (this.options.ajaxValidation && this.options.toggleSubmit) { delete this.mustValidateEls[el.element.id]; if (el.options.mustValidate) { if (!this.mustValidateEls.hasValue(true)) { this.toggleSubmit(true); } } } }, toggleSubmit: function (on) { var submit = this._getButton('Submit'); if (typeOf(submit) !== 'null') { if (on === true) { submit.disabled = ''; submit.setStyle('opacity', 1); if (this.options.toggleSubmitTip !== '') { jQuery(this.form).find('.fabrikSubmitWrapper').tooltip('destroy'); this.toggleSubmitTipAdded = false; } } else { submit.disabled = 'disabled'; submit.setStyle('opacity', 0.5); if (this.options.toggleSubmitTip !== '') { if (!this.toggleSubmitTipAdded) { //jQuery(this.form).find('.fabrikSubmitWrapper').data('toggle', 'tooltip'); //jQuery(this.form).find('.fabrikSubmitWrapper').attr('title', 'Your form cannot be saved until all inputs have been validated'); jQuery(this.form).find('.fabrikSubmitWrapper').tooltip(); this.toggleSubmitTipAdded = true; } } } Fabrik.fireEvent('fabrik.form.togglesubmit', [this, on]); } }, addPlugins: function (a) { var self = this; jQuery.each(a, function (k, p) { p.form = self; }); this.plugins = a; } }); // Deprecated - think its no longer used. Fabrik.form = function (ref, id, opts) { var form = new FbForm(id, opts); Fabrik.addBlock(ref, form); return form; }; return FbForm; });
| ver. 1.4 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0.09 |
proxy
|
phpinfo
|
Настройка