Background:

Problem: Design the API for fetching specific scenes, and their items.

We currently have a method to get the current scene based on a specific View. But we did not have a method to get a specific scene.

Current XJS 2.x API is:

import { Scene } from 'xjs-framework';

Scene.getSceneByIndex(0).then(scene => scene.getItems()).then(..rest);

That API feels natural. The implementation here is that getSceneByIndex is a static method that returns a Scene instance.

In 3.x, we cannot make use of static methods because it has to be aware of its context. Since it’s possible to have multiple XJS instance in one page, everything we do should be aware of its context, and static methods cannot do that.

Proposed implementation:

So we (me and Marlo) talked about this in length on how to implement this in a way that would be “natural to the consumers”, and came up with this:

import Xjs from 'xjs-framework';
import XjsTypes from 'xjs-framework/types';

const xjs = new Xjs({ type: XjsTypes.LOCAL });
const xjsRemote = new Xjs({ type: XjsTypes.REMOTE, onSend, onReceive });

const { id: sceneId } = await xjs.Scene.getByIndex(1); // Gets local scenes
const items = await xjs.Scene.getItems(sceneId);

const { id: sceneIdRemote } = await xjsRemote.Scene.getByIndex(1); // Get remote scenes
const itemsRemote = await xjs.Scene.getItems(sceneIdRemote);

There’s some key differences here compared with 2.x.

  • In 2.x, xjs.Scene is a class, while in 3.x, it would be an instance that gets assigned in Xjs’ constructor.
  • In 2.x, Scene.getByIndex returns a scene instance. In 3.x, we’re proposing to only return a plain JS object that holds the necessary information of the specific scene.
  • In 2.x, you will have access to a specific scene’s instance methods, which would return the values related to the specific scene defined by that scene’s instance. In 3.x, it would be sort of a functional approach, wherein the scene instance would NOT know which scene it is. It only exists to store the Xjs instance it would run on. Details about which scene, etc. will be passed to the method’s parameters.

So it’s more of a hybrid of the FP approach while still retaining its OO sides, if that makes sense.

Other approaches

We had other approaches that we discussed, which we did not push through, but might still be considered based on the teams feedback.

Use main xjs instance to get “almost” everything.

We also considered that we just do proper get* methods for getting scenes, views, etc. in the main xjs instance:

import xjs from 'xjs-framework';

const xjs = new Xjs();
const scene = await xjs.getScene(index);
const items = await scene.getItems();

There would be a couple of “top level” instances that you can only get through a method in an xjs instance, like: getScene, getView, getConfigWindow, etc.

I think this is the most simple and naive way to go about. The only issue is that the main xjs.ts could easily be bloated in the future as more “top level” functionalities are implemented.

Do the proper FP approach

We also considered doing it the functional way, but due to how the Xjs context would be shared, we decided not to push through this one.

import { createXjs } from 'xjs-framework';
import { getScene } from 'xjs-framework/scenes';
import { getItems } from 'xjs-framework/items';

const xjs = createXjs(config); // Returns an object 
const scene = await getScene({ xjs }, 0); // Will return { xjs, value }
const items = await getItems(scene); // Will return { xjs, value }

It would’ve looked better with pipes, but that just isn’t yet supported, even by transpilers out of the box. (I wrote a blog post about this thought)

Conclusion

Me and @marloeleven would want to share this with the rest of the Frontend team sometime next week to gather their thoughts on this matter. But as for now, our codebase would reflect the proposed implementation written out in the first part of this blog post.