Nobody's Perfect, Man

    mangojuice-core

    1.2.11 • Public • Published

    MangoJuice Core

    Reference

    Table of Contents

    Basic

    The most common APIs that you will work with most of the time

    Block

    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 = { fieldnumber };
    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);

    LogicBase

    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:

    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 };
      }
    }
    config

    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 }
      };
    }
    children

    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

    computed

    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

    port

    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));
    }
    hubAfter

    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>?)

    hubBefore

    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

    Returns (Command? | Array<Command>?)

    cmd

    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. With noInitCall=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

    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

    • block Block A block that you wan to run
    • opts Object Same options as in bind (optional, default {})

    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

    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

    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

    task

    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

    cancel

    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.

    child

    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.

    depends

    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

    Extras

    Other pieces of MangoJuice

    Process

    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

    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

    Run the process – run children processes, then run port and init commands defined in config. Also run all model observers created by observe

    destroy

    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

    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() };
        }
      }
    }
    finished

    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

    Command

    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.
    exec

    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

    Clone instance of the command

    Returns Command

    is

    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)

    appendArgs

    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

    bind

    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

    TaskMeta

    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(+ b + c);
      await this.call(delay, 100);
      return (/ 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)
      }
    }
    notify

    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

    Returns TaskMeta

    success

    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

    Returns TaskMeta

    fail

    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

    Returns TaskMeta

    multithread

    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

    Returns TaskMeta

    engine

    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

    args

    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

    DefaultLogger

    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() });
    onStartExec

    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
    onStartHandling

    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
    onEndHandling

    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
    onExecuted

    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
    onEndExec

    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
    onCatchError

    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

    • error Error The error instance
    • cmd Command? Command that was executing while the error happened

    ComputedField

    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.
    compute

    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.

    Mounter

    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')
        );
      }
    }

    defineCommand

    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 }));

    ensureCommand

    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

    decorateLogic

    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

    handleBefore

    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

    handleAfter

    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

    handle

    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

    logicOf

    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

    observe

    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

    procOf

    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

    bind

    This function do the following:

    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 as this.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

    hydrate

    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

    • block Block An original block
    • model Object A model object that you want to use

    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.

    delay

    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

    Basic

    The most common APIs that you will work with most of the time

    Block

    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 = { fieldnumber };
    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);

    LogicBase

    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:

    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 };
      }
    }

    config

    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 }
      };
    }

    children

    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

    computed

    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

    port

    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));
    }

    hubAfter

    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>?)

    hubBefore

    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

    Returns (Command? | Array<Command>?)

    cmd

    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. With noInitCall=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

    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

    • block Block A block that you wan to run
    • opts Object Same options as in bind (optional, default {})

    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

    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

    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

    task

    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

    cancel

    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.

    child

    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.

    depends

    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

    Extras

    Other pieces of MangoJuice

    Process

    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

    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

    Run the process – run children processes, then run port and init commands defined in config. Also run all model observers created by observe

    destroy

    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

    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() };
        }
      }
    }

    finished

    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

    Command

    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.

    exec

    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

    Clone instance of the command

    Returns Command

    is

    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)

    appendArgs

    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

    bind

    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

    TaskMeta

    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(+ b + c);
      await this.call(delay, 100);
      return (/ 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)
      }
    }

    notify

    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

    Returns TaskMeta

    success

    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

    Returns TaskMeta

    fail

    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

    Returns TaskMeta

    multithread

    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

    Returns TaskMeta

    engine

    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

    args

    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

    DefaultLogger

    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() });

    onStartExec

    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

    onStartHandling

    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

    onEndHandling

    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

    onExecuted

    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

    onEndExec

    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

    onCatchError

    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

    • error Error The error instance
    • cmd Command? Command that was executing while the error happened

    ComputedField

    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.

    compute

    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.

    Mounter

    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')
        );
      }
    }

    defineCommand

    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 }));

    ensureCommand

    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

    decorateLogic

    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

    handleBefore

    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

    handleAfter

    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

    handle

    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

    logicOf

    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

    observe

    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

    procOf

    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

    bind

    This function do the following:

    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 as this.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

    hydrate

    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

    • block Block An original block
    • model Object A model object that you want to use

    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.

    delay

    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

    Keywords

    none

    Install

    npm i mangojuice-core

    DownloadsWeekly Downloads

    14

    Version

    1.2.11

    License

    MIT

    Last publish

    Collaborators

    • artem.artemev