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>