Writing a plugin


 * See also: Writing a plugin II

This tutorial will give a short overview for writing a new plugin from scratch. The goal of this plugin will be to add a new button to the existing navigation bar and add custom functionality to it. This functionality will implement jumping to a certain anchor in the book. The name of the anchor will be changeable via a setting the plugin defines.

There are multiple ways of achieving custom GUI elements. The two preferred methods are explained here.

= Prerequisites =

To compile a plugin you will need the sources from the Subversion repository (pick the latest tag). Download them to a working directory. If using windows, give TortoiseSVN a try, it nicely integrates in the shell.

This way you'll also get the "dummy plugin" class, mentioned in the next paragraph, which is located under.

= Step 1: Basic skeleton =

First off, create a directory for your plugin, and copy the dummy plugin class into it. Rename it accordingly and adjust the name in the constructor, too. You will see two other functions already defined.

register(...)
This function can be used to register things the plugin needs. This includes registering the plugin as attribute handler for book chapters, pages and elements, as well as registering new settings that should be parsed from the attributes set in the tag.

initialize(...)
This function can be used for further initialization of the plugin, and will be called after the book has finished initializing, i.e. all basic definitions from the book's definition XML have been parsed, pages have been created and content loading has been triggered. Create GUI components here.

Plugin order
In either case, the register and the initialize function, the code will be executed after that of plugins your plugin depends on. For example, if you plugin depends on the NavigationBar plugin (as this example will), you can safely assume the NavigationBar plugin has completed it's initialization step when the initialize function in your plugin is called, meaning the GUI of the NavigationBar has been created and can be accessed.

