5. Plugin Handler

alvarofcgm edited this page Mar 27, 2016 · 17 revisions

Handles the lifecycle and communication of client side plugins. I recommend reading JavaScript Event-Driven plug-in framework

Demo

You can see a demo that illustrates this documentation here.

The code of the demo is here.

Plug-in Handler, init, stop and reset

Initialize

// Plug-in Handler can use cache for better performance,
// so it is recommended to init cache before initializing
// the Plug-in Handler. Do not initialize cache if you don't want
// to use it.
CH.init("session");

// Initialize Plug-in Handler.
PH.init("plugins/");

When initializing the Plug-in Handler we need to provide the URL where all the packaged plugins are stored. If you don't want to load packaged do not add any path. Multiple URLs can be provided, it is possible to load packaged plugins from multiple origins.

These three forms are correct:

PH.init();

PH.init("plugins/");

PH.init("plugins/","/public/plugins/");

What Plug-in Handler do is load a loader.json from each provided directory, this json tells the Plug-in Handler what plugins we want to load and how.

Example of loader.json file:

{
	"init-ph-after":{
		"indexandprint.add-index" : {}
	},
	"load-text-after":{
		"wordhighlight.word-highlight" : {"load":"eager"},
		"betterworddefinition.word-tooltip" : {},
		"worddefinition.word-tooltip" : {}
	}
}

This json contains this information:

Plug-in events: "init-ph-after", "load-text-after".

Plug-in ids: "indexandprint.add-index", "wordhighlight.word-highlight", "betterworddefinition.word-tooltip", "worddefinition.word-tooltip"

Plug-in loading instructions: {} , {"load":"eager"}

wordhighlight.word-highlight plug-in will be loaded on Plug-in Handler initialization, while the other plug-ins will be lazy loaded when their respective plug-in events will be fired.

Stop

Stops Plug-in Handler, removing all the plug-ins.

PH.stop();

Reset

Stops Plug-in Handler and initialize it again.

PH.reset();

Create a javascript packaged plug-in

Packaged plug-ins are plug-ins stored in the server and loaded by the Plug-in Handler. Their lifecycle during application runtime is automatically managed by the Plug-in Handler. The purpose of having packaged plug-ins is that they can be shared, imported, or deleted easily without the need of modifying the application code.

File structure

In the plug-ins directory we will need to create a directory for the package:

