Component Development - fkling/JSMashup GitHub Wiki

A component consists of two parts, the component definition and the component implementation. In some cases, an implementation does not have to be provided as the definition alone is enough to define the functionality of the component.

Component definition

The definition of a component can be provided in any format for which a mapper exists. The mapper is responsible for parsing the definition and creating an instance of ComponentDescriptor.

A definition typically contains the following information:

  • The ID of the component, subject to the rules of the environment the engine runs in.
  • A description for the end user.
  • Configuration parameters which can be changed by the user of the component.
  • Operations which can be invoked on the component (called by other components).
  • Events which are generated by the component and other components can listen to.
  • Requests, calls to external services, mostly used to retrieve data.

In addition, operations and events should define input and output parameters which describe what kind of data an operation expects or is emitted by an event.

Example: The following component definition is written in our own XML dialect, EMDL:

<?xml version="1.0" encoding="UTF-8"?>
<component id="jsmashup.Concat" name="Concatenation">
    <description>
        Takes a list of strings and concatenates them with the given delimiter
    </description>

    <config ref="delimiter">
        <option name="label" value="Delimiter"/>
    </config>

    <operation name="Concatenate" ref="concatenate" triggers="concatenated">
        <input name="strings" type="string" collection="true" />
    </operation>

    <event name="Concatenation finished" ref="concatenated">
         <output name="string" type="string" collection="false"/>
    </event>
</component>

Which and how the data has to be provided in the definition depends on the format. Have a look at the EMDL page for more information about this format.

Component implementation

The base component implementation provides certain "hooks" with which the behaviour of the component can be extended. Let component refer to an instance of a component, then all extensions have to be assigned as property to the object component.fn. This includes implementation for operations and hooks.

Operations

If a component exposes operations, a function implementing this operation has to be assigned to component.fn.<operation_name>. The function for the operation in the above example would therefore be assigned to component.fn.concatenate. Each method defined in that way must accept one parameter which is an object, containing the input parameters as described in the definition. Example:

component.fn.concatenate = function(parameters) {
    // parameters['strings'] == ['foo', 'bar', 'baz']
    // parameters has one property, "strings", corresponding to the input name from the definition
    var concatenation = parameters['strings'].join('');
    // the result can either be returned from the function
    return concatenation; // since the event has only one output, JSMashup will figure this out
    // or to be more correct
    return {string: concatenation};
    // or instead of returning the value, `this.finish` can be called:
    this.finish(concatenation);
    // resp.
    this.finish({string: concatenation});
};

Inside this function, this will refer to an object containing functions to get information about the component and to control the state of execution of the operation. Currently available are:

  • getConfiguration()

  • finish([result]): This marks the operation as finished and triggers the associated event (if any). result is passed on as event data. If the operation does not contain asynchronous code, returning any value but undefined has the same effect. That means that either this.finish(result) or return result; have to be called.

  • makeRequest(name, [...]): A wrapper around component.makeRequest. The difference is that if a named request is made, inside the callback, this.finish() will mark both, the request and the operation as finished and trigger corresponding events.

Associating operations with events and requests

An operation can be associated with an event or request. operation.setTrigger accepts either the name of an event or request. This helps to automate certain processes, as mentioned above.

If the operation is associated with an event, it will automatically be triggered if finish is called or the function returns.
If a named request is associated with an operation, the request is executed before the implementation of the operation and the response is passed as second parameter to the function (the first is a reference to the received event data).

A named request can also be associated with an event, which in turn is triggered when the operation returns or this.finish is called.

With all these combinations, we get three execution flows:

Operation -> Implementation
Operation -> Implementation -> Event
Operation -> Request -> Implementation -> Event

Methods save to override

The components behaviour can be customised with the following hooks:

  • validate is a method to validate the configuration parameters of the component. The method gets one parameter of the form {parameterName: {display: displayedValue, value: actualValue}, ...}. It should return true if the configuration is valid, or an array of strings, containing possible error messages.

  • getConfiguationTemplate can be used to customize the appearance of the configuration form. It should either return an HTML string, or a DOM node.
    If a string is returned, {{parameterName}} acts as placeholder for the engine to inject the actual configuration field.
    If a DOM node is returned, <span> elements will act as placeholders and must have the following structure:

      <span class="jsm_placeholder" data-name="parameterName"></span>
    
  • autorun: This method gets called whenever the composition is started.

  • onParameterChange: Gets called whenever the value of a configuration parameter changes. The parameter (jsm.ui.input.BaseInput) is passed to the function.

  • setConfiguration: Gets called when an existing configuration is loaded. It is past as parameter to the function (a parameter name -> value mapping). Can also return a new mapping. This allows to filter or change a components configuration.

  • getConfiguration: Gets called when the components configuration is stored (or otherwise read). Works in the same way as setConfiguration.

As said, in which form the implementation has to be provided depends on the component mapper. Here is an example implementation format we use together with the EMDL format.

⚠️ **GitHub.com Fallback** ⚠️