libflitter/controller/ControllerUnit.js

/**
 * @module libflitter/controller/ControllerUnit
 */

const rra = require('recursive-readdir-async')
const path = require('path')
const Unit = require('../Unit')

/**
 * This unit loads controllers from the specified directory and makes
 * instances of them available in a global function. This is used when
 * defining route handlers.
 * 
 * @extends module:libflitter/Unit~Unit
 */
class ControllerUnit extends Unit {
    
    /**
     * Instantiates the unit. Resolves the path to the directory with the controller definition files.
     * @param {string} [controller_directory = './app/controllers'] - The path to the directory with the controller definition files
     */
    constructor(controller_directory = './app/controllers'){
        super()

        /**
         * The fully-qualified path to the directory with the controller definition files.
         * @type {string}
         * @name ControllerUnit#directory
         */
        this.directory = path.resolve(controller_directory)
    }

    /**
     * Loads the unit. Loads the controller definition files from {@link module:libflitter/controller/ControllerUnit~ControllerUnit#directory}
     * and binds the controllers and helper functions to the appropriate contexts.
     * @param {module:libflitter/app/FlitterApp~FlitterApp} app - the Flitter app
     * @param {module:libflitter/Context~Context} context - the Unit's context
     * @returns {Promise<void>}
     */
    async go(app, context){

        /*
         * Get the controllers.
         */
        const files = await rra.list(this.directory)
        const controllers = {}

        /*
         * Get the name of the controller by parsing
         * the file name.
         * e.g. "Home.controller.js" becomes "Home"
         */
        for ( let key in files ){
            if ( files[key].fullname.endsWith('.controller.js') ) {
                let name = files[key].fullname.replace(this.directory, '')
                    .replace(/.controller.js/g, '')
                    .replace(/\//g, ':').substr(1)

                /*
                 * Instantiate the controller class.
                 * Store it according to its name.
                 */
                let controller_class = require(files[key].fullname)
                controllers[name] = new controller_class()
            }
        }
        
        context.bind('daemon', controllers)
        context.bind('get', this.getcontroller)
        app.global.bind('controller', this.getcontroller)
    }

    /**
     * Helper function that returns an object with the self-bound methods from the controller with the specified name.
     * @param {string} name - the Flitter canonical name of the controller to retrieve
     * @returns {Object} - collection of self-bound methods from the controller
     */
    getcontroller(name){
        const instance = this.d.controllers.daemon[name]
        
        let handler = {
            get( target, key, receiver ){
                const method = target[key]
                if ( typeof method === 'function' ){
                    return (...args)=>{
                        return method.apply(instance, args)
                    }
                }
                else {
                    return method
                }
            }
        }
        
        return new Proxy(instance, handler)
    }

    /**
     * Get the name of the unit.
     * @returns {string} "controllers"
     */
    name(){
        return "controllers"
    }

    /**
     * Get the directories provided by the unit.
     * Currently "controllers" bound to {@link module:libflitter/controller/ControllerUnit~ControllerUnit#directory}
     * @returns {{controllers: string}}
     */
    directories() {
        return {
            controllers: this.directory
        }
    }

    /**
     * Get the templates provided by the unit.
     * "controller" provided by {@link module:libflitter/templates/controller}.
     * @returns {{controller: {template: ((function(string): string)|*|controller), extension: string, directory: string}}}
     */
    templates(){
        return {
            controller: {
                template: require('libflitter/templates/controller'),
                    directory: this.directory,
                    extension: '.controller.js'
            }
        }
    }
}

module.exports = ControllerUnit