Writing a plugin II

From MegaZine3
Jump to navigation Jump to search
See also: Writing a plugin

This page explains an additional use of plugins. It is recommended to read Writing a plugin prior to reading this article, as instructions here may build on information explained in that tutorial.

In this tutorial you will learn how to write a plugin which registers itself as an attribute handler, i.e. which processes certain attributes of elements, and how to add GUI components directly to the stage. The plugin created by following these steps will display a small window with some information text when a user clicks on an element. The text will be changeable via an attribute of the element.

Additionally, a small button will be added to the overall plugin to demonstrate how to add common GUI elements which are always available (such as the NavigationBar).

Another concept explained in this tutorial is the use of the setElementProperty and getElementProperty functions available through the AbstractPlugin class.

Registering as an attribute handler

Create a blank plugin by copying the dummy plugin and renaming it. In the register() function we will let the plugin register itself as an attribute handler for elements. In general, it is possible to register a plugin as an event handler for the following objects:

The fine difference between ElementProxy and Element is that the attribute handling for an ElementProxy will only happen once, when the page containing the element the proxy represents is added to the book. The Element attribute handler on the other hand will be called whenever the element finishes loading, which can be multiple times during the lifetime of a book, if the containing page is unloaded, then loaded again, due to the user's navigation through book (i.e. if the page containing the element should be unloaded because it leaves the area of pages to be kept in memory, and at a later point needs to be loaded again).

To register the plugin as an attribute handler, use the registerForAttribute function. It takes three parameters:

  • type:Class, the type of element for which to register. The value given must be one of IChapter, IPageSide, IElementProxy or IElement, i.e. the interfaces of the objects for which to register.
  • attribute:String, the name of the attribute for which to register as a handler.
  • priority:uint, with default value 10, a priority for when to call the handler. Handlers with a lower priority value get called first, i.e. a handler registered with priority 5 gets called before a handler registered with priority 10. The order of handlers with the same priority may vary.

Because we want to register for an element attribute, and we wish to act upon a click on the element, the call necessary in the register() function looks like this:

registerForAttribute(IElement, "infotext");

