The Plugin base class is the primary base class that all plugins are based on. Any other base class is eventually an instance of Plugin. This base class provides an event system, load and unload flows and keeps state such as events, ui elements and css for clean up when a plugin is unloaded. In addition it provides an API to fetch and store private data.

Approach

Javascript gives a lot of freedom to system builders in terms of what object creation and inheritance looks like. As a result there are many different ways this is done in different frameworks. Cloud9 settled on a way that values some aspects of Javascript more than others. For our purpose, we deem prototypical inheritance as unnecessarily complex and we value closures as a great way to simplify code - specifically removing the need for the this keyword which can be very confusing when writing asynchronous code.

As a consequence we created an API that allows you to decorate a plugin, rather than inherit from it. In addition all functions are local to the main() function and deal with variables in that scope as well.

define(function(require, exports, module) {
    main.consumes = ["dependency"];
    main.provides = ["myplugin"];
    return main;

    function main(options, imports, register) {
        var dependency = imports.dependency;
        
        var plugin = new Plugin("Your Name", main.consumes);

        function doSomething(){}
    
        plugin.freezePublicAPI({
            doSomething : doSomething
        });
        
        register("", {
            "myplugin": plugin
        });
    }
});

A constructor for a new object that is based on Plugin() is in fact a simple factory that returns a new plugin object, offering the exact same simple way of writing code for your plugin as the singleton plugin above.

function Foo(){        
    var plugin = new Plugin("Your Name", main.consumes);
    var emit = plugin.getEmitter();

    plugin.freezePublicAPI({});
    plugin.load(null, "foo");
    
    return plugin;
}

var bar = new Foo();

For more information on creating base classes check out this guide.

Immutable API

It is common practice in javascript to extend objects with new properties and methods at will. This often is a great way to add state to an object or quickly hack around something that was not originally designed in an API. The down side of this practice is that code can quickly rot and that there is potential for conflicts when multiple plugins from different vendors would do something like that. This is the main reason that Cloud9 plugins have an immutable API - which means that outside plugins are not able to add, remove or overwrite properties on a plugin.

There is a security aspect here as well: if a third party plugin could overwrite something like http.request it would have access to any private data that another plugin sends. This is prevented by the immutable APIs of Cloud9 plugins, which is achieved by the freezePublicAPI() method. This function freezes your plugin object.

Because all properties on the API are immutable, you'll work with property getters and setters to set any dynamic properties and keep the state in a local variable. Also make sure to list the events that your plugin emits for documentation purpose and for future use.

var propertyName;

function doSomething(){}

/**
 * Describe your plugin here
 */
plugin.freezePublicAPI({
    /**
     * @property type description
     */
    type: "type",
    
    /**
     * @property propertyName description
     */
    get propertyName(){ return propertyName; },
    set propertyName(value){ propertyName = value; },
    
    _events: [
        /**
         * @event eventName description
         */
        "eventName"
    ],
    
    /**
     * Description
     * @param {String} name description
     */
    doSomething: doSomething
});

The Cloud9 documentation parser - which is forthcoming - will generate documentation for your plugin and make that available to whoever wants to build a plugin that consumes the APIs that you provide.

Event System

Events are an integral part of Cloud9's plugin system. Events provide hooks that other plugins can use to add or alter functionality.

Emitting events

Emit plugins using the emit() function. You get a reference to this function by calling getEmitter() on the plugin.

var plugin = new Plugin("Your Name", main.consumes);
var emit = plugin.getEmitter();

❗️

Only emit events from within your plugin

Events must only be emitted by your plugin itself (or any of it's super classes). This helps keep code flows clean and prevents hacky solutions that will eventually cause spaghetti code and code paths with unexpected consequences.

The getEmitter() method is available until you call freezePublicAPI(), after which that function will no longer be provided. This secures your plugin from external plugins that might otherwise try to fire an event on your plugin.

The emit() function takes two arguments; the event name and an optional event object.

emit("beforeExample", { data: [1, 2, 3] });

Make an event sticky by calling emit.sticky().

emit.sticky("initialized", { worker: worker });

For more information on emitting events see this guide.

Listening to events

Similar to Node.js use the on() and off() methods to set and remove events.

plugin.on("beforeExample", function(e){
    console.log("Event fired", e.data);
}, otherPlugin);

The third argument is a reference to the plugin that is setting the event listener. When the otherPlugin is unloaded this event handler is automatically cleared.

plugin.off("beforeExample", someFunction);

For more in-depth information on events see this guide.

Load, Unload and CleanUp

The load() method on Plugin will register the plugin and give it a name. Every plugin must have a name and the name is immutable once it's set. Fetch the name of a plugin using the name property. The load() method will fire the load event which you can use to execute code that initializes your plugin.

plugin.on("load", function(){
    // i.e. add a menu item
});

The unload() method will unregister the plugin and clean up any resources that were used by your plugin. Note that a plugin won't unload if other plugins depend on it and will return false in that case.

plugin.unload();

Clean up

Throughout the Cloud9 APIs you'll find functions that take a plugin reference as their last argument. Some examples include adding a command, create a menu, adding an event listener, setting css and inserting html. By referencing your plugin as the plugin responsible for those resources, they will be cleaned up when your plugin unloads.

You can also add some clean up code to a plugin by calling the addOther() method.

var resource = createSomething();
plugin.addOther(function(){
    resource.destroy();
});