Release of v5.1.1-alpha5

Refactor initialization flow to accommodate future scalability and integration with all designated areas. Refactor the Creator Builders class. Refactor the FieldString and FieldXML classes.
This commit is contained in:
2025-05-13 13:39:32 +00:00
parent 0b7e68d14e
commit 3b502eb09b
336 changed files with 22863 additions and 20677 deletions

View File

@ -291,7 +291,7 @@ function checkFunctionName(functioName) {
jQuery('#jform_function_name').val('');
} else {
// set an error that message was not send
jQuery.UIkit.notify({message: Joomla.JText._('COM_COMPONENTBUILDER_FUNCTION_NAME_ALREADY_TAKEN_PLEASE_TRY_AGAIN'), timeout: 5000, status: 'danger', pos: 'top-right'});
jQuery.UIkit.notify({message: Joomla.Text._('COM_COMPONENTBUILDER_FUNCTION_NAME_ALREADY_TAKEN_PLEASE_TRY_AGAIN'), timeout: 5000, status: 'danger', pos: 'top-right'});
jQuery('#jform_function_name').val('');
}
// set custom code placeholder
@ -299,7 +299,7 @@ function checkFunctionName(functioName) {
});
} else {
// set an error that message was not send
jQuery.UIkit.notify({message: Joomla.JText._('COM_COMPONENTBUILDER_YOU_MUST_ADD_AN_UNIQUE_FUNCTION_NAME'), timeout: 5000, status: 'danger', pos: 'top-right'});
jQuery.UIkit.notify({message: Joomla.Text._('COM_COMPONENTBUILDER_YOU_MUST_ADD_AN_UNIQUE_FUNCTION_NAME'), timeout: 5000, status: 'danger', pos: 'top-right'});
jQuery('#jform_function_name').val('');
// set custom code placeholder
setCustomCodePlaceholder();

View File

@ -1401,6 +1401,306 @@ function isSet(val)
}
/* ======================================================================== *\
ComponentBuilder Subform helpers
Fully productionready, ESLintclean, duplicateID safe
\* ======================================================================== */
(function () {
'use strict';
/* --------------------------------------------------------------------- *
| Configuration helpers |
* --------------------------------------------------------------------- */
/**
* Return the Joomlarouter URL for the given AJAX task.
* Falls back to a raw path if JRouter() is not defined.
*
* @param {string} task e.g. "ajax.viewTableColumns"
* @return {string}
*/
function route(task) {
const url = `index.php?option=com_componentbuilder&task=${task}&format=json&raw=true`;
return typeof window.JRouter === 'function' ? window.JRouter(url) : url;
}
/**
* CSRF token (expects a global `token` variable).
*/
const csrf = window.token ?? Joomla.getOptions('csrf.token');
/* --------------------------------------------------------------------- *
| Generic server fetcher |
* --------------------------------------------------------------------- */
/**
* Fetch column data for either a *view* or a *db* table.
*
* @param {"view"|"db"} type
* @param {string} idOrName View → GUID, DB → table name
* @param {string} asKey Alias key (usually "a")
* @param {number} rowType 1based rowtype index
* @return {Promise<any>} JSON payload (resolved) or thrown Error
*/
function fetchColumns(type, idOrName, asKey, rowType) {
if (!csrf || !idOrName || !asKey) {
return Promise.reject(
new Error('[fetchColumns] Missing CSRF token, alias or identifier')
);
}
const task = type === 'view' ? 'ajax.viewTableColumns' : 'ajax.dbTableColumns';
const paramName = type === 'view' ? 'id' : 'name';
const url = `${route(task)}&${csrf}=1&as=${asKey}&type=${rowType}&${paramName}=${encodeURIComponent(idOrName)}`;
return fetch(url, { method: 'GET' })
.then(r => r.json());
}
/* --------------------------------------------------------------------- *
| UI utilities |
* --------------------------------------------------------------------- */
/**
* Safely fetch the radio value for “select all / custom”.
* @return {number} 1  select all, 0  custom
*/
function currentSelectAll() {
const radio = /** @type {HTMLInputElement|null} */ (
document.querySelector('#jform_select_all input[type="radio"]:checked')
);
return radio ? Number(radio.value) : 0;
}
/**
* Update the selection `<textarea>` (main or subrow) with new data.
*
* @param {string|false} data
* @param {"view"|"db"} type
* @param {string|number} key Field index inside the row
* @param {boolean} main TRUE  main selection textarea
* @param {number|string} table_ Jointable index (subrows)
* @param {number|string} nr_ Clone suffix (subrows)
*/
function loadSelectionData(data, type, key, main, table_, nr_) {
let selector;
if (main) {
selector = `textarea#jform_${key}_selection`;
} else {
selector = `textarea#jform_join_${type}_table${table_}_join_${type}_table${key}${nr_}_selection`;
}
const textarea = /** @type {HTMLTextAreaElement|null} */ (document.querySelector(selector));
if (!textarea) {
console.warn('[loadSelectionData] Textarea not found:', selector);
return;
}
textarea.value = data || '';
}
/* --------------------------------------------------------------------- *
| Public helpers (exposed via window.*) |
* --------------------------------------------------------------------- */
/**
* Handle the “Select all / Custom select” radio buttons.
*
* @param {number} selectAll 1  select all, 0  custom
*/
function setSelectAll(selectAll) {
const mainSource = Number(document.getElementById('jform_main_source')?.value ?? 0);
const key = mainSource === 1 ? 'view' : mainSource === 2 ? 'db' : null;
if (!key) return;
const textarea = document.getElementById(`jform_${key}_selection`);
if (!textarea) return;
if (selectAll === 1) {
textarea.value = 'a.*';
textarea.readOnly = true;
} else {
textarea.readOnly = false;
/* Trigger a fresh column fetch so the user sees all fields. */
if (key === 'view') {
const guid = /** @type {HTMLInputElement} */ (
document.getElementById('jform_view_table_main_id')
)?.value;
if (guid) {
getViewTableColumns(guid, 'a', key, 3, true, '', '');
}
} else {
const name = /** @type {HTMLSelectElement} */ (
document.getElementById('jform_db_table_main')
)?.value;
if (name) {
getDbTableColumns(name, 'a', key, 3, true, '', '');
}
}
}
}
/**
* Wrapper around `fetchColumns("view", …)` that keeps the original
* callsignature (`id, asKey, key, rowType, main, table_, nr_`).
*/
function getViewTableColumns(id, asKey, key, rowType, main, table_, nr_) {
if (main && currentSelectAll() === 1) {
setSelectAll(1);
return;
}
fetchColumns('view', id, asKey, rowType)
.then(res => {
if (res?.error) {
console.error(res.error);
loadSelectionData(false, 'view', key, main, table_, nr_);
} else {
loadSelectionData(res, 'view', key, main, table_, nr_);
}
})
.catch(err => {
console.error(err);
loadSelectionData(false, 'view', key, main, table_, nr_);
});
}
/**
* Wrapper around `fetchColumns("db", …)` that keeps the original
* callsignature (`name, asKey, key, rowType, main, table_, nr_`).
*/
function getDbTableColumns(name, asKey, key, rowType, main, table_, nr_) {
if (main && currentSelectAll() === 1) {
setSelectAll(1);
return;
}
fetchColumns('db', name, asKey, rowType)
.then(res => {
if (res?.error) {
console.error(res.error);
loadSelectionData(false, 'db', key, main, table_, nr_);
} else {
loadSelectionData(res, 'db', key, main, table_, nr_);
}
})
.catch(err => {
console.error(err);
loadSelectionData(false, 'db', key, main, table_, nr_);
});
}
/* --------------------------------------------------------------------- *
| updateSubItems duplicateID safe handler |
* --------------------------------------------------------------------- */
/**
* Attach duplicateIDsafe, delegated change handling to a subform row.
*
* @param {string} fieldName "view" | "db"
* @param {number} fieldNr Rowfield index
* @param {number} table_ Jointable index
* @param {number} nr_ Clone suffix
*/
function updateSubItems(fieldName, fieldNr, table_, nr_) {
/* Build selectors (works for hidden input, text input, select). */
const base = `jform_join_${fieldName}_table${table_}_join_${fieldName}_table${fieldNr}${nr_}`;
const sel = {
tableId : `#${base}_${fieldName}_table_id`, // hidden <input>
table : `#${base}_${fieldName}_table`, // <select> OR dup. inputs
alias : `#${base}_as`,
rowType : `#${base}_row_type`,
};
const adminForm = document.getElementById('adminForm');
if (!adminForm) {
console.error('[updateSubItems] #adminForm not found.');
return;
}
/* Guard: avoid rebinding the same row. */
if (adminForm.dataset[`boundFor${base}`]) return;
adminForm.dataset[`boundFor${base}`] = 'true';
const tableSelectors = fieldName === 'view'
? [sel.tableId, sel.table]
: [sel.table];
const delegateSelectors = [
...tableSelectors, sel.alias, sel.rowType,
].join(', ');
adminForm.addEventListener('change', handleChange);
/* --- Delegated change handler ----------------------------------- */
function handleChange(e) {
if (!e.target.matches(delegateSelectors)) return;
e.preventDefault();
const tableEl = pickElement(tableSelectors);
const aliasEl = pickElement(sel.alias);
const rowTypeEl = pickElement(sel.rowType);
if (!tableEl || !aliasEl || !rowTypeEl) return;
const tableVal = getElementValue(tableEl);
const aliasVal = getElementValue(aliasEl);
const rowTypeVal = getElementValue(rowTypeEl);
if (fieldName === 'view') {
getViewTableColumns(
tableVal, aliasVal, fieldNr, rowTypeVal, false, table_, nr_
);
} else {
getDbTableColumns(
tableVal, aliasVal, fieldNr, rowTypeVal, false, table_, nr_
);
}
}
/* --- Helper: choose the correct node among duplicated IDs -------- */
function pickElement(selectors) {
const nodes = [...[].concat(selectors).flatMap(
sel => [...document.querySelectorAll(sel)]
)];
if (!nodes.length) return null;
if (nodes.length === 1) return nodes[0];
/* 1⃣ Prefer hidden input with GUID length 38. */
for (const n of nodes) {
if (isHidden(n) && getElementValue(n).length === 38) return n;
}
/* 2⃣ Any hidden input with nonempty value. */
for (const n of nodes) {
if (isHidden(n) && getElementValue(n)) return n;
}
/* 3⃣ Fallback: newest element (last in DOM order). */
return nodes[nodes.length - 1];
}
function isHidden(el) {
return el.tagName === 'INPUT' && el.type === 'hidden';
}
function getElementValue(el) {
if (isHidden(el)) return el.value;
if (el.tagName === 'SELECT') {
const s = /** @type {HTMLSelectElement} */ (el);
return s.selectedIndex >= 0 ? s.options[s.selectedIndex].value : '';
}
return '';
}
}
/* --------------------------------------------------------------------- *
| Expose public helpers |
* --------------------------------------------------------------------- */
window.setSelectAll = setSelectAll;
window.getViewTableColumns = getViewTableColumns;
window.getDbTableColumns = getDbTableColumns;
window.loadSelectionData = loadSelectionData;
window.updateSubItems = updateSubItems;
})();
document.addEventListener('DOMContentLoaded', function() {
// get the linked details
getLinked();
@ -1410,175 +1710,6 @@ document.addEventListener('DOMContentLoaded', function() {
getEditCustomCodeButtons();
});
function setSelectAll(select_all) {
// get source type
let main_source = document.getElementById("jform_main_source").value;
let key;
if (main_source == 1) {
key = 'view';
} else if (main_source == 2) {
key = 'db';
} else {
return true;
}
// only continue if set
if (select_all == 1) {
// set default notice
document.getElementById("jform_" + key + "_selection").value = 'a.*';
// set the selection text area to read only
document.getElementById("jform_" + key + "_selection").readOnly = true;
} else {
// remove the read only from selection text area
document.getElementById("jform_" + key + "_selection").readOnly = false;
// get selected options
let value_main = document.getElementById("jform_" + key + "_table_main").selectedOptions[0].value;
// make sure that all fields are set as selected
if (key === 'view') {
getViewTableColumns(value_main, 'a', key, 3, true, '', '');
} else {
getDbTableColumns(value_main, 'a', key, 3, true, '', '');
}
}
}
function getViewTableColumns_server(viewId, asKey, rowType) {
let getUrl = JRouter("index.php?option=com_componentbuilder&task=ajax.viewTableColumns&format=json&raw=true");
let request = '';
if (token.length > 0 && viewId > 0 && asKey.length > 0) {
request = token + '=1&as=' + asKey + '&type=' + rowType + '&id=' + viewId;
}
return fetch(getUrl + '&' + request, { method: 'GET' }).then(function(response) {
return response.json();
});
}
function getViewTableColumns(id, asKey, key, rowType, main, table_, nr_) {
// check if this is the main view
if (main) {
let select_all = document.querySelector("#jform_select_all input[type='radio']:checked").value;
// do not continue if set
if (select_all == 1) {
setSelectAll(select_all);
return true;
}
}
getViewTableColumns_server(id, asKey, rowType).then(function(result) {
if (result.error) {
console.error(result.error);
} else if (result) {
loadSelectionData(result, 'view', key, main, table_, nr_);
} else {
loadSelectionData(false, 'view', key, main, table_, nr_);
}
});
}
function getDbTableColumns_server(name, asKey, rowType) {
let getUrl = JRouter("index.php?option=com_componentbuilder&task=ajax.dbTableColumns&format=json&raw=true");
let request = '';
if (token.length > 0 && name.length > 0 && asKey.length > 0) {
request = token + '=1&as=' + asKey + '&type=' + rowType + '&name=' + name;
}
return fetch(getUrl + '&' + request, { method: 'GET' }).then(function(response) {
return response.json();
});
}
function getDbTableColumns(name, asKey, key, rowType, main, table_, nr_) {
// check if this is the main view
if (main) {
let select_all = document.querySelector("#jform_select_all input[type='radio']:checked").value;
// do not continue if set
if (select_all === 1) {
setSelectAll(select_all);
return true;
}
}
getDbTableColumns_server(name, asKey, rowType).then(function(result) {
if (result.error) {
console.error(result.error);
} else if (result) {
loadSelectionData(result, 'db', key, main, table_, nr_);
} else {
loadSelectionData(false, 'db', key, main, table_, nr_);
}
});
}
function loadSelectionData(result, type, key, main, table_, nr_) {
var textArea;
if (main) {
textArea = document.querySelector('textarea#jform_' + key + '_selection');
} else {
textArea = document.querySelector('textarea#jform_join_' + type + '_table' + table_ + '_join_' + type + '_table' + key + nr_ + '_selection');
}
// update the text area
if (result) {
textArea.value = result;
} else {
textArea.value = '';
}
}
function updateSubItems(fieldName, fieldNr, table_, nr_) {
let selector = '#jform_join_' + fieldName + '_table' + table_ + '_join_' + fieldName + '_table' + fieldNr + nr_ + '_' + fieldName + '_table';
if (document.querySelector(selector)) {
document.getElementById('adminForm').addEventListener('change', function(e) {
if (e.target.matches(selector)) {
e.preventDefault();
// get options
let selectElement = document.querySelector(selector);
let value1 = selectElement.options[selectElement.selectedIndex].value;
let asSelectElement = document.querySelector('#jform_join_' + fieldName + '_table' + table_ + '_join_' + fieldName + '_table' + fieldNr + nr_ + '_as');
let as_value2 = asSelectElement.options[asSelectElement.selectedIndex].value;
let rowTypeElement = document.querySelector('#jform_join_' + fieldName + '_table' + table_ + '_join_' + fieldName + '_table' + fieldNr + nr_ + '_row_type');
let row_value1 = rowTypeElement.options[rowTypeElement.selectedIndex].value;
if (fieldName === 'view') {
getViewTableColumns(value1, as_value2, fieldNr, row_value1, false, table_, nr_);
} else {
getDbTableColumns(value1, as_value2, fieldNr, row_value1, false, table_, nr_);
}
}
});
document.getElementById('adminForm').addEventListener('change', function(e) {
if (e.target.matches('#jform_join_' + fieldName + '_table' + table_ + '_join_' + fieldName + '_table' + fieldNr + nr_ + '_as')) {
e.preventDefault();
// get options
let selectElement = document.querySelector(selector);
let value1 = selectElement.options[selectElement.selectedIndex].value;
let asSelectElement = document.querySelector('#jform_join_' + fieldName + '_table' + table_ + '_join_' + fieldName + '_table' + fieldNr + nr_ + '_as');
let as_value2 = asSelectElement.options[asSelectElement.selectedIndex].value;
let rowTypeElement = document.querySelector('#jform_join_' + fieldName + '_table' + table_ + '_join_' + fieldName + '_table' + fieldNr + nr_ + '_row_type');
let row_value1 = rowTypeElement.options[rowTypeElement.selectedIndex].value;
if (fieldName === 'view') {
getViewTableColumns(value1, as_value2, fieldNr, row_value1, false, table_, nr_);
} else {
getDbTableColumns(value1, as_value2, fieldNr, row_value1, false, table_, nr_);
}
}
});
document.getElementById('adminForm').addEventListener('change', function(e) {
if (e.target.matches('#jform_join_' + fieldName + '_table' + table_ + '_join_' + fieldName + '_table' + fieldNr + nr_ + '_row_type')) {
e.preventDefault();
// get options
let selectElement = document.querySelector(selector);
let value1 = selectElement.options[selectElement.selectedIndex].value;
let asSelectElement = document.querySelector('#jform_join_' + fieldName + '_table' + table_ + '_join_' + fieldName + '_table' + fieldNr + nr_ + '_as');
let as_value2 = asSelectElement.options[asSelectElement.selectedIndex].value;
let rowTypeElement = document.querySelector('#jform_join_' + fieldName + '_table' + table_ + '_join_' + fieldName + '_table' + fieldNr + nr_ + '_row_type');
let row_value1 = rowTypeElement.options[rowTypeElement.selectedIndex].value;
if (fieldName === 'view') {
getViewTableColumns(value1, as_value2, fieldNr, row_value1, false, table_, nr_);
} else {
getDbTableColumns(value1, as_value2, fieldNr, row_value1, false, table_, nr_);
}
}
});
}
}
function getDynamicScripts(id) {
if (id == 1) {
// get the current values

View File

@ -575,7 +575,7 @@ function getFieldPropertyDesc(field, targetForm){
jQuery('#'+id).val('');
jQuery('#'+id).trigger("liszt:updated");
// give out a notice
jQuery.UIkit.notify({message: Joomla.JText._('COM_COMPONENTBUILDER_PROPERTY_ALREADY_SELECTED_TRY_ANOTHER'), timeout: 5000, status: 'warning', pos: 'top-center'});
jQuery.UIkit.notify({message: Joomla.Text._('COM_COMPONENTBUILDER_PROPERTY_ALREADY_SELECTED_TRY_ANOTHER'), timeout: 5000, status: 'warning', pos: 'top-center'});
// update the values
jQuery('#'+target[0]+'__desc').val('');
jQuery('#'+target[0]+'__value').val('');
@ -595,7 +595,7 @@ function getFieldPropertyDesc(field, targetForm){
jQuery('#'+target[0]+'__value').val(result.value);
} else {
// update the values
jQuery('#'+target[0]+'__desc').val(Joomla.JText._('COM_COMPONENTBUILDER_NO_DESCRIPTION_FOUND'));
jQuery('#'+target[0]+'__desc').val(Joomla.Text._('COM_COMPONENTBUILDER_NO_DESCRIPTION_FOUND'));
jQuery('#'+target[0]+'__value').val('');
}
});
@ -627,7 +627,7 @@ function propertyDynamicSet() {
}
}
// trigger chosen on the list fields
// jQuery('.field_list_name_options').chosen({"disable_search_threshold":10,"search_contains":true,"allow_single_deselect":true,"placeholder_text_multiple":Joomla.JText._("COM_COMPONENTBUILDER_TYPE_OR_SELECT_SOME_OPTIONS"),"placeholder_text_single":Joomla.JText._("COM_COMPONENTBUILDER_SELECT_A_PROPERTY"),"no_results_text":Joomla.JText._("COM_COMPONENTBUILDER_NO_RESULTS_MATCH")});
// jQuery('.field_list_name_options').chosen({"disable_search_threshold":10,"search_contains":true,"allow_single_deselect":true,"placeholder_text_multiple":Joomla.Text._("COM_COMPONENTBUILDER_TYPE_OR_SELECT_SOME_OPTIONS"),"placeholder_text_single":Joomla.Text._("COM_COMPONENTBUILDER_SELECT_A_PROPERTY"),"no_results_text":Joomla.Text._("COM_COMPONENTBUILDER_NO_RESULTS_MATCH")});
// now build the list to keep
jQuery.each( propertiesArray, function( prop, name ) {
if (!propertiesSelectedArray.hasOwnProperty(prop)) {

View File

@ -485,7 +485,7 @@ function getClassCode(field, type){
jQuery('#'+id).val('');
jQuery('#'+id).trigger("liszt:updated");
// give out a notice
jQuery.UIkit.notify({message: Joomla.JText._('COM_COMPONENTBUILDER_ALREADY_SELECTED_TRY_ANOTHER'), timeout: 5000, status: 'warning', pos: 'top-center'});
jQuery.UIkit.notify({message: Joomla.Text._('COM_COMPONENTBUILDER_ALREADY_SELECTED_TRY_ANOTHER'), timeout: 5000, status: 'warning', pos: 'top-center'});
} else {
// set the active removed value
selectedIdRemoved[type] = id;

View File

@ -41,7 +41,7 @@ function checkPlaceholderName(placeholderName) {
jQuery('#jform_target').val('');
} else {
// set an error that message was not send
jQuery.UIkit.notify({message: Joomla.JText._('COM_COMPONENTBUILDER_PLACEHOLDER_ALREADY_TAKEN_PLEASE_TRY_AGAIN'), timeout: 5000, status: 'danger', pos: 'top-right'});
jQuery.UIkit.notify({message: Joomla.Text._('COM_COMPONENTBUILDER_PLACEHOLDER_ALREADY_TAKEN_PLEASE_TRY_AGAIN'), timeout: 5000, status: 'danger', pos: 'top-right'});
jQuery('#jform_target').val('');
}
// set custom code placeholder
@ -49,7 +49,7 @@ function checkPlaceholderName(placeholderName) {
});
} else {
// set an error that message was not send
jQuery.UIkit.notify({message: Joomla.JText._('COM_COMPONENTBUILDER_YOU_MUST_ADD_AN_UNIQUE_PLACEHOLDER'), timeout: 5000, status: 'danger', pos: 'top-right'});
jQuery.UIkit.notify({message: Joomla.Text._('COM_COMPONENTBUILDER_YOU_MUST_ADD_AN_UNIQUE_PLACEHOLDER'), timeout: 5000, status: 'danger', pos: 'top-right'});
jQuery('#jform_target').val('');
// set custom code placeholder
setPlaceholderName();

View File

@ -249,7 +249,7 @@ function getClassCode(field, type){
jQuery('#'+id).val('');
jQuery('#'+id).trigger("liszt:updated");
// give out a notice
jQuery.UIkit.notify({message: Joomla.JText._('COM_COMPONENTBUILDER_ALREADY_SELECTED_TRY_ANOTHER'), timeout: 5000, status: 'warning', pos: 'top-center'});
jQuery.UIkit.notify({message: Joomla.Text._('COM_COMPONENTBUILDER_ALREADY_SELECTED_TRY_ANOTHER'), timeout: 5000, status: 'warning', pos: 'top-center'});
} else {
// set the active removed value
selectedIdRemoved[type] = id;

View File

@ -72,13 +72,13 @@ function checkRuleName(ruleName) {
jQuery('#jform_name').val('');
} else {
// set an error that message was not send
jQuery.UIkit.notify({message: Joomla.JText._('COM_COMPONENTBUILDER_VALIDATION_RULE_NAME_ALREADY_TAKEN_PLEASE_TRY_AGAIN'), timeout: 7000, status: 'danger', pos: 'top-right'});
jQuery.UIkit.notify({message: Joomla.Text._('COM_COMPONENTBUILDER_VALIDATION_RULE_NAME_ALREADY_TAKEN_PLEASE_TRY_AGAIN'), timeout: 7000, status: 'danger', pos: 'top-right'});
jQuery('#jform_name').val('');
}
});
} else {
// set an error that message was not send
jQuery.UIkit.notify({message: Joomla.JText._('COM_COMPONENTBUILDER_YOU_MUST_ADD_AN_UNIQUE_VALIDATION_RULE_NAME'), timeout: 5000, status: 'danger', pos: 'top-right'});
jQuery.UIkit.notify({message: Joomla.Text._('COM_COMPONENTBUILDER_YOU_MUST_ADD_AN_UNIQUE_VALIDATION_RULE_NAME'), timeout: 5000, status: 'danger', pos: 'top-right'});
jQuery('#jform_name').val('');
}
}