JSPM

  • Created
  • Published
  • Downloads 153
  • Score
    100M100P100Q52814F

action-manager is a software framework that facilitates defining, publishing, and executing actions. Do more with less effort!

Package Exports

    This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (action-manager) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

    Readme

    action-manager

    Action-Manager is a framework for defining, invoking, and integrating actions. Do more with less effort!

    Visit Action-Manager for more information.

    🚧 Under Construction 🚧

    We will be incrementally uploading our entire framework over the next few months.

    Install

    yarn add action-manager
    npm install action-manager

    Usage

    Create an action

    const printTime: Action = {
      handler: () => console.log(new Date().toISOString())
    }

    Invoke an action (without context)

    await invoke(printTime)

    Invoke an action with context and type parameters. The context, all context properties, and all type parameters are optional.

    const printTime: Action<void, {
      type: string
    }, {
      timeZone: 'EST'
    }> = {
      handler: () => console.log(new Date().toISOString())
    }
    
    await invoke(printTime,
      {
        // These are the targets of the action
        targets: [],
        // These are the action parameters
        params: {type: 'log'},
        // This is the environment
        env: {timeZone: 'EST'}
      }
    )

    Benefits

    • Eliminate boilerplate code
    • Increase reusability
    • Streamline testing
    • Access a rich ecosystem of shared libraries

    Features

    • Plugin based system for confirmation dialogs
    • Generate user interfaces
    • Dynamic labels support
    • Fully asynchronous
    • Action pipelines
    • No dependencies

    Creating Actions

    The only required property in the Action interface is the handler. Handlers accept a single argument: the context.

    const printTime: Action = {
      handler: () => console.log(new Date().toISOString())
    }

    The power of action-manager emerges as you begin to implement the optional properties.

    const printTime: Action = {
      id: 'print_time',
      name: 'Print Time',
      description: 'Print the current time, in ISO format, to the console.'
      log: true,
      handler: () => console.log(new Date().toISOString())
    }

    The action can now be used to generate user interface items, such as buttons and menu items.

    Action Type Parameters

    Although it is not required to type the targets, params, and environment, it is foolish not to. 😉 Action<TARGET,PARAMS,ENVIRONMENT>

    Parameter Information Default
    TARGET The action target type any
    PARAMS The action params type any
    ENVIRONMENT The action environment type any

    These parameter types can be set to a specific type, any, or void.

    Invoking Actions

    There are many advantages to invoking an action instead of directly calling its handler.

    • Undo / Redo support
    • Display confirmation
    • Action chaining
    • Error handling
    • Unified sync/async
    • Easier debugging
    • Cardinality checking
    • Parameter checking
    • Default environment

    [todo show examples]

    Action Widgets

    There are several types of action widgets. These widgets are implemented using several libraries.

    • Buttons
    • Menu Items
    • Spreadsheets
    • External API

    Action Context

    A context object is provided to the action handler as it's only parameter. The context is also provided to MaybeRef properties.

    The action context triages properties into three buckets.

    1. Targets
    2. Parameters
    3. Environment

    Targets

    Targets are the objects an action is to be performed on. Because actions indicate the cardinality of the targets they perform on, targets can be used not only for the handler, but for calculating user interface state. For instance, a delete action can work on 1 or more targets, while a compare action can require exactly two targets. In contrast, an open file action would not require any targets.

    Parameters

    Parameters are properties that indicate on how the action should go about acting on the targets for a particular invocation.

    Environment

    The environment are common properties that typically remain the same for multiple actions.

    Action Registry

    [todo]

    Developer

    TODO

    • Remove Vue dependency
    • Create driver for prompts
    • Create driver for errors
    • Create driver for logging
    • Organize for various UI frameworks

    Testing

    yarn test

    Building

    yarn build

    Refactoring Example

    Here is an example of how you might refactor your code to use action-manager.

    Your existing code is hardwired to call a function:

    function logMessage(message) {
      console.log(message)
    }
    <button onclick="logMessage('hello')">Log A Message</button>

    Migrating to an invokable handler

    We start by wrapping the function in an action handler, and typing it. We then call invoke on the action:

    const logMessage: Action<void, {
      message: string
    }> = {
      handler(context){
        console.log(context.params.message)
      }
    }
    <button onclick="invoke(logMessage, {params: {message:'hello'}})">Log A Message</button>

    With this in place, and not really using much more code, we have enabled all sorts of additional functionality.

    Adding targets to log

    We start with defining an environment that will be sent into every action.

    // We now make our action typed, and also allow it to log targets.
    const logMessage: Action<any, {
      message: string
    }> = {
      handler(context) {
        console.log(context.params)
        if (context.targets && context.targets.length > 0) {
          console.log('TARGETS:')
          for (let target of context.targets) {
            console.log(target)
          }
        }
      }
    }

    Great, now our action can be used to log not just a message, but user selected items as well.

    Creating an environment

    We can define an environment that will be sent into every action.

    const env = {
      // We could redirect to a file, use console.warn, do nothing, or whatever
      log: (...args: any[]) => console.log(...args),
      // A custom formatter for strings
      format: (item: T) => {
        if (typeof item === 'string') {
          return item.toLowerCase()
        } else {
          return item
        }
      }
    }
    
    // This is a quick way to create a type from an existing object.
    // You can create the type explicity if you desire.
    type ENV = typeof env
    
    // We now add the ENV type to our action, and use it
    const logMessage: Action<any, {
      message: string
    }, ENV> = {
      handler(context) {
        context.env.log(context.env.format(context.params))
        if (context.targets) {
          for (let target of context.targets) {
            context.env.log(context.env.format(target))
          }
        }
      }
    }

    This environment can be used in other projects. It can also be replaced by some mock when testing.

    Add UI hints

    The extensibility of this design is now showing. We can add even more properties to enable all sorts of additional features.

    const logMessage: Action<any, {
      message: string
    }> = {
      id: 'logMessage',
      name: 'Log Message',
      description: 'Logs a message and/or action targets',
      debug: true,
      log: true,
      confirm: {
        message: 'Log it? Really?'
      },
      cardinality: 'any',
      onError: ['prompt', 'log'],
      status: {
        message: 'Logging...'
      },
      usage: true,
    
      handler(context) {
        // ...
      }
    }
    registerAction(logMessage)

    Because we registered our action, it can now be invoked by 'ID' instead of the object. This will reduce dependencies.

    Widgets such as buttons, menu items, and search results can now use the action as meta-data to generate content.

    The invoker can check cardinality to ensure the action is compatible with the context.

    Our runtime can log and debug information when the action is invoked.

    We have instructed the runtime:

    • confirm with the user before invoking the action
    • to present the user with any errors that occur, and also log them
    • show a status bar with a message
    • collect usage data

    So ALL OF THIS comes for free by just using actions instead of functions...

    Dynamic Properties

    The benefits do not stop here. Most action properties support a propnameRef alternative that can dynamically calculate and reactively update the value.

    [todo]