- Basic
- Extras
The most common APIs that you will work with most of the time
Block is a top level component in MangoJuice which consists of three parts:
- Model factory function
createModel
which should return a plain object. The main requirement for this function is that it should be able to return some model with zero arguments. But it can also accept some arguments as well. - A logic class which implements LogicBase interface
- A View function which is aimed to render the model created by
createModel
The View part is optional and it could have different type depending on selected View library to render a model and particular binding library.
Properties
-
Logic
LogicBase A logic class that should be attached to the model. -
createModel
Function A function that shuld return a plain object which will be an init model of the block. -
View
any? Something that some particular view library can render
Examples
// You can define block in short way using an object
const MyBlock = {
createModel: () => ({ field: 1 }),
Logic: class MyLogic {
\@cmd SomeCommand() {
return { field: this.model.field + 1 };
}
},
View: ({ model }) => (
<div>{model.field}</div>
)
};
run(MyBlock);
// Or you can split it with modules (recommended)
// ./MyBlock/index.js
import Logic from './Logic';
import View from './View';
import { createModel } from './Model';
export default { View, Logic, createModel };
export { View, Logic, createModel };
export type { Model } from './Model';
// ./MyBlock/Logic.js
import type { Model } from './Model';
import { LogicBase, cmd } from 'mangojuice-core';
export default class MyLogic extends LogicBase<Model> {
\@cmd SomeCommand() {
return { field: this.model.field + 1 };
}
}
// ./MyBlock/Model.js
export type Model = { field: number };
export const createModel = () => ({
field: 1
});
// ./MyBlock/View.js
const MyBlockView = ({ model }) => (
<div>{model.field}</div>
);
export default MyBlockView;
// ./index.js
import MyBlock from './MyBlock';
import { run } from 'mangojuice-core';
run(MyBlock);
Basic class for every logic. It is not necessaty to inherit an actual logic class from it, but for better type-checking it could be really usefull.
Logic class defines:
- A set of commands that could change an associated model – methods decorated with cmd
- What fields of the model should be associated with which logic class (children blocks) – LogicBase#children method
- A set of computed model fields – LogicBase#computed method
- A set of rules to handle commands from children logic – LogicBase#hubAfter and LogicBase#hubBefore methods
- A logic to handle external events – LogicBase#port method.
The logic class defines all the business-logic of some part of your application. The main goal of the logic – change associated model. Check cmd and Process#exec to have a better understanding how command can be defined and how it is executed.
Properties
-
model
Object A model object which is associated with the logic class in parent logic. -
meta
Object An object defined in LogicBase#config method for storing some internal logic state. -
shared
Object An object with some shared state, defined while run process of the app.
Examples
// ./blocks/MyBlock/Logic.js
// @flow
import type { Model } from './Model';
import ChildBlock from '../ChildBlock';
import { Command, cmd } from 'mangojuice-core';
export default class MyLogic extends LogicBase<Model> {
children() {
return { child: ChildBlock.Logic };
}
hubAfter(cmd: Command) {
if (cmd.is(ChildBlock.Logic.prototype.SomeCommand)) {
return this.TestCommand(123);
}
}
\@cmd TestCommand(arg) {
return { field: arg + this.model.field };
}
}
A function that defines init commands and meta object of the
logic. Can accept any type and number of arguments. Config
will take arguments passed to child function (for example
child(MyLogic, 1, 2, 3)
defines that the config
method of
MyLogic
will be invoked like config(1, 2, 3)
)
Could return an object with the fields:
-
initCommands
which could be an array or a single Command, that will be executed every time when the logic instance created and associated with some model (when Process#run invoked) -
meta
which could be an object with some internal state of the logic
Examples
config(props) {
return {
initCommands: [ this.StartThis, this.SendThat(123) ],
meta: { something: props.amount }
};
}
This function defines what logic class should be associated with which model field. Should return an object where a key is a model field name, and a value is a logic class or an object that defines logic with arguments (return from child function)
Examples
children() {
return {
searchForm: child(SearchForm.Logic, { timeout: 100 }),
searchResults: SearchResults.Logic
};
}
Returns Object
Should return an object which defines computed fields of the model. A key of the object is a model field, and value is a function or computed function definition object (return from depends function).
Computed field is lazy, which means that it is computed on the first use of the field and the computation result cached until the next update of the model.
If you want to define a computed field which ueses in computation not only fields from the own model, but maybe some values from child model, or from shared model, then you will need to use depends function to define what models used while computation to observe changes of these models to trigger an update event of the own model (to update views).
Examples
computed() {
return {
simple: () => this.model.a + this.model.b,
withDeps: depends(this.model.child, this.shared).compute(() => {
return this.model.child.a + this.shared.b + this.model.c;
})
};
}
Returns Object
A function to handle global event, such as browser-level events,
websocket, intervals. The main porpouse of the port
is to subscribe
to some global events, execute appropriate commands from the logic
on the events and remove the handlers when the logic was destroyed.
Parameters
-
exec
Function It is a function that execute a passed command -
destroyed
Promise A promise that will be resolved when the logic destroyed
Examples
port(exec, destroyed) {
const timer = setInterval(() => {
exec(this.SecondPassed);
}, 1000);
destroyed.then(() => clearInterval(timer));
}
Hub for all commands executed in the children blocks. The only
arguments is a command object that was executed.
Could return a Command or an array of Commands that should
be executed next.
Most of the time this function will look like a switch
,
but maybe with some additional rules for the cases. Take a look
to Command#is function to have a better understanding how you
can determine that the command is exactly what you are waiting for.
Parameters
-
cmd
Command Command that was executed in some child logic or in any child of the child and so on down to the leaf of the tree.
Examples
hubAfter(cmd) {
if (cmd.is(ChildBlock.Logic.prototype.SomeCommand)) {
return this.HandlerCommand(123);
}
if (cmd.is('AnotherChild.SomeAnothercommand')) {
return [ this.HandlerCommand(321), this.AnotherHandler ];
}
}
Returns (Command? | Array<Command>?)
Same as LogicBase#hubAfter, but catch commands before it will be actually executed, so you can compare prev and next model state for example.
Parameters
-
cmd
Command
Returns (Command? | Array<Command>?)
Decorator that converts a logic method to a command factory. The result function (command factory function) is a function that return a Command instnace with the decorated function and arguments that you will pass to a command factory function.
You can use this decorator without arguments or with an options argument (object)
Check Process#exec to see what the origin function could return to do something. If in short, it can return: nothing, command (or factory or array of commands), task, model update object.
Parameters
-
obj
Object If only this argument passed then it will be considered as options object which can customize command behaviour.-
obj.debounce
number If greeter than zero then an exeuction of the command will be debounced by given amount of milliseconds -
obj.throttle
number If greeter than zero then an exeuction of the command will be throttled by given amount of milliseconds -
obj.noInitCall
bool If true then debounced/throttled execution won't start with init command call. By default debounced/throttled command will be instantly exected and only then wait for a chance to execute again. WithnoInitCall=true
there is no instant init command call. -
obj.internal
bool Make the command invisible for parent LogicBase#hubBefore and LogicBase#hubAfter. Use it for commands that shouldn't be handled by hubs to increse performance. -
obj.name
string By default name of the command taken from origin function. With this option you can override the name with any other value.
-
methodName
descr
Examples
class RegularLogic {
\@cmd RegularCommand() {
// do something
}
}
class RegularLogic {
\@cmd({ debounce: 300 })
DebouncedCommand() {
// if you will call this command 3 times every
// 100ms then it will be executed 2 times,
// first right when you call it for a first time,
// and second in 600ms.
}
}
class RegularLogic {
\@cmd({ throttle: 300 })
ThrottledCommand() {
// if you will call this command 3 times every
// 100ms then it will be executed 2 times,
// first right when you call it for a first time,
// and second in 300ms.
}
}
class RegularLogic {
\@cmd({ throttle: 300, noInitCall: true })
ThrottledCommand() {
// if you will call this command 3 times every
// 100ms then it will be executed 1 time in 300ms.
}
}
class RegularLogic {
\@cmd({ internal: true })
_InternalCommand() {
// this command can't be caught in `hubBefore` or `hubAfter`
// in parent logics. Use it carefully.
}
}
Returns (Function | object) Command factory function (if all three arguemnts passed) or a decorator function with binded options object (that you pass as a first argument)
Run given block. It is a short-hand function for running bind
and then running a Process by Process#run.
Also it returns an additional finished
Promise which is resolved
when all async tasks finished and all handler commands executed.
Parameters
Returns {proc: Process, model: Object, block: Block, finished: Promise} An object
which is almost the same as returned from bind, but with
additional finished
field, which is a Promise that will be resolved
when all blocks will execute all commands and all async tasks will be
finished.
Mount running block. As third and next arguments you can pass other running blocks which will be stopped with the main running block (useful for HMR).
Parameters
-
mounter
Mounter An implementation of Mounter interface -
runRes
{proc: Process, block: Block, model: Object} An object that returned from run function. -
otherBlocks
...any
Examples
const MyBlock = {
createModel: () => ({ field: 1 }),
Logic: class MyLogic {
\@cmd SomeCommand() {
return { field: this.model.field + 1 };
}
},
View: ({ model }) => (
<div>{model.field}</div>
)
};
mount(new ReactMounter(), run(MyBlock));
Returns {view: any, stop: Function} An object with the result form Mounter#mount
function (in view
field) and stop function which destroy the process and
unmount a view using Mounter#unmount
Creates a TaskMeta object that could be returned from async task command. It describes the task that should be executed.
For more information what the Task is and how it should be implemented take a look to TaskMeta.
Parameters
-
taskFn
Function A function that could return a Promise
Properties
-
CANCEL
string A symbol that you can use to make a promise "cancellable"
Returns Object
This function should be used to cancel async task execution. Also it can cancel the debounced/throttled command.
- In case with task, if task was executing while you call a cancel command, the task will be cancelled (execution stopped). Otherwise nothing will happen.
- In case with debounced/throttled command, if the command scheduled to be executed then the schedule timer will be cleared and the command won't be executed. If the command is not scheduled then nothing happens.
To use it just pass a command or command factory as a first argument and return it from some other command.
Parameters
-
cmd
(Command | function) Command or command factory that you wan to cancel. Could be a command that returns task or debounced/throttled command
Examples
class SomeLogic {
\@cmd CancelSomethingCommand() {
return [
cancel(this.TaskCommand),
cancel(this.DebounceCommand)
];
}
\@cmd TaskCommand() {
return task(Tasks.SomeTask);
}
\@cmd({ debounce: 300 })
DebounceCommand() {
// do something
}
}
Returns Command Returns a new command that will cancel the execution of the command passed as an argument.
Creates an object which describes child logic which will be attached to some model field. Should be used in LogicBase#children to define a logic that should be instantiated with some arguments passed to LogicBase#config.
Parameters
Examples
class ChildLogic {
config(amount) {
return { meta: { amount } };
}
}
class RootLogic {
children() {
return {
modelField: child(ChildLogic, 10)
// `config` of `ChildLogic` will be invoked with
// `10` as a first argument
}
}
}
Returns Object Object that contains a logic class and an arguments array that
should be used to invoke config
method of provided logic class.
Function that helps to describe a computed field with external model dependncies. Should be used in LogicBase#computed. It creates an instance of ComputedField with dependencies passed as arguments.
A compute field with external dependencies is a field that should be updated (re-computed) not only when the own model udpated, but also when depdendency models updated.
Parameters
-
deps
...deps A list of models with attached logic
Examples
class ChildLogic {
// some logic
}
class MyLogic {
children() {
return { childModel: ChildLogic };
}
computed() {
return {
// depends on `childModel`
computedField: depends(this.model.childModel).compute(() => {
return this.model.a + this.model.childModel.b;
}),
// depends on `shared` model
computeWithShared: depends(this.shared).compute(() => {
return this.model.b + this.shared.someField;
})
}
}
}
Returns ComputedField
Other pieces of MangoJuice
The main class of MangoJuice that ties model, logic and view together.
It works in the following way:
- You create a model for your app (object) using
createModel
of the root block. The result is a plain object. - Then you need to create an instance of Process and pass logic class in the options object.
- Then you
bind
the Process instance to the model object you created on step one. "To bind Process to a model" means that in the model will be created a hidden field__proc
with a reference to the Process instance. - After that you can
run
the Process, which will execute init commands, port.
bind
and run
also look at the object returned from LogicBase#children
and create/run Process instances for children models.
Most of the time you do not need to care about all of these and just use run and mount. These small functions do everything described above for you.
Examples
import { Process, logicOf } from 'mangojuice-core';
const ExampleBlock = {
createModel() {
return { a: 10 };
},
Logic: class Example {
\@cmd Increment(amount = 1) {
return { a: this.model.a + amount };
}
}
};
const model = ExampleBlock.createModel();
const proc = new Process({ logic: ExampleBlock.Logic });
proc.bind(model);
proc.run();
proc.exec(logicOf(model).Increment(23));
Bind the process instance to a given model, which means that hidden
__proc
field will be created in the model with a reference to the
Process instance.
Also bind all children models – go through children definition returned by LogicBase#children, create Process for each child and bind it to an appropreat child model object.
Parameters
-
model
Object A model object that you want to bind to the Process.
Run the process – run children processes, then run port and init commands defined in config. Also run all model observers created by observe
Destroy the process with unbinding from the model and cleaning
up all the parts of the process (stop ports, computed fields).
__proc
field will be removed from the model object and all
children objects.
Parameters
-
deep
Boolean If true then all children blocks will be destroyed. By default, if not provided then considered as true.
Exec a command in scope of the Process instance. It will use binded model and logic object to run the command. The command could be function (command factory) or object (Command instance).
A command origin function could return three types of things:
- Another Command/command factory or an array of commands (an element of the array also could be another array with commands or null/undefined/false). All commands will be execute in provided order.
- An instance of TaskMeta (which is usually created by task helper function). The returned task will be started and do not block execution of next commands in the stack.
- A plain object which is a model update object. The object will be merged with current model.
If command is undefined or Process
instance not binded
to any model it will do nothing.
Parameters
Examples
class MyLogic {
\@cmd BatchCommand() {
return [
1 > 2 && this.SomeCoomand,
this.AnotheCommand(123),
2 > 1 && [
this.AndSomeOtherCommand,
this.FinalCommand()
]
];
}
\@cmd TaskCommand() {
return task(Tasks.SomeTask)
.success(this.SuccessCommand)
.fail(this.FailCommand)
}
\@cmd ModelUpdateCommand() {
if (Math.random() > 0.6) {
return { field: Date.now() };
}
}
}
Returns a promise which will be resolved when all async tasks and related handlers in the process and all children processes will be finished.
Returns Promise
Class which declares a command that will be executed in the future by some Process#exec. It contains a function that will be executed, arguments that should be passed to the function and command name.
The command also keep a context that should be used to execute a function. Usually this context is some logic instance. You can bind a context to the command using Command#bind function.
The command instance is immutable, so any function that makes some change in the command will produce a new command instead of changing the original one.
Parameters
Properties
-
func
Function An origin function that should be executed -
args
Array<any> A set of arguments that should be used to execute the fucntion.
Execute a function stored in the command instance. It passes stored arguments to the function and call it in stored context. Returns the value that was returned form the function.
Returns any
Clone instance of the command
Returns Command
Check is the command equal to some other command or have a reference to the same origin function. Optionally you can also check that the command binded to some concrete model (using the second argument).
You can use many different formats to define the command to check:
- Just a string, which should be constructed like
Logic Class Name + Function Name
- Command object or command factory that you can get from prorotype of some logic class.
- Binded command object or command factory that you can get from concrete model using logicOf function
Parameters
-
cmd
(Command | string) Command object or command factory or command name -
childModel
Object? Model object that should be binded to the command (optional)
Examples
class ChildLogic {
\@cmd SomeCommand() {}
}
class MyLogic {
hubAfter(cmd) {
if (cmd.is('ChildLogic.SomeCommand')) {
// by command name
}
if (cmd.is(ChildLogic.prorotype.SomeCommand)) {
// by command factory
}
if (cmd.is(logicOf(this.model.child).SomeCommand)) {
// by binded to a concrete model command factory (commands
// binded for other models will be ignored)
}
if (cmd.is(ChildLogic.prorotype.SomeCommand, this.model.child)) {
// by command factory and concrete child model (commands
// binded for other models will be ignored)
}
}
}
Returns Boolean Returns true
if the command have same name, or same origin function
and binded to same model (if second argument provided)
Creates a new Command instance (clone the command) and append given list of arguments to the current list of arguments. Returns a new Command with new list of arguments.
Parameters
-
args
Array<any> List of arguments that will be appened to tne new Command
Returns Command New command with appended arguments
Creates a new Command instance and set given logic instance in it. Also update command name by name of the origin function and name of the logic class.
Parameters
-
logic
LogicBase A logic instance that should be binded to a command
Returns Command New command binded to given logic instance
Class for declaring an async task. An instance of this object returned from taks function.
A task function is a function that could return a Promise. The resolved value will be passed to a success command handler, the rejected command will be passed to a fail command handler.
If task function do not return a Promise, then the returned value passed to a success command, and if the function throw an error then the error passed to a fail command.
The task function receive at least one argument – an object with model
, shared
and meta
. All the next arguments will be given from a command that returned a task,
if it is not overrided by TaskMeta#args
By default a task is single-threaded. It means that every call to a task will cancel previously running task if it is running. You can make the task to be multi-threaded by TaskMeta#multithread, so every call to a task won't cancel the running one.
In a context of the task function defined special call
function. This function should
be used to invoke sub-tasks. It is important to use call
to run sub-tasks because
call
create a cancellation point of the task – if the task cancelled, then nothing
will be executed after currently running call
sub-task.
call
returns an object with result
and error
fields. If error
field is not
empty, then something weird happened in the sub-task. call
internally have a try/catch,
so it is not necessary to wrap it with try/catch, just check the error
field in the
returned object and if it is not empty – handle it somehow (throw it, or do any custom
error handling)
Also in a context of the task defined a notify
function. It is a function that execute
notify command if it is defined. The command executed with the same arguments as passed
to notify
function. You can use it to incrementally update a model while the long
task is executing, to show some progress for the user.
Parameters
Examples
async function SubTask(a, b, c) {
this.notify(a + b + c);
await this.call(delay, 100);
return (a / b * c);
}
async function RootTask({ model }) {
const { result, error } = await this.call(SubTask, 1, 0, 3);
if (error) {
throw new Error('Something weird happened in subtask')
}
return result + 10;
}
class MyLogic {
\@cmd AsyncCommand() {
return task(Tasks.RootTask)
.success(this.SuccessCommand)
.fail(this.FailCommand)
.notify(this.NotifyCommand)
}
}
Set a notify handler command. This command executed by call of this.notify
inside a task with the same arguments as passed to this.notify
Parameters
-
cmd
Command
Returns TaskMeta
Set a success handler command. Will be executed with a value returned from the task, or if the task returned a Promise – with resovled value.
Parameters
-
cmd
Command
Returns TaskMeta
Set a fail handler command. Will be executed with error throwed in the task, or if the task returned a Promise – with rejected value.
Parameters
-
cmd
Command
Returns TaskMeta
Define the task to be "multi-thread", so every call will run in parallel with other calls of the same task in scope of one process (model).
Parameters
-
val
boolean
Returns TaskMeta
Set task executor function. The executor function should return an object
that should have at least two fields: exec
and cancel
functions.
exec
should return a promise which should be resolved or rejected
with an object { result, error }
. A cancel
function should stop
the task execution.
Parameters
-
engine
Function A function that returns{ exec: Function, cancel: Function }
object
Returns TaskMeta
Override arguments which will be passed to the task starting from second argument. By default a task will receive the same set of arguments as a task command. If this function invoked, then the task will receive given arguments instead of command arguments.
Parameters
-
args
...any
Returns TaskMeta
A class which defins a logger for tracking command execution process. The default implmenetation just defines all possible logger methods (log points) and print errors to the console. Extend this class to define your own logging functionality (like logging errors with Setnry or Loggly)
Examples
// ./index.js
import { DefaultLogger, run } from 'mangojuice-core';
import MainBlock from './blocks/MainBlock';
// Implement custom logger
class SetnryLogger extends DefaultLogger {
onCatchError(error, cmd) {
Raven.catchException(error);
}
}
// Pass instance of the Logger to `run` options object.
run(MainBlock, { logger: new SetnryLogger() });
This method invoked right before anything else - as the first thing in Process#exec. Even before any LogicBase#hubBefore.
Parameters
-
cmd
Command Command that just started execution process
Invoked right before command will go through all LogicBase#hubBefore or LogicBase#hubAfter up in the blocks tree. The second argument indicates is it called for LogicBase#hubBefore or for LogicBase#hubAfter.
Parameters
-
cmd
Command Command that is going to go through hubs in parent blocks -
isAfter
Boolean If true then the command is going through "after" hubs, "before" otherwise
Invoked when the command went through all hubs and when all sync commands returned from hubs was executed.
Parameters
-
cmd
Command Command that went through all hubs -
isAfter
Boolean If true then the command going through "after" hubs, "before" otherwise
Invoked when the command function executed and the return of the function processed – all returned commands executed, all async tasks started, the model updated. But it invoked before the command go through "after" hubs.
Parameters
-
cmd
Command Command that was exectued and the return processed -
result
any Returned object from the command's function
Invoked right after the command go throught "after" hubs. It is the final stage of command execution.
Parameters
-
cmd
Command Command that was fully executed and handled by parent blocks
Invoked if any uncaught error happened while execution of the command
or anywhere else in the logic, like in LogicBase#port or in LogicBase#computed.
By default print the error using console.error
.
Parameters
Class for declaring computed field with model dependencies. Given array shuold contain models, binded to some logec. They will be passed to compute function in the same order. Also they will be used to track changed and re-compute.
The class is immutable, so any call to any method will create
a new instance of ComputedField
and the old one won't be touched.
Parameters
Properties
-
deps
Array<Object> An array of models with binded logic (attached some Process) -
computeFn
Function A compute function that will be used to compute value of the computed field.
Creates a new instance of the field and set compute function in it.
Parameters
-
func
Function A compute function that should return a value that will be used as a value for some computed field in a model. This function will be invoked with all dependency models as arguments
Examples
// A way to override computed field of some base logic class
// (thanks to immutability of `ComputedField`)
class MyLogic extends SomeOtherLogic {
computed() {
const oldComputed = super.computed();
return {
...oldComputed,
// Override `oldField` compute function with saved dependencies
// and use overrided compute function inside new compute function
oldField: oldComputed.compute((...deps) => {
return 1 > 0 || oldComputed.oldField.computeFn(...deps);
})
};
}
}
Returns ComputedField New instance of the ComputedField with computeFn
set to given function.
To use mount function you need to implement a Mounter interface
which should have mount
and unmount
functions. It is up to the developer
how these functions will be implemented and what view library they will use.
There is a rule that mounter and view library should follow: view of a model should be updated only when the model updated and the update shouldn't touch views of children models (children views shouldn't be updated because their models is not changed).
React perfectly fits for this rule. By implementing shuoldComponentUpdate
you can control when the component should be updated and when shouldn't. In this
case this function should always return false
and componenent should be
updated using forceUpdate
only when the model is updated (using observe).
Properties
-
mount
Function A function that should expect two arguments: model object with attached Process instance and Block#View. It should render the model using given View (somehow). -
unmount
Function A function that shuold unmount mounted view from DOM.
Examples
// Minimal react mounter which follow the rules
class ViewWrapper extends React.Component {
componentDidMount() {
const { model } = this.props;
this.stopObserver = observe(model, () => this.forceUpdate())
}
componentWillUnmount() {
this.stopObserver();
}
shouldComponentUpdate() {
return false;
}
render() {
const { View, model } = this.props;
const Logic = logicOf(model);
return <View model={model} Logic={Logic} />
}
}
class ReactMounter {
mount(model, View) {
return React.render(
<ViewWrapper View={View} model={model} />,
document.querySelector('#container')
);
}
unmount() {
return React.unmountComponentAtNode(
document.querySelector('#container')
);
}
}
Provides a way to define a command in the prototype without usign a decorator. You should give a prototype of the logic class, name of the function which should be converted to a command factory and the decorator function (optional).
If the decorator function is not provided, then cmd will be used by default.
Parameters
-
proto
Object Logic class prototype -
name
string Method name that you want to convert to a command factory -
decorator
function? Optional decorator function
Examples
class MyLogic {
SomeCommand() {
return { field: this.model.field + 1 };
}
// throttle 100
ThrottledCommand() {
return this.SomeCommand();
}
}
defineCommand(MyLogic.prototype, 'SomeCommand');
defineCommand(MyLogic.prototype, 'ThrottledCommand', cmd({ throttle: 100 }));
Returns a Command instance or throw an error if given argument can't be used to create a Command (is not a Command or command factory).
Parameters
-
cmd
(Command | function) Command instance or command factory. When command factory then it will be invoked with no arguments to create a command
Examples
class MyLogic() {
\@cmd SomeCommand() {
// do something
}
}
const logic = new MyLogic();
ensureCommand(logic.SomeCommand) // returns Command instance
// equals to...
logic.SomeCommand()
Returns Command
By given logic class go through prototypes chain and decorate all cmd functions. Use it if you can't use decorators in your project
It determine that the function should be converted to a command factory (decorated by cmd) by its name. If the function starts with uppercase letter or underscore+upper-letter, then it is considered as a command.
Parameters
-
LogicClass
LogicBase Logic class that you want to decorate -
deep
bool If true then it will go though prototypes chain and decorate every prototype in the chain.
Examples
class MyLogic {
SomeCommand() {
}
_SomePrivateCommand() {
}
notACommand() {
return 123;
}
}
// `SomeCommand` and `_SomePrivateCommand` will be decorated with `cmd`
// (the prorotype will be changed in-place). `notACommand` won't
// be decorated.
decorateLogic(MyLogic);
const logic = new MyLogic();
logic.SomeCommand() // returns Command instance
logic._SomePrivateCommand() // returns Command instance
logic.notACommand() // returns 123
Function adds a handler to the Process attached to a given model that will be invoked before every command running for this model. The handler won't be invoked for commands from children models of given model.
Returns a function that removes the handler from the Process (stop handling)
Parameters
-
model
Object A model object with attached Process -
handler
Function A function that will be called before every own command
Examples
import { handleBefore, run, logicOf } from 'mangojuice-core';
// Define root and child logic
class ChildLogic {
\@cmd ChildCommand() {}
}
class MyLogic {
children() {
return { childModel: ChildLogic };
}
\@cmd RootCommand() {}
}
// Run block with empty models
const res = run({
Logic: MyLogic,
createModel: () => ({ childModel: {} })
});
// Adds a handler to a root model
handleBefore(res.model, (cmd) => console.log(cmd));
// Run commands on root and child models
res.proc.exec(logicOf(res.model).RootCommand);
res.proc.exec(logicOf(res.model.childModel).ChildCommand);
// In the console will be printed only `RootCommand` command object
// right before the command will be executed
Returns Function A function that stopps the handler
It is the same as handleBefore but the handler will be invoked after every own command.
Parameters
-
model
Object A model object with attached Process -
handler
Function A function that will be called after every own command
Returns Function A function that stopps the handler
Alias for handleAfter
Parameters
-
model
Object A model object with attached Process -
handler
Function A function that will be called after every own command
Returns Function A function that stopps the handler
Returns a Logic instance attached to a given model object. The logic instance stored in attached Process instance, so it execute procOf for the model to get a Process instance and then get the logic instance form a Process and return it.
If Process is not attached to the model, then an Error will be throwed. If the second argument passed then it will also check that the logic instance is instance of some particular class.
Parameters
-
model
Object A model with attached Process -
logicClass
Class? Optional logic class to check that the logic instance is instance of the class
Returns LogicBase Returns a logic instance
A function that adds a handler to the Process instance attached to a given model, that will be invoked on every command that changed the model. Useful for tracking changes to re-render a view of the model.
Parameters
-
model
Object A model with attached Process instance -
handler
Function A function that will be invoked after every command that changed the model. -
options
Object? An object with options-
options.batched
bool If true then the handler will be invoked only when the commands stack is empty and model was changed during the stack execution. Useful to reduce amount of not necessary view re-renderings.
-
Examples
import { run, cmd, observe, logicOf } from 'mangojuice-core';
class MyLogic {
\@cmd MultipleUpdates() {
return [
this.UpdateOne,
this.UpdateTwo
];
}
\@cmd UpdateOne() {
return { one: this.model.one + 1 };
}
\@cmd UpdateTwo() {
return { two: this.model.two + 1 };
}
}
const res = run({
Logic: MyLogic,
createModel: () => ({ one: 1, two: 1 })
});
observe(res.model, () => console.log('not-batched'));
observe(res.model, () => console.log('batched'), { batched: true });
res.proc.exec(logicOf(res.model).MultipleUpdates);
// `not-batched` will be printed two times
// `batched` will be printed only once
Returns Function
Get a Process instance attached to the given model object.
Internally it get a __proc
field from the given model and returns it.
If given model do not have attached Process, then an Error will be throwed, but only if the second argument is not true, which ignores the error.
Parameters
-
model
Object A model with attached Process -
ignoreError
bool If true then no error will be throwed if Process is not attached to the model
Returns Process An instance of Process attached to the model
This function do the following:
- Creates a model object using Block#createModel. It is invoked without arguments.
- Create a Process instance with logic from Block#Logic
- Call Process#bind with created model
Returns an object with created process instance, model object and given block. Usefull when you want to prepare the block to run but you want to run it manually.
Parameters
-
block
Block A Block object -
opts
Object? Options object that could change Process behaviour (optional, default{}
)-
opts.logger
DefaultLogger? A custom logger instance that will be used in the Process to track commands execution. By default DefaultLogger will be used. -
opts.shared
Object? An object that will be available in any logic in the tree asthis.shared
. Could be anything you want, but you will get more benifites if you will pass model object with attached Process as shared object to be able to make computed fields with depending of shared model. -
opts.args
Array<any>? An array of arguments that will be passed to LogicBase#config of the logic. -
opts.Process
Process? A class that will be used instead of Process. By default general Process is used, but you can customize it for your specific needs and pass here. Then every other process in the tree will be an instance of this custom Process implementation
-
Returns {proc: Process, model: Object, block: Block} An object with Process instance, model object and block that was passed to the function
This function replace a createModel
function in given block
with new function that just returns a given model. It is useful
when you have some old model and just want to run everytihing with
it (for example for server rendering, or hot module replacement)
Parameters
Examples
// Hot module replacement example
import MyBlock from './MyBlock';
import { hydrate, run } from 'mangojuice-core';
// Run initial block
let res = run(MyBlock);
// When block changed destroy the old process, hydrate a new
// block with existing model and run hydrated new block again
module.hot.accept(['./MyBlock'], () => {
res.proc.destroy();
const newBlock = hydrate(require('./MyBlock'), res.model);
res = run(newBlock);
});
Returns Block A new block with replaced createModel
function
which just returns a model that you passed as
second argument.
A helper function for delaying execution. Returns a Promise which will be resolved in given amount of milliseconds. You can use it in task to implement some delay in execution, for debouncing for example.
Parameters
-
ms
number An amount of milliseconds to wait
Returns Promise A promise that resolved after given amount of milliseconds
The most common APIs that you will work with most of the time
Block is a top level component in MangoJuice which consists of three parts:
- Model factory function
createModel
which should return a plain object. The main requirement for this function is that it should be able to return some model with zero arguments. But it can also accept some arguments as well. - A logic class which implements LogicBase interface
- A View function which is aimed to render the model created by
createModel
The View part is optional and it could have different type depending on selected View library to render a model and particular binding library.
Properties
-
Logic
LogicBase A logic class that should be attached to the model. -
createModel
Function A function that shuld return a plain object which will be an init model of the block. -
View
any? Something that some particular view library can render
Examples
// You can define block in short way using an object
const MyBlock = {
createModel: () => ({ field: 1 }),
Logic: class MyLogic {
\@cmd SomeCommand() {
return { field: this.model.field + 1 };
}
},
View: ({ model }) => (
<div>{model.field}</div>
)
};
run(MyBlock);
// Or you can split it with modules (recommended)
// ./MyBlock/index.js
import Logic from './Logic';
import View from './View';
import { createModel } from './Model';
export default { View, Logic, createModel };
export { View, Logic, createModel };
export type { Model } from './Model';
// ./MyBlock/Logic.js
import type { Model } from './Model';
import { LogicBase, cmd } from 'mangojuice-core';
export default class MyLogic extends LogicBase<Model> {
\@cmd SomeCommand() {
return { field: this.model.field + 1 };
}
}
// ./MyBlock/Model.js
export type Model = { field: number };
export const createModel = () => ({
field: 1
});
// ./MyBlock/View.js
const MyBlockView = ({ model }) => (
<div>{model.field}</div>
);
export default MyBlockView;
// ./index.js
import MyBlock from './MyBlock';
import { run } from 'mangojuice-core';
run(MyBlock);
Basic class for every logic. It is not necessaty to inherit an actual logic class from it, but for better type-checking it could be really usefull.
Logic class defines:
- A set of commands that could change an associated model – methods decorated with cmd
- What fields of the model should be associated with which logic class (children blocks) – LogicBase#children method
- A set of computed model fields – LogicBase#computed method
- A set of rules to handle commands from children logic – LogicBase#hubAfter and LogicBase#hubBefore methods
- A logic to handle external events – LogicBase#port method.
The logic class defines all the business-logic of some part of your application. The main goal of the logic – change associated model. Check cmd and Process#exec to have a better understanding how command can be defined and how it is executed.
Properties
-
model
Object A model object which is associated with the logic class in parent logic. -
meta
Object An object defined in LogicBase#config method for storing some internal logic state. -
shared
Object An object with some shared state, defined while run process of the app.
Examples
// ./blocks/MyBlock/Logic.js
// @flow
import type { Model } from './Model';
import ChildBlock from '../ChildBlock';
import { Command, cmd } from 'mangojuice-core';
export default class MyLogic extends LogicBase<Model> {
children() {
return { child: ChildBlock.Logic };
}
hubAfter(cmd: Command) {
if (cmd.is(ChildBlock.Logic.prototype.SomeCommand)) {
return this.TestCommand(123);
}
}
\@cmd TestCommand(arg) {
return { field: arg + this.model.field };
}
}
A function that defines init commands and meta object of the
logic. Can accept any type and number of arguments. Config
will take arguments passed to child function (for example
child(MyLogic, 1, 2, 3)
defines that the config
method of
MyLogic
will be invoked like config(1, 2, 3)
)
Could return an object with the fields:
-
initCommands
which could be an array or a single Command, that will be executed every time when the logic instance created and associated with some model (when Process#run invoked) -
meta
which could be an object with some internal state of the logic
Examples
config(props) {
return {
initCommands: [ this.StartThis, this.SendThat(123) ],
meta: { something: props.amount }
};
}
This function defines what logic class should be associated with which model field. Should return an object where a key is a model field name, and a value is a logic class or an object that defines logic with arguments (return from child function)
Examples
children() {
return {
searchForm: child(SearchForm.Logic, { timeout: 100 }),
searchResults: SearchResults.Logic
};
}
Returns Object
Should return an object which defines computed fields of the model. A key of the object is a model field, and value is a function or computed function definition object (return from depends function).
Computed field is lazy, which means that it is computed on the first use of the field and the computation result cached until the next update of the model.
If you want to define a computed field which ueses in computation not only fields from the own model, but maybe some values from child model, or from shared model, then you will need to use depends function to define what models used while computation to observe changes of these models to trigger an update event of the own model (to update views).
Examples
computed() {
return {
simple: () => this.model.a + this.model.b,
withDeps: depends(this.model.child, this.shared).compute(() => {
return this.model.child.a + this.shared.b + this.model.c;
})
};
}
Returns Object
A function to handle global event, such as browser-level events,
websocket, intervals. The main porpouse of the port
is to subscribe
to some global events, execute appropriate commands from the logic
on the events and remove the handlers when the logic was destroyed.
Parameters
-
exec
Function It is a function that execute a passed command -
destroyed
Promise A promise that will be resolved when the logic destroyed
Examples
port(exec, destroyed) {
const timer = setInterval(() => {
exec(this.SecondPassed);
}, 1000);
destroyed.then(() => clearInterval(timer));
}
Hub for all commands executed in the children blocks. The only
arguments is a command object that was executed.
Could return a Command or an array of Commands that should
be executed next.
Most of the time this function will look like a switch
,
but maybe with some additional rules for the cases. Take a look
to Command#is function to have a better understanding how you
can determine that the command is exactly what you are waiting for.
Parameters
-
cmd
Command Command that was executed in some child logic or in any child of the child and so on down to the leaf of the tree.
Examples
hubAfter(cmd) {
if (cmd.is(ChildBlock.Logic.prototype.SomeCommand)) {
return this.HandlerCommand(123);
}
if (cmd.is('AnotherChild.SomeAnothercommand')) {
return [ this.HandlerCommand(321), this.AnotherHandler ];
}
}
Returns (Command? | Array<Command>?)
Same as LogicBase#hubAfter, but catch commands before it will be actually executed, so you can compare prev and next model state for example.
Parameters
-
cmd
Command
Returns (Command? | Array<Command>?)
Decorator that converts a logic method to a command factory. The result function (command factory function) is a function that return a Command instnace with the decorated function and arguments that you will pass to a command factory function.
You can use this decorator without arguments or with an options argument (object)
Check Process#exec to see what the origin function could return to do something. If in short, it can return: nothing, command (or factory or array of commands), task, model update object.
Parameters
-
obj
Object If only this argument passed then it will be considered as options object which can customize command behaviour.-
obj.debounce
number If greeter than zero then an exeuction of the command will be debounced by given amount of milliseconds -
obj.throttle
number If greeter than zero then an exeuction of the command will be throttled by given amount of milliseconds -
obj.noInitCall
bool If true then debounced/throttled execution won't start with init command call. By default debounced/throttled command will be instantly exected and only then wait for a chance to execute again. WithnoInitCall=true
there is no instant init command call. -
obj.internal
bool Make the command invisible for parent LogicBase#hubBefore and LogicBase#hubAfter. Use it for commands that shouldn't be handled by hubs to increse performance. -
obj.name
string By default name of the command taken from origin function. With this option you can override the name with any other value.
-
methodName
descr
Examples
class RegularLogic {
\@cmd RegularCommand() {
// do something
}
}
class RegularLogic {
\@cmd({ debounce: 300 })
DebouncedCommand() {
// if you will call this command 3 times every
// 100ms then it will be executed 2 times,
// first right when you call it for a first time,
// and second in 600ms.
}
}
class RegularLogic {
\@cmd({ throttle: 300 })
ThrottledCommand() {
// if you will call this command 3 times every
// 100ms then it will be executed 2 times,
// first right when you call it for a first time,
// and second in 300ms.
}
}
class RegularLogic {
\@cmd({ throttle: 300, noInitCall: true })
ThrottledCommand() {
// if you will call this command 3 times every
// 100ms then it will be executed 1 time in 300ms.
}
}
class RegularLogic {
\@cmd({ internal: true })
_InternalCommand() {
// this command can't be caught in `hubBefore` or `hubAfter`
// in parent logics. Use it carefully.
}
}
Returns (Function | object) Command factory function (if all three arguemnts passed) or a decorator function with binded options object (that you pass as a first argument)
Run given block. It is a short-hand function for running bind
and then running a Process by Process#run.
Also it returns an additional finished
Promise which is resolved
when all async tasks finished and all handler commands executed.
Parameters
Returns {proc: Process, model: Object, block: Block, finished: Promise} An object
which is almost the same as returned from bind, but with
additional finished
field, which is a Promise that will be resolved
when all blocks will execute all commands and all async tasks will be
finished.
Mount running block. As third and next arguments you can pass other running blocks which will be stopped with the main running block (useful for HMR).
Parameters
-
mounter
Mounter An implementation of Mounter interface -
runRes
{proc: Process, block: Block, model: Object} An object that returned from run function. -
otherBlocks
...any
Examples
const MyBlock = {
createModel: () => ({ field: 1 }),
Logic: class MyLogic {
\@cmd SomeCommand() {
return { field: this.model.field + 1 };
}
},
View: ({ model }) => (
<div>{model.field}</div>
)
};
mount(new ReactMounter(), run(MyBlock));
Returns {view: any, stop: Function} An object with the result form Mounter#mount
function (in view
field) and stop function which destroy the process and
unmount a view using Mounter#unmount
Creates a TaskMeta object that could be returned from async task command. It describes the task that should be executed.
For more information what the Task is and how it should be implemented take a look to TaskMeta.
Parameters
-
taskFn
Function A function that could return a Promise
Properties
-
CANCEL
string A symbol that you can use to make a promise "cancellable"
Returns Object
This function should be used to cancel async task execution. Also it can cancel the debounced/throttled command.
- In case with task, if task was executing while you call a cancel command, the task will be cancelled (execution stopped). Otherwise nothing will happen.
- In case with debounced/throttled command, if the command scheduled to be executed then the schedule timer will be cleared and the command won't be executed. If the command is not scheduled then nothing happens.
To use it just pass a command or command factory as a first argument and return it from some other command.
Parameters
-
cmd
(Command | function) Command or command factory that you wan to cancel. Could be a command that returns task or debounced/throttled command
Examples
class SomeLogic {
\@cmd CancelSomethingCommand() {
return [
cancel(this.TaskCommand),
cancel(this.DebounceCommand)
];
}
\@cmd TaskCommand() {
return task(Tasks.SomeTask);
}
\@cmd({ debounce: 300 })
DebounceCommand() {
// do something
}
}
Returns Command Returns a new command that will cancel the execution of the command passed as an argument.
Creates an object which describes child logic which will be attached to some model field. Should be used in LogicBase#children to define a logic that should be instantiated with some arguments passed to LogicBase#config.
Parameters
Examples
class ChildLogic {
config(amount) {
return { meta: { amount } };
}
}
class RootLogic {
children() {
return {
modelField: child(ChildLogic, 10)
// `config` of `ChildLogic` will be invoked with
// `10` as a first argument
}
}
}
Returns Object Object that contains a logic class and an arguments array that
should be used to invoke config
method of provided logic class.
Function that helps to describe a computed field with external model dependncies. Should be used in LogicBase#computed. It creates an instance of ComputedField with dependencies passed as arguments.
A compute field with external dependencies is a field that should be updated (re-computed) not only when the own model udpated, but also when depdendency models updated.
Parameters
-
deps
...deps A list of models with attached logic
Examples
class ChildLogic {
// some logic
}
class MyLogic {
children() {
return { childModel: ChildLogic };
}
computed() {
return {
// depends on `childModel`
computedField: depends(this.model.childModel).compute(() => {
return this.model.a + this.model.childModel.b;
}),
// depends on `shared` model
computeWithShared: depends(this.shared).compute(() => {
return this.model.b + this.shared.someField;
})
}
}
}
Returns ComputedField
Other pieces of MangoJuice
The main class of MangoJuice that ties model, logic and view together.
It works in the following way:
- You create a model for your app (object) using
createModel
of the root block. The result is a plain object. - Then you need to create an instance of Process and pass logic class in the options object.
- Then you
bind
the Process instance to the model object you created on step one. "To bind Process to a model" means that in the model will be created a hidden field__proc
with a reference to the Process instance. - After that you can
run
the Process, which will execute init commands, port.
bind
and run
also look at the object returned from LogicBase#children
and create/run Process instances for children models.
Most of the time you do not need to care about all of these and just use run and mount. These small functions do everything described above for you.
Examples
import { Process, logicOf } from 'mangojuice-core';
const ExampleBlock = {
createModel() {
return { a: 10 };
},
Logic: class Example {
\@cmd Increment(amount = 1) {
return { a: this.model.a + amount };
}
}
};
const model = ExampleBlock.createModel();
const proc = new Process({ logic: ExampleBlock.Logic });
proc.bind(model);
proc.run();
proc.exec(logicOf(model).Increment(23));
Bind the process instance to a given model, which means that hidden
__proc
field will be created in the model with a reference to the
Process instance.
Also bind all children models – go through children definition returned by LogicBase#children, create Process for each child and bind it to an appropreat child model object.
Parameters
-
model
Object A model object that you want to bind to the Process.
Run the process – run children processes, then run port and init commands defined in config. Also run all model observers created by observe
Destroy the process with unbinding from the model and cleaning
up all the parts of the process (stop ports, computed fields).
__proc
field will be removed from the model object and all
children objects.
Parameters
-
deep
Boolean If true then all children blocks will be destroyed. By default, if not provided then considered as true.
Exec a command in scope of the Process instance. It will use binded model and logic object to run the command. The command could be function (command factory) or object (Command instance).
A command origin function could return three types of things:
- Another Command/command factory or an array of commands (an element of the array also could be another array with commands or null/undefined/false). All commands will be execute in provided order.
- An instance of TaskMeta (which is usually created by task helper function). The returned task will be started and do not block execution of next commands in the stack.
- A plain object which is a model update object. The object will be merged with current model.
If command is undefined or Process
instance not binded
to any model it will do nothing.
Parameters
Examples
class MyLogic {
\@cmd BatchCommand() {
return [
1 > 2 && this.SomeCoomand,
this.AnotheCommand(123),
2 > 1 && [
this.AndSomeOtherCommand,
this.FinalCommand()
]
];
}
\@cmd TaskCommand() {
return task(Tasks.SomeTask)
.success(this.SuccessCommand)
.fail(this.FailCommand)
}
\@cmd ModelUpdateCommand() {
if (Math.random() > 0.6) {
return { field: Date.now() };
}
}
}
Returns a promise which will be resolved when all async tasks and related handlers in the process and all children processes will be finished.
Returns Promise
Class which declares a command that will be executed in the future by some Process#exec. It contains a function that will be executed, arguments that should be passed to the function and command name.
The command also keep a context that should be used to execute a function. Usually this context is some logic instance. You can bind a context to the command using Command#bind function.
The command instance is immutable, so any function that makes some change in the command will produce a new command instead of changing the original one.
Parameters
Properties
-
func
Function An origin function that should be executed -
args
Array<any> A set of arguments that should be used to execute the fucntion.
Execute a function stored in the command instance. It passes stored arguments to the function and call it in stored context. Returns the value that was returned form the function.
Returns any
Clone instance of the command
Returns Command
Check is the command equal to some other command or have a reference to the same origin function. Optionally you can also check that the command binded to some concrete model (using the second argument).
You can use many different formats to define the command to check:
- Just a string, which should be constructed like
Logic Class Name + Function Name
- Command object or command factory that you can get from prorotype of some logic class.
- Binded command object or command factory that you can get from concrete model using logicOf function
Parameters
-
cmd
(Command | string) Command object or command factory or command name -
childModel
Object? Model object that should be binded to the command (optional)
Examples
class ChildLogic {
\@cmd SomeCommand() {}
}
class MyLogic {
hubAfter(cmd) {
if (cmd.is('ChildLogic.SomeCommand')) {
// by command name
}
if (cmd.is(ChildLogic.prorotype.SomeCommand)) {
// by command factory
}
if (cmd.is(logicOf(this.model.child).SomeCommand)) {
// by binded to a concrete model command factory (commands
// binded for other models will be ignored)
}
if (cmd.is(ChildLogic.prorotype.SomeCommand, this.model.child)) {
// by command factory and concrete child model (commands
// binded for other models will be ignored)
}
}
}
Returns Boolean Returns true
if the command have same name, or same origin function
and binded to same model (if second argument provided)
Creates a new Command instance (clone the command) and append given list of arguments to the current list of arguments. Returns a new Command with new list of arguments.
Parameters
-
args
Array<any> List of arguments that will be appened to tne new Command
Returns Command New command with appended arguments
Creates a new Command instance and set given logic instance in it. Also update command name by name of the origin function and name of the logic class.
Parameters
-
logic
LogicBase A logic instance that should be binded to a command
Returns Command New command binded to given logic instance
Class for declaring an async task. An instance of this object returned from taks function.
A task function is a function that could return a Promise. The resolved value will be passed to a success command handler, the rejected command will be passed to a fail command handler.
If task function do not return a Promise, then the returned value passed to a success command, and if the function throw an error then the error passed to a fail command.
The task function receive at least one argument – an object with model
, shared
and meta
. All the next arguments will be given from a command that returned a task,
if it is not overrided by TaskMeta#args
By default a task is single-threaded. It means that every call to a task will cancel previously running task if it is running. You can make the task to be multi-threaded by TaskMeta#multithread, so every call to a task won't cancel the running one.
In a context of the task function defined special call
function. This function should
be used to invoke sub-tasks. It is important to use call
to run sub-tasks because
call
create a cancellation point of the task – if the task cancelled, then nothing
will be executed after currently running call
sub-task.
call
returns an object with result
and error
fields. If error
field is not
empty, then something weird happened in the sub-task. call
internally have a try/catch,
so it is not necessary to wrap it with try/catch, just check the error
field in the
returned object and if it is not empty – handle it somehow (throw it, or do any custom
error handling)
Also in a context of the task defined a notify
function. It is a function that execute
notify command if it is defined. The command executed with the same arguments as passed
to notify
function. You can use it to incrementally update a model while the long
task is executing, to show some progress for the user.
Parameters
Examples
async function SubTask(a, b, c) {
this.notify(a + b + c);
await this.call(delay, 100);
return (a / b * c);
}
async function RootTask({ model }) {
const { result, error } = await this.call(SubTask, 1, 0, 3);
if (error) {
throw new Error('Something weird happened in subtask')
}
return result + 10;
}
class MyLogic {
\@cmd AsyncCommand() {
return task(Tasks.RootTask)
.success(this.SuccessCommand)
.fail(this.FailCommand)
.notify(this.NotifyCommand)
}
}
Set a notify handler command. This command executed by call of this.notify
inside a task with the same arguments as passed to this.notify
Parameters
-
cmd
Command
Returns TaskMeta
Set a success handler command. Will be executed with a value returned from the task, or if the task returned a Promise – with resovled value.
Parameters
-
cmd
Command
Returns TaskMeta
Set a fail handler command. Will be executed with error throwed in the task, or if the task returned a Promise – with rejected value.
Parameters
-
cmd
Command
Returns TaskMeta
Define the task to be "multi-thread", so every call will run in parallel with other calls of the same task in scope of one process (model).
Parameters
-
val
boolean
Returns TaskMeta
Set task executor function. The executor function should return an object
that should have at least two fields: exec
and cancel
functions.
exec
should return a promise which should be resolved or rejected
with an object { result, error }
. A cancel
function should stop
the task execution.
Parameters
-
engine
Function A function that returns{ exec: Function, cancel: Function }
object
Returns TaskMeta
Override arguments which will be passed to the task starting from second argument. By default a task will receive the same set of arguments as a task command. If this function invoked, then the task will receive given arguments instead of command arguments.
Parameters
-
args
...any
Returns TaskMeta
A class which defins a logger for tracking command execution process. The default implmenetation just defines all possible logger methods (log points) and print errors to the console. Extend this class to define your own logging functionality (like logging errors with Setnry or Loggly)
Examples
// ./index.js
import { DefaultLogger, run } from 'mangojuice-core';
import MainBlock from './blocks/MainBlock';
// Implement custom logger
class SetnryLogger extends DefaultLogger {
onCatchError(error, cmd) {
Raven.catchException(error);
}
}
// Pass instance of the Logger to `run` options object.
run(MainBlock, { logger: new SetnryLogger() });
This method invoked right before anything else - as the first thing in Process#exec. Even before any LogicBase#hubBefore.
Parameters
-
cmd
Command Command that just started execution process
Invoked right before command will go through all LogicBase#hubBefore or LogicBase#hubAfter up in the blocks tree. The second argument indicates is it called for LogicBase#hubBefore or for LogicBase#hubAfter.
Parameters
-
cmd
Command Command that is going to go through hubs in parent blocks -
isAfter
Boolean If true then the command is going through "after" hubs, "before" otherwise
Invoked when the command went through all hubs and when all sync commands returned from hubs was executed.
Parameters
-
cmd
Command Command that went through all hubs -
isAfter
Boolean If true then the command going through "after" hubs, "before" otherwise
Invoked when the command function executed and the return of the function processed – all returned commands executed, all async tasks started, the model updated. But it invoked before the command go through "after" hubs.
Parameters
-
cmd
Command Command that was exectued and the return processed -
result
any Returned object from the command's function
Invoked right after the command go throught "after" hubs. It is the final stage of command execution.
Parameters
-
cmd
Command Command that was fully executed and handled by parent blocks
Invoked if any uncaught error happened while execution of the command
or anywhere else in the logic, like in LogicBase#port or in LogicBase#computed.
By default print the error using console.error
.
Parameters
Class for declaring computed field with model dependencies. Given array shuold contain models, binded to some logec. They will be passed to compute function in the same order. Also they will be used to track changed and re-compute.
The class is immutable, so any call to any method will create
a new instance of ComputedField
and the old one won't be touched.
Parameters
Properties
-
deps
Array<Object> An array of models with binded logic (attached some Process) -
computeFn
Function A compute function that will be used to compute value of the computed field.
Creates a new instance of the field and set compute function in it.
Parameters
-
func
Function A compute function that should return a value that will be used as a value for some computed field in a model. This function will be invoked with all dependency models as arguments
Examples
// A way to override computed field of some base logic class
// (thanks to immutability of `ComputedField`)
class MyLogic extends SomeOtherLogic {
computed() {
const oldComputed = super.computed();
return {
...oldComputed,
// Override `oldField` compute function with saved dependencies
// and use overrided compute function inside new compute function
oldField: oldComputed.compute((...deps) => {
return 1 > 0 || oldComputed.oldField.computeFn(...deps);
})
};
}
}
Returns ComputedField New instance of the ComputedField with computeFn
set to given function.
To use mount function you need to implement a Mounter interface
which should have mount
and unmount
functions. It is up to the developer
how these functions will be implemented and what view library they will use.
There is a rule that mounter and view library should follow: view of a model should be updated only when the model updated and the update shouldn't touch views of children models (children views shouldn't be updated because their models is not changed).
React perfectly fits for this rule. By implementing shuoldComponentUpdate
you can control when the component should be updated and when shouldn't. In this
case this function should always return false
and componenent should be
updated using forceUpdate
only when the model is updated (using observe).
Properties
-
mount
Function A function that should expect two arguments: model object with attached Process instance and Block#View. It should render the model using given View (somehow). -
unmount
Function A function that shuold unmount mounted view from DOM.
Examples
// Minimal react mounter which follow the rules
class ViewWrapper extends React.Component {
componentDidMount() {
const { model } = this.props;
this.stopObserver = observe(model, () => this.forceUpdate())
}
componentWillUnmount() {
this.stopObserver();
}
shouldComponentUpdate() {
return false;
}
render() {
const { View, model } = this.props;
const Logic = logicOf(model);
return <View model={model} Logic={Logic} />
}
}
class ReactMounter {
mount(model, View) {
return React.render(
<ViewWrapper View={View} model={model} />,
document.querySelector('#container')
);
}
unmount() {
return React.unmountComponentAtNode(
document.querySelector('#container')
);
}
}
Provides a way to define a command in the prototype without usign a decorator. You should give a prototype of the logic class, name of the function which should be converted to a command factory and the decorator function (optional).
If the decorator function is not provided, then cmd will be used by default.
Parameters
-
proto
Object Logic class prototype -
name
string Method name that you want to convert to a command factory -
decorator
function? Optional decorator function
Examples
class MyLogic {
SomeCommand() {
return { field: this.model.field + 1 };
}
// throttle 100
ThrottledCommand() {
return this.SomeCommand();
}
}
defineCommand(MyLogic.prototype, 'SomeCommand');
defineCommand(MyLogic.prototype, 'ThrottledCommand', cmd({ throttle: 100 }));
Returns a Command instance or throw an error if given argument can't be used to create a Command (is not a Command or command factory).
Parameters
-
cmd
(Command | function) Command instance or command factory. When command factory then it will be invoked with no arguments to create a command
Examples
class MyLogic() {
\@cmd SomeCommand() {
// do something
}
}
const logic = new MyLogic();
ensureCommand(logic.SomeCommand) // returns Command instance
// equals to...
logic.SomeCommand()
Returns Command
By given logic class go through prototypes chain and decorate all cmd functions. Use it if you can't use decorators in your project
It determine that the function should be converted to a command factory (decorated by cmd) by its name. If the function starts with uppercase letter or underscore+upper-letter, then it is considered as a command.
Parameters
-
LogicClass
LogicBase Logic class that you want to decorate -
deep
bool If true then it will go though prototypes chain and decorate every prototype in the chain.
Examples
class MyLogic {
SomeCommand() {
}
_SomePrivateCommand() {
}
notACommand() {
return 123;
}
}
// `SomeCommand` and `_SomePrivateCommand` will be decorated with `cmd`
// (the prorotype will be changed in-place). `notACommand` won't
// be decorated.
decorateLogic(MyLogic);
const logic = new MyLogic();
logic.SomeCommand() // returns Command instance
logic._SomePrivateCommand() // returns Command instance
logic.notACommand() // returns 123
Function adds a handler to the Process attached to a given model that will be invoked before every command running for this model. The handler won't be invoked for commands from children models of given model.
Returns a function that removes the handler from the Process (stop handling)
Parameters
-
model
Object A model object with attached Process -
handler
Function A function that will be called before every own command
Examples
import { handleBefore, run, logicOf } from 'mangojuice-core';
// Define root and child logic
class ChildLogic {
\@cmd ChildCommand() {}
}
class MyLogic {
children() {
return { childModel: ChildLogic };
}
\@cmd RootCommand() {}
}
// Run block with empty models
const res = run({
Logic: MyLogic,
createModel: () => ({ childModel: {} })
});
// Adds a handler to a root model
handleBefore(res.model, (cmd) => console.log(cmd));
// Run commands on root and child models
res.proc.exec(logicOf(res.model).RootCommand);
res.proc.exec(logicOf(res.model.childModel).ChildCommand);
// In the console will be printed only `RootCommand` command object
// right before the command will be executed
Returns Function A function that stopps the handler
It is the same as handleBefore but the handler will be invoked after every own command.
Parameters
-
model
Object A model object with attached Process -
handler
Function A function that will be called after every own command
Returns Function A function that stopps the handler
Alias for handleAfter
Parameters
-
model
Object A model object with attached Process -
handler
Function A function that will be called after every own command
Returns Function A function that stopps the handler
Returns a Logic instance attached to a given model object. The logic instance stored in attached Process instance, so it execute procOf for the model to get a Process instance and then get the logic instance form a Process and return it.
If Process is not attached to the model, then an Error will be throwed. If the second argument passed then it will also check that the logic instance is instance of some particular class.
Parameters
-
model
Object A model with attached Process -
logicClass
Class? Optional logic class to check that the logic instance is instance of the class
Returns LogicBase Returns a logic instance
A function that adds a handler to the Process instance attached to a given model, that will be invoked on every command that changed the model. Useful for tracking changes to re-render a view of the model.
Parameters
-
model
Object A model with attached Process instance -
handler
Function A function that will be invoked after every command that changed the model. -
options
Object? An object with options-
options.batched
bool If true then the handler will be invoked only when the commands stack is empty and model was changed during the stack execution. Useful to reduce amount of not necessary view re-renderings.
-
Examples
import { run, cmd, observe, logicOf } from 'mangojuice-core';
class MyLogic {
\@cmd MultipleUpdates() {
return [
this.UpdateOne,
this.UpdateTwo
];
}
\@cmd UpdateOne() {
return { one: this.model.one + 1 };
}
\@cmd UpdateTwo() {
return { two: this.model.two + 1 };
}
}
const res = run({
Logic: MyLogic,
createModel: () => ({ one: 1, two: 1 })
});
observe(res.model, () => console.log('not-batched'));
observe(res.model, () => console.log('batched'), { batched: true });
res.proc.exec(logicOf(res.model).MultipleUpdates);
// `not-batched` will be printed two times
// `batched` will be printed only once
Returns Function
Get a Process instance attached to the given model object.
Internally it get a __proc
field from the given model and returns it.
If given model do not have attached Process, then an Error will be throwed, but only if the second argument is not true, which ignores the error.
Parameters
-
model
Object A model with attached Process -
ignoreError
bool If true then no error will be throwed if Process is not attached to the model
Returns Process An instance of Process attached to the model
This function do the following:
- Creates a model object using Block#createModel. It is invoked without arguments.
- Create a Process instance with logic from Block#Logic
- Call Process#bind with created model
Returns an object with created process instance, model object and given block. Usefull when you want to prepare the block to run but you want to run it manually.
Parameters
-
block
Block A Block object -
opts
Object? Options object that could change Process behaviour (optional, default{}
)-
opts.logger
DefaultLogger? A custom logger instance that will be used in the Process to track commands execution. By default DefaultLogger will be used. -
opts.shared
Object? An object that will be available in any logic in the tree asthis.shared
. Could be anything you want, but you will get more benifites if you will pass model object with attached Process as shared object to be able to make computed fields with depending of shared model. -
opts.args
Array<any>? An array of arguments that will be passed to LogicBase#config of the logic. -
opts.Process
Process? A class that will be used instead of Process. By default general Process is used, but you can customize it for your specific needs and pass here. Then every other process in the tree will be an instance of this custom Process implementation
-
Returns {proc: Process, model: Object, block: Block} An object with Process instance, model object and block that was passed to the function
This function replace a createModel
function in given block
with new function that just returns a given model. It is useful
when you have some old model and just want to run everytihing with
it (for example for server rendering, or hot module replacement)
Parameters
Examples
// Hot module replacement example
import MyBlock from './MyBlock';
import { hydrate, run } from 'mangojuice-core';
// Run initial block
let res = run(MyBlock);
// When block changed destroy the old process, hydrate a new
// block with existing model and run hydrated new block again
module.hot.accept(['./MyBlock'], () => {
res.proc.destroy();
const newBlock = hydrate(require('./MyBlock'), res.model);
res = run(newBlock);
});
Returns Block A new block with replaced createModel
function
which just returns a model that you passed as
second argument.
A helper function for delaying execution. Returns a Promise which will be resolved in given amount of milliseconds. You can use it in task to implement some delay in execution, for debouncing for example.
Parameters
-
ms
number An amount of milliseconds to wait
Returns Promise A promise that resolved after given amount of milliseconds