/* global IITC */
/**
* Toolbox API
*
* @memberof IITC
* @namespace toolbox
*/
/**
* @typedef {Object} ButtonArgs
* @property {string} [id] - Optional. The ID of the button.
* @property {string|undefined} label - The label text of the button.
* @property {Function|undefined} action - The onclick action for the button.
* @property {string|null} [class] - Optional. The class(es) for the button.
* @property {string|null} [title] - Optional. The title (tooltip) for the button.
* @property {string|null} [accessKey] - Optional. The access key for the button.
* @property {Function|null} [mouseover] - Optional. The mouseover event for the button.
* @property {string|null} [icon] - Optional. Icon name from FontAwesome for the button.
*/
IITC.toolbox = {
buttons: {},
_defaultSortMethod: (a, b) => a.label.localeCompare(b.label),
sortMethod: (...args) => IITC.toolbox._defaultSortMethod(...args),
/**
* Adds a button to the toolbox.
*
* @param {ButtonArgs} buttonArgs - The arguments for the button.
* @returns {string|null} The ID of the added button or null if required parameters are missing.
*
* @example
* const buttonId = IITC.toolbox.addButton({
* label: 'AboutIITC',
* action: window.AboutIITC
* });
*
* @example
* const buttonId = IITC.toolbox.addButton({
* label: 'Test Button',
* action: () => alert('Clicked!')
* });
*/
addButton(buttonArgs) {
if (!buttonArgs.label) {
console.warn('Required parameter "label" are missing.');
return null;
}
if (!buttonArgs.action) {
console.warn('Required parameter "action" are missing.');
return null;
}
let id = buttonArgs.id || `toolbox-btn-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
this.buttons[id] = buttonArgs;
this._renderButton(id);
this._applySort();
return id;
},
/**
* Updates an existing button in the toolbox.
*
* @param {string} buttonId - The ID of the button to update.
* @param {ButtonArgs} newButtonArgs - The new arguments for the button.
* @returns {boolean} True if the button is successfully updated, false otherwise.
*
* @example
* const isUpdated = IITC.toolbox.updateButton(buttonId, { label: 'Updated Button', action: () => console.log('New Action') });
*/
updateButton(buttonId, newButtonArgs) {
if (this.buttons[buttonId]) {
Object.assign(this.buttons[buttonId], newButtonArgs);
this._renderButton(buttonId);
this._applySort();
return true;
} else {
console.warn(`Button with ID ${buttonId} not found.`);
return false;
}
},
/**
* Removes a button from the toolbox.
*
* @param {string} buttonId - The ID of the button to remove.
* @returns {boolean} True if the button is successfully removed, false otherwise.
*
* @example
* const isRemoved = IITC.toolbox.removeButton(buttonId);
*/
removeButton(buttonId) {
if (this.buttons[buttonId]) {
delete this.buttons[buttonId];
const buttonElement = document.getElementById(buttonId);
if (buttonElement) {
buttonElement.remove();
}
this._applySort();
return true;
} else {
console.warn(`Button with ID ${buttonId} not found for removal.`);
return false;
}
},
/**
* Internal method to render a button.
*
* @private
* @param {string} buttonId - The ID of the button to render.
*/
_renderButton(buttonId) {
const buttonData = this.buttons[buttonId];
if (!buttonData) return; // The button with the given ID was not found
let buttonElement = document.getElementById(buttonId) || document.createElement('a');
buttonElement.id = buttonId;
buttonElement.textContent = buttonData.label;
buttonElement.onclick = buttonData.action;
if (typeof buttonData.title === 'string') buttonElement.title = buttonData.title;
if (typeof buttonData.class === 'string') buttonElement.className = buttonData.class;
if (typeof buttonData.access_key === 'string') buttonElement.accessKey = buttonData.access_key;
if (typeof buttonData.accesskey === 'string') buttonElement.accessKey = buttonData.accesskey;
if (typeof buttonData.accessKey === 'string') buttonElement.accessKey = buttonData.accessKey;
if (typeof buttonData.mouseover === 'function') buttonElement.onmouseover = buttonData.mouseover;
if (typeof buttonData.icon === 'string') {
const iconHTML = `<i class="fa ${buttonData.icon}"></i>`;
buttonElement.innerHTML = iconHTML + buttonElement.innerHTML;
}
const toolbox_component = document.querySelector('#toolbox_component');
if (!document.getElementById(buttonId)) {
toolbox_component.appendChild(buttonElement);
}
},
/**
* Internal method to apply sorting to the buttons.
*
* @private
*/
_applySort() {
const toolbox_component = document.querySelector('#toolbox_component');
const buttonElements = Array.from(toolbox_component.children);
try {
buttonElements.sort((a, b) => this.sortMethod(this.buttons[a.id], this.buttons[b.id]));
} catch (e) {
console.error('Sorting function produced error', e);
buttonElements.sort((a, b) => this._defaultSortMethod(this.buttons[a.id], this.buttons[b.id]));
}
buttonElements.forEach((buttonElement) => toolbox_component.appendChild(buttonElement));
},
/**
* Sets the sorting method for the toolbox buttons.
*
* @param {Function} sortMethod - The sorting method to be used.
* @returns {void}
*
* @example
* IITC.toolbox.setSortMethod((a, b) => a.label.localeCompare(b.label));
*/
setSortMethod(sortMethod) {
this.sortMethod = sortMethod;
this._applySort();
},
/**
* Internal method to synchronize the toolbox with the legacy toolbox.
*
* @private
* @returns {void}
*/
_syncWithLegacyToolbox() {
// Select the old toolbox element
const oldToolbox = document.querySelector('#toolbox');
// Function to process an individual button
const processButton = (node) => {
// Check if the node is an 'A' tag (anchor/link, which represents a button)
if (node.tagName === 'A') {
let iconClass = null;
// Find an icon element within the button, if it exists
const iconElement = node.querySelector('i.fa');
if (iconElement) {
// Extract the icon class
const iconClasses = Array.from(iconElement.classList).filter((cls) => cls.startsWith('fa-'));
if (iconClasses.length > 0) iconClass = iconClasses[0];
}
// Prepare the button arguments for either updating or adding the button
const buttonArgs = {
id: node.id,
label: node.textContent.trim(),
action: () => node.click(),
class: node.className,
title: node.title,
accessKey: node.accessKey,
mouseover: node.mouseover,
icon: iconClass,
};
// Update an existing button or add a new one
buttonArgs['id'] = `legacy-toolbox-btn-${buttonArgs.id || buttonArgs.label}`;
if (this.buttons[buttonArgs.id]) {
this.updateButton(buttonArgs.id, buttonArgs);
} else {
this.addButton(buttonArgs);
}
}
};
// Initialize for existing buttons in the toolbox
oldToolbox.querySelectorAll('a').forEach(processButton);
// Mutation observer to watch for changes in the toolbox
const observer = new MutationObserver((mutations) => {
// Iterate through mutations
mutations.forEach((mutation) => {
// Process each added node and attribute changes
mutation.addedNodes.forEach(processButton);
if (mutation.type === 'attributes') {
processButton(mutation.target);
}
});
});
// Start observing the toolbox for changes
observer.observe(oldToolbox, { childList: true, subtree: true, attributes: true });
},
};
IITC.toolbox._syncWithLegacyToolbox();