Commit e4f04f7b authored by JC Brand's avatar JC Brand

Update plugin docs.

- Remove section on UMD
- Add section explaining how to override templates
- Add overrides example to the relevant section
- Mention composition to add methods to existing classes
parent aece9562
...@@ -10,30 +10,30 @@ Writing a plugin ...@@ -10,30 +10,30 @@ Writing a plugin
Introduction Introduction
------------ ------------
Converse.js exposes a plugin architecture through which developers can modify Converse exposes a plugin architecture through which developers can modify
and extend its functionality. and extend its functionality.
Using plugins is good engineering practice, and using them is the *only* recommended Using plugins is good engineering practice, and using them is the *only* recommended
way of changing converse.js or adding new features to it. way of changing Converse or adding new features to it.
In particular, plugins have the following advantages: In particular, plugins have the following advantages:
The main benefit of plugins is their *isolation of concerns* (and features). The main benefit of plugins is their *isolation of concerns* (and features).
From this benefit flows various 2nd degree advantages, such as the ability to From this benefit flows various 2nd degree advantages, such as the ability to
make smaller production builds (by excluding unused plugins) and an easier make smaller production builds (by excluding unused plugins) and an easier
upgrade path by avoiding touching converse.js's internals. upgrade path by avoiding touching Converse's internals.
Each plugin comes in its own file, and converse.js's plugin architecture, Each plugin comes in its own file, and Converse's plugin architecture,
called `pluggable.js <https://github.com/jcbrand/pluggable.js/>`_, provides you called `pluggable.js <https://github.com/jcbrand/pluggable.js/>`_, provides you
with the ability to "hook in" to the core code and other plugins. with the ability to "hook in" to the core code and other plugins.
Converse.js itself is composed out of plugins and uses pluggable.js. Take a look at the Converse itself is composed out of plugins and uses pluggable.js. Take a look at the
`src <https://github.com/jcbrand/converse.js/tree/master/src>`_ directory. All `src <https://github.com/jcbrand/converse.js/tree/master/src>`_ directory. All
the files that follow the pattern `converse-*.js` are plugins. the files that follow the pattern `converse-*.js` are plugins.
Plugins (by way of Pluggable.js) enable developers to extend and override existing objects, Plugins (by way of Pluggable.js) enable developers to extend and override existing objects,
functions and the `Backbone <http://backbonejs.org/>`_ models and views that make up functions and the `Backbone <http://backbonejs.org/>`_ models and views that make up
Converse.js. Converse.
Besides that, in plugins you can also write new Backbone (or other) models and views, Besides that, in plugins you can also write new Backbone (or other) models and views,
in order to add a new functionality. in order to add a new functionality.
...@@ -45,16 +45,16 @@ and to understand its inner workings, please refer to the `annotated source code ...@@ -45,16 +45,16 @@ and to understand its inner workings, please refer to the `annotated source code
.. note:: **Trying out a plugin in JSFiddle** .. note:: **Trying out a plugin in JSFiddle**
Because Converse.js consists only of JavaScript, HTML and CSS (with no backend Because Converse consists only of JavaScript, HTML and CSS (with no backend
code required like PHP, Python or Ruby) it runs fine in JSFiddle. code required like PHP, Python or Ruby) it runs fine in JSFiddle.
Here's a Fiddle with a Converse.js plugin that calls ``alert`` once it gets Here's a Fiddle with a Converse plugin that calls ``alert`` once it gets
initialized and also when a chat message gets rendered: https://jsfiddle.net/4drfaok0/15/ initialized and also when a chat message gets rendered: https://jsfiddle.net/4drfaok0/15/
.. note:: **Generating a plugin with Yeoman** .. note:: **Generating a plugin with Yeoman**
The rest of this document explains how to write a plugin for Converse.js and The rest of this document explains how to write a plugin for Converse and
ends with a documented example of a plugin. ends with a documented example of a plugin.
There is a `Yeoman <http://yeoman.io/>`_ code generator, called There is a `Yeoman <http://yeoman.io/>`_ code generator, called
...@@ -71,12 +71,12 @@ Registering a plugin ...@@ -71,12 +71,12 @@ Registering a plugin
Plugins need to be registered (and whitelisted) before they can be loaded and Plugins need to be registered (and whitelisted) before they can be loaded and
initialized. initialized.
You register a converse.js plugin by calling ``converse.plugins.add``. You register a Converse plugin by calling ``converse.plugins.add``.
The plugin itself is a JavaScript object which usually has at least an The plugin itself is a JavaScript object which usually has at least an
``initialize`` method, which gets called at the end of the ``initialize`` method, which gets called at the end of the
``converse.initialize`` method which is the top-level method that gets called ``converse.initialize`` method which is the top-level method that gets called
by the website to configure and initialize Converse.js itself. by the website to configure and initialize Converse itself.
Here's an example code snippet: Here's an example code snippet:
...@@ -101,7 +101,7 @@ Here's an example code snippet: ...@@ -101,7 +101,7 @@ Here's an example code snippet:
Whitelisting of plugins Whitelisting of plugins
----------------------- -----------------------
As of converse.js 3.0.0 and higher, plugins need to be whitelisted before they As of Converse 3.0.0 and higher, plugins need to be whitelisted before they
can be used. This is because plugins have access to a powerful API. For can be used. This is because plugins have access to a powerful API. For
example, they can read all messages and send messages on the user's behalf. example, they can read all messages and send messages on the user's behalf.
...@@ -112,7 +112,7 @@ To whitelist a plugin simply means to specify :ref:`whitelisted_plugins` when ...@@ -112,7 +112,7 @@ To whitelist a plugin simply means to specify :ref:`whitelisted_plugins` when
you call ``converse.initialize``. you call ``converse.initialize``.
If you're adding a "core" plugin, which means a plugin that will be If you're adding a "core" plugin, which means a plugin that will be
included in the default, open-source version of converse.js, then you'll included in the default, open-source version of Converse, then you'll
instead whitelist the plugin by adding its name to the `core_plugins` array in instead whitelist the plugin by adding its name to the `core_plugins` array in
`./src/headless/converse-core.js <https://github.com/jcbrand/converse.js/blob/master/src/headless/converse-core.js>`_. `./src/headless/converse-core.js <https://github.com/jcbrand/converse.js/blob/master/src/headless/converse-core.js>`_.
or the `WHITELISTED_PLUGINS` array in `./src/converse.js <https://github.com/jcbrand/converse.js/blob/master/src/converse.js>`_. or the `WHITELISTED_PLUGINS` array in `./src/converse.js <https://github.com/jcbrand/converse.js/blob/master/src/converse.js>`_.
...@@ -138,43 +138,6 @@ The inner ``_converse`` object is made private in order to safely hide and ...@@ -138,43 +138,6 @@ The inner ``_converse`` object is made private in order to safely hide and
encapsulate sensitive information and methods which should not be exposed encapsulate sensitive information and methods which should not be exposed
to any 3rd-party scripts that might be running in the same page. to any 3rd-party scripts that might be running in the same page.
Loading a plugin module
-----------------------
Converse.js uses the UMD (Universal Modules Definition) as its module syntax.
This makes modules loadable via `require.js`, `webpack` or other module
loaders, but also includable as old-school `<script>` tags in your HTML.
Here's an example of the plugin shown above wrapped inside a UMD module:
.. code-block:: javascript
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as a module called "myplugin"
define(["converse"], factory);
} else {
// Browser globals. If you're not using a module loader such as require.js,
// then this line below executes. Make sure that your plugin's <script> tag
// appears after the one from converse.js.
factory(converse);
}
}(this, function (converse) {
converse.plugins.add('myplugin', {
initialize: function () {
// This method gets called once converse.initialize has been called
// and the plugin itself has been loaded.
// Inside this method, you have access to the closured
// _converse object as an attribute on "this".
// E.g. this._converse
},
});
});
Accessing 3rd party libraries Accessing 3rd party libraries
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...@@ -182,20 +145,13 @@ Accessing 3rd party libraries ...@@ -182,20 +145,13 @@ Accessing 3rd party libraries
Immediately inside the module shown above you can access 3rd party libraries (such Immediately inside the module shown above you can access 3rd party libraries (such
dayjs and lodash) via the ``converse.env`` map. dayjs and lodash) via the ``converse.env`` map.
The code for it would look something like this: The code for it could look something like this:
.. code-block:: javascript .. code-block:: javascript
// Commonly used utilities and variables can be found under the "env" // Commonly used utilities and variables can be found under the "env"
// namespace of the "converse" global. // namespace of the "converse" global.
const Strophe = converse.env.Strophe, const { Backbone, Promise, Strophe, dayjs, sizzle, _, $build, $iq, $msg, $pres } = converse.env;
$iq = converse.env.$iq,
$msg = converse.env.$msg,
$pres = converse.env.$pres,
$build = converse.env.$build,
_ = converse.env._,
dayjs = converse.env.dayjs;
These dependencies are closured so that they don't pollute the global These dependencies are closured so that they don't pollute the global
namespace, that's why you need to access them in such a way inside the module. namespace, that's why you need to access them in such a way inside the module.
...@@ -203,22 +159,117 @@ namespace, that's why you need to access them in such a way inside the module. ...@@ -203,22 +159,117 @@ namespace, that's why you need to access them in such a way inside the module.
Overrides Overrides
--------- ---------
Plugins can override core code or code from other plugins. Refer to the full Plugins can override core code or code from other plugins. You can specify
example at the bottom for code details. overrides in the object passed to ``converse.plugins.add``.
In an override you can still call the overridden function, by calling
``this.__super__.methodName.apply(this, arguments);`` where ``methodName`` is
the name of the function or method you're overriding.
The following code snippet provides an example of two different overrides:
.. code-block:: javascript
overrides: {
/* The *_converse* object has a method "onConnected".
* You can override that method as follows:
*/
onConnected: function () {
// Overrides the onConnected method in Converse
// Top-level functions in "overrides" are bound to the
// inner "_converse" object.
const _converse = this;
Use the ``overrides`` functionality with caution. It basically resorts to // Your custom code can come here ...
// You can access the original function being overridden
// via the __super__ attribute.
// Make sure to pass on the arguments supplied to this
// function and also to apply the proper "this" object.
_converse.__super__.onConnected.apply(this, arguments);
// Your custom code can come here ...
},
/* On the XMPPStatus Backbone model is a method sendPresence.
* We can override is as follows:
*/
XMPPStatus: {
sendPresence: function (type, status_message, jid) {
// The "_converse" object is available via the __super__
// attribute.
const _converse = this.__super__._converse;
// Custom code can come here ...
// You can call the original overridden method, by
// accessing it via the __super__ attribute.
// When calling it, you need to apply the proper
// context as reference by the "this" variable.
this.__super__.sendPresence.apply(this, arguments);
}
}
}
Use the ``overrides`` feature with caution. It basically resorts to
monkey patching which pollutes the call stack and can make your code fragile monkey patching which pollutes the call stack and can make your code fragile
and prone to bugs when Converse.js gets updated. Too much use of ``overrides`` and prone to bugs when Converse gets updated. Too much use of ``overrides``
is therefore a "code smell" which should ideally be avoided. is therefore a "code smell" which should ideally be avoided.
A better approach is to listen to the events emitted by Converse.js, and to add A better approach is to listen to the events emitted by Converse, and to add
your code in event handlers. This is however not always possible, in which case your code in event handlers. This is however not always possible, in which case
the overrides are a powerful tool. the overrides are a powerful tool.
Also, while it's possible to add new methods to classes via the ``overrides``
feature, it's better and more explicit to use composition with
``Object.assign``.
For example:
.. code-block:: javascript
function doSomething () {
// Your code comes here
}
Object.assign(_converse.ChatBoxView.prototype, { doSomething });
Overriding a template
~~~~~~~~~~~~~~~~~~~~~
Converse uses various templates, loaded with lodash, to generate its HTML.
It's not possible to override a template with the plugin's ``overrides``
feature, instead you should configure a new path to your own template via your
module bundler.
For example, with Webpack (which Converse uses internall), you can specify an
``alias`` for the template you want to override. This alias then points to your
own custom template.
For example, in your webpack config file, you could add the following to the
``config`` object that gets exported:
.. code-block:: javascript
resolve: {
extensions: ['.js'],
modules: [
path.join(__dirname, 'node_modules'),
path.join(__dirname, 'node_modules/converse.js/src')
],
alias: {
'templates/profile_view.html$': path.resolve(__dirname, 'templates/profile_view.html')
}
}
.. _`dependencies`: .. _`dependencies`:
Plugin dependencies Plugin dependencies
~~~~~~~~~~~~~~~~~~~ -------------------
When using ``overrides``, the code that you want to override (which is either When using ``overrides``, the code that you want to override (which is either
in ``converse-core`` or in other plugins), needs to be parsed already by the in ``converse-core`` or in other plugins), needs to be parsed already by the
...@@ -246,10 +297,10 @@ In this case, you can't specify the plugin as a dependency in the ``define`` ...@@ -246,10 +297,10 @@ In this case, you can't specify the plugin as a dependency in the ``define``
statement at the top of the plugin, since it might not always be available, statement at the top of the plugin, since it might not always be available,
which would cause ``require.js`` to throw an error. which would cause ``require.js`` to throw an error.
Extending converse.js's configuration settings Extending Converse's configuration settings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ----------------------------------------------
Converse.js comes with various :ref:`configuration-settings` that can be used to Converse comes with various :ref:`configuration-settings` that can be used to
modify its functionality and behavior. modify its functionality and behavior.
All configuration settings have default values which can be overridden when All configuration settings have default values which can be overridden when
...@@ -260,9 +311,9 @@ these settings with the `_converse.api.settings.update` method (see ...@@ -260,9 +311,9 @@ these settings with the `_converse.api.settings.update` method (see
:ref:`settings-update`). :ref:`settings-update`).
Exposing promises Exposing promises
~~~~~~~~~~~~~~~~~ -----------------
Converse.js has a ``waitUntil`` API method (see :ref:`waituntil-grouping`) Converse has a ``waitUntil`` API method (see :ref:`waituntil-grouping`)
which allows you to wait for various promises to resolve before executing a which allows you to wait for various promises to resolve before executing a
piece of code. piece of code.
...@@ -276,7 +327,7 @@ only resolves the plugin but will also emit an event with the same name. ...@@ -276,7 +327,7 @@ only resolves the plugin but will also emit an event with the same name.
Dealing with asynchronicity Dealing with asynchronicity
--------------------------- ---------------------------
Due to the asynchronous nature of XMPP, many subroutines in Converse.js execute Due to the asynchronous nature of XMPP, many subroutines in Converse execute
at different times and not necessarily in the same order. at different times and not necessarily in the same order.
In many cases, when you want to execute a piece of code in a plugin, you first In many cases, when you want to execute a piece of code in a plugin, you first
...@@ -366,13 +417,7 @@ generated by `generator-conversejs <https://github.com/jcbrand/generator-convers ...@@ -366,13 +417,7 @@ generated by `generator-conversejs <https://github.com/jcbrand/generator-convers
// Commonly used utilities and variables can be found under the "env" // Commonly used utilities and variables can be found under the "env"
// namespace of the "converse" global. // namespace of the "converse" global.
const Strophe = converse.env.Strophe, const { Backbone, Promise, Strophe, dayjs, sizzle, _, $build, $iq, $msg, $pres } = converse.env;
$iq = converse.env.$iq,
$msg = converse.env.$msg,
$pres = converse.env.$pres,
$build = converse.env.$build,
_ = converse.env._,
dayjs = converse.env.dayjs;
// The following line registers your plugin. // The following line registers your plugin.
converse.plugins.add("myplugin", { converse.plugins.add("myplugin", {
...@@ -390,13 +435,13 @@ generated by `generator-conversejs <https://github.com/jcbrand/generator-convers ...@@ -390,13 +435,13 @@ generated by `generator-conversejs <https://github.com/jcbrand/generator-convers
* If the setting "strict_plugin_dependencies" is set to true, * If the setting "strict_plugin_dependencies" is set to true,
* an error will be raised if the plugin is not found. * an error will be raised if the plugin is not found.
*/ */
'dependencies': [], dependencies: [],
/* Converse.js's plugin mechanism will call the initialize /* Converse's plugin mechanism will call the initialize
* method on any plugin (if it exists) as soon as the plugin has * method on any plugin (if it exists) as soon as the plugin has
* been loaded. * been loaded.
*/ */
'initialize': function () { initialize: function () {
/* Inside this method, you have access to the private /* Inside this method, you have access to the private
* `_converse` object. * `_converse` object.
*/ */
...@@ -453,15 +498,15 @@ generated by `generator-conversejs <https://github.com/jcbrand/generator-convers ...@@ -453,15 +498,15 @@ generated by `generator-conversejs <https://github.com/jcbrand/generator-convers
}, },
/* If you want to override some function or a Backbone model or /* If you want to override some function or a Backbone model or
* view defined elsewhere in converse.js, then you do that under * view defined elsewhere in Converse, then you do that under
* the "overrides" namespace. * the "overrides" namespace.
*/ */
'overrides': { overrides: {
/* For example, the private *_converse* object has a /* For example, the private *_converse* object has a
* method "onConnected". You can override that method as follows: * method "onConnected". You can override that method as follows:
*/ */
'onConnected': function () { onConnected: function () {
// Overrides the onConnected method in converse.js // Overrides the onConnected method in Converse
// Top-level functions in "overrides" are bound to the // Top-level functions in "overrides" are bound to the
// inner "_converse" object. // inner "_converse" object.
...@@ -478,11 +523,11 @@ generated by `generator-conversejs <https://github.com/jcbrand/generator-convers ...@@ -478,11 +523,11 @@ generated by `generator-conversejs <https://github.com/jcbrand/generator-convers
// Your custom code can come here ... // Your custom code can come here ...
}, },
/* Override converse.js's XMPPStatus Backbone model so that we can override the /* Override Converse's XMPPStatus Backbone model so that we can override the
* function that sends out the presence stanza. * function that sends out the presence stanza.
*/ */
'XMPPStatus': { XMPPStatus: {
'sendPresence': function (type, status_message, jid) { sendPresence: function (type, status_message, jid) {
// The "_converse" object is available via the __super__ // The "_converse" object is available via the __super__
// attribute. // attribute.
const _converse = this.__super__._converse; const _converse = this.__super__._converse;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment