Source

controller.js

import Model from './model.js'
import View from './view.js'
import Component from './component.js'

/**
 * Class representing a Controller.
 */
export class Controller {
    /**
     * Create a controller.
     * @param {object} model - The model object.
     * @param {object} view - The view object.
     */
    constructor(model, view) {
        this.model = model
        this.view = view
        this.context = null
        this.locale = 'en-US'
        this.minFractionDigits = 0
        this.maxFractionDigits = 2
        this.router = null
    }

    /**
     * Configurate the behavior of the controller.
     * Derivated controllers can define their own properties by overriding the method propertyNames.
     *
     * @param {object} data - Configuration data as object with key/value pairs.
     */
    configurate(data) {
        if (typeof data === 'object') {
            for (const propertyName of this.getPropertyNames()) {
                if (data[propertyName] !== undefined) {
                    this[propertyName] = data[propertyName]
                }
            }
        }
    }

    /**
     * Get property names.
     *
     * @param {array} customPropertyNames - An array with the names of a derived class.
     * @return {array} An array with property names or undefined, if no properties exist.
     */
    getPropertyNames(customPropertyNames) {
        const propertyNames = ['locale', 'minFractionDigits', 'maxFractionDigits']

        if (customPropertyNames !== undefined) {
            return propertyNames.concat(customPropertyNames)
        }
        else {
            return propertyNames
        }
    }

    /**
     * Set properties for a single component.
     *
     * @param {int} id - The id of the component to change.
     * @param {object} data - The properties to change.
     */
    setProperties(id, data) {
        const component = this.view.getComponentById(id)

        if (typeof component === 'object') {
            component.setProperties(data)
        }
    }

    /**
     * Set new controller context.
     *
     * @param {class} context - The new context to be activated.
     */
    setContext(context) {
        this.context = context
    }

    /**
     * Build the DOM elements.
     *
     * @param {string} selector - DOM selector to the element, where to mount the app.
     */
    buildView(selector) {
        this.view.buildDOM(selector)
    }

    /**
     * Get a component by id.
     *
     * @param {string} id - Id of component to get.
     */
    getComponentById(id) {
        return this.view.getComponentById(id)
    }

    /**
     * Send a message to the current view.
     *
     * @param {any} message - The component is responsible for the interpretation of the message.
     */
    sendMessageToView(message) {
        this.view.handleMessage(message)
    }

    /**
     * Send a message to a specific component.
     *
     * @param {string} id - The id of the component, which should receive the message.
     * @param {any} message - The component is responsible for the interpretation of the message.
     */
    sendMessageToComponent(id, message) {
        const component = this.view.getComponentById(id)

        if (typeof component === 'object') {
            component.handleMessage(message)
        }
    }

    /**
     * Fetch JSON data from URL.
     *
     * @param {string} url - URL to the data source.
     * @param {any} identifier - Property which handlers can use to identify the type of data.
     */
    fetchJsonData(url, identifier) {
        // Make an API request using fetch or XMLHttpRequest
        fetch(url)
            .then((response) => response.json())
            .then((data) => {
                // Update the model with the fetched data.
                if (this.context !== null) {
                    this.context.onDataChanged(data, identifier)
                }
                else {
                    this.onDataChanged(data, identifier)
                }
            })
            .catch((error) => {
                console.error('Error fetching data:', error)
            })
    }

    /**
     * Formats a value for display
     *
     * @param {number} value - The value to format.
     * @param {string} locale - An optional locale, i.e. 'us/EN' or 'de/DE'.
     * @param {int} minFractionDigits - An optional value for the minium amount of digits.
     * @param {int} maxFractionDigits - An optional value for the maxium amount of digits.
     */
    formatNumber(value, locale, minFractionDigits, maxFractionDigits) {
        if (typeof value === 'undefined' || value === null) {
            return undefined
        }
        else {
            let usedLocale = this.locale

            if (typeof locale === 'string') {
                usedLocale = locale
            }

            if (minFractionDigits === undefined) {
                minFractionDigits = this.minFractionDigits
            }

            if (maxFractionDigits === undefined) {
                maxFractionDigits = this.maxFractionDigits
            }

            return value.toLocaleString(usedLocale, {
                style: 'decimal',
                minimumFractionDigits: minFractionDigits,
                maximumFractionDigits: maxFractionDigits
            })
        }
    }

    /**
     * Converts a float to a string with a maximum of fractional digits.
     *
     * @param {number} value - The value to convert.
     * @param {int} maxFractionDigits - An optional value for the maxium amount of decimal digits.
     * @return {string} A string representing the number with specified decimal digits.
     */
    static numberToString(value, maxFractionDigits) {
        if (value === undefined || value === null) {
            return ''
        }

        if (maxFractionDigits === undefined) {
            maxFractionDigits = 2
        }

        return parseFloat(value.toFixed(maxFractionDigits))
    }
}