Home | How to Configure and Use The Desktop Plugin

 


Basic Setup

Minimally the <head> section of your HTML needs to contain the below code (replace {Path_to_JS_Directory} with the actual path to your JavaScript directory[1]):

<head> <meta http-equiv="Content-type" value="text/html; charset=UTF-8" /> <!-- Load the jQuery library. --> <script language="JavaScript" type="text/javascript" src="/{Path_to_JS_Directory}/wymeditor/jquery/jquery.js"></script> <!-- Load the WYMeditor code. --> <script language="JavaScript" type="text/javascript" src="/{Path_to_JS_Directory}/wymeditor/wymeditor/jquery.wymeditor.js"></script> <!-- Load the Desktop plugin for WYMeditor. --> <script language="JavaScript" type="text/javascript" src="/{Path_to_JS_Directory}/wymeditor/wymeditor/plugins/desktop/jquery.wymeditor.desktop.js"></script> <!-- The JavaScript code which will initialize WYMeditor and the Desktop plugin. --> <!-- This will run as soon as the document is ready (typically after the HTML document is fully loaded). --> <!-- This code can safely be placed in an external JavaScript file. --> <script language="JavaScript" type="text/javascript"> $(document).ready(function() { $(".wymeditor").wymeditor({ html: '<p>Testing... Hello, World!<\/p>', stylesheet: '/{Path_to_JS_Directory}/wymeditor/wymeditor/skins/styles.css', skin: 'desktop', postInit: function(wym) { wym.desktop.setParent(wym); wym.desktop.setDebug(true); wym.desktop.init(':default'); } }); }); </script> </head>

While the <body> section of your HTML needs to contain something to the effect of:

<body> <form method="POST" action=""> <textarea class="wymeditor"></textarea> </form> </body>

Now load your HTML file in a web browser.  And… that is everything!  You should now be viewing something like the following image:

 [ Image displaying the above example code in action. ]

 Notes: 

[1]  If you are using a web server (like Apache or XAMPP) to serve up the resulting page, absolute paths are preferred as they remove the uncertainty of where the code is being retrieved from.  However, if you are viewing the reulting page directly in a web browser (i.e., the location bar starts with file:///), then relative paths are preferred as writing the correct absolute path in the HTML file can get perversely interesting.  Remember, an absolute path starts with a forward slash character (/).  And if you are on Windows, remember to convert your paths to use forward slash characters because JavaScript interprets backslashes as metacharacter escape sequences (e.g., convert from this path\to\some\file.html format to this path/to/some/file.html format; unless, of course, you actually managed to shove a tab character and a form-feed character into your path).


Extending the Basic Setup

Now that we have verified your installation of WYMeditor and the Desktop plugin is working, let's tweak the code to include some buttons which are unique to the Desktop plugin.

To modify the basic setup, we only need to change the inline JavaScript:

<!-- The JavaScript code which will initialize WYMeditor and the Desktop plugin. --> <!-- This will run as soon as the document is ready (typically after the HTML document is fully loaded). --> <!-- This code can safely be placed in an external JavaScript file. --> <script language="JavaScript" type="text/javascript"> $(document).ready(function() { $(".wymeditor").wymeditor({ html: '<p>Testing... Hello, World!<\/p>', stylesheet: '/{Path_to_JS_Directory}/wymeditor/wymeditor/skins/styles.css', skin: 'desktop', postInit: function(wym) { wym.desktop.setParent(wym); wym.desktop.setDebug(true); /* Get the various icons and icon lists we want to use. */ var docNewIcon = wym.desktop.generateToolList(['DocumentNew']); var formatList = wym.desktop.generateToolList([':format']); var editList = wym.desktop.generateToolList([':edit']); var helpIcon = wym.desktop.generateToolList(['HelpBrowser']); /* Initialize our array of requested icons. */ var requested = new Array(); requested.push(docNewIcon[0]); /* Now place the format icons on the requested list in the order we want then to appear. */ var formatOrder = [ 'FormatTextBold', 'FormatTextItalic', 'FormatTextUnderline', 'FormatTextStrikethrough', 'FormatSuperscript', 'FormatSubscript', 'FormatJustifyLeft', 'FormatJustifyCenter', 'FormatJustifyRight', 'FormatJustifyFill', 'FormatIndentMore', 'FormatIndentLess' ]; for(var i = 0; i < formatList.length; i++) { var pos = 0; while((pos < formatList.length) && (formatList[pos].name != formatOrder[i])) { pos++; } if((pos < formatList.length) && (formatList[pos].name == formatOrder[i])) { requested.push(formatList[pos]); } } /* Now place the edit icons on the requested list in the order we want then to appear. */ var editOrder = ['EditCut', 'EditCopy', 'EditPaste', 'EditClear', 'EditUndo', 'EditRedo']; for(var i = 0; i < editList.length; i++) { var pos = 0; while((pos < editList.length) && (editList[pos].name != editOrder[i])) { pos++; } if((pos < editList.length) && (editList[pos].name == editOrder[i])) { requested.push(editList[pos]); } } /* Finally add our last icon. */ requested.push(helpIcon[0]); /* Initialize the Desktop plugin. */ wym.desktop.init(requested); } }); }); </script>

Now load your HTML file in a web browser.  You should now be viewing something like the following image:

 [ Image displaying the above example code in action. ]

Awesome!  We are no longer restricted to a limited button selection.   :-)

