Actions API

Action

An Action is an encapsulation of a UI operation. In the ideal world an Action works on an injected context.
The context can be any object, but for pluggability context format is recommended
(See Context).
The action consumer is usually a view.

How to Export Action

Actions can be defined in the same way as views are defined in static plugins:

export const avid = {
    actions: {
        'av-my-plugin-action-first': actionFirstConfig,
        'av-my-plugin-action-second': actionSecondConfig,                
    } 
};

Action keys should be unique. Use namespacing for your action ids.

If you use Dependency API, to expose avid features:

export const avid = [
    {
        name: 'av-my-plugin-action-first',
        provides: ['actions'],
        create: () => actionFirstConfig,
    },
    {
        name: 'av-my-plugin-action-second',
        provides: ['actions'],
        create: () => actionSecondConfig,
    }];

Action Configuration

Minimum

A minimal action implementation must provide a text property. The text is a localized string which will be displayed by
ui presentation (Menu item name, toolbar tooltip). An action must also provide a “handler” function. The handler function implements the
actual operation which will be executed if the user presses the menu item or toolbar button.

{
    text: "text",
    handler: function() {
        alert("HelloWorld);
    }
}

Selection and Component Aware

The example above is a full functional action but it is not really useful. In most cases an action needs an execution context.
The execution context is mostly a selection (or collection of objects) provided by a ui component and component public API
(view publicScope).

{
    getText({ selection, component }) { 
       return "text";
    },
    handler: function({ selection, component }) {
       alert("Hello Selection: " + selection);
    }
}

Note. View selection should have the same format as context (See Context).

Keyboard Shortcuts Binding to Action

When you bound keyboard shortcuts to actions, you will received event data.

{
    isEnabled({ event, selection, component }) { 
       return true;
    },
    handler: function({ event, selection, component }) {
       alert("Hello Selection: " + selection);
    }
}

Important. event available in keyboard shortcuts only (see link).

Action Lifecycle

An action implementation like described before is just a template. This template will be used to create multiple instances
if needed (for every pane menu).

{
    getText: function ({ selection, component }) {
        //...
    },

    isEnabled: function({ selection, component }) {
        return Ext.isArray(selection) && selection.length > 0;
    },

    isVisible: function({ selection, component }) {
        //...
    },

    //only for type "checkitem"
    isChecked: function ({ selection, component }) {
        //...
    },

    handler: function({ selection, component }) {
        alert("Hello Selection: " + selection['text/plain'] );
    }
}

The actual lifecycle of an action instance is:

isEnabled -> getText -> isChecked -> isVisible -> handler (if user clicks item)

All methods except handler are optional.

If appropriate method is no defined, default values for action state are:

property | default value
---------------------------------
enabled  | true
visible  | true
text     | Empty 'text' property
checked  | false

Consumer Context

Each action instance is associated with a consumer component. The consumer component can be any object but is in the most
cases the View (precisely View publicScope).

Text

To define text for action item:

  • property text for action (if text is known at the moment of action export)
  • define method getText for action. This method can return string or a promise that fulfills with a string.

If getText promise is rejected then action item in menu will be disabled.

Note. It is recommended always define property text for action then if even getText
fail for some reason the item has predefined in property text.
If text is not defined then menu item has default text value Empty ‘text’ property.

Example. Define text in action method getText (allows access to localization)

{
    text: "Create shortcut", 
    getText: function () {
        return localize("shortcuts.action.create");
    },
    handler:function () {
        ...
    }
});

Note. Text setting for action can be asynchronous.
The method getText can return a native promise that fulfills with a string.

Enable State

An Action might want to control if the given context is suitable for the implemented operations. The Action might be
disabled in case the context is not suitable. It can be defined immediately or in asynchronous way.
This behavior can be achieved by providing a “isEnabled” function.

Example

{
    id: "org.mycompany.Helloworld",
    text: "Hello World",

    // action works with assets only    
    isEnabled: function({ selection } ) {
        return selection["text/x.avid.asset-list"];
    },

    handler: function({ selection }) {
        alert("Hello Selection: " + selection["text/x.avid.asset-list"] );
    }
}

