Files
Component-Builder/media/js/custom_code.js
T
Robot 9397b5f6aa Release of v6.1.6-alpha1
Add Health Check buttons to the JCB Compiler and Component list view to verify system PHP configuration. Add the Toolbar Custom Buttons subform to the Global Subform settings for improved usability and presentation. Fix the Field edit view to ensure full compliance with Joomla 6 standards and updated UI/UX conventions. Begin migration from UIkit to Bootstrap 5 to align JCB with Joomla 6+ standards. Complete Bootstrap 5 refactor of all primary views and core UI. Continue phased removal of UIkit with full transition planned for upcoming releases. Fix deprecated JText usage in Admin Custom Tabs area for Joomla 5+ compatibility. #1302.
2026-04-09 15:24:59 +00:00

664 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @package Joomla.Component.Builder
*
* @created 30th April, 2015
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
// Some Global Values
jform_vvvvwajvwl_required = false;
jform_vvvvwakvwm_required = false;
jform_vvvvwakvwn_required = false;
jform_vvvvwakvwo_required = false;
// Initial Script
document.addEventListener('DOMContentLoaded', function()
{
var target_vvvvwaj = jQuery("#jform_target input[type='radio']:checked").val();
vvvvwaj(target_vvvvwaj);
var target_vvvvwak = jQuery("#jform_target input[type='radio']:checked").val();
vvvvwak(target_vvvvwak);
var target_vvvvwal = jQuery("#jform_target input[type='radio']:checked").val();
var type_vvvvwal = jQuery("#jform_type input[type='radio']:checked").val();
vvvvwal(target_vvvvwal,type_vvvvwal);
var type_vvvvwam = jQuery("#jform_type input[type='radio']:checked").val();
var target_vvvvwam = jQuery("#jform_target input[type='radio']:checked").val();
vvvvwam(type_vvvvwam,target_vvvvwam);
});
// the vvvvwaj function
function vvvvwaj(target_vvvvwaj)
{
// set the function logic
if (target_vvvvwaj == 2)
{
jQuery('#jform_function_name').closest('.control-group').show();
// add required attribute to function_name field
if (jform_vvvvwajvwl_required)
{
updateFieldRequired('function_name',0);
jQuery('#jform_function_name').prop('required','required');
jQuery('#jform_function_name').attr('aria-required',true);
jQuery('#jform_function_name').addClass('required');
jform_vvvvwajvwl_required = false;
}
jQuery('.note_jcb_placeholder').closest('.control-group').show();
jQuery('#jform_system_name').closest('.control-group').show();
}
else
{
jQuery('#jform_function_name').closest('.control-group').hide();
// remove required attribute from function_name field
if (!jform_vvvvwajvwl_required)
{
updateFieldRequired('function_name',1);
jQuery('#jform_function_name').removeAttr('required');
jQuery('#jform_function_name').removeAttr('aria-required');
jQuery('#jform_function_name').removeClass('required');
jform_vvvvwajvwl_required = true;
}
jQuery('.note_jcb_placeholder').closest('.control-group').hide();
jQuery('#jform_system_name').closest('.control-group').hide();
}
}
// the vvvvwak function
function vvvvwak(target_vvvvwak)
{
// set the function logic
if (target_vvvvwak == 1)
{
jQuery('#jform_component').closest('.control-group').show();
// add required attribute to component field
if (jform_vvvvwakvwm_required)
{
updateFieldRequired('component',0);
jQuery('#jform_component').prop('required','required');
jQuery('#jform_component').attr('aria-required',true);
jQuery('#jform_component').addClass('required');
jform_vvvvwakvwm_required = false;
}
jQuery('#jform_path').closest('.control-group').show();
// add required attribute to path field
if (jform_vvvvwakvwn_required)
{
updateFieldRequired('path',0);
jQuery('#jform_path').prop('required','required');
jQuery('#jform_path').attr('aria-required',true);
jQuery('#jform_path').addClass('required');
jform_vvvvwakvwn_required = false;
}
jQuery('#jform_from_line').closest('.control-group').show();
jQuery('#jform_hashtarget').closest('.control-group').show();
jQuery('#jform_to_line').closest('.control-group').show();
jQuery('#jform_type').closest('.control-group').show();
// add required attribute to type field
if (jform_vvvvwakvwo_required)
{
updateFieldRequired('type',0);
jQuery('#jform_type').prop('required','required');
jQuery('#jform_type').attr('aria-required',true);
jQuery('#jform_type').addClass('required');
jform_vvvvwakvwo_required = false;
}
}
else
{
jQuery('#jform_component').closest('.control-group').hide();
// remove required attribute from component field
if (!jform_vvvvwakvwm_required)
{
updateFieldRequired('component',1);
jQuery('#jform_component').removeAttr('required');
jQuery('#jform_component').removeAttr('aria-required');
jQuery('#jform_component').removeClass('required');
jform_vvvvwakvwm_required = true;
}
jQuery('#jform_path').closest('.control-group').hide();
// remove required attribute from path field
if (!jform_vvvvwakvwn_required)
{
updateFieldRequired('path',1);
jQuery('#jform_path').removeAttr('required');
jQuery('#jform_path').removeAttr('aria-required');
jQuery('#jform_path').removeClass('required');
jform_vvvvwakvwn_required = true;
}
jQuery('#jform_from_line').closest('.control-group').hide();
jQuery('#jform_hashtarget').closest('.control-group').hide();
jQuery('#jform_to_line').closest('.control-group').hide();
jQuery('#jform_type').closest('.control-group').hide();
// remove required attribute from type field
if (!jform_vvvvwakvwo_required)
{
updateFieldRequired('type',1);
jQuery('#jform_type').removeAttr('required');
jQuery('#jform_type').removeAttr('aria-required');
jQuery('#jform_type').removeClass('required');
jform_vvvvwakvwo_required = true;
}
}
}
// the vvvvwal function
function vvvvwal(target_vvvvwal,type_vvvvwal)
{
// set the function logic
if (target_vvvvwal == 1 && type_vvvvwal == 1)
{
jQuery('#jform_hashendtarget').closest('.control-group').show();
jQuery('#jform_to_line').closest('.control-group').show();
}
else
{
jQuery('#jform_hashendtarget').closest('.control-group').hide();
jQuery('#jform_to_line').closest('.control-group').hide();
}
}
// the vvvvwam function
function vvvvwam(type_vvvvwam,target_vvvvwam)
{
// set the function logic
if (type_vvvvwam == 1 && target_vvvvwam == 1)
{
jQuery('#jform_hashendtarget').closest('.control-group').show();
jQuery('#jform_to_line').closest('.control-group').show();
}
else
{
jQuery('#jform_hashendtarget').closest('.control-group').hide();
jQuery('#jform_to_line').closest('.control-group').hide();
}
}
/**
* Update the "not required" field list by adding or removing a field name.
*
* Mirrors the original jQuery logic exactly but uses pure JavaScript.
*
* @param {string} name The field name to add or remove.
* @param {number} status 1 to add as not required, 0 to remove.
*
* @return {void}
* @since 3.1.3
*/
function updateFieldRequired(name, status) {
// Check if #jform_not_required exists
const notRequiredField = document.getElementById('jform_not_required');
if (!notRequiredField) {
return;
}
// Split the comma-separated list into an array
let not_required = notRequiredField.value ? notRequiredField.value.split(',') : [];
// Add or remove the field name from the list
if (status == 1) {
not_required.push(name);
} else {
not_required = removeFieldFromNotRequired(not_required, name);
}
// Clean and deduplicate the list
const fixedList = fixNotRequiredArray(not_required);
// Write back the updated comma-separated list
notRequiredField.value = fixedList.toString();
}
/**
* Remove a specific field name from the "not required" array.
*
* @param {Array<string>} array The list of not-required field names.
* @param {string} what The field name to remove.
*
* @return {Array<string>} The updated array.
* @since 3.1.3
*/
function removeFieldFromNotRequired(array, what) {
return array.filter(function (element) {
return element !== what;
});
}
/**
* Deduplicate and clean a "not required" array.
*
* @param {Array<string>} array The array to fix.
*
* @return {Array<string>} A cleaned, unique array.
* @since 3.1.3
*/
function fixNotRequiredArray(array) {
const seen = {};
return removeEmptyFromNotRequiredArray(array).filter(function (item) {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
}
/**
* Remove empty or invalid entries from a "not required" array.
*
* Also removes the literal '一_一' token (legacy quirk preserved for compatibility).
*
* @param {Array<string>} array The array to process.
*
* @return {Array<string>} The cleaned array.
* @since 3.1.3
*/
function removeEmptyFromNotRequiredArray(array) {
return array.filter(function (el) {
return el && el.length > 0 && el !== '一_一';
});
}
// the isSet function
function isSet(val)
{
if ((val != undefined) && (val != null) && 0 !== val.length){
return true;
}
return false;
}
document.addEventListener('DOMContentLoaded', function () {
var target = document.querySelector('#jform_target input[type="radio"]:checked');
if (target && target.value == 2) {
document.getElementById('usedin').style.display = '';
var functioName = document.getElementById('jform_function_name').value;
// check if this function name is taken
checkFunctionName(functioName);
}
var type = document.querySelector('#jform_comment_type input[type="radio"]:checked');
if (type && type.value == 2) {
document.getElementById('html-comment-info').style.display = '';
document.getElementById('phpjs-comment-info').style.display = 'none';
} else {
document.getElementById('html-comment-info').style.display = 'none';
document.getElementById('phpjs-comment-info').style.display = '';
}
// check and load all the custom code edit buttons
getEditCustomCodeButtons();
});
function setCustomCodePlaceholder() {
var ide = document.getElementById('jform_id').value;
var functioName = document.getElementById('jform_function_name').value;
if (ide > 0 && functioName.length > 2) {
document.getElementById('jcb-placeholder').innerHTML = '<code>[CUSTO'+'MCODE='+functioName+']</code>';
document.getElementById('jcb-placeholder-arg').innerHTML = '<code>[CUSTO'+'MCODE='+functioName+'&#43;value1,value2]</code>';
} else if (ide > 0) {
document.getElementById('jcb-placeholder').innerHTML = '<code>[not ready]</code>';
document.getElementById('jcb-placeholder-arg').innerHTML = '<code>[not ready]</code>';
} else if (functioName.length > 2) {
document.getElementById('jcb-placeholder').innerHTML = '<code>[CUSTO'+'MCODE='+functioName+']</code>';
document.getElementById('jcb-placeholder-arg').innerHTML = '<code>[CUSTO'+'MCODE='+functioName+'&#43;value1,value2]</code>';
} else {
document.getElementById('jcb-placeholder').innerHTML = '<code>[save to see]</code>';
document.getElementById('jcb-placeholder-arg').innerHTML = '<code>[save to see]</code>';
}
// update the notes
if (ide > 0) {
document.querySelectorAll('.placeholder-key-id').forEach(function (el) { el.textContent = ide; });
}
}
function checkFunctionName(functioName) {
if (functioName.length > 2) {
var ide = document.getElementById('jform_id').value;
if (ide == 0) {
ide = -1;
}
checkFunctionName_server(functioName, ide).then(function(result) {
if(result.name && result.message){
// show notice that functioName is okay
showNotice(result.message, result.status);
document.getElementById('jform_function_name').value = result.name;
// now start search for where the function is used
usedin(result.name, ide);
} else if(result.message){
// show notice that functioName is not okay
showNotice(result.message, result.status);
document.getElementById('jform_function_name').value = '';
} else {
// set an error that message was not send
showNotice(Joomla.Text._('COM_COMPONENTBUILDER_FUNCTION_NAME_ALREADY_TAKEN_PLEASE_TRY_AGAIN'), 'danger');
document.getElementById('jform_function_name').value = '';
}
// set custom code placeholder
setCustomCodePlaceholder();
});
} else {
// set an error that message was not send
showNotice(Joomla.Text._('COM_COMPONENTBUILDER_YOU_MUST_ADD_AN_UNIQUE_FUNCTION_NAME'), 'danger');
document.getElementById('jform_function_name').value = '';
// set custom code placeholder
setCustomCodePlaceholder();
}
}
// check Function name
function checkFunctionName_server(functioName, ide){
var getUrl = 'index.php?option=com_componentbuilder&task=ajax.checkFunctionName&raw=true&format=json';
if(token.length > 0){
var request = 'token='+token+'&functioName='+functioName+'&id='+ide;
}
return fetch(getUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json' },
body: request
}).then(function(r) { return r.json(); });
}
//show notices
function showNotice(message, status) {
var bsClass = status === 'danger' ? 'danger' : status === 'warning' ? 'warning' : 'success';
var container = document.getElementById('jcb-toast-container');
if (!container) {
container = document.createElement('div');
container.id = 'jcb-toast-container';
container.style.cssText = 'position:fixed;top:1rem;right:1rem;z-index:1090;max-width:350px;';
document.body.appendChild(container);
}
var alert = document.createElement('div');
alert.className = 'alert alert-' + bsClass + ' alert-dismissible fade show shadow';
alert.setAttribute('role', 'alert');
alert.innerHTML = message +
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>';
container.appendChild(alert);
setTimeout(function () {
alert.classList.remove('show');
setTimeout(function () { alert.remove(); }, 300);
}, 5000);
}
/**
* Checks where a given function is used by iterating through a list of numeric targets (0–29).
* For each target, it calls usedin_server() concurrently and updates the UI based on the responses.
*
* @param {string} functioName - The functioName parameter to send to the server.
* @param {string|number} ide - The identifier to send.
*/
function usedin(functioName, ide) {
let found = false;
// Helper functions to show/hide elements by ID.
const hideElement = (id) => {
const el = document.getElementById(id);
if (el) {
el.style.display = 'none';
}
};
const showElement = (id) => {
const el = document.getElementById(id);
if (el) {
el.style.display = 'block';
}
};
// Hide initial UI elements.
hideElement('before-usedin');
hideElement('note-usedin-not');
hideElement('note-usedin-found');
showElement('loading-usedin');
// Create a targets array of 30 integers (0 to 29).
const targets = Array.from({ length: 30 }, (_, i) => i);
// Map each target to a promise that makes an AJAX call.
const promises = targets.map((target) => {
return usedin_server(functioName, ide, target)
.then((used) => {
if (used && used.in) {
// Check if the element with id "usedin-{used.id}" exists.
let funcElement = document.getElementById('usedin-' + used.id);
if (!funcElement) {
// Create the main container div.
funcElement = document.createElement('div');
funcElement.id = 'usedin-' + used.id;
// Create the header element with the area name.
const header = document.createElement('h2');
header.textContent = used.area_name;
// Create the inner div element that will contain the result.
const innerDiv = document.createElement('div');
innerDiv.id = 'area-' + used.id;
// Append the header and inner div to the main element.
funcElement.appendChild(header);
funcElement.appendChild(innerDiv);
// Append this element to the container with id "usedin-targets".
const container = document.getElementById('usedin-targets');
if (container) {
container.appendChild(funcElement);
} else {
console.error(
"Container with id 'usedin-targets' not found. Appending to document.body instead."
);
}
}
// Ensure the element is visible.
showElement('usedin-' + used.id);
// Update the inner div's content with the response.
const areaEl = document.getElementById('area-' + used.id);
if (areaEl) {
areaEl.innerHTML = used.in;
}
// Notify the user.
showNotice(used.in, 'success');
found = true;
} else {
// If no valid response, hide the element with id "usedin-{target}".
hideElement('usedin-' + target);
}
})
.catch((error) => {
console.error('Error in usedin_server for target ' + target + ':', error);
});
});
// Once all Ajax calls are completed, update the UI accordingly.
Promise.all(promises).then(() => {
hideElement('loading-usedin');
if (found) {
showElement('note-usedin-found');
} else {
showElement('note-usedin-not');
}
});
}
/**
* Sends an AJAX GET request to the server with the specified parameters.
* The function builds a URL with query parameters and returns a promise
* that resolves with the JSON response.
*
* @param {string} functioName - The functioName to send with the request.
* @param {string|number} ide - The identifier to send.
* @param {string|number} target - The target functioName to send.
*
* @returns {Promise<Object>} - A promise that resolves to the JSON response.
*/
function usedin_server(functioName, ide, target) {
// Check if the global variable 'token' exists and has a non-empty functioName.
// 'token', 'functioName', and 'return_here' are assumed to be defined elsewhere in your code.
if (token && token.length > 0) {
var request =
token +
'=1&functioName=' +
functioName +
'&id=' +
ide +
'&target=' +
target +
'&raw=true&return_here=' +
return_here;
} else {
console.error(
'There was a issue with the functioNames passed to the [usedin_server] method and we could not make the Ajax call.'
);
return Promise.reject(new Error('Invalid token or parameters.'));
}
// Base URL for the AJAX request.
const baseUrl = `index.php?option=com_componentbuilder&task=ajax.usedin&format=json&${request}`;
// Use the Fetch API to perform a GET request.
return fetch(baseUrl, {
method: "GET",
headers: {
"Accept": "application/json"
}
}).then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
});
}
/**
* Retrieve the Edit Custom Code buttons from the server.
*
* @param {number} id The record ID to load custom code buttons for.
*
* @return {Promise<object|null>} Returns JSON object of buttons or null on failure.
* @since 3.1.3
*/
async function getEditCustomCodeButtons_server(id) {
try {
// --- Validation ---
if (typeof token !== 'string' || !token.trim()) {
console.error('[getEditCustomCodeButtons_server] Missing or invalid CSRF token.');
return null;
}
if (typeof id !== 'number' || id <= 0) {
console.error('[getEditCustomCodeButtons_server] Invalid ID provided:', id);
return null;
}
if (typeof return_here !== 'string' || !return_here.trim()) {
console.warn('[getEditCustomCodeButtons_server] "return_here" not set; continuing without it.');
}
// --- Build URL safely ---
const baseUrl = 'index.php';
const params = new URLSearchParams({
option: 'com_componentbuilder',
task: 'ajax.getEditCustomCodeButtons',
format: 'json',
raw: 'true',
[token]: '1',
id: id,
return_here: return_here || ''
});
if (typeof vastDevMod === 'string' && vastDevMod.length > 0) {
params.append('vdm', vastDevMod);
}
const urlWithParams = JRouter(`${baseUrl}?${params.toString()}`);
// --- Execute request ---
const response = await fetch(urlWithParams, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
cache: 'no-store',
credentials: 'same-origin'
});
// --- Handle network errors ---
if (!response.ok) {
console.error(`[getEditCustomCodeButtons_server] HTTP ${response.status}: ${response.statusText}`);
return null;
}
// --- Parse JSON result ---
const data = await response.json();
return data ?? null;
} catch (error) {
console.error('[getEditCustomCodeButtons_server] Fetch failed:', error);
return null;
}
}
/**
* Load and inject Edit Custom Code buttons into the DOM.
*
* @return {Promise<void>}
* @since 3.1.3
*/
async function getEditCustomCodeButtons() {
try {
// --- Get record ID from the form ---
const idField = document.querySelector('#jform_id');
if (!idField) {
console.error('[getEditCustomCodeButtons] #jform_id not found.');
return;
}
const idValue = parseInt(idField.value, 10);
if (isNaN(idValue) || idValue <= 0) {
console.warn('[getEditCustomCodeButtons] Invalid or empty ID; skipping button load.');
return;
}
// --- Request data from server ---
const result = await getEditCustomCodeButtons_server(idValue);
if (!result || typeof result !== 'object') {
console.warn('[getEditCustomCodeButtons] No result returned or invalid format.');
return;
}
// --- Inject returned button groups ---
Object.entries(result).forEach(([field, buttons]) => {
// Create the container div
const div = document.createElement('div');
div.className = 'control-group';
div.innerHTML = `
<div class="control-label">
<label>Add/Edit Customcode</label>
</div>
<div class="controls control-customcode-buttons-${field}"></div>
`;
// Find where to insert (before .control-wrapper-{field})
const insertBeforeElement = document.querySelector(`.control-wrapper-${field}`);
if (insertBeforeElement && insertBeforeElement.parentNode) {
insertBeforeElement.parentNode.insertBefore(div, insertBeforeElement);
}
// Append buttons to the new container
const controlsDiv = div.querySelector(`.control-customcode-buttons-${field}`);
if (controlsDiv && typeof buttons === 'object') {
Object.entries(buttons).forEach(([name, buttonHtml]) => {
if (typeof buttonHtml === 'string') {
const wrapper = document.createElement('div');
wrapper.innerHTML = buttonHtml.trim();
const buttonNode = wrapper.firstElementChild;
if (buttonNode) {
controlsDiv.appendChild(buttonNode);
}
}
});
}
});
} catch (error) {
console.error('[getEditCustomCodeButtons] Error rendering buttons:', error);
}
}