Lets add some functionality.  For this example we will extend the StrikeThrough button (the button with a line through the middle of a lowercase A, the fifth button from the left in the above image) to place <strike> tags around whatever text is highlighted.  We are also modifying the initial HTML so we can better test the new functionality.  Obviously we want this new functionality to trigger when we click the StrikeThrough button, so our postInit function now becomes (please note the ' ' character is used to indicate duplicate code from the previous code section):

 ⁝ html: '<p>Testing... <b>Test</b><i>ing</i>... Hello, World!<\/p>',  ⁝ postInit: function(wym) { wym.desktop.setParent(wym); wym.desktop.setDebug(true); /* Get the various icons and icon lists we want to use. */ var docNewIcon = wym.desktop.generateToolList(['DocumentNew']); var formatList = wym.desktop.generateToolList([':format']); var editList = wym.desktop.generateToolList([':edit']); var helpIcon = wym.desktop.generateToolList(['HelpBrowser']); /* Initialize our array of requested icons. */ var requested = new Array(); requested.push(docNewIcon[0]);  ⁝ /* Finally add our last icon. */ requested.push(helpIcon[0]); /* Find the FormatTextStrikethrough tool. */ var toolPos = 0; while(requested[toolPos].name != 'FormatTextStrikethrough') { toolPos++; } /* Our new FormatTextStrikethrough click handler. */ requested[toolPos].click = function(eventObj) { /* * In a dynamically loaded button (such as those found in the .../jquery.wymeditor.desktop.toolFunctions/ * directory), to get to the 'wym' object would require using 'this.selfObject.parent.parent' */ /* The actual selection. */ var sel = wym.selection(); /* Some anchor shortcuts. */ var anchor = sel.anchorNode; var anchorValue = anchor.nodeValue; var anchorParent = anchor.parentNode; /* Some focus shortcuts. */ var focus = sel.focusNode; var focusValue = focus.nodeValue; var focusParent = focus.parentNode; /* The beginning and ending location within the respective nodes. */ var begin = sel.anchorOffset; var end = sel.focusOffset; /* Is the selection within a single node? */ if(wym.desktop.domUtils.sameNode(anchor, focus)) { /* Setup the before, strike, and after nodes. */ var beforeNode = document.createTextNode(anchorValue.substring(0, begin)); var strikeNode = document.createElement("strike"); strikeNode.innerHTML = anchorValue.substring(begin, end); var afterNode = document.createTextNode(anchorValue.substring(end, anchorValue.length)); /* Place the nodes where they are supposed to be. */ /* * Because the DOM (in its infinite wisdom) does not have an .insertAfter() method, * _always_ start and the end of the modification sequence for a given node. For * example, the original node is a text node containing "foo, bar, baz" inside a * paragraph node (i.e., "<p>foo, bar, baz</p>") and we want to strike * out the substring "bar,": * 1) Replace the paragraph child with the text after the stricken text: * <p> baz</p> * 2) Insert the new strike node before the text node: * <p><strike>bar,</strike> baz</p> * 3) Insert the text which was before the stricken text: * <p>foo, <strike>bar,</strike> baz</p> */ anchorParent.replaceChild(afterNode, anchor); anchorParent.insertBefore(strikeNode, afterNode); anchorParent.insertBefore(beforeNode, strikeNode); } /* Okay, so the selection must span multiple nodes. */ else { /* Setup the anchor node replacements. */ var beforeNode = document.createTextNode(anchorValue.substring(0, begin)); var strikeNodeBegin = document.createElement("strike"); strikeNodeBegin.innerHTML = anchorValue.substring(begin, anchorValue.length); /* Wrap the contents of all nodes between the anchor and focus nodes. */ var curNode = (anchor.nextSibling != null) ? anchor.nextSibling : anchor.parentNode.nextSibling.firstChild; while((curNode != null) && (!wym.desktop.domUtils.sameNode(curNode, focus))) { curNode.innerHTML = "<strike>" + curNode.innerHTML + "</strike>"; curNode = (curNode.nextSibling != null) ? curNode.nextSibling : curNode.parentNode.nextSibling.firstChild; } /* Setup the focus node replacements. */ var afterNode = document.createTextNode(focusValue.substring(end, focusValue.length)); var strikeNodeEnd = document.createElement("strike"); strikeNodeEnd.innerHTML = focusValue.substring(0, end); /* Modify the anchor and focus nodes. */ anchorParent.replaceChild(strikeNodeBegin, anchor); anchorParent.insertBefore(beforeNode, strikeNodeBegin); focusParent.replaceChild(afterNode, focus); focusParent.insertBefore(strikeNodeEnd, afterNode); } }; wym.desktop.init(requested); }  ⁝