The attribute we will be using is named "infotext". So to have a test element in our book, add the following to any page in a book (include the Overlays plugin to see it, you can still click on it if you don't):

<area width="100" height="100" overlay="color(0.5,1,0xff0000)" infotext="This is some text we will display in the box opened when this element is clicked!"/>

ASUL definitions

See also: ASUL Document and Editing the GUI.

In the constructor of the plugin, change the super call so that ASUL definitions for the plugin will be loaded:

super("example2", "1.0.0", [], true);

Then create the example2.asul file. In it we will define how our window and button will look. In general, define all GUI components you will use inside this file.

<?xml version="1.0" encoding="utf-8"?>
<asul>
    <style>
    <![CDATA[
    button.close box { background: image(gui/example2/btn_ok.png); }
    button.example box { background: image(gui/example2/btn_example.png); }
    ]]>
    </style>
    
    <!-- Display window centered, button centered at bottom of window -->
    <window id="infobox" style="container" width="300" height="200" anchors="(pw-w)/2,(ph-h)/2">
        <text name="infotext" anchors="10,10,pw-10,ph-25" font="Verdana" color="0xffffff"/>
        <button name="$btn_close$" title="Close" style="common close" anchors="(pw-w)/2,ph-h">
            <box name="$up$"/>
            <box name="$over$" style="over"/>
            <box name="$down$" style="down"/>
        </button>
    </window>

    <!-- Example button, positioned to the top right of the book. -->
    <button id="btn_example" title="Example button" style="common example" anchors="pw/2+pagew-w,(ph-pageh)/2-h">
        <box name="$up$"/>
        <box name="$over$" style="over"/>
        <box name="$down$" style="down"/>
    </button>
</asul>

The important part are the ids and (non-ASUL) names. In this case:

  • id: infobox, the window we will display when an element that has the infotext attribute set is clicked.
  • id: btn_example, the example button permanently displayed.
  • name: infotext, the name of the textfield used to display the content of the infotext attribute of an element.

Creating the GUI components

GUI creation should take place in the initialize() function of the plugin, so we will create the window and button here, and initially hide the window. The window will be stored in an instance variable of the plugin to make it visible it in the attribute handling function, as will a reference to the textfield, to change its value.

private var window:DisplayObject;
private var textfield:IText;

// ...

override protected function initialize():void {
    window = createAsulObject("infobox");
    if (window && window is IAsulObject) {
        window.visible = false;
        megazine.pluginLayer.addChild(window);
        textfield = IAsulObject(window).deepGetChildByName("infotext") as IText;
    }

    var btn:DisplayObject = createAsulObject("btn_example");
    if (btn) {
        megazine.pluginLayer.addChild(btn);
        // do something with the button, e.g. add event listener
        //btn.addEventListener(MouseEvent.CLICK, handleButtonClick);
        // and do something in the handleButtonClick function.
    }
}

Implementing the attribute handler

For all of the attribute handling purposes, convenience functions which can be overridden are available in the AbstractPlugin class. They follow the naming pattern "handleXXXAttribute", where "XXX" is the type of element for which to handle the attribute. In our case it's "handleElementAttribute". This function is called whenever an element finishes loading and it's attributes should be processed by plugins.

Parameters of the handleElementAttribute function are

  • element:IElement, the element which finished loading.
  • attribute:String, the name of the attribute to be handled.
  • value:*, the value/content of the attribute.

Although this will only be called if the attribute is actually set (exists in the XML definition), we will still want to check if it has actually any value at all (is not an empty string). We will add a click listener to the element's interactiveObject which will update the text and show the window.

To store the text for each element for access in the event handler we will use the setElementProperty function. This allows assigning values to any object at all. The mapping uses a Dictionary with weak keys, making it safe for use in regards of memory leakage.

override protected function handleElementAttribute(element:IElement,
        attribute:String, value:*):void
{
    // Note: in this case the switch is completely superfluous, of course,
    // as this plugin only registers for one attribute. But it's always better
    // to doublecheck, and makes the code easier extensible.
    switch (attribute) {
        case "infotext":
            // Input validation.
            var infotext:String = Validation.validateString(value, "");
            if (infotext) {
                // Got some text.
                // Store the text by setting it as a property of the interactive object.
                setElementProperty(element.interactiveObject, "infotext", infotext);
                // Register click handler.
                element.interactiveObject.addEventListener(MouseEvent.CLICK, handleElementClicked);
            }
            break;
    }
}

private function handleElementClicked(e:MouseEvent):void {
    // Get the interactive object that was clicked.
    var iao:DisplayObjectContainer = DisplayObjectContainer(e.target);
    // Get the info text and set it.
    textfield.text = String(getElementProperty(iao, "infotext"));
    // Show window.
    window.visible = true;
}

Final code

package de.mightypirates.megazine.plugins.example2 {
import de.mightypirates.asul.interfaces.IAsulObject;
import de.mightypirates.asul.interfaces.IText;
import de.mightypirates.megazine.interfaces.IElement;
import de.mightypirates.megazine.interfaces.IMegaZine;
import de.mightypirates.megazine.interfaces.IPluginManager;
import de.mightypirates.megazine.plugins.AbstractPlugin;
import de.mightypirates.utils.Validation;
import de.mightypirates.utils.interfaces.ISettings;

import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.events.MouseEvent;

public class Example2 extends AbstractPlugin {

    private var window:DisplayObject;
    private var textfield:IText;

    public function Example2(data:PluginData = null) {
        super(data || new PluginData("example2", "1.0.0", [], true));
    }

    override protected function register():void {
        registerForAttribute(IElement, "infotext");
    }

    override protected function initialize():void {
        window = createAsulObject("infobox");
        if (window && window is IAsulObject) {
            window.visible = false;
            megazine.pluginLayer.addChild(window);
            textfield = IAsulObject(window).deepGetChildByName("infotext") as IText;
        }
        
        var btn:DisplayObject = createAsulObject("btn_example");
        if (btn) {
            megazine.pluginLayer.addChild(btn);
            // do something with the button, e.g. add event listener
            //btn.addEventListener(MouseEvent.CLICK, handleButtonClick);
            // and do something in the handleButtonClick function.
        }
    }
    
    override protected function handleElementAttribute(element:IElement,
            attribute:String, value:*):void
    {
        // Note: in this case the switch is completely superfluous, of course,
        // as this plugin only registers for one attribute. But it's always better
        // to doublecheck, and makes the code easier extensible.
        switch (attribute) {
            case "infotext":
                // Input validation.
                var infotext:String = Validation.validateString(value, "");
                if (infotext) {
                    // Got some text.
                    // Store the text by setting it as a property of the interactive object.
                    setElementProperty(element.interactiveObject, "infotext", infotext);
                    // Register click handler.
                    element.interactiveObject.addEventListener(MouseEvent.CLICK, handleElementClicked);
                }
                break;
        }
    }
    
    private function handleElementClicked(e:MouseEvent):void {
        // Get the interactive object that was clicked.
        var iao:DisplayObjectContainer = DisplayObjectContainer(e.target);
        // Get the info text and set it.
        textfield.text = String(getElementProperty(iao, "infotext"));
        // Show window.
        window.visible = true;
    }
}
}
MegaZine3 Plugin-related articles
Plugins Anchors · Background Sounds · Batchpages · Bookmarks · Console · ElementIDs · Gallery · Google Analytics · Help · JavaScript · Keyboard Navigation · Links · NavigationBar · Options · Overlays · Password · PDFLinks · Print · PrintPDF · SWFAddress· Search · Sidebar · Slideshow · Titles
Plugin Articles Writing a plugin · Writing a plugin II · Plugin development

{{#if: | |}}