js/CustomFields.js

/**=====LICENSE STATEMENT START=====
    Translator++ 
    CAT (Computer-Assisted Translation) tools and framework to create quality
    translations and localizations efficiently.
        
    Copyright (C) 2018  Dreamsavior<dreamsavior@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
=====LICENSE STATEMENT END=====*/

// ===========================================================
// Custom field class
// ===========================================================
/**
 * CustomFields class for handling custom form fields.
 * @extends require("www/js/BasicEventHandler.js")
 */
class CustomFields extends require("www/js/BasicEventHandler.js") {
    /**
     * Constructor for CustomFields class.
     * @param {jQuery|Object} $form - jQuery object or object representing form.
     * @param {Object} [options={}] - Options for customization.
     */
    constructor($form, options = {}) {
        super($form);


        // Warns about creation
        console.log("Creating custom fields", arguments);

        // Handling different argument scenarios
        // if (arguments.length == 1 && typeof $form == "object" && $form.constructor.name !== "jQuery") {
        //     options = {
        //         jsonForm: $form
        //     }
        //     $form = $("<form></form>");
        //     $form.submit(false);
        // }
		if (arguments.length == 1) {
			options = CustomFields.sanitize(arguments[0]);
			$form = $("<form class='autogenerateForm'></form>");
		} else {
			options = CustomFields.sanitize(options);
			$form ||= $("<form></form>");
		}

        /**
         * jQuery form element.
         * @type {jQuery}
         */
        this.$elm = $form;
		this.$elm.on("submit", (e)=> {
			e.preventDefault();
		});

		this.getElm = function() {
			return this.$elm;
		}


        /**
         * Initialize the CustomFields instance.
         */
        this.init = (value) => {
			this.$elm.empty();
			if (value) {
				if (options.jsonForm) {
					options.jsonForm.value = value;
				}
			}
            if (options.jsonForm) this.addJSONForm(options.jsonForm);
            if (options.html) this.addHTML(options.html);
            console.warn("CustomFields rendered", this);
            this.resolveState("ready");
        };

        this.init();
    }

    /**
     * Adds JSON form to the form element.
     * @param {Object} [settings={}] - Settings for the JSON form.
     */
    addJSONForm(settings = {}) {
        console.log("Adding jsonform", settings);
        try {
            this.$elm.jsonForm(settings);
            CustomFields.bindExternalLinks(this.$elm[0]);
        } catch (e) {
            console.warn("Invalid jsonForm options", e);
        }
    }

    /**
     * Gets the JSON form tree.
     * @returns {Object} - JSON form tree data.
     */
    getJsonFormTree() {
        return this.$elm.data("jsonformTree");
    }

    /**
     * Validates the form.
     * @returns {boolean} - Validation result.
     */
    validate() {
        const ft = this.getJsonFormTree();
        if (!ft) return;
        return ft.validate();
    }

    /**
     * Adds HTML content to the form.
     * @param {jQuery} $elm - jQuery object representing HTML content.
     */
    addHTML($elm) {
        this.$elm.append($elm);
    }

    /**
     * Gets values from the form.
     * @returns {Object} - Form field values.
     */
    getValues() {
        let fd = new FormData(this.$elm[0]);
        return Object.fromEntries(fd);
    }

    /**
     * set values from the form.
     * @param {Object} values - Form field values.
     */
	setValues(values) {
		if (!values) return;
		this.init(values);
	}

	/**
     * Gets JSONForm's value.
     * @returns {Object} - Form field values.
     */
	getFormValues() {
		const jsonForm = this.getJsonFormTree();
		if (!jsonForm) throw new Error("Not a jsonform");
		return jsonForm.root.getFormValues()
	}

    /**
     * Sets value for a field by its name.
     * @param {string} name - Name of the field.
     * @param {any} val - Value to set.
     */
    setValueByName(name, val) {
        console.log("Updating value", name, val, "of", this);
        const $fld = this.$elm.find(`[name="${name}"]`);
        $fld.val(val);
    }
}

/**
 * Sanitizes options for CustomFields class.
 * @param {Object} option - Options object.
 * @returns {Object} - Sanitized options.
 */
CustomFields.sanitize = function(option) {
	console.log("CustomFields - sanitizing option", option);
	if (typeof option == "function") {
		option = option();
	}
    if (common.isHTMLNode(option)) return {
        html: option
    }
    if (CustomFields.isJsonForm(option)) return {
        jsonForm: option
    }
    return option;
};

/**
 * Checks if the data represents a JSON form.
 * @param {Object|string|function} [data={}] - Data to check.
 * @returns {boolean} - Whether the data represents a JSON form.
 */
CustomFields.isJsonForm = function(data = {}) {
    if (typeof data == "string") {
        if (common.isJSON(data)) data = JSON.parse(data);
    } else if (typeof data == "function") {
        data = data() || {};
    }
    if (!data) return false;
    if (data.constructor.name !== "Object") return false;
    if (!data?.schema && !data?.form) return false;
    return true;
};

CustomFields.bindExternalLinks = function(parentElement) {
    // Select all elements with the 'help-block' class within the parentElement
    var helpBlocks = parentElement.querySelectorAll('.help-block');
  
    // Iterate over each 'help-block' element
    helpBlocks.forEach(function(helpBlock) {
      // Use a regular expression to find URL-like text
      var urlPattern = /https?:\/\/[^\s]+/g;
      var text = helpBlock.textContent;
      var urls = text.match(urlPattern);
  
      // If URLs are found, create clickable elements
      if (urls) {
        urls.forEach(function(url) {
          var link = document.createElement('a');
          link.href = "#";
          link.textContent = url;
          $(link).attr('data-url', url);
          $(link).attr("external", true);
          // Bind the click event to open the URL externally

  
          // Replace the URL text with the clickable link
          helpBlock.innerHTML = helpBlock.innerHTML.replace(url, link.outerHTML);

          $(helpBlock).find("a[external]").on("click", function(e) {
                e.preventDefault(); // Prevent the default link behavior
                $(this).addClass("externalRendered");

                console.log("opening external link", $(this).attr("data-url"));
                nw.Shell.openExternal($(this).attr("data-url")); // Open the URL in the system's browser
            });
        });
      }
    });

    $(parentElement).find("a[external]").not(".externalRendered").on("click", function(e) {
        $(this).addClass("externalRendered");
        e.preventDefault(); // Prevent the default link behavior
        console.log("opening external link", $(this).attr("href"));
        nw.Shell.openExternal($(this).attr("href")); // Open the URL in the system's browser
    });
    $(parentElement).find("a.externalLink").not(".externalRendered").on("click", function(e) {
        $(this).addClass("externalRendered");
        e.preventDefault(); // Prevent the default link behavior
        console.log("opening external link", $(this).attr("href"));
        nw.Shell.openExternal($(this).attr("href")); // Open the URL in the system's browser
    });
}


CustomFields.loadJSONForm = async function() {
    // load the main jsonform module
    await common.loadDomScript("/www/modules/jsonform/lib/jsonform.js");
    
    // load extended jsonform modules
    await common.loadDomScript("/www/js/jsonform.ext.js");
}

module.exports = CustomFields;