feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
- Base: wintercms/winter branch 1.2 (full framework) - Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS - Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication - Partials: hero (offline-first), features, modes (offline/nube toggle), screenshots, pricing (3 planes), comparison, FAQ, CTA - Plugin VivesPOS.Site with ContactForm - Dockerfile: PHP 8.2 Apache, port 80, healthcheck - Added winter/wn-pages, blog, sitemap, seo plugins - Active theme set to vivespos
This commit is contained in:
391
modules/backend/widgets/form/assets/js/winter.form.js
Normal file
391
modules/backend/widgets/form/assets/js/winter.form.js
Normal file
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
* Form Widget
|
||||
*
|
||||
* Dependences:
|
||||
* - Nil
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
var Base = $.wn.foundation.base,
|
||||
BaseProto = Base.prototype
|
||||
|
||||
var FormWidget = function (element, options) {
|
||||
this.$el = $(element)
|
||||
this.options = options || {}
|
||||
this.fieldElementCache = null
|
||||
|
||||
/*
|
||||
* Throttle dependency updating
|
||||
*/
|
||||
this.dependantUpdateInterval = 300
|
||||
this.dependantUpdateTimers = {}
|
||||
|
||||
$.wn.foundation.controlUtils.markDisposable(element)
|
||||
Base.call(this)
|
||||
this.init()
|
||||
}
|
||||
|
||||
FormWidget.prototype = Object.create(BaseProto)
|
||||
FormWidget.prototype.constructor = FormWidget
|
||||
|
||||
FormWidget.prototype.init = function() {
|
||||
|
||||
this.$form = this.$el.closest('form')
|
||||
|
||||
this.bindDependants()
|
||||
this.bindCheckboxlist()
|
||||
this.toggleEmptyTabs()
|
||||
this.bindLazyTabs()
|
||||
this.bindCollapsibleSections()
|
||||
|
||||
this.$el.on('oc.triggerOn.afterUpdate', this.proxy(this.toggleEmptyTabs))
|
||||
this.$el.one('dispose-control', this.proxy(this.dispose))
|
||||
}
|
||||
|
||||
FormWidget.prototype.dispose = function() {
|
||||
this.unbindDependants()
|
||||
this.unbindCheckboxList()
|
||||
this.unbindLazyTabs()
|
||||
this.unbindCollapsibleSections()
|
||||
|
||||
this.$el.off('dispose-control', this.proxy(this.dispose))
|
||||
this.$el.removeData('oc.formwidget')
|
||||
|
||||
this.$el = null
|
||||
this.$form = null
|
||||
this.options = null
|
||||
this.fieldElementCache = null
|
||||
|
||||
BaseProto.dispose.call(this)
|
||||
}
|
||||
|
||||
/*
|
||||
* Logic for checkboxlist
|
||||
*/
|
||||
FormWidget.prototype.bindCheckboxlist = function() {
|
||||
|
||||
var checkAllBoxes = function($field, flag) {
|
||||
$('input[type=checkbox]', $field)
|
||||
.prop('checked', flag)
|
||||
.first()
|
||||
.trigger('change')
|
||||
}
|
||||
|
||||
this.$el.on('click', '[data-field-checkboxlist-all]', function() {
|
||||
checkAllBoxes($(this).closest('.field-checkboxlist'), true)
|
||||
})
|
||||
|
||||
this.$el.on('click', '[data-field-checkboxlist-none]', function() {
|
||||
checkAllBoxes($(this).closest('.field-checkboxlist'), false)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Unbind checkboxlist handlers
|
||||
*/
|
||||
FormWidget.prototype.unbindCheckboxList = function() {
|
||||
this.$el.off('click', '[data-field-checkboxlist-all]')
|
||||
this.$el.off('click', '[data-field-checkboxlist-none]')
|
||||
}
|
||||
|
||||
/*
|
||||
* Get all fields elements that belong to this form, nested form
|
||||
* fields are removed from this collection.
|
||||
*/
|
||||
FormWidget.prototype.getFieldElements = function() {
|
||||
if (this.fieldElementCache !== null) {
|
||||
return this.fieldElementCache
|
||||
}
|
||||
|
||||
var form = this.$el,
|
||||
nestedFields = form.find('[data-control="formwidget"] [data-field-name]')
|
||||
|
||||
return this.fieldElementCache = form.find('[data-field-name]').not(nestedFields)
|
||||
}
|
||||
|
||||
/*
|
||||
* Bind dependant fields
|
||||
*/
|
||||
FormWidget.prototype.bindDependants = function() {
|
||||
var self = this,
|
||||
fieldMap = this._getDependants()
|
||||
|
||||
/*
|
||||
* When a field is updated, refresh its dependents
|
||||
*/
|
||||
$.each(fieldMap, function(fieldName, toRefresh) {
|
||||
$(document).on('change.oc.formwidget',
|
||||
'[data-field-name="' + fieldName + '"]',
|
||||
$.proxy(self.onRefreshDependants, self, fieldName, toRefresh)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Dispose of the dependant field handlers
|
||||
*/
|
||||
FormWidget.prototype.unbindDependants = function() {
|
||||
var fieldMap = this._getDependants()
|
||||
|
||||
$.each(fieldMap, function(fieldName, toRefresh) {
|
||||
$(document).off('change.oc.formwidget', '[data-field-name="' + fieldName + '"]')
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve the dependant fields
|
||||
*/
|
||||
FormWidget.prototype._getDependants = function() {
|
||||
if (!$('[data-field-depends]', this.$el).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fieldMap = {},
|
||||
fieldElements = this.getFieldElements()
|
||||
|
||||
/*
|
||||
* Map master and slave fields
|
||||
*/
|
||||
fieldElements.filter('[data-field-depends]').each(function() {
|
||||
var name = $(this).data('field-name'),
|
||||
depends = $(this).data('field-depends')
|
||||
|
||||
$.each(depends, function(index, depend){
|
||||
if (!fieldMap[depend]) {
|
||||
fieldMap[depend] = { fields: [] }
|
||||
}
|
||||
|
||||
fieldMap[depend].fields.push(name)
|
||||
})
|
||||
})
|
||||
|
||||
return fieldMap
|
||||
}
|
||||
|
||||
/*
|
||||
* Refresh a dependancy field
|
||||
* Uses a throttle to prevent duplicate calls and click spamming.
|
||||
*
|
||||
* The event parameter is passed automatically by jQuery as the third
|
||||
* argument (after the two preset arguments from $.proxy). It carries
|
||||
* a cascadeChain array that tracks which fields have already been
|
||||
* refreshed in the current cascade, preventing infinite loops when
|
||||
* fields have circular dependsOn declarations.
|
||||
*/
|
||||
FormWidget.prototype.onRefreshDependants = function(fieldName, toRefresh, event) {
|
||||
var self = this,
|
||||
form = this.$el,
|
||||
formEl = this.$form,
|
||||
fieldElements = this.getFieldElements(),
|
||||
cascadeChain = (event && event.cascadeChain) || []
|
||||
|
||||
/*
|
||||
* If this field already appears in the cascade chain, we have
|
||||
* a circular dependency. Stop the cascade to prevent an
|
||||
* infinite loop. See: https://github.com/wintercms/winter/issues/421
|
||||
*/
|
||||
if (cascadeChain.indexOf(fieldName) !== -1) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.dependantUpdateTimers[fieldName] !== undefined) {
|
||||
window.clearTimeout(this.dependantUpdateTimers[fieldName])
|
||||
}
|
||||
|
||||
this.dependantUpdateTimers[fieldName] = window.setTimeout(function() {
|
||||
var refreshData = $.extend({},
|
||||
toRefresh,
|
||||
paramToObj('data-refresh-data', self.options.refreshData)
|
||||
)
|
||||
|
||||
formEl.request(self.options.refreshHandler, {
|
||||
data: refreshData
|
||||
}).success(function() {
|
||||
self.toggleEmptyTabs()
|
||||
|
||||
var newChain = cascadeChain.concat([fieldName])
|
||||
$.each(toRefresh.fields, function(key, field) {
|
||||
var cascadeEvent = $.Event('change')
|
||||
cascadeEvent.cascadeChain = newChain
|
||||
$('[data-field-name="' + field + '"]').trigger(cascadeEvent)
|
||||
})
|
||||
})
|
||||
}, this.dependantUpdateInterval)
|
||||
|
||||
$.each(toRefresh.fields, function(index, field) {
|
||||
fieldElements.filter('[data-field-name="'+field+'"]:visible')
|
||||
.addClass('loading-indicator-container size-form-field')
|
||||
.loadIndicator()
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Render tab form fields once a lazy tab is selected.
|
||||
*/
|
||||
FormWidget.prototype.bindLazyTabs = function() {
|
||||
var tabControl = $('[data-control=tab]', this.$el),
|
||||
tabContainer = $('.nav-tabs', tabControl)
|
||||
|
||||
tabContainer.on('click', '.tab-lazy [data-toggle="tab"]', function() {
|
||||
var $el = $(this),
|
||||
handlerName = $el.data('tab-lazy-handler')
|
||||
|
||||
$.request(handlerName, {
|
||||
data: {
|
||||
target: $el.data('target'),
|
||||
name: $el.data('tab-name'),
|
||||
section: $el.data('tab-section'),
|
||||
},
|
||||
success: function(data) {
|
||||
this.success(data)
|
||||
$el.parent().removeClass('tab-lazy')
|
||||
// Trigger all input presets to populate new fields.
|
||||
setTimeout(function() {
|
||||
$('[data-input-preset]').each(function() {
|
||||
var preset = $(this).data('oc.inputPreset')
|
||||
if (preset && preset.$src) {
|
||||
preset.$src.trigger('input')
|
||||
}
|
||||
})
|
||||
}, 0)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// If initial active tab is lazy loaded, load it immediately
|
||||
if ($('> li.active.tab-lazy', tabContainer).length) {
|
||||
$('> li.active.tab-lazy > [data-toggle="tab"]', tabContainer).trigger('click')
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Unbind the lazy tab handlers
|
||||
*/
|
||||
FormWidget.prototype.unbindLazyTabs = function() {
|
||||
var tabControl = $('[data-control=tab]', this.$el)
|
||||
|
||||
$('.nav-tabs', tabControl).off('click', '.tab-lazy [data-toggle="tab"]')
|
||||
}
|
||||
|
||||
/*
|
||||
* Hides tabs that have no content, it is possible this can be
|
||||
* called multiple times in a single cycle due to input.trigger.
|
||||
*/
|
||||
FormWidget.prototype.toggleEmptyTabs = function() {
|
||||
var self = this,
|
||||
form = this.$el
|
||||
|
||||
if (this.toggleEmptyTabsTimer !== undefined) {
|
||||
window.clearTimeout(this.toggleEmptyTabsTimer)
|
||||
}
|
||||
|
||||
this.toggleEmptyTabsTimer = window.setTimeout(function() {
|
||||
|
||||
var tabControl = $('[data-control=tab]', self.$el),
|
||||
tabContainer = $('.nav-tabs', tabControl)
|
||||
|
||||
if (!tabControl.length || !form || !form.length || !$.contains(form.get(0), tabControl.get(0)))
|
||||
return
|
||||
|
||||
/*
|
||||
* Check each tab pane for form field groups
|
||||
*/
|
||||
$('.tab-pane:not(.lazy)', tabControl).each(function() {
|
||||
$('[data-target="#' + $(this).attr('id') + '"]', tabControl)
|
||||
.closest('li')
|
||||
.toggle(!!$('> .form-group:not(:empty):not(.hide)', $(this)).length)
|
||||
})
|
||||
|
||||
/*
|
||||
* If a hidden tab was selected, select the first visible tab
|
||||
*/
|
||||
if (!$('> li.active:visible', tabContainer).length) {
|
||||
$('> li:visible:first', tabContainer)
|
||||
.find('> a:first')
|
||||
.tab('show')
|
||||
}
|
||||
|
||||
}, 1)
|
||||
}
|
||||
|
||||
/*
|
||||
* Makes sections collapsible by targeting every field after
|
||||
* up until the next section
|
||||
*/
|
||||
FormWidget.prototype.bindCollapsibleSections = function() {
|
||||
$('.section-field[data-field-collapsible]', this.$form)
|
||||
.addClass('collapsed')
|
||||
.find('.field-section:first')
|
||||
.addClass('is-collapsible')
|
||||
.end()
|
||||
.on('click', function() {
|
||||
$(this)
|
||||
.toggleClass('collapsed')
|
||||
.nextUntil('.section-field').toggle()
|
||||
})
|
||||
.nextUntil('.section-field').hide()
|
||||
}
|
||||
|
||||
/*
|
||||
* Unbinds collapsible section handlers
|
||||
*/
|
||||
FormWidget.prototype.unbindCollapsibleSections = function() {
|
||||
$('.section-field[data-field-collapsible]', this.$form).off('click')
|
||||
}
|
||||
|
||||
FormWidget.DEFAULTS = {
|
||||
refreshHandler: null,
|
||||
refreshData: {}
|
||||
}
|
||||
|
||||
// FORM WIDGET PLUGIN DEFINITION
|
||||
// ============================
|
||||
|
||||
var old = $.fn.formWidget
|
||||
|
||||
$.fn.formWidget = function (option) {
|
||||
var args = arguments,
|
||||
result
|
||||
|
||||
this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('oc.formwidget')
|
||||
var options = $.extend({}, FormWidget.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||
if (!data) $this.data('oc.formwidget', (data = new FormWidget(this, options)))
|
||||
if (typeof option == 'string') result = data[option].call($this)
|
||||
if (typeof result != 'undefined') return false
|
||||
})
|
||||
|
||||
return result ? result : this
|
||||
}
|
||||
|
||||
$.fn.formWidget.Constructor = FormWidget
|
||||
|
||||
// FORM WIDGET NO CONFLICT
|
||||
// =================
|
||||
|
||||
$.fn.formWidget.noConflict = function () {
|
||||
$.fn.formWidget = old
|
||||
return this
|
||||
}
|
||||
|
||||
// FORM WIDGET DATA-API
|
||||
// ==============
|
||||
|
||||
function paramToObj(name, value) {
|
||||
if (value === undefined) value = ''
|
||||
if (typeof value == 'object') return value
|
||||
|
||||
try {
|
||||
return ocJSON("{" + value + "}")
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error('Error parsing the '+name+' attribute value. '+e)
|
||||
}
|
||||
}
|
||||
|
||||
$(document).render(function() {
|
||||
$('[data-control="formwidget"]').formWidget();
|
||||
})
|
||||
|
||||
}(window.jQuery);
|
||||
Reference in New Issue
Block a user