| // create a domain-based "package" to keep the global namespace clean |
| var org; |
| if (!org) org = new Object(); |
| if (!org.apache) org.apache = new Object(); |
| if (!org.apache.lenya) org.apache.lenya = new Object(); |
| if (!org.apache.lenya.editors) org.apache.lenya.editors = new Object(); |
| |
| //alert("Module 'org.apache.lenya.editors' loaded."); |
| |
| /** |
| * ObjectData constructor, the interface between generic editor usecases |
| * and editor modules. |
| * |
| * The idea is to use the same data structure for links, images and assets. |
| * Thus, insertLink, insertImage and insertAsset can all share most of the javascript code. |
| * |
| * FIXME: objectData is an exceptionally stupid term. Please fix if you can think of |
| * something that encompasses "data for to-be-inserted links, images and assets in general". |
| * |
| * @param an optional hash map of initial values |
| */ |
| org.apache.lenya.editors.ObjectData = function(init) { |
| if (init) { |
| for (var i in this) { |
| if (typeof this[i] == "function") continue; // skip the methods! |
| //alert("Checking this[" + i + "], init[" + i + "] is '" + init[i] + "'."); |
| this[i] = init[i]; |
| } |
| //alert("Created new ObjectData = " + this.toString()); |
| } |
| } |
| |
| |
| /** |
| * href for links, src for assets and images |
| */ |
| org.apache.lenya.editors.ObjectData.prototype.url = undefined; |
| |
| /** |
| * XHTML title attribute: |
| */ |
| org.apache.lenya.editors.ObjectData.prototype.title = undefined; |
| |
| /** |
| * element content for links and assets, alt text for images: |
| */ |
| org.apache.lenya.editors.ObjectData.prototype.text = undefined; |
| |
| /** |
| * width for images |
| */ |
| org.apache.lenya.editors.ObjectData.prototype.width = undefined; |
| |
| /** |
| * height for images |
| */ |
| org.apache.lenya.editors.ObjectData.prototype.height = undefined; |
| |
| /** |
| * MIME Type for images and assets. |
| */ |
| org.apache.lenya.editors.ObjectData.prototype.type = undefined; |
| |
| |
| /** |
| * Utility function to ease debugging. Will dump all fields in |
| * a human-readable fashion, including their types. |
| */ |
| org.apache.lenya.editors.ObjectData.prototype.toString = function() { |
| var s = "\n"; |
| for (var i in this) { |
| if (typeof this[i] != "function") { |
| s += "\t" + i + ": [" + this[i] + "] (" + typeof this[i] + ")\n"; |
| } |
| } |
| return s; |
| } |
| |
| |
| /** |
| * set objectData object in editor |
| * |
| * This callback must be implemented in the editor window. |
| * It will be called when the usecase has completed successfully |
| * and make the obtained data available to your editor. |
| * Here you must implement the necessary code to either add the |
| * data to your editor area directly (you may want to use |
| * org.apache.lenya.editors.generateContentSnippet and |
| * org.apache.lenya.editors.insertContent as helpers), or |
| * to fill the values into some editor-specific dialog. |
| * |
| * If you want to allow for multiple active usecase windows in parallel, |
| * you can use the windowName parameter to uniquely identify each window |
| * and do your own bookkeeping. |
| * Consider using org.apache.lenya.editors.openUsecaseWindow to |
| * manage your windows. |
| * |
| * @param objectData a data object as defined by objectDataTemplate |
| * @param windowName the ID of the usecase window (window.name). |
| * @see org.apache.lenya.editors.ObjectData |
| */ |
| org.apache.lenya.editors.setObjectData = function(objectData, windowName) { |
| alert("Programming error:\n You must override org.apache.lenya.editors.setObjectData(objectData, windowName)!"); |
| }; |
| |
| |
| /** |
| * get objectData object from editor |
| * |
| * This callback must be implemented in the editor window. |
| * The usecase will query your editor for an objectData object, which |
| * it will use to fill form fields with default values (if provided). |
| * All form fields whose values in objectData are undefined will be |
| * deactivated, so that your editor can handle them. |
| * |
| * Usually, default values are based on selected text or user settings. |
| * @param windowName the ID of the usecase window (window.name). |
| * @returns an objectData object. |
| * @see org.apache.lenya.editors.ObjectData |
| * @see org.apache.lenya.editors.setObjectData |
| */ |
| org.apache.lenya.editors.getObjectData = function(windowName) { |
| alert("Programming error:\n You must override org.apache.lenya.editors.getObjectData(windowName)!"); |
| }; |
| |
| |
| /** |
| * sets default values of the usecase form |
| * |
| * The form field names must correspond to the properties of |
| * objectDataTemplate. |
| * Note: if a value in objectData is undefined (as opposed to ""), |
| * the corresponding form field will be disabled (greyed out). |
| * Editors should use this to deactivate properties they wish |
| * to handle themselves. |
| * @param formName the "name" attribute of the form |
| * @param objectData |
| * @see org.apache.lenya.editors.objectDataTemplate |
| */ |
| org.apache.lenya.editors.setFormValues = function(formName, objectData) { |
| var form = document.forms[formName]; |
| for (var i in org.apache.lenya.editors.ObjectData.prototype) { |
| if (form[i] !== undefined) { |
| if (objectData[i] !== undefined) { |
| form[i].value = objectData[i]; |
| } else { |
| form[i].disabled = true; |
| form[i].title = "disabled by editor"; |
| } |
| } |
| } |
| } |
| |
| |
| /** |
| * reads the values from the usecase form |
| * |
| * The form field names must correspond to the properties of |
| * objectDataTemplate. |
| * @param formName the "name" attribute of the form |
| * @returns objectData |
| */ |
| org.apache.lenya.editors.getFormValues = function(formName) { |
| var form = document.forms[formName]; |
| var objectData = new org.apache.lenya.editors.ObjectData(); |
| for (var i in org.apache.lenya.editors.ObjectData.prototype) { |
| if (form[i] !== undefined) { |
| objectData[i] = form[i].value; |
| } |
| } |
| return objectData; |
| } |
| |
| /* |
| org.apache.lenya.editors.findAncestor = function() { |
| // check our parent window: |
| return org.apache.lenya.editors.__findAncestor(window.opener.top); |
| } |
| |
| org.apache.lenya.editors.__findAncestor = function(ancestor) { |
| // if ancestor is undefined, we don't have an ancestor that |
| // provides the org.apache.lenya.editors package. |
| if (!ancestor) { |
| alert("no ancestor found. baling out..."); |
| return undefined; |
| } |
| try { |
| // probe for the package in the ancestor: |
| var probe = ancestor.org.apache.lenya.editors; |
| } catch (exception) { |
| alert("probe: " + probe + "\n" + exception); |
| // woops. no package. check the ancestor's parent. |
| return org.apache.lenya.editors.__findAncestor(ancestor.opener.top); |
| } |
| // gotcha: ancestor has the package. |
| alert("found ancestor '" + ancestor.name + "' with package!"); |
| return ancestor; |
| } |
| */ |
| |
| /** |
| * handle the submit event of the form |
| * |
| * @param formName the "name" attribute of the form |
| */ |
| org.apache.lenya.editors.handleFormSubmit = function(formName) { |
| var objectData = org.apache.lenya.editors.getFormValues(formName); |
| window.opener.org.apache.lenya.editors.setObjectData(objectData, window.name); |
| window.close(); |
| } |
| |
| |
| /** |
| * handle the load event of the form |
| * |
| * @param formName the "name" attribute of the form |
| */ |
| org.apache.lenya.editors.handleFormLoad = function(formName) { |
| var objectData = window.opener.org.apache.lenya.editors.getObjectData(window.name); |
| org.apache.lenya.editors.setFormValues(formName, objectData); |
| } |
| |
| |
| /** |
| * default attributes for usecase windows (window.open()...) |
| */ |
| org.apache.lenya.editors.usecaseWindowOptions = |
| "toolbar=no," |
| + "scrollbars=yes," |
| + "status=no," |
| + "resizable=yes," |
| // + "dependent=yes," not in IE6, and the moz people want to get rid of it, too... |
| + "width=600," |
| + "height=700"; |
| |
| |
| org.apache.lenya.editors.USECASE_INSERTLINK = 'insertLink'; |
| org.apache.lenya.editors.USECASE_INSERTIMAGE = 'insertImage'; |
| org.apache.lenya.editors.USECASE_INSERTASSET = 'insertAsset'; |
| |
| |
| org.apache.lenya.editors.generateUniqueWindowName = function() { |
| return new String("Lenya_" + Math.random().toString().substr(2)); |
| } |
| |
| |
| /** |
| * a helper function to open new usecase windows. |
| * |
| * If everyone used this, we'd save some maintenance work in the long run |
| * and can ensure consistent behaviour across different editors. |
| * |
| * @param usecase the name of the usecase to invoke, one of |
| * org.apache.lenya.editors.USECASE_INSERTLINK |
| * | org.apache.lenya.editors.USECASE_INSERTIMAGE |
| * | org.apache.lenya.editors.USECASE_INSERTASSET |
| * @param windowName the name of the new window, in case the editor needs |
| * that info later on. |
| * @returns the new window object |
| */ |
| org.apache.lenya.editors.openUsecaseWindow = function(usecase, windowName) { |
| var currentBaseURL; |
| var usecaseWindow; |
| |
| switch (usecase) { |
| case org.apache.lenya.editors.USECASE_INSERTLINK: |
| case org.apache.lenya.editors.USECASE_INSERTASSET: |
| case org.apache.lenya.editors.USECASE_INSERTIMAGE: |
| currentBaseURL = window.location.href.replace(/\?.*$/,""); |
| usecaseWindow = window.open( |
| currentBaseURL + "?lenya.usecase=editors." + usecase, |
| windowName, |
| org.apache.lenya.editors.usecaseWindowOptions |
| ); |
| usecaseWindow.focus(); //IE6 likes to open windows in the back... |
| break; |
| default: |
| alert("openUsecaseWindow: Unknown usecase '" + usecase + "'. This is likely a programming error."); |
| } |
| return usecaseWindow; |
| } |
| |
| |
| /** |
| * this data structure helps with the insertion of generated tags |
| */ |
| org.apache.lenya.editors.ContentSnippet = function( |
| beforeSelection, |
| afterSelection, |
| replaceSelection |
| ) { |
| this.beforeSelection = beforeSelection, |
| this.afterSelection = afterSelection, |
| this.replaceSelection = replaceSelection |
| } |
| |
| |
| /** |
| * the characters to be inserted before the selected text |
| */ |
| org.apache.lenya.editors.ContentSnippet.prototype.beforeSelection = ""; |
| |
| /** |
| * the characters to be inserted after the selected text |
| */ |
| org.apache.lenya.editors.ContentSnippet.prototype.afterSelection = ""; |
| |
| /** |
| * the text to replace the currently selected area (if any) |
| */ |
| org.apache.lenya.editors.ContentSnippet.prototype.replaceSelection = undefined; |
| |
| /** |
| * @see org.apache.lenya.editors.ObjectData.prototype.toString |
| */ |
| org.apache.lenya.editors.ContentSnippet.prototype.toString = |
| org.apache.lenya.editors.ObjectData.prototype.toString; |
| |
| |
| /** |
| * generates a content snippet to be inserted into the editor area |
| * |
| * @param usecase the usecase for which the snippet should be generated |
| * @param objectData an objectData object for the contents |
| * @param namespace an optional namespace URI (usually http://www.w3.org/1999/xhtml) |
| * @returns an object of type ContentSnippet |
| * @see org.apache.lenya.editors.ContentSnippet |
| */ |
| org.apache.lenya.editors.generateContentSnippet = function(usecase, objectData, namespace) { |
| var snippet = new org.apache.lenya.editors.ContentSnippet(); |
| |
| switch (usecase) { |
| |
| case org.apache.lenya.editors.USECASE_INSERTLINK: |
| snippet.beforeSelection = '<a' |
| + (namespace ? ' xmlns="' + namespace + '"' : '') |
| + (objectData.url ? ' href="' + objectData.url + '"' : '') |
| + (objectData.title ? ' title="' + objectData.title + '"' : '') |
| + '>'; |
| snippet.afterSelection = '</a>'; |
| snippet.replaceSelection = |
| objectData.text ? objectData.text : undefined; |
| break; |
| |
| case org.apache.lenya.editors.USECASE_INSERTASSET: |
| snippet.beforeSelection = '<a' |
| + (namespace ? ' xmlns="' + namespace + '"' : '') |
| + (objectData.url ? ' href="' + objectData.url + '"' : '') |
| + (objectData.title ? ' title="' + objectData.title + '"' : '') |
| + ' class="lenya.asset">'; |
| snippet.afterSelection = '</a>'; |
| snippet.replaceSelection = |
| objectData.text ? objectData.text : undefined; |
| break; |
| |
| case org.apache.lenya.editors.USECASE_INSERTIMAGE: |
| snippet.beforeSelection = '<img' |
| + (namespace ? ' xmlns="' + namespace + '"' : '') |
| + (objectData.url ? ' src="' + objectData.url + '"' : '') |
| + (objectData.title ? ' title="' + objectData.title + '"' : '') |
| + (objectData.text ? ' alt="' + objectData.text + '"' : '') |
| + (objectData.width ? ' width="' + objectData.width + '"' : '') |
| + (objectData.height ? ' height="' + objectData.height + '"' : '') |
| + '/>'; |
| snippet.afterSelection = ""; |
| snippet.replaceSelection = undefined; |
| break; |
| |
| default: |
| alert("setObjectData: Unknown usecase " + currentUsecase + ". This is likely a programming error."); |
| return undefined; |
| } |
| return snippet; |
| } |
| |
| |
| /** |
| * a cross-browser helper to obtain selected text |
| * |
| * @param sourceElement an XHTML input or window object (optional), such as |
| * document.forms['youreditor'].elements['content'] or window.top. |
| * You must specify it if you want to retrieve selections from |
| * form fields, or from frames other than "window" (the default). |
| * |
| * @returns the selected text or the empty string. |
| * |
| * NOTE: This function is really versatile and powerful. It is also not |
| * particularly well tested except with IE 6/7 and Firefox under Windows and Linux. |
| */ |
| org.apache.lenya.editors.getSelectedText = function(sourceElement) { |
| var debug = false; |
| |
| if (!sourceElement) sourceElement = window; |
| |
| // FF and friends if a form input element was specified |
| if (sourceElement.selectionStart) { |
| debug && alert("FF or similar, using 'selectionStart' on an XHTML <input> element named '" + sourceElement.name + "'."); |
| return sourceElement.value.substr( |
| sourceElement.selectionStart, |
| sourceElement.selectionEnd - sourceElement.selectionStart); |
| } else |
| // FF and friends in other parts of the document |
| if (sourceElement.getSelection) { |
| debug && alert("FF or similar, using a 'getSelection()' on a window element named '" + sourceElement.name + "'."); |
| return sourceElement.getSelection().toString(); |
| } else |
| // IE if a window element was specified |
| if (sourceElement.document.selection) { |
| debug && alert("IE, using using 'selection' on the document element of a window named '" + sourceElement.name + "'."); |
| return sourceElement.document.selection.createRange().text; |
| } else { |
| // IE if a form object was specified (ignore it and use document instead) |
| if (window.document.selection) { |
| debug && alert("IE, ignoring form element and using 'selection' on the document element of the current window '" + window.name + "'."); |
| return window.document.selection.createRange().text; |
| } else |
| debug && alert("Sorry, your browser apparently doesn't support text selection via javascript."); |
| return ""; |
| } |
| } |
| |
| |
| /** |
| * a cross-browser helper to insert data at the selected position in a form field (textarea etc.) |
| * |
| * @param formElement a XHTML form element (document.forms[foo].bar) |
| * @param contentSnippet a org.apache.lenya.editors.ContentSnippet with the text to insert |
| * |
| * inspired by http://aktuell.de.selfhtml.org/artikel/javascript/bbcode/ |
| */ |
| org.apache.lenya.editors.insertContent = function(formElement, snippet) { |
| |
| //alert("snippet: '" + snippet.toString() + "'\n"); |
| |
| // Danger, Will Robinson: you are leaving the w3c sector! |
| // Selections are not properly standardized yet... |
| formElement.focus(); |
| // Firefox and friends will support this for textareas etc. |
| if (formElement.selectionStart !== undefined) { |
| var begin = formElement.selectionStart; |
| var end = formElement.selectionEnd; |
| var content = formElement.value; |
| var selection = content.substring(begin, end); |
| // alert("Found selection beginning at [" + begin + "], ending at [" + end + "]."); |
| formElement.value = content.substr(0, begin) |
| + snippet.beforeSelection |
| + (snippet.replaceSelection ? snippet.replaceSelection : selection) |
| + snippet.afterSelection |
| + content.substr(end); |
| // update cursor position: |
| formElement.selectionStart = begin; |
| formElement.selectionEnd = begin; |
| } else |
| // IE does it thusly: |
| if (document.selection) { |
| alert("Hey, you are using IE, right? Please get in touch with dev@lenya.apache.org to test this feature!"); |
| var range = document.selection.createRange(); |
| var selection = range.text; |
| range.text = snippet.beforeSelection |
| + (snippet.replaceSelection ? snippet.replaceSelection : selection) |
| + snippet.afterSelection; |
| range.select(); |
| } else { |
| // for all other browsers, paste the stuff at the end... |
| alert("Hey, what kind of browser is this? Please get in touch with dev@lenya.apache.org to make this feature work properly for you!"); |
| formElement.value = formElement.value |
| + snippet.beforeSelection |
| + (snippet.replaceSelection ? snippet.replaceSelection : selection) |
| + snippet.afterSelection; |
| } |
| } |
| |