You may notice we are using the .selection() method in the above example.  Yet this method is not part of the core WYMeditor, but defined in the WYMeditor rangy interface plugin.  The explanation is the Desktop plugin loads all of its dependencies within the .init() method.  Thus, there is no need for additional <script> tags to load dependencies; the Desktop plugin tries very hard to be self-contained and easy to implement and use.

Now to test our code.  On page load, nothing but the initial HTML seems different:

 [ Image displaying the above example code in action. ]

So we highlight the Testing... phrases:

 [ Image displaying the above example code in action. ]

Then click the StrikeThrough button:

 [ Image displaying the above example code in action. ]

Huzza, it worked!  This was a triumph.  I'm making a note here: HUGE SUCCESS.   :-D

Let's add some more functionality.  For this example we will extend the HelpBrowser button (that would be the button on the right end of the button list, it looks like a ring buoy) to display a help dialog retrieved from a server.  Again, we will use the click event handler; except it will be on the HelpBrowser button this time, so our postInit function now becomes:

 ⁝ postInit: function(wym) { wym.desktop.setParent(wym); wym.desktop.setDebug(true); /* Get the various icons and icon lists we want to use. */ var docNewIcon = wym.desktop.generateToolList(['DocumentNew']); var formatList = wym.desktop.generateToolList([':format']); var editList = wym.desktop.generateToolList([':edit']); var helpIcon = wym.desktop.generateToolList(['HelpBrowser']); /* Initialize our array of requested icons. */ var requested = new Array(); requested.push(docNewIcon[0]);  ⁝ /* Finally add our last icon. */ requested.push(helpIcon[0]); /* Find the FormatTextStrikethrough tool. */ var toolPos = 0; while(requested[toolPos].name != 'FormatTextStrikethrough') { toolPos++; } /* Our new FormatTextStrikethrough click handler. */ requested[toolPos].click = function(eventObj) { }; /* Find the HelpBrowser tool. */ toolPos = 0; while(requested[toolPos].name != 'HelpBrowser') { toolPos++; } /* Our new HelpBrowser click handler. */ requested[toolPos].click = function(eventObj) { /* * In a dynamically loaded button (such as those found in the .../jquery.wymeditor.desktop.toolFunctions/ * directory), to get to the 'wym' object would require using 'this.selfObject.parent.parent' */ /* Build the path to the HTML files. */ var baseDir = wym.computeBasePath() + "plugins/desktop/docs"; /* Build IFRAME element. */ var iframeSource = baseDir + '/iframe.html'; var iframeStyle = 'width: 600px; height: 200px; ' + 'margin: 3px; padding: 3px; ' + 'background-color: #FFFFFF; ' + 'border: 2px solid #999999'; var iframeOnLoad = '$( "#htmlHelpContent",' + /* For reasons only known to jQuery, $("#id") does not work in the line below. :-( */ ' document.getElementById("iframeHelpContent").contentDocument )' + '.load( "' + baseDir + '/ExampleHelp.html",' + ' {help: 1},' + ' function() {' + ' alert("Help file loaded.");' + ' });'; var helpIframe = '<iframe id="iframeHelpContent" src="' + iframeSource + '" style="' + iframeStyle + '" onLoad=\'' + iframeOnLoad + '\'>' + 'Please use a browser which supports the IFRAME element.' + '</iframe>'; /* Build close button. */ var helpButton = '<input type="button" id="buttonHelpClose" value="Close Help" />'; /* Now we can build the Help DIV element. */ var helpDivStyle = 'border: 6px ridge #996666; ' + 'background-color: #FFFFFF; ' + 'z-index: 10; position: fixed; ' + 'top: 5px; left: 5px; ' + 'margin: 3px; padding: 3px;'; var helpDiv = '<div id="divHelp" style="' + helpDivStyle + '">' + '<center>' + helpIframe + '<br />' + helpButton + '</center>' + '</div>'; /* Place the Help DIV element in the DOM. */ $(wym._box).after(helpDiv); /* Set the close button click event handler. */ $("#buttonHelpClose").click(function() { $("#divHelp").remove(); }); }; wym.desktop.init(requested); }  ⁝

