Annotation of kupuMPIWG/doc/EXTENDING.txt, revision 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>