FabricLabs / maki

:bento: declarative application framework for high-performance cross-platform services
https://maki.io
MIT License
69 stars 33 forks source link

Define and create Application State #118

Open chrisinajar opened 8 years ago

chrisinajar commented 8 years ago

As per #117, we need an Application State layer in the Maki stack.

This is the state of the instanced application being run by an Identity. The state can be any arbitrary tree of values, all of which are observable and evented. You can bind any attribute or sub-tree to an addressable object from the Data Layer which then synchronizes two way using the events from either layer.

Discuss libraries and techniques to use, as well as the rigid interfaces in which it communicates.

chrisinajar commented 8 years ago

I propose we use http://github.com/raynos/observ along with the array, struct, and assorted helper projects and http://github.com/bendrucker/dover for giving events access to the application state statelessly.

Components of the application can define their local state and event handlers like this

var state = Dover({
  enabled: Observ(false),
  channels: {
    toggle: toggle
  }
});

function toggle(state) {
  state.enabled.set(!state.enabled());
}

and then the rendered state will contain the toggle methods which will pass the state back in to the handlers. This can either be run as the handler itself for a UI event or called as part of one.

This makes the whole thing totally stateless with no need to actually expose a special Application State API to the UI components. It also defines static channels in which the state can be mutated from.

chrisinajar commented 8 years ago

Example external API

Here's a little app with posts which have comments, and a modal settings window. I only show the includes for relative files and assume Observ, ObservStruct, ObservArray, and Dover all exist. This also doesn't show any of the data binding, only the schema definition of the Application State.

Root App

var Post = require('./post');
var Settings = require('./settings');

module.exports = App;

function App(initialState) {
  return ObservStruct({
    settings: Settings(initialState.settings)
    posts: ObservArray((initialState.posts || initialState.posts = []).map(Post))
  });
}

Posts

var Comment = require('./comment');

module.exports = Post;

function Post(data) {
  return Dover({
    title: Observ(data.title),
    body: Observ(data.body),
    comments: ObservArray(data.comments.map(Comment)),
    channels: {
      leaveComment: leaveComment
    }
  })
}

function leaveComment(state, data) {
  state.comments.push(Comment(data));
}

Comment

module.exports = Comment;

function Comment(data) {
  return ObservStruct({
    body: Observ(data.body)
  })
}

Settings

module.exports = Settings;

function Settings(data) {
  return Dover({
    modalVisible: Observ(false),
    favoriteColor: Observ(data.favoriteColor)
    channels: {
      toggleModal: toggleModal,
      changeFavoriteColor: changeFavoriteColor
    }
  });
}

// presumably from a UI event to open the modal
// the change events cause the UX and UI to update to show it
function toggleModal(state) {
  state.modalVisible.set(!state.modalVisible());
}

// presumable from a UI event from inside the modal
function changeFavoriteColor(state, newColor) {
  state.favoriteColor.set(newColor);
}

What this results in

This code would not be in your maki app, but instead here to show how this layer will communicate with the UX

var App = require('./app');

var app = App();

// this is a JSON blob with all channels referencing usable javascript functions
var myCurrentState = app();

app(function() {
  // This is a change event handler for something change in the app.
  // In the Application State implementation, this is where I would grab
  // a reference to the latest app() JSON and send it to the UX layer
});

// data binding would be implemented basically like this
// read in and set all changes from the network
myPostsDataStore.onChange(app.posts.set);
// whenever we edit the posts locally then we try to get the network to agree with our patch
app.posts(myPostsDataStore.proposeChange);