assets_js_controllers_branch-links-controller.js

import { Controller } from "@hotwired/stimulus"

/**
 * BranchLinks Stimulus Controller
 * 
 * Manages dynamic branch link collection with URL validation, link type categorization,
 * and form integration. Provides a user-friendly interface for adding, removing, and
 * organizing branch-related links with Bootstrap UI components.
 * 
 * Features:
 * - Dynamic link addition with URL sanitization
 * - Link type selection with Bootstrap Icons
 * - Duplicate prevention and validation
 * - Real-time form value synchronization
 * - Bootstrap-styled UI components
 * 
 * Targets:
 * - new: Input field for new link URLs
 * - formValue: Hidden field containing JSON array of all links
 * - displayList: Container for displaying added links
 * - linkType: Element for link type selection with icon display
 * 
 * Usage:
 * <div data-controller="branch-links">
 *   <input data-branch-links-target="new" type="url" placeholder="Enter URL">
 *   <div data-branch-links-target="linkType" data-value="link" class="bi bi-link"></div>
 *   <button data-action="click->branch-links#add">Add Link</button>
 *   <div data-branch-links-target="displayList"></div>
 *   <input data-branch-links-target="formValue" type="hidden" name="links">
 * </div>
 */
class BrancheLinks extends Controller {
    static targets = ["new", "formValue", "displayList", "linkType"];

    /**
     * Initialize controller state
     * Sets up empty items array for link management
     */
    initialize() {
        this.items = [];
    }

    /**
     * Set the link type for new link additions
     * Updates the link type icon and stores the selected type
     * 
     * @param {Event} event - Click event from link type selector
     */
    setLinkType(event) {
        event.preventDefault();
        let linkType = event.target.getAttribute('data-value');
        let previousLinkType = this.linkTypeTarget.dataset.value;
        this.linkTypeTarget.classList.remove('bi-' + previousLinkType);
        this.linkTypeTarget.classList.add('bi-' + linkType);
        this.linkTypeTarget.dataset.value = linkType;
    }

    /**
     * Add a new link to the collection
     * Validates input, sanitizes URL, prevents duplicates, and updates display
     * 
     * @param {Event} event - Click event from add button
     */
    add(event) {
        event.preventDefault();
        if (!this.newTarget.checkValidity()) {
            this.newTarget.reportValidity();
            return;
        }
        if (!this.newTarget.value) {
            return;
        }
        let url = KMP_utils.sanitizeUrl(this.newTarget.value);
        let type = this.linkTypeTarget.dataset.value;
        //check urls for duplicate url and type
        if (this.items.find(item => item.url === url && item.type === type)) {
            return;
        }
        let item = { "url": KMP_utils.sanitizeUrl(this.newTarget.value), "type": this.linkTypeTarget.dataset.value };
        this.items.push(item);
        this.createListItem(item);
        this.formValueTarget.value = JSON.stringify(this.items);
        this.newTarget.value = '';
        this.linkTypeTarget.dataset.value = 'link';
        this.linkTypeTarget.classList.remove('bi-' + type);
        this.linkTypeTarget.classList.add('bi-link');
    }

    /**
     * Remove a link from the collection
     * Filters out the specified item and updates the display
     * 
     * @param {Event} event - Click event from remove button
     */
    remove(event) {
        event.preventDefault();
        let id = event.target.getAttribute('data-id');
        let removeItem = JSON.parse(id);
        this.items = this.items.filter(item => {
            return item.url !== removeItem.url || item.type !== removeItem.type;
        });
        this.formValueTarget.value = JSON.stringify(this.items);
        event.target.parentElement.remove();
    }

    /**
     * Connect controller to DOM
     * Loads existing links from form value and recreates the display
     */
    connect() {
        if (this.formValueTarget.value && this.formValueTarget.value.length > 0) {
            this.items = JSON.parse(this.formValueTarget.value);
            this.items.forEach(item => {
                //create a remove button
                this.createListItem(item);
            });
        }
    }

    /**
     * Create a visual list item for a link
     * Generates Bootstrap input group with icon, URL display, and remove button
     * 
     * @param {Object} item - Link object with url and type properties
     */
    createListItem(item) {
        let removeButton = document.createElement('button');
        removeButton.innerHTML = 'Remove';
        removeButton.setAttribute('data-action', 'branch-links#remove');
        removeButton.setAttribute('data-id', JSON.stringify(item));
        removeButton.setAttribute('class', 'btn btn-danger btn-sm');
        removeButton.setAttribute('type', 'button');
        //create a list item
        let inputGroup = document.createElement('div');
        inputGroup.setAttribute('class', 'input-group mb-1');
        let iconSpan = document.createElement('span');
        iconSpan.setAttribute('class', 'input-group-text bi bi-' + item.type);
        inputGroup.appendChild(iconSpan);
        let span = document.createElement('span');
        span.innerHTML = item.url
        span.setAttribute('class', 'form-control');
        inputGroup.appendChild(span);
        inputGroup.appendChild(removeButton);
        this.displayListTarget.appendChild(inputGroup);
    }
}
// add to window.Controllers with a name of the controller
if (!window.Controllers) {
    window.Controllers = {};
}
window.Controllers["branch-links"] = BrancheLinks;