The action above will only be enabled in case the selection context conains assets.

The handler function must not validate the selection because it will only be called if “isEnabled” returns true.

Note: A context menu will filter out all disabled actions.

Note: isEnabled is called automatically before showing menu.

As noticed before it can be defined asynchronously if item is enabled.

For that purpose isEnabled should return native Promise.

Visibility

There 2 possibilities to control an action menu item visibility:

  • define method isVisible

  • define property keepIfEmpty (only for items with type menu )

Default value for visibility is equal true.

isVisible

An action might want to control if the menu item that corresponds to this action should be displayed.
It can depends on current selection, view state etc. It can be defined immediately or in asynchronous way.

Example. The Action should be hidden in case the context is not suitable.
This behavior can be achieved by providing method isVisible.

{
    id: "org.mycompany.Helloworld",
    text: "Hello World",

    isVisible: function({ selection }) {
        return !selection;
    },

    handler: function({ selection }) {
        alert("Hello Selection: " + selection['text/plain'] );
    }
}

The action above will only be visible in case the selection context is not empty.

Note: isVisible is called automatically before menu showing.

Note: isVisible can return a native promise if visibility should be defined asynchronously.
If isVisible promise is rejected then the action item will be visible but disabled.
See example for async isEnabled.

keepIfEmpty

The property keepIfEmpty allows to show container items (items with submenus) even if they don’t have subitems.
In that case such item is shown in disabled state.

Example.

{
    text: "Kept Empty Menu",
    keepIfEmpty: true,
    type: "menu"
}

Example. Action item with the property keepIfEmpty in view menu

keepIfEmpty

Note. If the action has defined keepIfEmpty==true and isVisible the priority has isVisible method.

Action Binding

Use action bindings to associate action with appropriate consumer context (views).

Actions binding can be defined in the same way as actions are defined in static plugins:

export const avid = {
    actionBindings: {
        'av-my-plugin-action-binding': actionBindingConfig,              
    } 
};

Action binding keys should be unique. Use namespacing for your action binding ids.

If you use Dependency API to expose avid features:

export const avid = [
    {
        name: 'av-my-plugin-action-binding',
        provides: ['actionBindings'],
        create: () => actionBindingConfig,
    },

Action Binding Config

Config parameters that you need to pass is next:

  • places - constants which point type of menus where you can bind your action.

    Possible places:

    'contextMenu', 'viewMenu'
  • filter - is an Array or String (for single one) of view ids (viewTypes) where we want to bind menu

    To bind menu for plugin with id static-plugin-example.example-view

filter: ["static-plugin-example.example-view"]
  • defaults
    a position per item is of course a lot work and a maintenance hell (imagine to add a new action in the middle.
    For that reason allows a action binding to specify a default index for all actions.
defaults: {
    index: 500,
},
  • menuModelProvider - is a function that returns a menu model.

    Menu model is an Array of items that contains Objects that represent menu item.

    If we want to bind action with action id named “com.avid.sent-to-email”:

menuModelProvider: function() { 
  return [ {  actionId: "com.avid.sent-to-email", index: 500 } ]; 
}

Property index is used for action positioning in menu.
The model will be merged with all other plugins and the elements will be sorted by the given index.

Example

To summarize let’s get into a small example that shows how to binding action for tab menu may look like.

{
     // places to bind action
     places: [ 'contextMenu', 'viewMenu' ],
     // filter by plugin id 
     filter: ["static-plugin-example.example-view", "av-ext4-example"],
     // function that returns array that represents the menu model
     menuModelProvider: function (viewPublic) {
         // viewPublic method that are publicly provided by view
         // depends on view
         var text = viewPublic.name()
         return [
             { type: 'separator' },
             {
                 // Text that will be in menu. Your action will 
                 // be named in menu by getting this property
                 text: text
                 //You need to know your Action id (*actionId*) that you want to bind
                 actionId: "com.avid.example.example-action-enabled",
             },
             binder.model.SEPARATOR
         ];
     }
};