Using Appcore - dsriseah/ursys GitHub Wiki
As described in Overview of URSYS WebApps, an "AppCore" is a bridge model that can talk to both DataCore and the GUI. There can be multiple AppCore modules, typically named with the ac-
prefix in the filename. AppCores consolidate GUI-specific data processing and requirements while acting as a buffer to the sensitive data operations in datacore.
An AppCore module has:
- one or more State Managers with a pub/sub interface
- one or more DataCore modules to buffer
- methods that accept GUI events and convert their payloads into type-safe data operations
- methods that produce GUI-compatible viewmodel/state data that can be used to directly render a GUI component
- methods that sequence more complicated multi-step transactions so the GUI doesn't have to do it.
- detecting and managing complex conditional state changes
- broadcasting appropriate app-wide conditions to the rest of the app
AppCore modules are not easy to write, but they have the advantage of consolidating a related set of operations into the same source file. Since you can have many different AppCore modules, they help with splitting code into smaller units of operation.
Note
This example is based on GEMSTEP, which is the first codebase we tried separating appcore-out from the spaghetti logic witnessed in the GUI code. We are still clarifying our best practices, as it's difficult to discern the differences between all these kinds of operations. We want to strike a balance between "single file source of truth" and "easy to follow in chunks".
// import ursys core and a datacore for managing simulation pieces
import UR from '@ursys/core';
import PIECES from 'datacore/dc-simpieces';
import SIM from 'sim/simcontroller';
const { StateMgr } = UR.class;
const STORE = new UR.class.StateMgr('PieceList');
STORE._initializeState({
pieces: [],
piece_sel: null
});
/// STATE CHANGE LOGIC ///////////////////////////////////////////////////////
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
STORE._interceptState(state => {
const { pieces } = state;
});
/** OPERATION STYLE **/
function AddPiece (piece) {
PIECES.AddPiece(piece)
const pl = PIECES.GetPieceList();
// convert piece dictionary to GUI-friendly
// viewdata
pieces = [];
pl.forEach(p=>{
const { id, name } = p;
pieces.push({ label:name, option: id });
});
// update to users of 'PieceList' statemanager
STORE.SendState({ pieces });
};
/** EVENT HANDLER STYLE **/
function DispatchSelectEvent(event) {
event.preventDefault();
event.stopPropagation();
// validate event type logic
const { id } = event.data;
const piece_sel = PIECES.validate(id);
STORE.SendState({piece_sel});
console.log(`${fn}`, event);
}
/** CONDITIONAL FLAGS **/
function IsValidSelection() {
const { piece_sel, pieces } = STORE.state();
return piece_sel && pieces.length > 0;
}
/** DATA OPERATIONS **/
function DB_SaveViewDataPieceList() {
const { pieces } = STORE.state();
UR.netCall('SRV:SAVE_VIEWDATA', { pieces }).then( ()=> {
console.log('server saved view state');
});
}
/** EXPORTED STATE METHODS **/
const { State, SendState, SubscribeState, UnsubscribeState, QueueEffect } = STORE;
export { State, SendState, SubscribeState, UnsubscribeState, QueueEffect };
/** EXPORTED APPCORE METHODS **/
export {
AddPiece,
DispatchSelectEvent,
IsValidSelection,
DB_SaveViewDataPieceList
}
Tip
See Using AppStateMgr and Using React with URSYS for more information about the how these pieces all fit together.