(function () {
'use strict';
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
_setPrototypeOf(subClass, superClass);
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
return true;
} catch (e) {
return false;
}
}
function _construct(Parent, args, Class) {
if (_isNativeReflectConstruct()) {
_construct = Reflect.construct;
} else {
_construct = function _construct(Parent, args, Class) {
var a = [null];
a.push.apply(a, args);
var Constructor = Function.bind.apply(Parent, a);
var instance = new Constructor();
if (Class) _setPrototypeOf(instance, Class.prototype);
return instance;
};
}
return _construct.apply(null, arguments);
}
function _isNativeFunction(fn) {
return Function.toString.call(fn).indexOf("[native code]") !== -1;
}
function _wrapNativeSuper(Class) {
var _cache = typeof Map === "function" ? new Map() : undefined;
_wrapNativeSuper = function _wrapNativeSuper(Class) {
if (Class === null || !_isNativeFunction(Class)) return Class;
if (typeof Class !== "function") {
throw new TypeError("Super expression must either be null or a function");
}
if (typeof _cache !== "undefined") {
if (_cache.has(Class)) return _cache.get(Class);
_cache.set(Class, Wrapper);
}
function Wrapper() {
return _construct(Class, arguments, _getPrototypeOf(this).constructor);
}
Wrapper.prototype = Object.create(Class.prototype, {
constructor: {
value: Wrapper,
enumerable: false,
writable: true,
configurable: true
}
});
return _setPrototypeOf(Wrapper, Class);
};
return _wrapNativeSuper(Class);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
/**
* @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
(function (customElements) {
var KEYCODE = {
SPACE: 32,
ESC: 27,
ENTER: 13
};
/**
* Helper for testing whether a selection modifier is pressed
* @param {Event} event
*
* @returns {boolean|*}
*/
function hasModifier(event) {
return event.ctrlKey || event.metaKey || event.shiftKey;
}
var JoomlaFieldSubform = /*#__PURE__*/function (_HTMLElement) {
_inheritsLoose(JoomlaFieldSubform, _HTMLElement);
function JoomlaFieldSubform() {
var _this;
_this = _HTMLElement.call(this) || this;
var that = _assertThisInitialized(_this); // Get the rows container
_this.containerWithRows = _assertThisInitialized(_this);
if (_this.rowsContainer) {
var allContainers = _this.querySelectorAll(_this.rowsContainer); // Find closest, and exclude nested
Array.from(allContainers).forEach(function (container) {
if (container.closest('joomla-field-subform') === _assertThisInitialized(_this)) {
_this.containerWithRows = container;
}
});
} // Keep track of row index, this is important to avoid a name duplication
// Note: php side should reset the indexes each time, eg: $value = array_values($value);
_this.lastRowIndex = _this.getRows().length - 1; // Template for the repeating group
_this.template = ''; // Prepare a row template, and find available field names
_this.prepareTemplate(); // Bind buttons
if (_this.buttonAdd || _this.buttonRemove) {
_this.addEventListener('click', function (event) {
var btnAdd = null;
var btnRem = null;
if (that.buttonAdd) {
btnAdd = event.target.matches(that.buttonAdd) ? event.target : event.target.closest(that.buttonAdd);
}
if (that.buttonRemove) {
btnRem = event.target.matches(that.buttonRemove) ? event.target : event.target.closest(that.buttonRemove);
} // Check active, with extra check for nested joomla-field-subform
if (btnAdd && btnAdd.closest('joomla-field-subform') === that) {
var row = btnAdd.closest(that.repeatableElement);
row = row && row.closest('joomla-field-subform') === that ? row : null;
that.addRow(row);
event.preventDefault();
} else if (btnRem && btnRem.closest('joomla-field-subform') === that) {
var _row = btnRem.closest(that.repeatableElement);
that.removeRow(_row);
event.preventDefault();
}
});
_this.addEventListener('keydown', function (event) {
if (event.keyCode !== KEYCODE.SPACE) return;
var isAdd = that.buttonAdd && event.target.matches(that.buttonAdd);
var isRem = that.buttonRemove && event.target.matches(that.buttonRemove);
if ((isAdd || isRem) && event.target.closest('joomla-field-subform') === that) {
var row = event.target.closest(that.repeatableElement);
row = row && row.closest('joomla-field-subform') === that ? row : null;
if (isRem && row) {
that.removeRow(row);
} else if (isAdd) {
that.addRow(row);
}
event.preventDefault();
}
});
} // Sorting
if (_this.buttonMove) {
_this.setUpDragSort();
}
return _this;
}
/**
* Search for existing rows
* @returns {HTMLElement[]}
*/
var _proto = JoomlaFieldSubform.prototype;
_proto.getRows = function getRows() {
var _this2 = this;
var rows = Array.from(this.containerWithRows.children);
var result = []; // Filter out the rows
rows.forEach(function (row) {
if (row.matches(_this2.repeatableElement)) {
result.push(row);
}
});
return result;
}
/**
* Prepare a row template
*/
;
_proto.prepareTemplate = function prepareTemplate() {
var tmplElement = [].slice.call(this.children).filter(function (el) {
return el.classList.contains('subform-repeatable-template-section');
});
if (tmplElement[0]) {
this.template = tmplElement[0].innerHTML;
}
if (!this.template) {
throw new Error('The row template is required for the subform element to work');
}
}
/**
* Add new row
* @param {HTMLElement} after
* @returns {HTMLElement}
*/
;
_proto.addRow = function addRow(after) {
// Count how many we already have
var count = this.getRows().length;
if (count >= this.maximum) {
return null;
} // Make a new row from the template
var tmpEl;
if (this.containerWithRows.nodeName === 'TBODY' || this.containerWithRows.nodeName === 'TABLE') {
tmpEl = document.createElement('tbody');
} else {
tmpEl = document.createElement('div');
}
tmpEl.innerHTML = this.template;
var row = tmpEl.children[0]; // Add to container
if (after) {
after.parentNode.insertBefore(row, after.nextSibling);
} else {
this.containerWithRows.append(row);
} // Add draggable attributes
if (this.buttonMove) {
row.setAttribute('draggable', 'false');
row.setAttribute('aria-grabbed', 'false');
row.setAttribute('tabindex', '0');
} // Marker that it is new
row.setAttribute('data-new', '1'); // Fix names and ids, and reset values
this.fixUniqueAttributes(row, count); // Tell about the new row
this.dispatchEvent(new CustomEvent('subform-row-add', {
detail: {
row: row
},
bubbles: true
}));
row.dispatchEvent(new CustomEvent('joomla:updated', {
bubbles: true,
cancelable: true
}));
return row;
}
/**
* Remove the row
* @param {HTMLElement} row
*/
;
_proto.removeRow = function removeRow(row) {
// Count how much we have
var count = this.getRows().length;
if (count <= this.minimum) {
return;
} // Tell about the row will be removed
this.dispatchEvent(new CustomEvent('subform-row-remove', {
detail: {
row: row
},
bubbles: true
}));
row.dispatchEvent(new CustomEvent('joomla:removed', {
bubbles: true,
cancelable: true
}));
row.parentNode.removeChild(row);
}
/**
* Fix name and id for fields that are in the row
* @param {HTMLElement} row
* @param {Number} count
*/
;
_proto.fixUniqueAttributes = function fixUniqueAttributes(row, count) {
var _this3 = this;
var countTmp = count || 0;
var group = row.getAttribute('data-group'); // current group name
var basename = row.getAttribute('data-base-name');
var countnew = Math.max(this.lastRowIndex, countTmp);
var groupnew = basename + countnew; // new group name
this.lastRowIndex = countnew + 1;
row.setAttribute('data-group', groupnew); // Fix inputs that have a "name" attribute
var haveName = row.querySelectorAll('[name]');
var ids = {}; // Collect id for fix checkboxes and radio
// Filter out nested
haveName = [].slice.call(haveName).filter(function (el) {
return el.closest('joomla-field-subform') === _this3;
});
haveName.forEach(function (elem) {
var $el = elem;
var name = $el.getAttribute('name');
var aria = $el.getAttribute('aria-describedby');
var id = name.replace(/(\[\]$)/g, '').replace(/(\]\[)/g, '__').replace(/\[/g, '_').replace(/\]/g, ''); // id from name
var nameNew = name.replace("[" + group + "][", "[" + groupnew + "]["); // New name
var idNew = id.replace(group, groupnew).replace(/\W/g, '_'); // Count new id
var countMulti = 0; // count for multiple radio/checkboxes
var forOldAttr = id; // Fix "for" in the labels
if ($el.type === 'checkbox' && name.match(/\[\]$/)) {
// <input type="checkbox" name="name[]"> fix
// Recount id
countMulti = ids[id] ? ids[id].length : 0;
if (!countMulti) {
// Set the id for fieldset and group label
var fieldset = $el.closest('fieldset.checkboxes');
var elLbl = row.querySelector("label[for=\"" + id + "\"]");
if (fieldset) {
fieldset.setAttribute('id', idNew);
}
if (elLbl) {
elLbl.setAttribute('for', idNew);
elLbl.setAttribute('id', idNew + "-lbl");
}
}
forOldAttr += countMulti;
idNew += countMulti;
} else if ($el.type === 'radio') {
// <input type="radio"> fix
// Recount id
countMulti = ids[id] ? ids[id].length : 0;
if (!countMulti) {
// Set the id for fieldset and group label
var _fieldset = $el.closest('fieldset.radio');
var _elLbl = row.querySelector("label[for=\"" + id + "\"]");
if (_fieldset) {
_fieldset.setAttribute('id', idNew);
}
if (_elLbl) {
_elLbl.setAttribute('for', idNew);
_elLbl.setAttribute('id', idNew + "-lbl");
}
}
forOldAttr += countMulti;
idNew += countMulti;
} // Cache already used id
if (ids[id]) {
ids[id].push(true);
} else {
ids[id] = [true];
} // Replace the name to new one
$el.name = nameNew;
if ($el.id) {
$el.id = idNew;
}
if (aria) {
$el.setAttribute('aria-describedby', nameNew + "-desc");
} // Check if there is a label for this input
var lbl = row.querySelector("label[for=\"" + forOldAttr + "\"]");
if (lbl) {
lbl.setAttribute('for', idNew);
lbl.setAttribute('id', idNew + "-lbl");
}
});
}
/**
* Use of HTML Drag and Drop API
* https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
* https://www.sitepoint.com/accessible-drag-drop/
*/
;
_proto.setUpDragSort = function setUpDragSort() {
var that = this; // Self reference
var item = null; // Storing the selected item
var touched = false; // We have a touch events
// Find all existing rows and add draggable attributes
var rows = Array.from(this.getRows());
rows.forEach(function (row) {
row.setAttribute('draggable', 'false');
row.setAttribute('aria-grabbed', 'false');
row.setAttribute('tabindex', '0');
}); // Helper method to test whether Handler was clicked
function getMoveHandler(element) {
return !element.form // This need to test whether the element is :input
&& element.matches(that.buttonMove) ? element : element.closest(that.buttonMove);
} // Helper method to move row to selected position
function switchRowPositions(src, dest) {
var isRowBefore = false;
if (src.parentNode === dest.parentNode) {
for (var cur = src; cur; cur = cur.previousSibling) {
if (cur === dest) {
isRowBefore = true;
break;
}
}
}
if (isRowBefore) {
dest.parentNode.insertBefore(src, dest);
} else {
dest.parentNode.insertBefore(src, dest.nextSibling);
}
}
/**
* Touch interaction:
*
* - a touch of "move button" marks a row draggable / "selected",
* or deselect previous selected
*
* - a touch of "move button" in the destination row will move
* a selected row to a new position
*/
this.addEventListener('touchstart', function (event) {
touched = true; // Check for .move button
var handler = getMoveHandler(event.target);
var row = handler ? handler.closest(that.repeatableElement) : null;
if (!row || row.closest('joomla-field-subform') !== that) {
return;
} // First selection
if (!item) {
row.setAttribute('draggable', 'true');
row.setAttribute('aria-grabbed', 'true');
item = row;
} else {
// Second selection
// Move to selected position
if (row !== item) {
switchRowPositions(item, row);
}
item.setAttribute('draggable', 'false');
item.setAttribute('aria-grabbed', 'false');
item = null;
}
event.preventDefault();
}); // Mouse interaction
// - mouse down, enable "draggable" and allow to drag the row,
// - mouse up, disable "draggable"
this.addEventListener('mousedown', function (_ref) {
var target = _ref.target;
if (touched) return; // Check for .move button
var handler = getMoveHandler(target);
var row = handler ? handler.closest(that.repeatableElement) : null;
if (!row || row.closest('joomla-field-subform') !== that) {
return;
}
row.setAttribute('draggable', 'true');
row.setAttribute('aria-grabbed', 'true');
item = row;
});
this.addEventListener('mouseup', function () {
if (item && !touched) {
item.setAttribute('draggable', 'false');
item.setAttribute('aria-grabbed', 'false');
item = null;
}
}); // Keyboard interaction
// - "tab" to navigate to needed row,
// - modifier (ctr,alt,shift) + "space" select the row,
// - "tab" to select destination,
// - "enter" to place selected row in to destination
// - "esc" to cancel selection
this.addEventListener('keydown', function (event) {
if (event.keyCode !== KEYCODE.ESC && event.keyCode !== KEYCODE.SPACE && event.keyCode !== KEYCODE.ENTER || event.target.form || !event.target.matches(that.repeatableElement)) {
return;
}
var row = event.target; // Make sure we handle correct children
if (!row || row.closest('joomla-field-subform') !== that) {
return;
} // Space is the selection or unselection keystroke
if (event.keyCode === KEYCODE.SPACE && hasModifier(event)) {
// Unselect previously selected
if (row.getAttribute('aria-grabbed') === 'true') {
row.setAttribute('draggable', 'false');
row.setAttribute('aria-grabbed', 'false');
item = null;
} else {
// Select new
// If there was previously selected
if (item) {
item.setAttribute('draggable', 'false');
item.setAttribute('aria-grabbed', 'false');
item = null;
} // Mark new selection
row.setAttribute('draggable', 'true');
row.setAttribute('aria-grabbed', 'true');
item = row;
} // Prevent default to suppress any native actions
event.preventDefault();
} // Escape is the abort keystroke (for any target element)
if (event.keyCode === KEYCODE.ESC && item) {
item.setAttribute('draggable', 'false');
item.setAttribute('aria-grabbed', 'false');
item = null;
} // Enter, to place selected item in selected position
if (event.keyCode === KEYCODE.ENTER && item) {
item.setAttribute('draggable', 'false');
item.setAttribute('aria-grabbed', 'false'); // Do nothing here
if (row === item) {
item = null;
return;
} // Move the item to selected position
switchRowPositions(item, row);
event.preventDefault();
item = null;
}
}); // dragstart event to initiate mouse dragging
this.addEventListener('dragstart', function (_ref2) {
var dataTransfer = _ref2.dataTransfer;
if (item) {
// We going to move the row
dataTransfer.effectAllowed = 'move'; // This need to work in Firefox and IE10+
dataTransfer.setData('text', '');
}
});
this.addEventListener('dragover', function (event) {
if (item) {
event.preventDefault();
}
}); // Handle drag action, move element to hovered position
this.addEventListener('dragenter', function (_ref3) {
var target = _ref3.target;
// Make sure the target in the correct container
if (!item || that.rowsContainer && target.closest(that.rowsContainer) !== that.containerWithRows) {
return;
} // Find a hovered row, and replace it
var row = target.matches(that.repeatableElement) ? target : target.closest(that.repeatableElement);
if (!row) return;
switchRowPositions(item, row);
}); // dragend event to clean-up after drop or abort
// which fires whether or not the drop target was valid
this.addEventListener('dragend', function () {
if (item) {
item.setAttribute('draggable', 'false');
item.setAttribute('aria-grabbed', 'false');
item = null;
}
});
};
_createClass(JoomlaFieldSubform, [{
key: "buttonAdd",
get: // Attribute getters
function get() {
return this.getAttribute('button-add');
}
}, {
key: "buttonRemove",
get: function get() {
return this.getAttribute('button-remove');
}
}, {
key: "buttonMove",
get: function get() {
return this.getAttribute('button-move');
}
}, {
key: "rowsContainer",
get: function get() {
return this.getAttribute('rows-container');
}
}, {
key: "repeatableElement",
get: function get() {
return this.getAttribute('repeatable-element');
}
}, {
key: "minimum",
get: function get() {
return this.getAttribute('minimum');
}
}, {
key: "maximum",
get: function get() {
return this.getAttribute('maximum');
}
}, {
key: "name",
get: function get() {
return this.getAttribute('name');
},
set: function set(value) {
// Update the template
this.template = this.template.replace(new RegExp(" name=\"" + this.name.replace(/[[\]]/g, '\\$&'), 'g'), " name=\"" + value);
return this.setAttribute('name', value);
}
}]);
return JoomlaFieldSubform;
}( /*#__PURE__*/_wrapNativeSuper(HTMLElement));
customElements.define('joomla-field-subform', JoomlaFieldSubform);
})(customElements);
}());