Skip to content
Dan Alvidrez edited this page Dec 7, 2018 · 31 revisions

Laravel Micro JS

Laravel.js is a front-end framework for javascript applications that provides a dependency injection container and familiar framework design that encourages you to use object oriented principals in your frontend application.

Application extends Container

The container is the heart of the application and has many useful methods for interacting with services and bindings.

const App = new Application

Middleware Pipelines


const PipeState = { state: 0 }
import {PipeA, PipeB, PipeC} from "./Mocks"

App.bind('App', App)
App.bind('PipeA', PipeA)
App.bind('PipeB', PipeB)
App.bind('PipeC', PipeC)

const result = (new Pipeline(App))
    .send(PipeState)
    .through([PipeA, PipeB, PipeC])
    .via('handle')
    .then((obj) => {
        obj.state = (obj.state * 2)
        return obj
    })
//result.state === 10

Global Application Middleware

The global middleware pipeline can pass an instance of "something" (whatever type of object you want it to be) to each subsuquent class until it's final state can be acted upon.


/**
 * Register Middleware Stack
 * @param {Array}
 */
import Authenticate from './services/Middleware/Authenticate'
App.middleware([
    Authenticate,
])

App.bind('Request', {
	currentUser: null
})

App.boot()

Service Providers

App.register(MyServiceProvider)

Service Providers declare their provided bindings in the "provides" method.

Providers which set this.deferred=true will not have their boot method called until the first binding is resolved.

import ServiceProvider from "./ServiceProvider"
export default class AppServiceProvider extends ServiceProvider {

    constructor(App) {
        super(App)
        this.deferred = true
    }
    
    /**
     * Register any application services.
     * @return void
     */
    register() {
        this.app.bind('MyDeferredBinding', MyDeferredClass, true)
    }

    /**
     * Boot any application services.
     * @return void
     */
    boot() {

    }

    static get provides() {
        return ['MyDeferredBinding']
    }
}

Register Service Providers with the container.

App.register(MyServiceProvider)
App.register(MyOtherServiceProvider)

Boot the Service Providers.

App.boot()

App.isRegistered(string ProviderName)

Determines if a Service Provider is registered:

if(!App.isRegistered('NotificationServiceProvider')){
    App.register(AlertServiceProvider)
}

Application extends Container

The container is the heart of your application and has many useful methods for interacting with services and bindings.


Binding Alias References: From Abstract to Concrete

Add an binding for an abstract alias that returns a concrete instance.

App.bind(alias {String}, abstract {*}, isSharable {boolean})

Bind a Class Constructor with Auto-Dependency Injection:

App.bind('Config', ConfigClass)

Bind a Object:

App.bind('Config', {
    debug: false,
})

Bind a Class that provides a service and a callback that Injects the Dependency and provides a value:

App.bind('Auth', Auth, true)

const loginToken = App.make('Auth').getToken()

App.setInstance(abstract, concrete, shared = false)

App.setInstance('FileReader', new FileReader, true) //allow sharing

Dependency Injection

export default class Config {
    constructor() { }
}
export default class MyService {
    constructor(Config) {
        this.config = Config
    }
}
App.bind(Config)
App.bind(MyService)

const ServiceInstance = App.make('MyService')

App.make(abstract)

Build or Resolve an instance from the container.

const DatabaseService = App.make('DatabaseService')

App.destroy(abstract)

App.destroy('MyService')

Container: UnBinding: "MyService"...
Container: UnSharing "MyService"...
Container: Destroying shared instance of "MyService"...
Container: Cleaning up resolved references of "MyService"...

App.isBound(abstract)

Determines if a binding is available. Useful for binding temporary services or required services.

if(!App.isBound('App')) {
    App.bind('App', () => App, true) //allow sharing
}

App.isResolved(abstract)

Determines if a service abstract has an shared concrete instance that's already resolved.

if(App.isResolved('DatabaseService')){
    App.destroy('DatabaseService')
}

App.rebound(abstract)

Destroy and re-build a service abstract that has a shared concrete instance that's already resolved.

App.rebound('AuthToken')

Sharing References to Concrete Instances as Callable Methods

The container's sharing API allows you to share references to bindings as functions. You can share many references with many objects and revoke access to all instances of specific references at any time.

App.bind('TempService', () => {
    console.log('ok')
    return 'ok'
})

App.bind('tempInstance', () => {
    const tempInstance = App.make('TempService')
    setTimeout(() => App.unBind('tempInstance'), 10 * 1000)
    console.log('You have 10 seconds to use me!')
    return tempInstance
}, true)