Testing time again!  After loading the above code and clicking on the Help button, we are greeted with this:

 [ Image displaying the above example code in action. ]

That is a very good sign, as the alert() dialog will only show after the example help file is loaded.  After scrolling a bit, we confirm the file was indeed loaded:

 [ Image displaying the above example code in action. ]

And we're out of beta, we're releasing on time.   ;-)

Now that we have walked through two different additions of functionality, you should be ready to add to and extend the Desktop plugin to fit your needs.  A good starting point would be with the two click handlers above:

  • Modify the HelpBrowser click handler to only ask for the page on the first click and to show/hide the div for all subsequent clicks (and remove that annoying alert  :-P , while still unobstusivly displaying success/failure of the help page load request).
  • Modify the FormatTextStrikethrough click handler to remove the <strike> tags if said tags are already present while still adding them if the tags are not present.  You could even add the ability to toggle strike-through formatting on or off (like a desktop word processor).

Also, do not forget to check in at wymeditor.github.com and github.com/wymeditor/wymeditor for the latest news and most recent edition of the WYMeditor code and plugins.


Reference

DesktopObject.domUtils.sameNode(nodeOne, nodeTwo)

Compares two nodes and returns a boolean indicating if the two nodes are equivalent.  Notice we said equivalent, not pointing to the same node or identical nodes.  There may be some weird edge cases where this method will return true and the nodes are not actually equvalent.

DesktopObject.setParent(parentObject)

This method sets the parent object of the given Desktop object.

DesktopObject.setDebug(booleanValue)

This method turns Firebug console debugging on (true) or off (false).

DesktopObject.availableEventNames()

Provides the list of all available event names recognized by the Desktop plugin.

DesktopObject.availableTools()

