Coder Social home page Coder Social logo

isocroft / hecter Goto Github PK

View Code? Open in Web Editor NEW
3.0 3.0 0.0 28 KB

A small (experimental & toy) library that utilizes the idea of state-charts and state machines to manage web app behavior enabling predictable & responsive UIs by executing state transition graphs

License: MIT License

JavaScript 100.00%
state-charts scheduled-transitions predictable-uis app-behaviour behaviour-flow-management transient-ui-state non-transient-domain-state state-machines

hecter's Introduction

hecter

A small (experimental & toy) library that utilizes the idea of state-charts and state machines to manage web app behavior enabling predictable & responsive UIs by executing state transition graphs.

APIs

Getters / Triggers:

let machine = new HecterMachine(transitionGraph, initialState);

  • machine.get( void );
  • machine.scheduleNext(eventName, actionData);
  • machine.transit(eventName, actionData);
  • machine.next( void );
  • machine.nextAll( void );
  • machine.disconnect( void );

Hooks:

  • machine.setRerenderHook( Function< state, hasError > );
  • machine.setActionHandlerHook( Function< machine, action> );

Usage

Hecter can be used with any data flow management tool (e.g. Redux / MobX / Radixx )

  • asyncActions.js
/** asyncActions.js */

import axios from 'axios'

const networkRequest = (machine, action, params) => {
	
	let source = action.data.source;
	let type = String(action.type);
	
	return (dispatch, getState) => {
	
		const storeDispatch = function(_action){
			setTimeout(() => {
				dispatch(_action);
			},0);
		};

		return axios(params).then(function(data){
			source = null;
			machine.scheduleNext("$AJAX_SUCCESS_RESP", null);
			return storeDispatch({
				type:type,
				data:data
			});
		}).catch(function(thrownError){
			source = null;
			machine.scheduleNext("$AJAX_ERROR_RESP", thrownError);
			return storeDispatch({
				type:"",
				data:null
			});
		});
	}
};

const delay = (machine, action, promise) => (dispatch, getState) => {

	//....
};

export { networkRequest, delay }
  • hooks.js
/** hooks.js */

import { networkRequest, delay } from './asyncActions.js'

const rerenderHookFactory = (component) => (state, hasError) => {
			component._hasError = hasError;
			return component.setState((prevState) => {
				return Object.assign({}, prevState, {
					global:state
				})
			});
};

const actionHandlerHookFactory = (storeorActionDispatcher) => (machine, action) => {
			switch(action.type){
				case "MAKE_ENTRY":
					action = networkRequest(machine, action, {
						method:"GET",
						url:"https://jsonplaceholder.typicode.com/"+action.data.text,
						cancelToken:action.data.source.token
					});
				break;
			}
			
			return storeorActionDispatcher.dispatch(action);
};

export { actionHandlerHookFactory, rerenderHookFactory }
  • machine.js
/** machine.js */

const stateGraph = {
      idle:{
        $SEARCH_BUTTON_CLICK:{
          guard:function(stateGraph, currentState, actionData){
            return actionData.text.length > 0 
                ? 'searching' 
                : {current:null, error:new Error("No text entered in form...")};
          },
          action:function(actionData = null){
            return {type:"MAKE_ENTRY",data:actionData};
          },
          parallel:function(stateGraph, currentState){
            return "form.loading"
          }
        }
      },
      searching:{
        $AJAX_SUCCESS_RESP:{
          guard:function(stateGraph, currentState, actionData){
             return 'idle';
          },
	  action:null,
          parallel:function(stateGraph, currentState){
            return "form.ready"
          }
        },
        $AJAX_ERROR_RESP:{
          guard:function(stateGraph, currentState, actionData){
              return actionData instanceof Error 
	      ? {current:'idle', error:actionData} 
	      : {current:null, error:new Error("Invalid transition...")};
          },
          action:null,
	  parallel:function(stateGraph, currentState){
	  	return "form.ready";
	  }
        },
        $CANCEL_BUTTON_CLICK:{
            guard:function(stateGraph, currentState, actionData){
                return 'canceled';
            },
	    action:null,
	    parallel:function(stateGraph, currentState){
            	return "form.ready"
            }
        }
      },
      canceled:{
          $BUTTON_CLICK:{
            guard:function(stateGraph, currentState, actionData){
		return 'searching'
            },
	    action:null,
	    parallel:function(stateGraph, currentState){
            	return "form.loading"
            }
          }
      }
};

const machine = new HecterMachine(stateGraph, {
			current:'idle',
			parallel:'form.ready',
			error:null
		});

export default machine
  • store.js
/** store.js */

import { createStore, applyMiddleware } from 'redux'
import machine from './machine.js'

