Annotation of kupuMPIWG/doc/EXTENDING.txt, revision 1.1.1.1

1.1       dwinter     1: ==============
                      2: Extending Kupu
                      3: ==============
                      4: 
                      5: XXX this document needs a major overhaul
                      6: XXX we should cover the the typical process of implementing a new feature
                      7: XXX writing a new implementation of features -> INTEGRATING.txt
                      8: 
                      9: Abstract
                     10: --------
                     11: 
                     12: This document describes the typical process of extending Kupu with a
                     13: new feature, such as a toolbox with the underlying functionality.
                     14: Both, the ECMAScript API and the templating system are explained.
                     15: 
                     16: 1. Kupu Tools
                     17: -------------
                     18: 
                     19: Apart from the context menu, all UI functionality in Kupu is added to
                     20: the core using a simple plugin mechanism. This can also be used to
                     21: write your own extensions, either by copying existing tools and
                     22: modifying them or by subclassing KupuTool (and sometimes KupuToolBox)
                     23: and writing your own functionality.
                     24: 
                     25: 2. Tool interface
                     26: -----------------
                     27: 
                     28: The plugins have a simple interface to adhere to:
                     29: 
                     30:     interface KupuTool:
                     31: 
                     32:     attribute toolboxes = {};
                     33:       - id -> toolbox mapping
                     34: 
                     35:     method initialize(editor):
                     36:       - store reference to the editor, register event handlers, etc.
                     37: 
                     38:     method registerToolBox(id, toolbox):
                     39:       - register a UI element (ToolBox, more about those later)
                     40: 
                     41:     method updateState(selNode, event):
                     42:       - update the state of self and any toolboxes
                     43: 
                     44:     method createContextMenuElements(selNode, event) (optional):
                     45:       - return an array of ContextMenuElements
                     46: 
                     47: 3. Writing a simple tool
                     48: ------------------------
                     49: 
                     50: As an example we'll write our first simple tool, which displays the
                     51: path to the current node in the tree in the status bar. This is an
                     52: actual tool in the current Kupu code and you can see the result in the
                     53: status bar of any default Kupu installation. First we'll write a
                     54: class, and add a method called 'updateState' that will change the
                     55: status bar::
                     56: 
                     57:     // Example tool class
                     58:     function ExampleTool() {
                     59:         this.updateState = function(selNode, event) {
                     60:         };
                     61:     };
                     62: 
                     63: The updateState method is a method each tool can implement (actually
                     64: the code assumes it's available on each tool, but since the superclass
                     65: defines it as well it's not strictly necessary to add it to your
                     66: class) and that will be called when the user clicks inside the iframe,
                     67: presses enter or hits a cursor key (so basically, when the cursor can
                     68: end up in a different element).
                     69: 
                     70: As you can see we use in-line methods in Kupu, this is a choice of
                     71: style rather than for a functional reason, and if you wish you can
                     72: choose to use another coding style. Using
                     73: '<class>.prototype.<method> = function() {...};' will do equally well.
                     74: 
                     75: Now we'll add some functionality to our updateState method. The
                     76: updateState method's first argument will always be a reference to the
                     77: current selected element (or the element currently containing the
                     78: selection). To get a path to that element from the root, we can write
                     79: something like::
                     80:     
                     81:     // Example tool class
                     82:     function ExampleTool() {
                     83:         /* shows the path to the current element in the status bar */
                     84:         this.updateState = function(selNode, event) {
                     85:             /* calculate and display the path */
                     86:             var path = '';
                     87:             var currnode = selNode;
                     88:             while (currnode.nodeName != '#document') {
                     89:                 path = '/' + currnode.nodeName.toLowerCase() + path;
                     90:                 currnode = currnode.parentNode;
                     91:             };
                     92:             
                     93:             window.status = path;
                     94:         };
                     95:     };
                     96: 
                     97: The second argument is the event, if any, which started the
                     98: updateState chain (this doesn't have to be available, since
                     99: updateState can also be called by tools in certain situations).
                    100: 
                    101: Now that we have the updateState method complete, we have the
                    102: functionality we wanted available. To make sure the tool provides all
                    103: functionality required by the system, the tool should subclass
                    104: KupuTool. This is not strictly required, but is a nice way to provide
                    105: all the methods the editor requires to be available.
                    106: 
                    107:     // subclass KupuTool
                    108:     ExampleTool.prototype = new KupuTool;
                    109: 
                    110: To hook the tool up with the editor, it needs to be registered. For
                    111: this purpose the registerTool method is available on the KupuEditor
                    112: object, which should be called with an id and a tool as arguments
                    113: (note that the KupuEditor is called 'kupu' here, like in the default
                    114: initKupu function, where registration of our tool will most likely be
                    115: done)::
                    116: 
                    117:     var exampletool = new ExampleTool();
                    118:     kupu.registerTool('exampletool', exampletool);
                    119: 
                    120: That's it, we just wrote a replacement for (or actually a copy of) the
                    121: ShowPathTool, the most basic Kupu tool.
                    122: 
                    123: 4. A more complex example
                    124: -------------------------
                    125: 
                    126: Now we'll take a look at a more complex example: the ImageTool. This
                    127: will provide a way for users to add images to the iframe. First thing
                    128: we do is write a class like we did for the example tool::
                    129: 
                    130:     function ImageTool() {
                    131:         this.initialize = function(editor) {
                    132:             this.editor = editor;
                    133:             this.editor.logMessage('Image tool initialized');
                    134:         };
                    135:     };
                    136: 
                    137: As you can see we override the superclass' initialize method, we don't
                    138: strictly have to here, but it's a custom to send a message to the log
                    139: window telling the user the tool has initialized, and it shows us how
                    140: a reference to the editor object is stored on the tool and used to
                    141: call logMessage on (which in turn calls the logger registered in
                    142: initKupu).
                    143: 
                    144: Now let's add some functionality to the tool::
                    145: 
                    146:     function ImageTool() {
                    147: 
                    148:         // this will be required for later use, when the toolbox is added
                    149:         this.toolboxes = {};
                    150:         
                    151:         this.initialize  = function(editor) {
                    152:             this.editor = editor;
                    153:             this.editor.logMessage('Image tool initialized');
                    154:         };
                    155: 
                    156:         this.createImage = function(url) {
                    157:             /* insert an image */
                    158:             this.editor.execCommand('InsertImage', url);
                    159:             this.editor.logMessage('Image inserted');
                    160:         };
                    161:     };
                    162: 
                    163: This method calls 'execCommand' on the editor object, which in turn
                    164: calls execCommand on the iframe (actually there's another step, but
                    165: that's not important now). execCommand is a built-in method provided
                    166: by browsers, that provides a limited amount of commands to WYSIWYG
                    167: editor applications. The InsertImage command will, as you may have
                    168: guessed, insert an image at the cursor location.
                    169: 
                    170: All that is left to turn the tool into an Kupu Tool is set the prototype::
                    171: 
                    172:     ImageTool.prototype = new KupuTool;
                    173: 
                    174: register it to the editor (in initKupu or the HTML page)::
                    175: 
                    176:     var imagetool = new ImageTool();
                    177:     kupu.registerTool('imagetool', imagetool);
                    178: 
                    179: and we're done with the basic tool.
                    180: 
                    181: 5. ToolBoxes
                    182: ------------
                    183: 
                    184: Now we have a class with a method that is capable of inserting an
                    185: image, but no way to call the method yet. The most basic way to call
                    186: the method would be from an HTML button, so we could simply create an
                    187: HTML button that would call the method. For larger tools, however,
                    188: it's nice to attach the event handlers from the code rather then from
                    189: the HTML itself, keeps things neat and clean, but on the other hand
                    190: it's nasty to have references to UI elements in the classes that
                    191: contain the functionality, for instance since someone using your tool
                    192: may want to use an element with another event or interface, or perhaps
                    193: even a completely different type of input (e.g. the context menu
                    194: mentioned below). Therefore we want another abstraction layer,
                    195: toolboxes.
                    196: 
                    197: A ToolBox is basically a UI part of a Tool, containing references to
                    198: the HTML elements and containing and registering event handlers. For
                    199: the image tool we'd probably want a class with a reference to 2 HTML
                    200: elements: an input field to enter a URL in and a button to click on to
                    201: create the image::
                    202: 
                    203:     function ImageToolBox(inputel, buttonel) {
                    204:         this.inputel = inputel;
                    205:         this.buttonel = buttonel;
                    206: 
                    207:         this.initialize(tool, editor) {
                    208:             // always store the references to the tool and editor
                    209:             this.tool = tool;
                    210:             this.editor = editor;
                    211: 
                    212:             // addEventHandler registers an event handler in both IE and
                    213:             // Mozilla, last argument is the context in which to call the 
                    214:             // method
                    215:             addEventHandler(this.button, 'click', this.createImage, this);
                    216:         };
                    217:         
                    218:         this.createImage = function() {
                    219:             var src = this.inputel.value;
                    220:             if (!src) {
                    221:                 this.editor.logMessage('No image created since no URL was ' +
                    222:                                         'specified');
                    223:                 return;
                    224:             };
                    225:             this.tool.createImage(src);
                    226:         };
                    227:     };
                    228: 
                    229: We don't create an updateState method here, although we could, since
                    230: we don't have a way to change the image's src attribute anyway. The
                    231: updateState method of toolboxes will usually update the state of the
                    232: UI elements of the tool (as happens with the TableTool, when inside a
                    233: tool you will see editing elements, when you're not inside one you'll
                    234: find elements to add a new table) or set the value of certain form
                    235: fields (as in the LinkTool, when inside a link it will show you the
                    236: href of the link), both not appropriate in this case (although a
                    237: usecase could easily be written I would say ;).
                    238: 
                    239: 6. ContentFilters
                    240: -----------------
                    241: 
                    242: Before Kupu sends its contents to the server, it is converted from
                    243: HTML to XHTML and then passed through any registered content filters.
                    244: 
                    245: Content filters are simple classes that should have 2 methods::
                    246: initialize, which will be called on registration of the filter, with a
                    247: single argument which is a reference to the editor, and filter, which
                    248: will be called with 2 arguments: a reference to the owner document
                    249: (the document element of the iframe) and the html node of the
                    250: document. The idea is that the filter should use DOM functionality to
                    251: filter the incoming DOM and return the filtered version.  To register
                    252: a content filter to Kupu, use 'kupu.registerFilter(filter)'.
                    253: 
                    254: The XHTML conversion is configurable. By default it accepts any tags
                    255: defined in XHTML 1.0 transitional, as well as any attributes except
                    256: for the event attributes (those beginning with 'on'). Tags not in the
                    257: html namespace are stripped.
                    258: 
                    259: A tag is permitted if it is defined in XHTML and not blacklisted.
                    260: Optionally kupu can also filter to maintain a valid XHTML nesting of
                    261: tags, but this option is off by default (put a
                    262: `<filterstructure>1</filterstructure>` tags into the htmlfilter
                    263: configuration to turn it on, or set `editor.xhtmlvalid.filterstructure`
                    264: to true. When the structure filtering is turned on a tag is permitted
                    265: if it is allowed within the current context (e.g. you cannot nest `<A>`
                    266: tags directly inside other `<A>` tags, so any nested `<A>` tags will be
                    267: stripped). If a tag is stripped its content will still be present.  An
                    268: attribute is permitted if it is defined in XHTML and not blacklisted
                    269: for that tag. Text data is stripped if it occurs in a context not
                    270: valid in XHTML (e.g. inside a table but outside `<TH>` or `<TD>`).
                    271: 
                    272: The blacklist is taken from Kupu's xml configuration. An `<htmlfilter>` tag
                    273: encloses the blacklist, `<t>` marks a tag to be blacklisted, <a> marks
                    274: an attribute to be blacklisted, `<c>` marks attributes to be excluded
                    275: for specific tags. e.g. the following would blacklist the `center`
                    276: and `font` tags, the `cellspacing` attribute everywhere, and the
                    277: `width` attribute for the `th` and `td` tags only. ::
                    278: 
                    279:         <htmlfilter>
                    280:                 <t>center></t><t>font</t>
                    281:                 <a>cellspacing</a>
                    282:                 <c><t>th</t><t>td</t><a>width</a></c>
                    283:         </htmlfilter>
                    284: 
                    285: From Javascript you can use the excludeAttributes, excludeTags and
                    286: excludeTagAttributes methods of `editor.xhtmlvalid` to get the same
                    287: effect.
                    288: 
                    289: The `style` attribute filters its content to permit `text-align` and
                    290: `list-style-type` styles and reject all others (these styles are used
                    291: by kupu). The style whitelist may be extended by adding additional
                    292: entries to `editor.xhtmlvalid.styleWhitelist`.
                    293: 
                    294: The `class` attribute is filtered to reject classnames which appear in
                    295: the class blacklist. Add entries to `editor.xhtmlvalid.classBlacklist`
                    296: to reject additional classes.
                    297: 
                    298: Entries may be added to the style whitelist or the class blacklist
                    299: through the `<htmlfilter>` section of the configuration::
                    300: 
                    301:         <htmlfilter>
                    302:           ... as above ...
                    303:             <style>list-style-image</style>
                    304:             <class>badClassName</class>
                    305:         </htmlfilter>
                    306: 
                    307: Additional attributes may be added to existing tags by calling the
                    308: `setTagAttributes` and `setAttrFilter` methods. e.g. ::
                    309: 
                    310:     editor.xhtmlvalid.setTagAttributes(['div'], ['special']);
                    311:     editor.xhtmlvalid.setAttrFilter(['special']);
                    312: 
                    313: adds an attribute `special` to the `div` tag. The method tags three
                    314: parameters: a list of attributes, a list of tags, and optionally a
                    315: filter method to be called for that attribute. If the filter method is
                    316: omitted the attribute is simply copied.
                    317: 
                    318: The validation API is as follows (for the purposes of this api, a
                    319: `dict` means an object whose property names are the dict keys):
                    320: 
                    321: Set
                    322:   a javascript class used to quickly create a 'dict' where all
                    323:   the values are 1. The constructor takes a single keyname, or a list
                    324:   of keynames, or a Set.
                    325: 
                    326: attrFilters
                    327:   a dict keyed by attributeName, of filter functions for each
                    328:   attribute. Any attribute not in this dict is invalid.
                    329: 
                    330: tagAttributes
                    331:   a dict keyed by tagName, of arrays of valid attribute names for
                    332:   each tag. Any tag not in this dict is invalid.  N.B. Tags which share
                    333:   a common set of attributes may be sharing the same array.
                    334: 
                    335:   All attributeNames in the sets in tagAttributes must exist in
                    336:   attrFilters.
                    337: 
                    338: setAttrFilter(attributes,filter)
                    339:   sets the filter function in attrFilters for the list of attributes,
                    340:   adding the attributes if they don't already exist. A simple copy
                    341:   function is used if filter is not provided.
                    342: 
                    343: setTagAttributes(tags,attributes)
                    344:   for each tag in the list of tags, sets tagAttributes[tag] to the list of
                    345:   attributes. An empty list of attributes is used if attributes is
                    346:   omitted.
                    347:  
                    348: includeTagAttributes(tags, attributes)
                    349:   for each tag in the list of tags, adds the list of attributes to
                    350:   tagAttributes[tag]
                    351:  
                    352: excludeTagAttributes(tags, attributes)
                    353:   for each tag in the list of tags, removes any attribute in the list
                    354:   of attributes from the list in tagAttributes[tag].
                    355:  
                    356: excludeTags(tags)
                    357:    removes the list of tags from tagAttributes.
                    358:  
                    359: excludeAttributes(attributes)
                    360:   removes the set of attributes from attrFilters and all tags in
                    361:   tagAttributes.
                    362: 
                    363: styleWhitelist
                    364:   a Set. Each key is the css name of a style. e.g. text-align.
                    365:   Only those elements of the style attribute which are present in this
                    366:   set are permitted through the filtering.
                    367:   The default setting includes only those styles which may be set by
                    368:   kupu.
                    369: 
                    370: classBlacklist
                    371:   a Set. Each key is a classname which should not be permitted. All
                    372:   other classnames are allowed through the filtering. The default
                    373:   setting is a list of styles likely to be present when a pasting text
                    374:   copied out of Microsoft Word.
                    375:  
                    376: 
                    377: 7. Loggers
                    378: ----------
                    379: 
                    380: Loggers are classes to log messages. A logger will usually do nothing,
                    381: or at most send everything to some text area or div, unless an error
                    382: log message comes in, in which case most loggers will probably raise
                    383: an exception.  Loggers should have a single method called 'log' that
                    384: gets one mandatory and one optional argument: it must get a message
                    385: (which is a string that will, or in some cases will not, get logged)
                    386: and it may get a severity argument, which can be 0 for debug messages,
                    387: 1 for warnings and 2 for errors. The default should be 0.
                    388: 
                    389: A logger should be passed to the editor on instantiation time (it's
                    390: one of the constructor's arguments).
                    391: 
                    392: 8. And the rest
                    393: ---------------
                    394: 
                    395: Of course there's a lot more to Kupu that can be customized, added or
                    396: rewritten. For more details see the source: it should be quite clear
                    397: and simple and is written in a nice object oriented manner. Also make
                    398: sure to check out JSAPI.txt, which is a reference to Kupu' JavaScript
                    399: JSAPI.

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>