Provies the list of all available Desktop plugin tools, and their default settings.
The returned object has the following format:

{ToolName1}: { title: '{Tool_Title_1}', css: '{tool_css_class_1}' }, {ToolName2}: { title: '{Tool_Title_2}', css: '{tool_css_class_2}' }, …

Both the name: and file: values are autogenerated from the {ToolName}.  Optional configuration includes the use of {event} keys. Said key's value may either be a boolean value or an anonymous function; and the key itself must be one of the event types returned by the .availableEventNames() method. For example:

ClearFormat: { title: 'Clear_Format', css: 'wym_tools_clear' }, Undo: { title: 'Undo', css: 'wym_tools_undo', click: true }, Redo: { title: 'Redo', css: 'wym_tools_redo', hover: function(eventObject) { console.log(eventObject); }, click: true }

All of the above tool configuration examples are valid.

DesktopObject.availableToolNames()

Provides the list of all available tool names.

DesktopObject.defaultToolNames()

Provides the list of default tool names.

DesktopObject.validTags()

Provides the list of valid tag strings.

DesktopObject.expandTag(tagString)

Convert the given tag string into a list of tool names  The recognized tags are: ':default', ':all', and any string from the array returned by the .validTags() method.

DesktopObject.generateToolList(requestedTools)

Generate an array of objects (where each object contains the default configuration for that tool) from the given list of requested tools.  The requestedTools parameter must be an array of strings (tag strings are allowed).  Returns the generated list of requested tools with their default configurations (this is useful for tweaking a handful of tools without having to hard code large quantities of data which will likely never change).

DesktopObject.init(requestedTools, [parentObject], [isDebug], [dependList])

Initialize the Desktop plugin.  The .init() method requires one parameter and can accept up to four parameters:

  • requestedTools [Required]Either an array of strings (each string can be one of the names of which tools you want active or a tag string) or an array of objects (each object must have at least name:, title:, and css: keys).
    If you choose to pass in an array of objects, the valid keys within each object are:
    • name: [Required]The tool name.  Tool names take the form of CamelCase.
    • title: [Required]The tool title.  Spaces are forbidden and underscores are translated to spaces at the appropriate time.
    • css: [Required]The tool's CSS class name.  This must be the same as the class name in the Desktop skin CSS.
    • file: [Optional]The JavaScript file associated with this tool.  Will be dynamically loaded.
    • {event}: [Optional]Any event name returned by the .availableEventNames() method.  Can be set to either a boolean value or an anonymous function.  If boolean, sets the event handler according to the code in the file referenced by the file: key.
    Thus, examples for each of the three formats follows:
    /* List of Strings */ var requestedTools = [ 'ClearFormat', 'Undo', 'Redo' ];
    /* List of Objects */ var requestedTools = [ { name: 'ClearFormat', title: 'Clear_Format', css: 'wym_tools_clear' }, { name: 'Undo', title: 'Undo', css: 'wym_tools_undo', click: true }, { name: 'Redo', title: 'Redo', css: 'wym_tools_redo', hover: function(eventObject) { console.log(eventObject); }, click: true } ];
    /* Tag String */ var requestedTools = ':all';
    Valid tag strings are:  ':default', ':all', and any string from the array returned by the .validTags() method.
  • parentObject [Optional]A reference to the parent object (typically this is the WYMeditor object).  If you wish to leave the parent object at its default value (because, for example, you set it earlier with the .setParent() method), then pass in the null value.
  • isDebug [Optional]A boolean value which turns debugging console output on or off.  If you wish to leave debugging at its default value (because, for example, you set it earlier with the .setDebug() method), then pass in the null value.
  • dependList [Optional]A list of JavaScript dependencies to be loaded (they will be loaded in the order given).  If you wish to leave the dependency list at its default value (the recommended course of action), then omit this parameter.  Because this parameter is the last one and because it is optional, you can also simply omit this parameter; the same logic holds true for the debugging parameter if the dependency list is omitted and the parent object parameter if the debugging parameter and the dependency list are omitted.