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:
424
modules/backend/formwidgets/repeater/assets/js/repeater.js
Normal file
424
modules/backend/formwidgets/repeater/assets/js/repeater.js
Normal file
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* Field Repeater plugin
|
||||
*
|
||||
* Data attributes:
|
||||
* - data-control="fieldrepeater" - enables the plugin on an element
|
||||
* - data-option="value" - an option with a value
|
||||
*
|
||||
* JavaScript API:
|
||||
* $('a#someElement').fieldRepeater({...})
|
||||
*/
|
||||
|
||||
+function ($) { "use strict";
|
||||
|
||||
var Base = $.wn.foundation.base,
|
||||
BaseProto = Base.prototype
|
||||
|
||||
// FIELD REPEATER CLASS DEFINITION
|
||||
// ============================
|
||||
|
||||
var Repeater = function(element, options) {
|
||||
this.options = options
|
||||
this.$el = $(element)
|
||||
if (this.options.sortable) {
|
||||
this.$sortable = $(options.sortableContainer, this.$el)
|
||||
}
|
||||
|
||||
$.wn.foundation.controlUtils.markDisposable(element)
|
||||
Base.call(this)
|
||||
this.init()
|
||||
}
|
||||
|
||||
Repeater.prototype = Object.create(BaseProto)
|
||||
Repeater.prototype.constructor = Repeater
|
||||
|
||||
Repeater.DEFAULTS = {
|
||||
sortableHandle: '.repeater-item-handle',
|
||||
sortableContainer: 'ul.field-repeater-items',
|
||||
titleFrom: null,
|
||||
minItems: null,
|
||||
maxItems: null,
|
||||
sortable: false,
|
||||
mode: 'list',
|
||||
style: 'default',
|
||||
}
|
||||
|
||||
Repeater.prototype.init = function() {
|
||||
if (this.options.sortable) {
|
||||
this.bindSorting()
|
||||
}
|
||||
|
||||
this.$el.on('ajaxDone', '> .field-repeater-items > .field-repeater-item > .repeater-item-remove > [data-repeater-remove]', this.proxy(this.onRemoveItemSuccess))
|
||||
this.$el.on('ajaxDone', '> .field-repeater-items > .field-repeater-add-item > [data-repeater-add]', this.proxy(this.onAddItemSuccess))
|
||||
this.$el.on('click', '> ul > li > .repeater-item-collapse .repeater-item-collapse-one', this.proxy(this.toggleCollapse))
|
||||
this.$el.on('click', '> .field-repeater-items > .field-repeater-add-item > [data-repeater-add-group]', this.proxy(this.clickAddGroupButton))
|
||||
|
||||
this.$el.find('> ul > li > .repeater-item-collapsed-title')
|
||||
.css({'cursor': 'pointer', 'width': '100%'})
|
||||
.on('click', this.proxy(this.toggleCollapse))
|
||||
|
||||
this.$el.find('> ul > li > .field-repeater-form > .form-group > label')
|
||||
.css({'cursor': 'pointer', 'width': '100%'})
|
||||
.on('click', this.proxy(this.toggleCollapse))
|
||||
|
||||
this.$el.one('dispose-control', this.proxy(this.dispose))
|
||||
|
||||
this.togglePrompt()
|
||||
this.applyStyle()
|
||||
}
|
||||
|
||||
Repeater.prototype.dispose = function() {
|
||||
if (this.options.sortable) {
|
||||
this.$sortable.sortable('destroy')
|
||||
}
|
||||
|
||||
this.$el.off('ajaxDone', '> .field-repeater-items > .field-repeater-item > .repeater-item-remove > [data-repeater-remove]', this.proxy(this.onRemoveItemSuccess))
|
||||
this.$el.off('ajaxDone', '> .field-repeater-items > .field-repeater-add-item > [data-repeater-add]', this.proxy(this.onAddItemSuccess))
|
||||
this.$el.off('click', '> ul > li > .repeater-item-collapse .repeater-item-collapse-one', this.proxy(this.toggleCollapse))
|
||||
this.$el.off('click', '> ul > li > .repeater-item-collapsed-title', this.proxy(this.toggleCollapse))
|
||||
this.$el.off('click', '> ul > li > .field-repeater-form > .form-group > label', this.proxy(this.toggleCollapse))
|
||||
this.$el.off('click', '> .field-repeater-items > .field-repeater-add-item > [data-repeater-add-group]', this.proxy(this.clickAddGroupButton))
|
||||
|
||||
this.$el.off('dispose-control', this.proxy(this.dispose))
|
||||
this.$el.removeData('oc.repeater')
|
||||
|
||||
this.$el = null
|
||||
this.$sortable = null
|
||||
this.options = null
|
||||
|
||||
BaseProto.dispose.call(this)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
Repeater.prototype.unbind = function() {
|
||||
this.dispose()
|
||||
}
|
||||
|
||||
Repeater.prototype.bindSorting = function() {
|
||||
var sortableOptions = {
|
||||
handle: this.options.sortableHandle,
|
||||
nested: false,
|
||||
vertical: this.options.mode === 'list',
|
||||
}
|
||||
|
||||
this.$sortable.sortable(sortableOptions)
|
||||
}
|
||||
|
||||
Repeater.prototype.clickAddGroupButton = function(ev) {
|
||||
var $self = this
|
||||
var templateHtml = $('> [data-group-palette-template]', this.$el).html(),
|
||||
$target = $(ev.target),
|
||||
$form = this.$el.closest('form'),
|
||||
$loadContainer = $target.closest('.loading-indicator-container')
|
||||
|
||||
$target.ocPopover({
|
||||
content: templateHtml
|
||||
})
|
||||
|
||||
var $container = $target.data('oc.popover').$container
|
||||
|
||||
// Initialize the scrollpad control in the popup
|
||||
$container.trigger('render')
|
||||
|
||||
$container
|
||||
.on('click', 'a', function (ev) {
|
||||
setTimeout(function() { $(ev.target).trigger('close.oc.popover') }, 1)
|
||||
})
|
||||
.on('ajaxPromise', '[data-repeater-add]', function(ev, context) {
|
||||
$loadContainer.loadIndicator()
|
||||
|
||||
$(window).one('ajaxUpdateComplete', function() {
|
||||
$loadContainer.loadIndicator('hide')
|
||||
$self.togglePrompt()
|
||||
$($self.$el).find('.field-repeater-items > .field-repeater-add-item').each(function () {
|
||||
if ($(this).children().length === 0) {
|
||||
$(this).remove()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
$('[data-repeater-add]', $container).data('request-form', $form)
|
||||
|
||||
// Setup search functionality
|
||||
var $searchInput = $('.repeater-group-search', $container)
|
||||
var $clearButton = $('.repeater-group-search-clear', $container)
|
||||
var $noResults = $('.repeater-group-no-results', $container)
|
||||
var $itemsContainer = $('.repeater-group-items-container', $container)
|
||||
var $listItems = $('.repeater-group-item', $container)
|
||||
|
||||
// Add hover effects to grid items
|
||||
$listItems.on('mouseenter', 'a', function() {
|
||||
$(this).css({
|
||||
'border-color': '#3498db',
|
||||
'box-shadow': '0 2px 8px rgba(0,0,0,0.1)',
|
||||
'transform': 'translateY(-2px)'
|
||||
})
|
||||
}).on('mouseleave', 'a', function() {
|
||||
$(this).css({
|
||||
'border-color': '#e0e0e0',
|
||||
'box-shadow': 'none',
|
||||
'transform': 'translateY(0)'
|
||||
})
|
||||
})
|
||||
|
||||
$searchInput.on('input', function() {
|
||||
var searchTerm = $(this).val().toLowerCase().trim()
|
||||
|
||||
// Show/hide clear button
|
||||
$clearButton.toggle(searchTerm.length > 0)
|
||||
|
||||
if (searchTerm === '') {
|
||||
// Show all items
|
||||
$listItems.show()
|
||||
$noResults.hide()
|
||||
$itemsContainer.show()
|
||||
return
|
||||
}
|
||||
|
||||
var visibleCount = 0
|
||||
|
||||
// Filter items
|
||||
$listItems.each(function() {
|
||||
var $item = $(this)
|
||||
var title = $('.title', $item).text().toLowerCase()
|
||||
var description = $('.description', $item).text().toLowerCase()
|
||||
|
||||
if (title.indexOf(searchTerm) !== -1 || description.indexOf(searchTerm) !== -1) {
|
||||
$item.show()
|
||||
visibleCount++
|
||||
} else {
|
||||
$item.hide()
|
||||
}
|
||||
})
|
||||
|
||||
// Show/hide no results message and adjust container height
|
||||
if (visibleCount === 0) {
|
||||
$noResults.show()
|
||||
$itemsContainer.hide()
|
||||
} else {
|
||||
$noResults.hide()
|
||||
$itemsContainer.show()
|
||||
}
|
||||
})
|
||||
|
||||
// Clear search functionality
|
||||
$clearButton.on('click', function() {
|
||||
$searchInput.val('').trigger('input').focus()
|
||||
})
|
||||
|
||||
// Focus search input when popover opens
|
||||
setTimeout(function() {
|
||||
$searchInput.focus()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
Repeater.prototype.onRemoveItemSuccess = function(ev) {
|
||||
var $target = $(ev.target)
|
||||
|
||||
// Allow any widgets inside a deleted item to be disposed
|
||||
$target.closest('.field-repeater-item').find('[data-disposable]').each(function () {
|
||||
var $elem = $(this),
|
||||
control = $elem.data('control'),
|
||||
widget = $elem.data('oc.' + control)
|
||||
|
||||
if (widget && typeof widget['dispose'] === 'function') {
|
||||
widget.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
$target.closest('[data-field-name]').trigger('change.oc.formwidget')
|
||||
$target.closest('.field-repeater-item').remove()
|
||||
this.togglePrompt()
|
||||
}
|
||||
|
||||
Repeater.prototype.onAddItemSuccess = function(ev) {
|
||||
window.requestAnimationFrame(() => {
|
||||
this.togglePrompt()
|
||||
$(ev.target).closest('[data-field-name]').trigger('change.oc.formwidget')
|
||||
$(this.$el).find('.field-repeater-items > .field-repeater-add-item').each(function () {
|
||||
if ($(this).children().length === 0) {
|
||||
$(this).remove()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Repeater.prototype.togglePrompt = function () {
|
||||
if (this.options.minItems && this.options.minItems > 0) {
|
||||
var repeatedItems = this.$el.find('> .field-repeater-items > .field-repeater-item').length,
|
||||
$removeItemBtn = this.$el.find('> .field-repeater-items > .field-repeater-item > .repeater-item-remove')
|
||||
|
||||
$removeItemBtn.toggleClass('disabled', !(repeatedItems > this.options.minItems))
|
||||
}
|
||||
|
||||
if (this.options.maxItems && this.options.maxItems > 0) {
|
||||
var repeatedItems = this.$el.find('> .field-repeater-items > .field-repeater-item').length,
|
||||
$addItemBtn = this.$el.find('> .field-repeater-items > .field-repeater-add-item')
|
||||
|
||||
$addItemBtn.toggle(repeatedItems < this.options.maxItems)
|
||||
}
|
||||
}
|
||||
|
||||
Repeater.prototype.toggleCollapse = function(ev) {
|
||||
var $item = $(ev.target).closest('.field-repeater-item'),
|
||||
isCollapsed = $item.hasClass('collapsed')
|
||||
|
||||
ev.preventDefault()
|
||||
|
||||
if (this.getStyle() === 'accordion') {
|
||||
if (isCollapsed) {
|
||||
this.expand($item)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (ev.ctrlKey || ev.metaKey) {
|
||||
isCollapsed ? this.expandAll() : this.collapseAll()
|
||||
}
|
||||
else {
|
||||
isCollapsed ? this.expand($item) : this.collapse($item)
|
||||
}
|
||||
}
|
||||
|
||||
Repeater.prototype.collapseAll = function() {
|
||||
var self = this,
|
||||
items = $(this.$el).children('.field-repeater-items').children('.field-repeater-item')
|
||||
|
||||
$.each(items, function(key, item){
|
||||
self.collapse($(item))
|
||||
})
|
||||
}
|
||||
|
||||
Repeater.prototype.expandAll = function() {
|
||||
var self = this,
|
||||
items = $(this.$el).children('.field-repeater-items').children('.field-repeater-item')
|
||||
|
||||
$.each(items, function(key, item){
|
||||
self.expand($(item))
|
||||
})
|
||||
}
|
||||
|
||||
Repeater.prototype.collapse = function($item) {
|
||||
$item.addClass('collapsed')
|
||||
$('.repeater-item-collapsed-title', $item).text(this.getCollapseTitle($item))
|
||||
}
|
||||
|
||||
Repeater.prototype.expand = function($item) {
|
||||
if (this.getStyle() === 'accordion') {
|
||||
this.collapseAll()
|
||||
}
|
||||
$item.removeClass('collapsed')
|
||||
}
|
||||
|
||||
Repeater.prototype.getCollapseTitle = function($item) {
|
||||
var $target,
|
||||
defaultText = '',
|
||||
explicitText = $item.data('collapse-title'),
|
||||
selector = 'input[type=text], select:first, ul:first'
|
||||
|
||||
if (explicitText) {
|
||||
return explicitText
|
||||
}
|
||||
|
||||
if (this.options.titleFrom) {
|
||||
$target = $('[data-field-name="'+this.options.titleFrom+'"]', $item)
|
||||
if ($target.length) {
|
||||
selector = 'input, select:first, ul:first'
|
||||
}
|
||||
}
|
||||
if (!$target || !$target.length) {
|
||||
$target = $item
|
||||
}
|
||||
|
||||
var $textInput = $(selector, $target).first()
|
||||
if ($textInput.length) {
|
||||
switch($textInput.prop("tagName")) {
|
||||
case 'SELECT':
|
||||
return $textInput.find('option:selected').text()
|
||||
case 'UL':
|
||||
return $textInput.find('li.active').text()
|
||||
default:
|
||||
return $textInput.val()
|
||||
}
|
||||
} else {
|
||||
var $disabledTextInput = $('.form-control:first', $target)
|
||||
if ($disabledTextInput.length) {
|
||||
return $disabledTextInput.text()
|
||||
}
|
||||
}
|
||||
|
||||
return defaultText
|
||||
}
|
||||
|
||||
Repeater.prototype.getStyle = function() {
|
||||
var style = 'default'
|
||||
|
||||
// Validate style
|
||||
if (this.options.style && ['collapsed', 'accordion'].indexOf(this.options.style) !== -1) {
|
||||
style = this.options.style
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
|
||||
Repeater.prototype.applyStyle = function() {
|
||||
if (this.options.mode === 'grid') {
|
||||
return
|
||||
}
|
||||
|
||||
var style = this.getStyle(),
|
||||
self = this,
|
||||
items = $(this.$el).children('.field-repeater-items').children('.field-repeater-item')
|
||||
|
||||
$.each(items, function(key, item) {
|
||||
switch (style) {
|
||||
case 'collapsed':
|
||||
self.collapse($(item))
|
||||
break
|
||||
case 'accordion':
|
||||
if (key !== 0) {
|
||||
self.collapse($(item))
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// FIELD REPEATER PLUGIN DEFINITION
|
||||
// ============================
|
||||
|
||||
var old = $.fn.fieldRepeater
|
||||
|
||||
$.fn.fieldRepeater = function (option) {
|
||||
var args = Array.prototype.slice.call(arguments, 1), result
|
||||
this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('oc.repeater')
|
||||
var options = $.extend({}, Repeater.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||
if (!data) $this.data('oc.repeater', (data = new Repeater(this, options)))
|
||||
if (typeof option == 'string') result = data[option].apply(data, args)
|
||||
if (typeof result != 'undefined') return false
|
||||
})
|
||||
|
||||
return result ? result : this
|
||||
}
|
||||
|
||||
$.fn.fieldRepeater.Constructor = Repeater
|
||||
|
||||
// FIELD REPEATER NO CONFLICT
|
||||
// =================
|
||||
|
||||
$.fn.fieldRepeater.noConflict = function () {
|
||||
$.fn.fieldRepeater = old
|
||||
return this
|
||||
}
|
||||
|
||||
// FIELD REPEATER DATA-API
|
||||
// ===============
|
||||
|
||||
$(document).render(function() {
|
||||
$('[data-control="fieldrepeater"]').fieldRepeater()
|
||||
})
|
||||
|
||||
}(window.jQuery);
|
||||
Reference in New Issue
Block a user