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:
@@ -0,0 +1,538 @@
|
||||
/*
|
||||
* Drop-down cell processor for the table control.
|
||||
*/
|
||||
|
||||
/*
|
||||
* TODO: implement the search
|
||||
*/
|
||||
|
||||
+function ($) { "use strict";
|
||||
|
||||
// NAMESPACE CHECK
|
||||
// ============================
|
||||
|
||||
if ($.wn.table === undefined)
|
||||
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
|
||||
|
||||
if ($.wn.table.processor === undefined)
|
||||
throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");
|
||||
|
||||
// CLASS DEFINITION
|
||||
// ============================
|
||||
|
||||
var Base = $.wn.table.processor.base,
|
||||
BaseProto = Base.prototype
|
||||
|
||||
var DropdownProcessor = function(tableObj, columnName, columnConfiguration) {
|
||||
//
|
||||
// State properties
|
||||
//
|
||||
|
||||
this.itemListElement = null
|
||||
|
||||
this.cachedOptionPromises = {}
|
||||
this.searching = false
|
||||
this.searchQuery = null
|
||||
this.searchInterval = null
|
||||
|
||||
// Event handlers
|
||||
this.itemClickHandler = this.onItemClick.bind(this)
|
||||
this.itemKeyDownHandler = this.onItemKeyDown.bind(this)
|
||||
this.itemMouseMoveHandler = this.onItemMouseMove.bind(this)
|
||||
|
||||
//
|
||||
// Parent constructor
|
||||
//
|
||||
|
||||
Base.call(this, tableObj, columnName, columnConfiguration)
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype = Object.create(BaseProto)
|
||||
DropdownProcessor.prototype.constructor = DropdownProcessor
|
||||
|
||||
DropdownProcessor.prototype.dispose = function() {
|
||||
this.unregisterListHandlers()
|
||||
this.itemClickHandler = null
|
||||
this.itemKeyDownHandler = null
|
||||
this.itemMouseMoveHandler = null
|
||||
this.itemListElement = null
|
||||
this.cachedOptionPromises = null
|
||||
BaseProto.dispose.call(this)
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype.unregisterListHandlers = function() {
|
||||
if (this.itemListElement)
|
||||
{
|
||||
// This processor binds custom click handler to the item list,
|
||||
// the standard registerHandlers/unregisterHandlers functionality
|
||||
// can't be used here because the element belongs to the document
|
||||
// body, not to the table.
|
||||
this.itemListElement.removeEventListener('click', this.itemClickHandler)
|
||||
this.itemListElement.removeEventListener('keydown', this.itemKeyDownHandler)
|
||||
this.itemListElement.removeEventListener('mousemove', this.itemMouseMoveHandler)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Renders the cell in the normal (no edit) mode
|
||||
*/
|
||||
DropdownProcessor.prototype.renderCell = function(value, cellContentContainer) {
|
||||
let viewContainer;
|
||||
|
||||
if (this.columnConfiguration.readonly || this.columnConfiguration.readOnly) {
|
||||
viewContainer = this.createViewContainer(cellContentContainer, value)
|
||||
|
||||
cellContentContainer.classList.add('readonly');
|
||||
cellContentContainer.setAttribute('tabindex', 0);
|
||||
return;
|
||||
}
|
||||
|
||||
viewContainer = this.createViewContainer(cellContentContainer, '...')
|
||||
|
||||
this.fetchOptions(cellContentContainer.parentNode, function renderCellFetchOptions(options) {
|
||||
if (options[value] !== undefined)
|
||||
viewContainer.textContent = options[value]
|
||||
|
||||
cellContentContainer.setAttribute('tabindex', 0)
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* This method is called when the cell managed by the processor
|
||||
* is focused (clicked or navigated with the keyboard).
|
||||
*/
|
||||
DropdownProcessor.prototype.onFocus = function(cellElement, isClick) {
|
||||
if (this.activeCell === cellElement) {
|
||||
this.showDropdown()
|
||||
return
|
||||
}
|
||||
|
||||
this.activeCell = cellElement
|
||||
if (!this.columnConfiguration.readonly && !this.columnConfiguration.readOnly) {
|
||||
var cellContentContainer = this.getCellContentContainer(cellElement)
|
||||
this.buildEditor(cellElement, cellContentContainer, isClick)
|
||||
|
||||
if (!isClick) {
|
||||
cellContentContainer.focus()
|
||||
}
|
||||
} else {
|
||||
this.getCellContentContainer(cellElement).focus()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Forces the processor to hide the editor when the user navigates
|
||||
* away from the cell. Processors can update the sell value in this method.
|
||||
* Processors must clear the reference to the active cell in this method.
|
||||
*/
|
||||
DropdownProcessor.prototype.onUnfocus = function() {
|
||||
if (!this.activeCell)
|
||||
return
|
||||
|
||||
this.unregisterListHandlers()
|
||||
|
||||
this.hideDropdown()
|
||||
this.itemListElement = null
|
||||
this.activeCell = null
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype.buildEditor = function(cellElement, cellContentContainer, isClick) {
|
||||
// Create the select control
|
||||
var currentValue = this.tableObj.getCellValue(cellElement),
|
||||
containerPosition = this.getAbsolutePosition(cellContentContainer),
|
||||
self = this
|
||||
|
||||
this.itemListElement = document.createElement('div')
|
||||
|
||||
this.itemListElement.addEventListener('click', this.itemClickHandler)
|
||||
this.itemListElement.addEventListener('keydown', this.itemKeyDownHandler)
|
||||
this.itemListElement.addEventListener('mousemove', this.itemMouseMoveHandler)
|
||||
|
||||
this.itemListElement.setAttribute('class', 'table-control-dropdown-list')
|
||||
this.itemListElement.style.width = cellContentContainer.offsetWidth + 'px'
|
||||
this.itemListElement.style.left = containerPosition.left + 'px'
|
||||
this.itemListElement.style.top = containerPosition.top - 2 + cellContentContainer.offsetHeight + 'px'
|
||||
|
||||
this.fetchOptions(cellElement, function renderCellFetchOptions(options) {
|
||||
var listElement = document.createElement('ul')
|
||||
|
||||
for (var value in options) {
|
||||
var itemElement = document.createElement('li')
|
||||
itemElement.setAttribute('data-value', value)
|
||||
itemElement.textContent = options[value]
|
||||
itemElement.setAttribute('tabindex', 0)
|
||||
|
||||
if (value == currentValue)
|
||||
itemElement.setAttribute('class', 'selected')
|
||||
|
||||
listElement.appendChild(itemElement)
|
||||
}
|
||||
|
||||
self.itemListElement.appendChild(listElement)
|
||||
|
||||
if (isClick)
|
||||
self.showDropdown()
|
||||
|
||||
self = null
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Hide the drop-down, but don't delete it.
|
||||
*/
|
||||
DropdownProcessor.prototype.hideDropdown = function() {
|
||||
if (this.itemListElement && this.activeCell && this.itemListElement.parentNode) {
|
||||
var cellContentContainer = this.getCellContentContainer(this.activeCell)
|
||||
cellContentContainer.setAttribute('data-dropdown-open', 'false')
|
||||
|
||||
this.itemListElement.parentNode.removeChild(this.itemListElement)
|
||||
|
||||
cellContentContainer.focus()
|
||||
}
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype.showDropdown = function() {
|
||||
if (this.itemListElement && this.itemListElement.parentNode !== document.body) {
|
||||
this.getCellContentContainer(this.activeCell).setAttribute('data-dropdown-open', 'true')
|
||||
document.body.appendChild(this.itemListElement)
|
||||
|
||||
var activeItemElement = this.itemListElement.querySelector('ul li.selected')
|
||||
|
||||
if (!activeItemElement) {
|
||||
activeItemElement = this.itemListElement.querySelector('ul li:first-child')
|
||||
|
||||
if (activeItemElement)
|
||||
activeItemElement.setAttribute('class', 'selected')
|
||||
}
|
||||
|
||||
if (activeItemElement) {
|
||||
window.setTimeout(function(){
|
||||
activeItemElement.focus()
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype.fetchOptions = function(cellElement, onSuccess) {
|
||||
if (this.columnConfiguration.options) {
|
||||
onSuccess(this.columnConfiguration.options)
|
||||
}
|
||||
else {
|
||||
// If options are not provided and not found in the cache,
|
||||
// request them from the server. For dependent drop-downs
|
||||
// the caching key contains the master column values.
|
||||
|
||||
var row = cellElement.parentNode,
|
||||
cachingKey = this.createOptionsCachingKey(row),
|
||||
viewContainer = this.getViewContainer(cellElement)
|
||||
|
||||
// Request options from the server. When the table widget builds,
|
||||
// multiple cells in the column could require loading the options.
|
||||
// The AJAX promises are cached here so that we have a single
|
||||
// request per caching key.
|
||||
|
||||
viewContainer.setAttribute('class', 'loading')
|
||||
|
||||
if (!this.cachedOptionPromises[cachingKey]) {
|
||||
var requestData = {
|
||||
column: this.columnName,
|
||||
rowData: this.tableObj.getRowData(row)
|
||||
},
|
||||
handlerName = this.tableObj.getAlias()+'::onGetDropdownOptions'
|
||||
|
||||
this.cachedOptionPromises[cachingKey] = this.tableObj.$el.request(handlerName, {data: requestData})
|
||||
}
|
||||
|
||||
this.cachedOptionPromises[cachingKey].done(function onDropDownLoadOptionsSuccess(data){
|
||||
onSuccess(data.options)
|
||||
}).always(function onDropDownLoadOptionsAlways(){
|
||||
viewContainer.setAttribute('class', '')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype.createOptionsCachingKey = function(row) {
|
||||
var cachingKey = 'non-dependent',
|
||||
dependsOn = this.columnConfiguration.dependsOn
|
||||
|
||||
if (dependsOn) {
|
||||
if (typeof dependsOn == 'object') {
|
||||
for (var i = 0, len = dependsOn.length; i < len; i++ )
|
||||
cachingKey += dependsOn[i] + this.tableObj.getRowCellValueByColumnName(row, dependsOn[i])
|
||||
} else
|
||||
cachingKey = dependsOn + this.tableObj.getRowCellValueByColumnName(row, dependsOn)
|
||||
}
|
||||
|
||||
return cachingKey
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype.getAbsolutePosition = function(element) {
|
||||
// TODO: use the foundation library
|
||||
|
||||
var top = document.body.scrollTop,
|
||||
left = 0
|
||||
|
||||
do {
|
||||
top += element.offsetTop || 0;
|
||||
top -= element.scrollTop || 0;
|
||||
left += element.offsetLeft || 0;
|
||||
element = element.offsetParent;
|
||||
} while(element)
|
||||
|
||||
return {
|
||||
top: top,
|
||||
left: left
|
||||
}
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype.updateCellFromFocusedItem = function(focusedItem) {
|
||||
if (!focusedItem) {
|
||||
focusedItem = this.findFocusedItem();
|
||||
}
|
||||
this.setSelectedItem(focusedItem);
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype.findSelectedItem = function() {
|
||||
if (this.itemListElement)
|
||||
return this.itemListElement.querySelector('ul li.selected')
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype.setSelectedItem = function(item) {
|
||||
if (!this.itemListElement)
|
||||
return null;
|
||||
|
||||
if (item.tagName == 'LI' && this.itemListElement.contains(item)) {
|
||||
this.itemListElement.querySelectorAll('ul li').forEach(function (option) {
|
||||
option.removeAttribute('class');
|
||||
});
|
||||
item.setAttribute('class', 'selected');
|
||||
}
|
||||
|
||||
this.tableObj.setCellValue(this.activeCell, item.getAttribute('data-value'))
|
||||
this.setViewContainerValue(this.activeCell, item.textContent)
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype.findFocusedItem = function() {
|
||||
if (this.itemListElement)
|
||||
return this.itemListElement.querySelector('ul li:focus')
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype.onItemClick = function(ev) {
|
||||
var target = this.tableObj.getEventTarget(ev)
|
||||
|
||||
if (target.tagName == 'LI') {
|
||||
target.focus();
|
||||
this.updateCellFromFocusedItem(target)
|
||||
this.hideDropdown()
|
||||
}
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype.onItemKeyDown = function(ev) {
|
||||
if (!this.itemListElement)
|
||||
return
|
||||
|
||||
if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp')
|
||||
{
|
||||
// Up or down keys - find previous/next list item and select it
|
||||
var focused = this.findFocusedItem(),
|
||||
newFocusedItem = focused.nextElementSibling
|
||||
|
||||
if (ev.key === 'ArrowUp')
|
||||
newFocusedItem = focused.previousElementSibling
|
||||
|
||||
if (newFocusedItem) {
|
||||
newFocusedItem.focus()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (ev.key === 'Enter' || ev.key === '(Space character)' || ev.key === 'Spacebar' || ev.key === ' ') {
|
||||
// Return or space keys - update the selected value and hide the editor
|
||||
this.updateCellFromFocusedItem()
|
||||
this.hideDropdown()
|
||||
return
|
||||
}
|
||||
|
||||
if (ev.key === 'Tab') {
|
||||
// Tab - update the selected value and pass control to the table navigation
|
||||
this.updateCellFromFocusedItem()
|
||||
this.tableObj.navigation.navigateNext(ev)
|
||||
this.tableObj.stopEvent(ev)
|
||||
return
|
||||
}
|
||||
|
||||
if (ev.key === 'Escape') {
|
||||
// Esc - hide the drop-down
|
||||
this.hideDropdown()
|
||||
return
|
||||
}
|
||||
|
||||
this.searchByTextInput(ev, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Event handler for mouse movements over options in the dropdown menu
|
||||
*/
|
||||
DropdownProcessor.prototype.onItemMouseMove = function(ev) {
|
||||
if (!this.itemListElement)
|
||||
return
|
||||
|
||||
var target = this.tableObj.getEventTarget(ev)
|
||||
|
||||
if (target.tagName == 'LI') {
|
||||
target.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Event handler for the keydown event. The table class calls this method
|
||||
* for all processors.
|
||||
*/
|
||||
DropdownProcessor.prototype.onKeyDown = function(ev) {
|
||||
if (!this.itemListElement)
|
||||
return
|
||||
|
||||
if ((ev.key === '(Space character)' || ev.key === 'Spacebar' || ev.key === ' ') && !this.searching) { // Spacebar
|
||||
this.showDropdown()
|
||||
} else if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') { // Up and down arrow keys
|
||||
var selected = this.findSelectedItem(),
|
||||
newSelectedItem;
|
||||
|
||||
if (!selected) {
|
||||
if (ev.key === 'ArrowUp') {
|
||||
// Only show an initial item when the down array key is pressed
|
||||
return false
|
||||
}
|
||||
newSelectedItem = this.itemListElement.querySelector('ul li:first-child')
|
||||
} else {
|
||||
newSelectedItem = selected.nextElementSibling
|
||||
|
||||
if (ev.key === 'ArrowUp')
|
||||
newSelectedItem = selected.previousElementSibling
|
||||
}
|
||||
|
||||
if (newSelectedItem) {
|
||||
this.setSelectedItem(newSelectedItem);
|
||||
}
|
||||
|
||||
return false // Stop propogation of event
|
||||
} else {
|
||||
this.searchByTextInput(ev);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This method is called when a cell value in the row changes.
|
||||
*/
|
||||
DropdownProcessor.prototype.onRowValueChanged = function(columnName, cellElement) {
|
||||
// Determine if this drop-down depends on the changed column
|
||||
// and update the option list if necessary
|
||||
|
||||
// TODO: setting drop-down values with table.setRowValues() is not implemented currently
|
||||
|
||||
if (!this.columnConfiguration.dependsOn)
|
||||
return
|
||||
|
||||
var dependsOnColumn = false,
|
||||
dependsOn = this.columnConfiguration.dependsOn
|
||||
|
||||
if (typeof dependsOn == 'object') {
|
||||
for (var i = 0, len = dependsOn.length; i < len; i++ ) {
|
||||
if (dependsOn[i] == columnName) {
|
||||
dependsOnColumn = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
dependsOnColumn = dependsOn == columnName
|
||||
}
|
||||
|
||||
if (!dependsOnColumn)
|
||||
return
|
||||
|
||||
var currentValue = this.tableObj.getCellValue(cellElement),
|
||||
viewContainer = this.getViewContainer(cellElement)
|
||||
|
||||
this.fetchOptions(cellElement, function rowValueChangedFetchOptions(options) {
|
||||
var value = options[currentValue] !== undefined
|
||||
? options[currentValue]
|
||||
: '...'
|
||||
|
||||
viewContainer.textContent = value
|
||||
viewContainer = null
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Determines whether the specified element is some element created by the
|
||||
* processor.
|
||||
*/
|
||||
DropdownProcessor.prototype.elementBelongsToProcessor = function(element) {
|
||||
if (!this.itemListElement)
|
||||
return false
|
||||
|
||||
return this.tableObj.parentContainsElement(this.itemListElement, element)
|
||||
}
|
||||
|
||||
/*
|
||||
* Provides auto-complete like functionality for typing in a query and selecting
|
||||
* a matching list option
|
||||
*/
|
||||
DropdownProcessor.prototype.searchByTextInput = function(ev, focusOnly) {
|
||||
if (focusOnly === undefined) {
|
||||
focusOnly = false;
|
||||
}
|
||||
var character = ev.key;
|
||||
|
||||
if (character.length === 1 || character === 'Space') {
|
||||
if (!this.searching) {
|
||||
this.searching = true;
|
||||
this.searchQuery = '';
|
||||
}
|
||||
|
||||
this.searchQuery += (character === 'Space') ? ' ' : character;
|
||||
|
||||
// Search for a valid option in dropdown
|
||||
var validItem = null;
|
||||
var query = this.searchQuery;
|
||||
|
||||
this.itemListElement.querySelectorAll('ul li').forEach(function(item) {
|
||||
if (validItem === null && item.dataset.value && item.dataset.value.toLowerCase().indexOf(query.toLowerCase()) === 0) {
|
||||
validItem = item;
|
||||
}
|
||||
});
|
||||
|
||||
if (validItem) {
|
||||
// If a valid item is found, select item and allow for fine-tuning the search query
|
||||
if (focusOnly === true) {
|
||||
validItem.focus();
|
||||
} else {
|
||||
this.setSelectedItem(validItem);
|
||||
}
|
||||
|
||||
if (this.searchInterval) {
|
||||
clearTimeout(this.searchInterval);
|
||||
}
|
||||
|
||||
this.searchInterval = setTimeout(this.cancelTextSearch.bind(this), 1000);
|
||||
} else {
|
||||
this.cancelTextSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DropdownProcessor.prototype.cancelTextSearch = function() {
|
||||
this.searching = false;
|
||||
this.searchQuery = null;
|
||||
this.searchInterval = null;
|
||||
}
|
||||
|
||||
|
||||
$.wn.table.processor.dropdown = DropdownProcessor;
|
||||
}(window.jQuery);
|
||||
Reference in New Issue
Block a user