Autowire

Link javascript classes with DOM nodes and make everything communicate seamlessly with one another. Extensible.

Status: Stable, unpolished

Autowire is stable, but it is not polished. I have used it in production for quite awhile. mtgpaper.me is a good example. It's not "open-source" but you can just look at the javascript source files anyway, as they are not minified or obfuscated currently (Apr 11, 20222).

Issues:

  • there are no proper tests, runnable from cli
  • documentation could be better
    • i have not documented the BindParam extension.
  • API could be better
  • its not on npm or anything

Future Plans

See Status.md ... I don't have explicit plans to develop this further, but i may get around to it one day. It basically does what i need so ...

Install

Copy code/Autowire.js into your project & include it with:

<script src="/autowire.js"></script>  

Usage

For a functional example, see test/docs/kanban/. You should be able to load test/docs/kanban/kanban.html into your browser

  1. Create a class, such as class Item extends Autowire{}. see sample class below.
  2. Attach the class any of these ways
  • new Item(node, ...args)
  • Item.aw(): shorthand for Item.autowire('.Item')
  • Item.autowire('.some-css-selector')
  • const instance = Item.fromTemplate('.query-selector') where the selector can point to a <template> & clone the inner html or a non-<template> and will clone the node. If there are multiple direct children of the <template>, then wraps all those nodes in a <div>

Sample Class

This is nearly everything autowire can do. I didn't include stuff about how onReady() works or a couple other primarily internal things.

class Item extends Autowire {  
  
    /** generally, you shouldn't implement constructor */  
    constructor(node, ...args){  
        super(node,...args);  
    }  
    /** called from constructor after this.node is set   
     * @parma args are same as passed to new `Item(node, ...args);`   
     */  
    onCreate(...args){  
        this.node.innerHtml = 'whatever';  
    }  
    /** called after:  
     * - event listeners are setup   
     * - this object is added to the object map   
     * - extensions are initialized  
     */  
    onAttach(...args){  
        this.child_item = new \ChildItem(document.querySelector('.child-item'));  
        // change `this` to this class for any `on` events declared in html, such as `<button onclick="this.something()">` would now reference Item instead of the node  
        this.bindTo(this.node);  
        //this.bindToAll(node_list) will bind to a node list  
    }  
    /**  
     * called after all autowire objects are setup  
     */  
    onReady(){  
        // get all child objects of this.node. this.ga() is a shorthand  
        const children = this.getAnyList('ChildItem');  
        // get one child object of this.node. this.g() is a shorthand  
        const child = this.getAny('Child');  
  
        const parentNode = document.querySelector('something.whatever');j  
        // get all items of class name that are children of parentNode. parentNode may be null  
        const object_list = Autowire.getObjectsFromName('ClassName', parentNode);  
        // get the Autowire instance attached to the given node  
        const object = Autowire.getObjectFromNode(parentNode);  
    }  
  
    /**  
     * called when this.node is clicked. Even listeners are added for any method starting with `on`  
     *  
     * async is not required, but makes it easy to `await this.fetchJson()`  
     */  
    async onclick(event){  
        // this.fj() for shorthand  
        const new_data = await this.fetchJson('/data/',{"key":"value"}, "POST");  
        // this.ft() for shorthand  
        const new_text = await this.fetchText('/text/', {"key":"value"}, "GET");  
        const response_promise = await this.fetch(/*same params*/);  
        const other_text = response_promise.text(); //or .json() for json  
    }  
  
}  
  
// apply an extension  
Item.aw();  
  
  
class Alerter {  
    // called before onAttach() & after onCreate()  
    static onExtAttach(){  
        // `this` refers to the Autowire object being attached to  
        const node = this.node;  
        this.alert = function(msg){alert("ALERTED!!! "+msg);};  
        // basically just modify the node & the object however you want here ...  
        // such as Object.defineProperty() for ... idk, whatever  
    }  
}  
  
Autowire.expose('Alerter', Alerter);  
  
// turn all Item instances into Alerters  
Item.use(Alerter);  

PHP Integration

Get the file path to Autowire.js or another js file in this repo & add it to the page.

<?php  
$autowire_path = \Tlf\Js\Autowire::filePath();  
$modal_path = \Tlf\Js\Autowire::filePath('Modal.js');  
$bind_param_path = \Tlf\Js\Autowire::filePath('BindParam.js');  
$dropdown_path = \Tlf\Js\Autowire::filePath('addon/Dropdown.js');