const collect = require('collect.js');
const URI = require('uri-js');
const Observer = require('./Observer.js').Observer;
const StateMachine = require('./StateMachine.js').StateMachine;
const Transition = require('./Transition.js').Transition;
const Router = require('./Router.js').Router;
const CurrentViewState = require('./CurrentViewState.js').CurrentViewState;
/**
* This class provide some basic logic for a controller. It hold a statemachine and it is configured to the following
* behaviour.
* <img src="mvc/BaseControllerState.png">
*/
class BaseController extends Observer {
/**
* @param {string} activationUri
* The url which determines when it should be active.
* @param {Repo} repo
* The repo of proxy objects
* @param {View} view
* The view to talk to.
*/
constructor(activationUri, repo, view) {
super();
this._sm = new StateMachine(this);
this.activationUri = activationUri;
this.repo = repo;
this.view = view;
this.states = {};
this.populateStateMachine();
this.view.addEventListener(this);
let classes = this.view.classes.merge(this.view.dataLists.all());
classes.put(Router.name, Router.name); // Set behavior
classes.values().each(cls => {
let proxy = this.repo.get(cls);
proxy.addEventListener(this);
});
}
/**
* Gets current state
* @returns {string}
* The state
*/
get state() {
return this._sm.state;
}
/**
* Populate the statemachine with transitions according to state diagram
*/
populateStateMachine() {
this.states.start = 'start';
this.states.input = 'input';
// Start
this.addTransition(new Transition(this.states.start, this.states.input, this.show, this.canShow));
// Input
this.addTransition(new Transition(this.states.input, this.states.input, this.changed, this.canChange));
this.addTransition(new Transition(this.states.input, this.states.input, this.populate, this.canPopulate));
this.addTransition(new Transition(this.states.input, this.states.input, this.fail, this.hasFailed));
this.addTransition(new Transition(this.states.input, this.states.start, this.stop, this.canStop));
}
/**
* Add a transtion
* @param {Transtion} transition
* The transition
*/
addTransition(transition) {
this._sm.add(transition);
}
/**
* Handle the event by running the statemachine
* @param {Event} event
* The event
*/
handleEvent(event) {
this._sm.run(event);
}
/**
* Tells if the event is a fail event or not
* @param {Event} event
* The event
* @returns {boolean}
* true when failed other false
*/
hasFailed(event) {
try {
let proxy = this.repo.get(event.sender);
return event.name === proxy.eventFail;
}
catch(e) {
return false;
}
}
/**
* Show the error
* @param {Event} event
* The event
*/
fail(event) {
this.view.showErrors(event.body);
}
/**
* Tells if the event is from router and the given url == this.activationUrl
* @param {Event} event
* The event
* @returns {boolean}
* true on match otherwise false
*/
canShow(event) {
if (event.sender !== Router.name) {
return false;
}
let pathSegments = URI.parse(event.body).path.split('/');
if (pathSegments.length < 3) {
return false;
}
return pathSegments.slice(0, 3).join('/').toLowerCase() === this.activationUri.toLowerCase();
}
/**
* Show the view and request data from the proxies which are used in the view
* @param {Event} event
* The event
*/
show(event) {
let parsedUri = URI.parse(event.body);
let pathSegments = parsedUri.path.split('/');
let proxy = this.repo.get(pathSegments[2]);
// display view
this.repo.get(CurrentViewState.name).reset();
this.view.show();
// read datalist which are M-1 relations to proxy
this.view.dataLists.each(name => {
let proxy = this.repo.get(name);
proxy.read();
});
if (parsedUri.query !== undefined) {
parsedUri.query.split('&').forEach(elem => {
let parts = elem.split('=');
let name = parts[0];
proxy.setProperty(name, isNaN(parts[1]) ? parts[1] : parseInt(parts[1]));
});
}
if (pathSegments.length === 3) { // eg /list/customer
proxy.read();
}
else {
let lastSegment = pathSegments[pathSegments.length - 1];
if (lastSegment === 'new' || lastSegment === '0') {
proxy.create();
}
else {
let uid = parseInt(lastSegment);
// read other data-class with relation 1-M to this proxy
this.view.classes.each(name => {
if (name === proxy.cls) {
proxy.read(uid);
}
else {
let foreignKey = proxy.cls.toLowerCase() + '_uid';
let related = this.repo.get(name);
related.setProperty(foreignKey, uid);
related.read();
}
});
}
}
}
/**
* Tells if the event is a property change from the view.
* @param {Event} event
* The event
* @returns {boolean}
* true on match otherwise false
*/
canChange(event) {
return event.name === this.view.eventPropertyChanged;
}
changed(event) {
let proxy = this.repo.get(event.body.cls);
proxy.setPropertyByElement(event.body);
}
/**
* Tells if the event is from a proxy.
* @param {Event} event
* The event
* @returns {boolean}
* true on match otherwise false
*/
canPopulate(event) {
try {
let proxy = this.repo.get(event.sender);
return proxy.constructor.name !== Router.name && event.name === proxy.eventOk;
}
catch (e) {
return false;
}
}
/**
* Populate the view with data from a proxy
* @param {Event} event
* The event
*/
populate(event) {
this.view.populate(event.sender, event.body); //TODO convert to only event
}
/**
* Tells if the event is from router and the given url =! this.activationUrl
* @param {Event} event
* The event
* @returns {boolean}
* true on match otherwise false
*/
canStop(event) {
if (event.sender !== Router.name) {
return false;
}
let pathSegments = URI.parse(event.body).path.split('/');
if (pathSegments.length < 3) {
return false;
}
return pathSegments.slice(0, 3).join('/').toLowerCase() !== this.activationUri.toLowerCase();
}
/**
* Stop this controller.
* @param {Event} event
* The event
*/
stop(event) {
// Do nothing
}
}
exports.BaseController = BaseController;