plugins
	|_ wordhighlight (plug-in package directory)
		|_ client.json
		|_ configuration.json
		|_ manifest.json
		|_ client (client side plug-ins' code directory)
			|_ word-highlight.js
		|_ resources (plug-in's resources directory)

client.json: It is the file that will tell on which events a plug-in has to be run, if a plug-in has to be lazy loaded or eager loaded (lazy loaded by default).

Example of client.json file:

{"load-text-after":{"wordhighlight.word-highlight":{"load":"eager"}}}

In this example we can see that word-highlight plug-in of the wordhighlight package will be loaded on Plug-in Handler initialization (eager load) and will be run when "load-text-after" plug-in event is fired.

NOTE that the loader.json file seen above is just a concatenation of the client.json files of the plug-ins we want to use.

configuration.json: Each plug-in has its own configuration of properties like colors, ids or classes of DOM elements etc.

Example of configuration.json file:

{"mainContainerId":{"value":"demo-container","editor":"text"}}

This configuration can be used by the plug-in on runtime. In this example we can see there is a property named "mainContainerId" with value equals to "demo-container", it is probably the id of the DOM element the plug-in is going to manipulate. "editor" is the field editor we are going to use to edit the property.

manifest.json: Plug-in description, update repository, requirements.

Example of manifest.json file:

{
	"name": "Word highlight",
	"version": "1.0.0",
	"default_locale": "en",
	"description": "Highlight the words of a text based on a value taped in an input text box by the user.",
	"icon": "",
	"author": "cookinapps.io",
	"homepage_url": "https://cookinapps.io/",
	"store_url": "https://cookinapps.io/store/plugins/wordhighlight",
	"repository": "https://cookinapps.io/repository/plugins/wordhighlight",
	"requires":[],
	"requires_other_plugin": [],
}

client directory is where the plug-in code is. Inside there are javascript files with the plug-ins' code.

Here is an example of a basic plug-in code, word-highlight.js file:

/**
 * @plugin
 *  wordhighlight.word-highlight : Highlight the words of a text based on a value taped in
 *				   an input text box by the user.
 */
PH.add(function(){

  ////
  // private members
  ////
  var articleContentContainer = null;

  var highlightWord = function(e){
    // gets value from input and finds words that match this value and higlights them.
    var me = this;
    var query = e.srcElement.value;
    articleContentContainer.innerHTML = articleContentContainer.innerHTML.replace(new RegExp("<mark>", "g"), "");
    articleContentContainer.innerHTML = articleContentContainer.innerHTML.replace(new RegExp("</mark>", "g"), "");
    if (query != ""){
      var pEls = articleContentContainer.getElementsByTagName("p");
      for (var i = 0; i < pEls.length; i++){
        var el = pEls[i];
        el.innerHTML = el.innerHTML.replace(new RegExp(U.escapeSpecialCharsRegExp(query), "g"), "<mark>" + query + "</mark>");
      }
    }
  };

  ////
  // public members
  ////
  return {
    "id" : "wordhighlight.word-highlight",
    "events" : ["load-text-after"],
    "run" : function(params,configuration,eventName,success,failure){
     if (configuration.hasOwnProperty("mainContainerId")){
      articleContentContainer = params.textContainer;

      // add a textbox to the DOM so user can enters the words to highlight.
      // check if the search input already exists.
      var searchEl = document.getElementById("article-content-search");
      if(searchEl==null){
        var searchEl = document.createElement("input");
        searchEl.setAttribute("type", "text");
        searchEl.setAttribute("id", "article-content-search");
        searchEl.setAttribute("placeholder", "Search here the word");
        var container = document.getElementById(configuration["mainContainerId"]["value"]);
        container.insertBefore(searchEl, params.textContainer);

        searchEl.onkeyup = function(e){
         highlightWord(e);
        }
      }
      highlightWord({"srcElement":searchEl});
     }
    }
  };
}());

The most important members are:

  • id: The id of the plug-in
  • events: The plug-in events when the plug-in is executed, if no events indicated, plug-in will be executed just after being loaded by the Plug-in Handler.
  • run function: It is the function that will be called when a plug-in is executed. Its parameters are: params (parameters passed when plug-in event is fired), configuration (plug-in configuration) and eventName (name of the plug-in event fired that has provoked the plug-in execution);

resources directory : where the plug-in resources are, they can be images, other JSONs, HTML files etc. The translation of message can be managed throw resource objects.

Click here to access examples of client side plug-ins.

Add a plug-in on the fly

We have seen how to create a packaged plug-in, but we can add a plug-in on the fly with PH.add(plugin) function. To follow the example, we will just put the code of "word-highlight.js" file seen above in our application code. The behavior will be exactly as any other plug-in.

Fire plug-in events

Firing a plug-in event will execute all plug-ins "listening" to this event.

To fire a plug-in event:

PH.fire("event-name");

With parameters and success and failure callback:

PH.fire("event-name",{"id":27}, function(data){
	console.log("data returned by the run function of the plug-in => ", data);
}, function(data){
	console.log("unsuccess plug-in run => ", data);
});

Plug-in communication

Messages can be sent to plug-ins and plug-ins can communicate between them using messages.

Send message to one plug-in:

PH.sendMessage({"message":"hello world"},this,["wordhighlight.word-highlight"],function(data){
	console.log("data recieved from wordhighlight.word-highlight => ", data);
});

Send message to all plug-ins:

PH.sendMessage({"message":"hello world"},this,null);

Recieve a message, use the onMessage method:

/**
 * @plugin
PH.add({
"id" : "wordhighlight.word-highlight",
"events" : ["load-text-after"],
"run" : function(params,configuration){
	// send message to other plug-ins
	PH.sendMessage({"message":"hello world"},this,null);
},
"onMessage" : function(message,_from,callback){
	// recieve message from other plug-ins or application.
	callback("thank you for sending message");
  }
});

Messages will be sent to the plug-ins using the destinations plug-ins Ids. A message can be sent to all plug-ins letting the toIds parameter null. Destination plug-ins use the onMessage listener function to retrieve the messages.

Plug-in variables and methods

Default

By default a plug-in has this members:

plugin._root : Path of the packaged plug-in. "./" by default.

plugin.remove("plugin.id") | plugin.remove(plugin) : Removes given plug-in or plug-in with given id.

plugin.removeEvents() : Remove the events the plug-in is listening to.

plugin.getResourcePath(plugin) : Get resource directory path of a plug-in

plugin.call("function-name",params) : Call a function of the plug-in with a given name. Useful when a plug-in executes on different events and we want to derive the execution to one or another function depending on the event name. It is also useful for deriving actions when recieving messages.

Custom

You can also decorate plug-in with your custom members, you can do it using PH.addDecorator(memberName,member), it is recommended to do it before initializing the Plug-inHandler.

PH.addDecorator("sayHello", function(){
 	console.log("Hello from an examaple decorator method from plug-in => ", this);
});

And remove it using PH.removeDecorator(memberName) method

PH.removeDecorator("sayHello");

Get added plug-in by id

PH.getPlug-inById("plugin.id") : Returns a loaded plug-in with the given id from the PH.plugins object. Returns null if no plug-in found with the given id.

Extending plug-ins

A plug-in can extend more than one plug-in, that means it will integrate the public members of the extended plug-in that it has not overridden (Be careful with private members).

Once a plug-in is extended he remains inactive, other plug-ins can continue to extend it but it will not be executed again.

Example of a plug-in that extends another:

// Extended plugin
PH.add(function(){
 	var x = null;
 	return {
 	 	"id" : "plugin.tobeextended",
 	 	"events" : ["event-name-1"],
 	 	"run" : function(params,configuration){
 	 	 	console.log("run of plugin 1");
 	 	},
 	 	"sumToX" : function(a){
 	 	 	x += a;
 	 	 	return x;
 	 	},
 	 	"sayHello" : function(){
 	 	 	console.log("Hello world!");
 	 	}
 	};
}());

// Extension
PH.add(function(){
 	var x = null;
 	return {
 	 	"id" : "plugin2.tobeextended2",
 	 	"extends" : "plugin.tobeextended"
 	 	"events" : ["event-name-2"],
 	 	"sumToX" : function(a,b){
 	 	 	x = x + a + b;
 	 	 	return x;
 	 	 }
 	};
}());

The second plug-in extends the first one, so the first one will never be executed again, the second will take the members that it does not override.

Once the extention is done, the second plug-in will look like this:

PH.add(function(){
 	var x = null;
 	return {
 	 	"id" : "plugin2.tobeextended2",
 	 	"extends" : "plugin.tobeextended"
 	 	"events" : ["event-name-2"],
 	 	"run" : function(params,configuration){
 	 	 	console.log("run of plugin 1");
 	 	},
 	 	"sumToX" : function(a,b){
 	 	 	x = x + a + b;
 	 	 	return x;
 	 	 },
 	 	"sayHello" : function(){
 	 	 	console.log("Hello world!");
 	 	}
 	};
}());
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.