Code
The code so far looks like this (I've stripped all comments away to keep it shorter). I will name this plugin "Example".

= Step 2: Adding your GUI definitions =

See also: ASUL Document and Editing the GUI.

Possibility 1, extra ASUL definition file
This is route one. Here the GUI definitions will be added to an ASUL file for the new plugin. To let the engine know we want some ASUL definitions, we need to call the super constructor accordingly. Change the  call to the following:

Notice the empty array? That's where we can define dependencies. Now, because we want to add a button to the navigation bar, we want to be sure it's loaded before we get going, so we add a dependency. This is achieved by creating a new instance of the  class and put it into the array passed on:

The second parameter,, is optional and defaults to false. What it does is define whether the dependency is a strong or a weak one. Weak dependencies are only used to enforce a proper loading order. Strong dependencies mean that the plugin cannot work until the plugin it depends on is available. Because we definitely need the navigation bar, we have a strong dependency.

Now that the engine knows we want ASUL definitions, we'll have to prepare a file for it to load. This file must be named like the plugin, with the extension "asul", and must be located in the GUI folder (see ). So, create a new text file and save it. In this case it'll be called, because the plugin's name is. The basic ASUL file should look like this.

What we want is a button we can create and then add to the navigation bar. To do that, we'll create a Button definition, and because we want it to look nice, we define different behaviour for its three states using a  block. The style definitions look and behave a lot like CSS, but are only a very rudimentary implementation. E.g. paths / chains of element types will always mean direct children, no gaps will be "bridged". What does that mean? Well, say you have a box inside a button inside a box. The path  will be applied to the inner box, the path   won't. Another difference to CSS is, that what is defined in styles are actually attributes with their values. E.g. you can have a style which defines a Text element's default display value.

If this concept is new to you, I'd strongly recommend you to learn a bit about CSS, as this will definitely help you understand how ASUL styles work, and there are enough tutorials on CSS out there.

Button definition
Now, the button only needs to be a basic skeleton, and it will look like this:

A lot of attributes there all of a sudden. Let me explain:
 * : this is necessary to later reference the definition from within our plugin.
 * and the box styles (wait, box? Yes, those Box elements is what we will use to define the different states of the button.): this is what we'll use in the style definitions to declare a specific look for those elements.
 * : the name must be exactly that, as you can see in the documentation of the button element. This allows the button to use them for its three states.

Style defintiion
So, now we have a basic definition. To define the styles, we'll add a  block the the ASUL file:

Inside it, add the following definitions:

The four blocks do the following, top to bottom:
 * define the actual size of the button.
 * define the size of the boxes inside the button (the states) and the default image to use (the up state graphic).
 * define the image to use when the mouse hovers the button (over state).
 * define the image to use when the button is pressed (down state).

To keep the look of the GUI homogenous, it is recommended to use the  style for buttons, as defined in the   file. I will not use it hear to make it easier to understand what's going on. Just bear in mind that normally it is recommended to use something like this:  instead, and leave the size definitions to the   style.

Note: style definitions from the  file are also valid for every plugin's ASUL file, but may be overridden in the plugin's ASUL file's style block.

Small technical note: normally you'd define the size via  and , but because the navigation bar uses a layouting element we'll have to explicitly set the maximum and minimum sizes so the button's size won't be messed up.

See the paths in the definitions? The engine now expects to find three images in the  directory ('gui' is the default value of book@guipath, adjust this if you use another one). The images are:
 * btn_example_up.png, an image used for the up state of the button.
 * btn_example_over.png, an image used for the over state of the button.
 * btn_example_down.png, an image used for the down state of the button.

Final defintion file
The final file looks like this:

Possibility 2, add it to the NavigationBar plugin's ASUL definition file
An alternative is to add your GUI definition to the one of the navigation bar itself. This obvioulsy leads to a less "portable" implementation (necessary to change existing files instead of just adding new ones), but allows more freedom regarding the actual position of the button.

First off, as with the first possibility, define the dependency on the NavigationBar plugin in the constructor, but without the need for own ASUL definitions:

Now to add an element to the NavigationBar plugin's definitions, open the  file in the   folder.

Look for a spot where you want your button to appear, and insert the following:

In the style block of the file, insert the following:

If you want, change the path to the image. This image is the graphic used for the button, over and down states use the same image, but behave as defined in the  style in the.

= Step 2: Accessing the GUI definition and adding interaction =

Possibility 1, continued
All of the following goes into the  function. First, we'll have to create a new instance of the button we defined in the ASUL file. To do this, use the  function:

Note that  is the id of the element we wish to create.

Now, to add it to the navigation bar, we use the function given in it's interface.

Possibility 2, continued
As in the first route, the following code goes into the  function. To get access to the button we just search for it by name, starting at the very top of the plugin layer (which is used to display all plugin GUI).

Common code
Now, all that's left is add a function that acts as the event handler. As used in the registration process of the event listeners, we'll name it :

= Code thus far =

So, at this point we have the following code in our plugin (possibility 2 in comments):

= Using settings =

So far so good. But assuming you want to make whatever happens inside the handler a bit more dynamic? Say you want it to jump to a certain anchor and you don't want to hardcode the anchor name. That's where settings, and the  function come into play.

Note: if you do not use the register function at all, and it just calls super.register you can safely remove it. Note that you only need to call super.register if your plugin extends another plugin.

So, let's say we want to jump to an anchor, we register a setting name "exampleanchor" in the register function:


 * The first parameter is the name of setting, which is the name of the attribute of the book which will be parsed to get the actual value.
 * The second parameter is the default value, in this case it'll be "first", meaning it'll jump to the first page of the book (reserved anchor).

Validation
This paragraph is a short excursion into the realm of input validation.

For some basic types, namely String, Boolean and Number, the type will be recognized by the Settings.register function, and you won't have to worry about validation and so on. For int and uint values, there's a shortcut by passing the type as the third parameter (e.g. ).

If you want more influence over the values you get, you can pass validation functions as a third parameter. There are some validation functions defined in the  class in the   package, so you'll get the idea: a validation function takes at least two arguments, the first one is the value to parse, the second one a default value to use if the input value is invalid. There's also a utility function in the  class which allows binding of minimum and maximum values of validation functions. For example, if you only wish to allow integer values between 10 and 20, you could define the setting this way:

Using the setting in our event handler
Now that we have the setting registered, we can rewrite our event handler to jump to the specified anchor. To do that, we'll first need to import the  interface from the Anchors plugin. We should also define a dependency, because if the anchors plugin isn't loaded our plugin won't be able to do anything. So, change the call to the super constructor:

For route two remove the  defining ASUL definition use.

In our event handler we can now do this:

= Final code =

So, with everything implemented, the final code looks like this. Again, possibility two in comments.

= Compiling =

For help on compiling your plugin, please read the Compiling page, then copy one of the plugin targets in the  file and adjust it for your plugin.