App.share('tempInstance').withOthers(window)

window.tempInstance()

wait 10 seconds...

window.tempInstance()
Uncaught ReferenceError: tempInstance is not defined

App.make('tempInstance')
Container: No Binding found for "tempInstance".

let MortalA, MortalB = {}

App.bind('Potions', ['brewDragon', 'brewLizard', 'brewOger'])

App.bind('doMagic', (Potions) => (new Wizard(Potions)).brew())

App.bind('playMusic', () => (new MusicBox).play())

App.bind('toMereMortal', () => {
    App.unShare('playMusic')
    App.unShare('doMagic')
    App.unShare('toMereMortal')
})

App.share('doMagic', 'playMusic', 'toMereMortal').with(MortalA, MortalB)

MortalA.doMagic()
MortalB.playMusic() 

MortalA.doMagic()
MortalB.playMusic() 

MortalA.toMereMortal()
MortalB.toMereMortal()

MortalA.doMagic() undefined
MortalA.playMusic() undefined
MortalA.toMereMortal() undefined

MortalB.doMagic() undefined
MortalB.playMusic() undefined
MortalB.toMereMortal() undefined

Debugging Methods

Debugging methods allow you to list the current state of the application container by logging the results to the console for table display.

App.listProviders()
App.listBindings()
App.listMethods()
App.listShared()

App.getName(Thing)

Returns a string of the Object "name" property or "constructor name" (class name).


Class Traits (Mixins)

To make code as reusable as possible, a mixin is provided that allows you to create class traits.

Define a Class Trait:

import Mixin from "../Utilities/Mixin"
export default (instance) => Mixin(instance, {
    /**
     * Log if debugging.
     * @return void
     */
    _log(){
        if(this._debug){
            console.log.apply(this, arguments)
        }
    }
})

Trait Usage:

import CanDebug from "../Traits/CanDebug"
export default class MyClass{
    construct(){
         this._debug = true
     }
    doSomething(){
        this._debug(arguments)
    }
    
}
/** MyClass Traits **/
CanDebug(MyClass)

Service Providers

Error Handling

Setting an error handler on the container allows you to catch all errors that bubble up from conatiner bindings.

App.errorHandler(callback or ClassObject)

You can set the container error handler to a class or callback, (disabled by default).

const App = new Application
App.errorHandler(MyErrorHandlerClass)

or use a callback:

App.errorHandler((Error) => {
    console.error(Error)
})

Error Handler

The included error handler class is designed to interact with 'Exceptions" which can have a "handle" method. When the error is thrown it will get caught by the error handler and if there's a handle method that's callable it will call it can return the provided value.

export default class Handler{
    /**
     * Exception Handler Class
     * @param App {Application}
     */
    constructor(App) {
        this.app = App
    }
    /**
     * Handle Method
     * @param Error {Error|Exception}
     */
    handle(Error){
        if(typeof Error.handle === 'function'){
            try{
                return Error.handle(this.app)
            }catch (e) {
                console.error(`Failed to handle "${Error.name}"...`, Error)
            }
        }
        console.info(`"${Error.name}" Encountered...`, Error)
    }
}

Self-Handling Exceptions

If an exception is encountered that has a "handle" method, the method will be called and the exception can react to itself and provide a new instance from the container which will be resolved for the called binding.

In the example below:

  1. An exception is thrown as the container attempts to make "badBinding"
  2. The exception is handled and it's handle method is called.
  3. The handle method dictates the app should return an instance of "backupObject" in it's place.
  4. Every subsequnt call to "App.make('badBinding')" will return the shared instance of "backupObject".
App.bind('backupObject', () => { 
	return { yourGood: true } 
})

class Handler {
    handle(Error){
        return Error.handle ? Error.handle(this.app) : null
    }
}
class MyException extends Exception{
    constructor(...args) {
        super(...args)
        this.handle = (App) => {
            console.log('MyException Encountered, Self-Handling by Providing "backupObject"')
            return App.make('backupObject')
        }
    }
}

App.errorHandler(Handler)

App.bind('badBinding', () => {
	throw new MyException('HAHA Immediate Fail.')
})

const result =  App.make('badBinding')

Container: Sharing Instance of "App".
Container Binding: "backupObject"...
Container Binding: "badBinding"...
Container: Making "badBinding"...
Container: Resolving Binding for "badBinding"...
MyException Encountered, Self-Handling by Providing "backupObject"
Container: Making "backupObject"...
Container: Resolving Binding for "backupObject"...
Container: Instantaiated Concrete Instance successfully. {yourGood: true}
Container: "backupObject" is Sharable.
Container: Sharing Instance of "backupObject".
Container: "badBinding" is Sharable.
Container: Sharing Instance of "badBinding".
Result: {yourGood: true}

