Spec: Calling the server from Javascript

Plans

I want to implment this spec. I suspect one to two hours of time to code up a rudimentary first version, which could then be patched during my normal course of useage. This spec has already taken an hour, but will save significant coding time. This feature is not a priority. No projects I'm currently working on have an explicit need for this feature, so I would lose productive time. Therefore this is a hobby/side-project. I plan to implement it, but only when I feel like it & it sounds like fun. I'm guessing it will be ready by June 15... because I'm really excited about this! I intend to include the server-side code WITH the js library. PHP is what I'll write. Hopefully other languages will get server-side code through the open-source community

Details

Currently, calling the server requires a Request object, a couple then functions & overall, it's just a bunch of boilerplate. It makes it hard to really think about how user interactions happen. I'd like a simpler method of getting information. I'm seeking to solve this problem by using direct method-calls, which get sent to the server and call a function on the server, with the paramaters passed to the function call. It's supposed to FEEL like the server-side code is being called directly in the client. This requires the use of async functions, which means, if we make a really nice UX, we might need "loading" spinners... Maybe.

So, I'd like to make a pseudo-sample of this. Let's consider an application that shows you all the characters in Super Smash Ultimate (tm). When a user clicks on a character, the character's moves are loaded from the server and shown to the user. I likely would not normally lazy-load these things, but it will show off the spec idea well enough.

Spec

We have the following class which represents a character

class SmashCharacter extends RB.Autowire {

    function _dialog(){
        return 'MoveDialog';
    }
    function __attach(){
        this.characterName = this.node.getAttribute('data-charactername');
        //this.characterName = this.data('character_name');
        this.characterId = this.node.getAttribute('data-characterid');
        this.moves = null;
    }

    async function onclick(event){
        this.showSpinnerWheelDelayed(200/*ms*/); //so if we cancel within 200ms, the spinner wheel will never show. 50-100ms is probably more UX friendly
/*HERE*/const moves = this.moves || await this.retrieveMoves(this.characterId);
        this.moves = moves; //caching... which could be handled by the retrieveMoves call??
        const dialog = this.dialog;
        //const dialog = this.fromNode(document.getElementById("moves-dialog"));
        //const dialog = this.fromNode('#moves-dialog');
        dialog.clear();
        for (const move in moves){ 
            dialog.addMove(move);
        }
        dialog.show();
        this.cancelSpinnerWheel();
    }
}

Most of that is off-topic, but you can see the line - const moves = this.moves || await this.retrieveMoves(this.characterId); This line is what we're speccing here. But, I will note other features demonstrated here, which I would like to make real, in some form: - Retrieving another Autowire Object - cur: function _dialog(){return 'MoveDialog'}... this.dialog - new: this.fromNode(document.getElementById("moves-dialog")); - new: this.fromNode('#moves-dialog'); - new: this.dialog = this.refOne('MoveDialog'); this.refAll('MoveDialog') would return an array - old: this.MoveDialog... this requires setting auto-props based upon what is in the context, which I found to be cumbersome last time I tried it

Back to the spec at hand: - const moves = this.moves || await this.retrieveMoves(this.characterId); How does this work in javascript? We prepare a request to a configured url. We create an object like so: - const data = {method:"retrieveMoves", params:[arg0,arg1,arg2...]} It might be useful to explicitly order the params by giving them explicit numeric indices & we send data to the server. - we will ALSO package this data, so the PHP server retrieves it like $_POST['autowire-retrievals'], which is an array of retrieval requests. Thus, the first one will be our retrieveMoves call.

On the server (PHP), there is a class which receives this request:

class Moves extends \AutoWire\Responder { //we might not need to extend. Maybe just implment, to say YES I AM this

    public function retrieveMoves($characterId){
        $moves = \RDB::find("moves","characterId = ?",[$characterId]);
        $clean = \RDB::filterBeans($moves); //removes them from the beans & returns an array of the data in the format I
        $cleaner = $this->filterArray($clean,['id','description','photo','name']);
        return $cleaner;
    }
}

This is literally all the code the app-dev would have to write. Autowire\Backend will handle retrieving the request. What we need for this is: - an API endpoint, say /autowire/retrieve.php - in retrieve.php, do $backend = new \Autowire\Backend($classes=['Moves']); - We'd probably add the ability to add an entire directory... or maybe the client can specify the domain it's calling? This should just be less code writing to wire classes to the backend - Call $response = $backend->respond($_POST['autowire-retrievals']); The backend works by: - Load all the classes specified (by classes array or directory-scanning, etc), if they implement Autowire\Responder - use reflection to find all methods starting with retrieve - Build a map of all available methods & the class names (are these methods static or no?) - Cache this map - loop over the passed in autowire-retrievals, find the entries in the map - For each retrieval requested, - loop over all the available methods in the map - For each method: $responses[] = $obj->method(...$args); - $final = ['retrieveMoves'=>$responses, 'retrieveCharacters'=>$thoseResponses]; - echo json_encode(['responses'=>$final]) Then back to javascript, - Parse the response as json, get responses, and finally return the long-awaited-for-data to the retrieve method. - The returned data can be of any type. Object (keyed array) is probably the most common, but it doesn't have to be - Requests should have ids. This way, we can guarantee that same-named requests will each get their own unique response. These ids are generated on the javascript layer, are temporary, and are discarded when the response is given back to the js

Woah that's cool! (notes & stuff)

  • Calling retrieveMoves sets a 20ms timer. At the end of the 20ms, if any other retrieveWhatevers have been requested, they will be bundled & all these requests will be sent & responded to together.
  • Can I set this up to work in the other direction? Make a call in PHP, have it send to the client & have the client return? Generally, I don't want to request info from the client, but this would be cool as heck!