const thunkMiddleware = ({ dispatch, getState }) => next => action => {
	
    if (typeof action === 'function') {
      	return action(dispatch, getState);
    }

    return next(action);
};

const loggerMiddleware = ({ getState }) => next => action => {
	
	console.log("PREV APP DATA STATE: ", getState());
	next(action);
	console.log("NEXT APP DATA STATE: ", getState());
	
};

const store = createStore(
function(state, action){
  switch(action.type){
    	case "MAKE_ENTRY":
		return Object.assign({}, state, {
			items:action.data
		});
	break;
	default:
		return state
  }
},
{items:[]},
applyMiddleware(thunkMiddleware, loggerMiddleware)
);

store.subscribe(machine.nextAll.bind(machine));

export default store
  • FormUnitComponent.js
/** FormUnitComponent.js */

import machine from './machine.js'
import axios from 'axios'

const source = null

const searchButtonClick = event => {

	const CancelToken = axios.CancelToken;
	const source = CancelToken.source();
	source = source
	
	machine.transit('$SEARCH_BUTTON_CLICK', {text:event.target.form.elements['query'].value, source});
}

const cancelButtonClick = event => {

	if(source !== null)
		source.cancel();

	machine.transit('$CANCEL_BUTTON_CLICK', null);
}

const renderInput = (data, behavior) => (
	behavior.parallel == "form.loading"
	? <input type="text" name="query" value={p.text} readonly="readonly">
	: <input type="text" name="query" value={p.text}>
)

const renderSearchButton = (data, behavior) => (
	behavior.parallel == "form.loading"
	? <button type="button" name="search" onClick={searchButtonClick} disabled="disabled">Searching...</button> 
	: <button type="button" name="search" onClick={searchButtonClick}>Search</button>
)

const renderCancelButton = (data, behavior) => (
	behavior.current === 'idle'
	? <button type="button" name="cancel" onClick={cancelButtonClick} disabled="disabled">Cancel</button> 
	: (behavior.current === 'canceled' 
			? <button type="button" name="cancel" onClick={cancelButtonClick} disabled="disabled">Canceling...</button>
			: <button type="button" name="cancel" onClick={cancelButtonClick}>Cancel</button>)
)

const renderList = (data, behavior) => (
	data.length 
	? <ul>
		data.map(item => 
			<li>item</li>
		);
	  </ul>
	: <p>No search data yet!</p>
)

const renderLoadingMessage = (data, behavior) => {
	let message = `Loading Search Results...`;
	    
    	return (
		<p>
			<span>{message}</span>
		</p>
	);
}

const renderErrorMessage = (data, behavior) => {
	let message = `Error Loading Search Results: ${behavior.error}`;
	    
    	return (
		<p>
			<span>{message}</span>
		</p>
	);
}

const renderResult = (data, behavior) => (
		behavior.parallel === 'form.loading' 
		    ? renderLoadingMessage(data, behavior)
		    : behavior.error !== null ? renderErrorMessage(data, behavior) : renderList(data, behavior)
)



const FormUnit = props => 
	<div>
      		<form name="search" onSubmit={ (e) => e.preventDefault() }>
		  	{renderInput(null, props.behavior)}
	    	  	{renderSearchButton(null, props.behavior)}
	    		{renderCancelButton(null, props.behavior)}
              	</form>
	    	{renderResult(props.items, props.behavior)}
          </div>
	  
 export default FormUnit
  • App.js
  /** App.js */
  
  import React, { Component } from 'react'
  import { actionHandlerHookFactory, rerenderHookFactory } from './hooks.js'
  import FormUnit from './FormUnitComponent.js'
  
  class App extends Component {
  	
	constructor(props){

		super(props)
		
		this.state = {
			global:props.machine.get(), // any global state which every part/component of our app should be tracking continually
			values:{}, // any values e.g. form values that need to be tracked
			counts:{}, // any counter/counters that need to be tracked
			statuses:{} // any status/statuses that need to be tracked
		};

		props.machine.setRerenderHook(rerenderHookFactory(this));
		props.machine.setActionHandlerHook(actionHandlerHookFactory(
			props.store
		));
	}

     	componentDidUnmount(){
		
		this.props.machine.disconnect();
	}
	
	render(){
		let data = this.props.store.getState();
		
		<FormUnit items={data.items} behavior={this.state} />
	}
  }
  
  export default App
  
  • main.js
/** main.js */

import ReactDOM from 'react-dom'
import App from './App.js'
import store from './store.js'
import machine from './machine.js'

ReactDOM.render(<App store={store} machine={machine} />, document.getElementById("root"))

License

MIT

hecter's People

Contributors

isocroft avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.