Exception extends Error

The framework includes examples for using custom errors referred to as "Exceptions". Exceptions can extend the browsers built-in Error interface. In the example included Exceptions can have a "handle" method that is passed the application instance to its "handle" method. This allows Error to handle themselves by reacting to the application state.

export default class Exception extends Error{
    /**
     * Generic Exception Class
     * @param args {*}
     */
    constructor(...args) {
        super(...args)
        /**
         * Arguments passed to the exception.
         * @property args {Array}
         */
        this.args = args

        /**
         * The name of the Custom Exception.
         * @property name {String}
         */
        this.name = 'Exception'

        /**
         * Handle from the exception when it's thrown.
         * @param App {Application}
         * @return {*}
         */
        this.handle = (App) => {
        	//react to the exception when it's thrown.
        }
    }
}


Custom Error Handling

/** List available methods **/
//console.log(App.availableMethods())

/** List available services **/
//this.App.list()

/**
 * Alias this Instance as a Object Property.
 * @param obj {Object}
 * @param name {String}
 */
//console.log(window.$App)

/**
 * Register an Alias to the Application on another Object.
 * @param obj {Object}
 * @param name {String}
 */
//App.boot()

/**
 * Share references to services with other objects.
 * @param obj [String]
 * @param name [Object]
 */
//App.share('App').with(window)

// App.bind( '$object', {test: 123}, true)
//
// console.log(App.make('$object'))
// console.log(App.make('$object'))
//
// App.unShare('$object')
//
// console.log(App.make('$object'))
//
// App.listProviders()
// App.listBindings()
// App.listSharable()

// App.list()
// App.getName('asd')
// console.log(window.App())
// App.unshare('App')
// console.log(window.App())
/**
 * Request Service Provider
 * @type {Request}
 */
// const Request = App.make('$request')
// console.log(Request.all())
// console.log(Request.searchParams.get('asd'))

/**
 * Artisan Service Provider
 * @type {Artisan}
 */
// const Artisan = App.make('$artisan')
// Artisan.makeProvider('TestService')
/**
 * Auth Service Provider
 * @type {Auth}
 */
// const Auth = App.make('$auth')


/**
 * HttpStatus Service Provider
 * @type {HttpStatus}
 */
//Test HttpStatus Service Provider
// const HttpStatus = App.make('$http.status')

//Test Rebound method.
// console.log(App.rebound('$http.status'))

/**
 * Storage Service Provider
 * @type {Storage}
 */
// const Storage = App.make('$storage.persistent')

//Test Value
// Storage.set('test', 123)
// console.log(Storage.get('test'))

//Test Object
// Storage.set('test', {prop:123})
// console.log(Storage.get('test'))

//Test Array
// Storage.set('test', [{prop:123}, {prop:123}])
// console.log(Storage.get('test'))

/**
 * Cookies Service Provider
 * @type {Cookies}
 */
// const Cookies = App.make('$cookies')
// Cookies.set('test', 123)
// console.log(Cookies.get('test'))
// Cookies.forget('test')
// console.log(Cookies.get('test'))

/**
 * Config Service Provider
 * @type {Config}
 */
// const Config = App.make('$config')
// console.log(Config.get('env'))
// Config.set('env', 'production')
// console.log(Config.get('env'))

/**
 * Http Service Provider
 * @type {Http}
 */
// const Http = App.make('$http')
// Http.request({method: 'get', url: '/cms/pages'})
//     .then(({data})=> {
//         //done
//     })

/**
 * HttpResource Service Provider
 * @type {HttpResource}
 */
// const HttpResource = App.make('$http.pages')
// HttpResource
//     .route('edit', {id: 1})
//     .then(({data})=> {
//         //done
//     })

/**
 * Console Service Provider
 * @type {Console}
 */
// const Console = App.make('$console')
// Console.info('Console: Testing!')
// Console.error('Console: Testing!')
// Console.warn('Console: Testing!')
// Console.table(App.all())


/**
 * Store Service Provider
 * @type {Store}
 */
// let Store = App.make('$store')
// Store.commit('pages.entity', {
//     title: 'test'
// })
// console.log(Store.get('pages.entity'))
// App.destroy('$store')

// Store = App.rebound('$store')
// console.log(Store.get('pages.entity'))


Clone this wiki locally