001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/jsf/tags/sakai_2-4-1/widgets/src/java/org/sakaiproject/jsf/renderer/RichTextAreaRenderer.java $
003: * $Id: RichTextAreaRenderer.java 15703 2006-10-05 18:16:51Z chmaurer@iupui.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.jsf.renderer;
021:
022: import java.io.IOException;
023: import java.util.Map;
024:
025: import javax.faces.component.UIComponent;
026: import javax.faces.component.UIInput;
027: import javax.faces.component.ValueHolder;
028: import javax.faces.context.FacesContext;
029: import javax.faces.context.ResponseWriter;
030: import javax.faces.render.Renderer;
031:
032: import org.sakaiproject.jsf.util.RendererUtil;
033: import org.sakaiproject.component.cover.ServerConfigurationService;
034: import org.sakaiproject.tool.cover.ToolManager;
035: import org.sakaiproject.content.cover.ContentHostingService;
036: import org.sakaiproject.util.FormattedText;
037:
038: public class RichTextAreaRenderer extends Renderer {
039: public boolean supportsComponentType(UIComponent component) {
040: return (component instanceof org.sakaiproject.jsf.component.RichTextAreaComponent);
041: }
042:
043: public void encodeBegin(FacesContext context, UIComponent component)
044: throws IOException {
045: String clientId = component.getClientId(context);
046: String textareaId = clientId + "_textarea";
047:
048: ResponseWriter writer = context.getResponseWriter();
049:
050: // value (text) of the HTMLArea
051: Object value = null;
052: if (component instanceof UIInput)
053: value = ((UIInput) component).getSubmittedValue();
054: if (value == null && component instanceof ValueHolder)
055: value = ((ValueHolder) component).getValue();
056: if (value == null)
057: value = "";
058:
059: //escape the value so the wysiwyg editors don't get too clever and turn things
060: //into tags that are not tags.
061: value = FormattedText
062: .escapeHtmlFormattedTextarea((String) value);
063:
064: // pixel width of the HTMLArea
065: int width = -1;
066: String widthIn = (String) RendererUtil.getAttribute(context,
067: component, "width");
068: if (widthIn != null && widthIn.length() > 0)
069: width = Integer.parseInt(widthIn);
070:
071: // pixel height of the HTMLArea
072: int height = -1;
073: String heightIn = (String) RendererUtil.getAttribute(context,
074: component, "height");
075: if (heightIn != null && heightIn.length() > 0)
076: height = Integer.parseInt(heightIn);
077:
078: // character height of the textarea
079: int columns = -1;
080: String columnsStr = (String) RendererUtil.getAttribute(context,
081: component, "columns");
082: if (columnsStr != null && columnsStr.length() > 0)
083: columns = Integer.parseInt(columnsStr);
084:
085: // character width of the textarea
086: int rows = -1;
087: String rowsStr = (String) RendererUtil.getAttribute(context,
088: component, "rows");
089: if (rowsStr != null && rowsStr.length() > 0)
090: rows = Integer.parseInt(rowsStr);
091:
092: String editor = ServerConfigurationService
093: .getString("wysiwyg.editor");
094: if (editor != null && !editor.equalsIgnoreCase("FCKeditor")) {
095:
096: // Number of rows of buttons in the toolbar (0, 2, or 3 rows of buttons)
097: int toolbarButtonRows = 2;
098: String toolbarButtonRowsStr = (String) RendererUtil
099: .getAttribute(context, component,
100: "toolbarButtonRows");
101: if (toolbarButtonRowsStr != null
102: && toolbarButtonRowsStr.length() > 0)
103: toolbarButtonRows = Integer
104: .parseInt(toolbarButtonRowsStr);
105:
106: // if true, 0 rows of buttons (no toolbar buttons).
107: String justArea = (String) RendererUtil.getAttribute(
108: context, component, "justArea");
109: if (justArea != null
110: && ("true".equals(justArea) || "yes"
111: .equals(justArea))) {
112: toolbarButtonRows = 0;
113: }
114:
115: // the URL to the directory of the HTMLArea JavaScript
116: String javascriptLibrary = "/library/htmlarea";
117: boolean javascriptLibrarySakaiLegacy = true;
118: String newJsLibUrl = (String) RendererUtil.getAttribute(
119: context, component, "javascriptLibrary");
120: {
121: if (newJsLibUrl != null && newJsLibUrl.length() > 0) {
122: javascriptLibrary = newJsLibUrl;
123: javascriptLibrarySakaiLegacy = false;
124: }
125: }
126:
127: // whether to calculate the width, height, and toolbarButtonRows
128: // (instead of just taking the values given on the tag)
129: boolean autoConfig = false;
130: String autoConfigStr = (String) RendererUtil.getAttribute(
131: context, component, "autoConfig");
132: if (autoConfigStr != null
133: && ("true".equals(autoConfigStr) || "yes"
134: .equals(autoConfigStr))) {
135: autoConfig = true;
136: }
137:
138: // if necessary, calculate the width, height, and toolbarButtonRows
139: if (autoConfig) {
140: if (toolbarButtonRows == 0) {
141: if (width < 0 && height < 0) {
142: width = 400;
143: height = 100;
144: } else if (height < 0) {
145: height = 200;
146: }
147: } else // if (toolbarButtonRows != 0)
148: {
149: if (width < 0)
150: width = 450;
151: if (height < 0)
152: height = 80;
153:
154: // enforce minimum size of 450 by 80
155: // if width >= 630 set toolbarButtonRows = 2 (otherwise toolbarButtonRows = 3)
156: if ((height < 80) && (width < 450)) {
157: height = 80;
158: width = 450;
159: toolbarButtonRows = 3;
160: } else if ((height >= 80) && (width < 450)) {
161: width = 450;
162: toolbarButtonRows = 3;
163: } else if ((height >= 80) && (width >= 450)
164: && (width < 630)) {
165: toolbarButtonRows = 3;
166: } else if ((height >= 80) && (width >= 630)) {
167: toolbarButtonRows = 2;
168: } else if ((height < 80) && (width >= 630)) {
169: height = 80;
170: toolbarButtonRows = 2;
171: } else {
172: height = 80;
173: toolbarButtonRows = 3;
174: }
175: }
176:
177: } // if (autoConfig)
178:
179: // surround the HTMLArea in a table to correct the toolbar appearence
180: writer.write("<table border=\"0\"><tr><td>");
181: // output the text area
182: writer.write("<textarea name=\"" + textareaId + "\" id=\""
183: + textareaId + "\"");
184: if (columns > 0)
185: writer.write(" cols=\"" + columns + "\"");
186: if (rows > 0)
187: writer.write(" rows=\"" + rows + "\"");
188: //if (toolbarButtonRows == 0) writer.write(" disabled=\"disabled\"");
189: writer.write(">");
190: writer.write((String) value);
191: writer.write("</textarea>");
192: writer.write("</td></tr></table>\n");
193:
194: if (javascriptLibrarySakaiLegacy) {
195: if (width < 0)
196: width = 0;
197: if (height < 0)
198: height = 0;
199: writer.write("<script type=\"text/javascript\" src=\""
200: + javascriptLibrary
201: + "/sakai-htmlarea.js\"></script>\n");
202: writer
203: .write("<script type=\"text/javascript\" defer=\"1\">chef_setupformattedtextarea(\""
204: + textareaId
205: + "\", "
206: + width
207: + ", "
208: + height
209: + ", "
210: + toolbarButtonRows
211: + ");</script>");
212: } else {
213: // output the JavaScript libraries
214: writer
215: .write("<script type=\"text/javascript\">var _editor_url = \""
216: + javascriptLibrary + "/\";</script>\n");
217: writer.write("<script type=\"text/javascript\" src=\""
218: + javascriptLibrary
219: + "/htmlarea.js\"></script>\n");
220: writer.write("<script type=\"text/javascript\" src=\""
221: + javascriptLibrary
222: + "/dialog.js\"></script>\n");
223: writer.write("<script type=\"text/javascript\" src=\""
224: + javascriptLibrary
225: + "/popupwin.js\"></script>\n");
226: writer.write("<script type=\"text/javascript\" src=\""
227: + javascriptLibrary
228: + "/lang/en.js\"></script>\n");
229:
230: // output the JavaScript to configure and invoke the HTMLArea
231: writer
232: .write("<script type=\"text/javascript\">var config=new HTMLArea.Config();");
233: if (toolbarButtonRows == 0) {
234: writer.write("config.toolbar=[];\n");
235: } else if (toolbarButtonRows == 2) {
236: /*
237: * writer.write(" <textarea name=\"" + clientId +
238: * "_textarea\" id=\"" + clientId + "_textarea\"" + "
239: * cols=\"" + outCol + "\" rows=\"" + outRow + "\"
240: * onclick=\"var config=new HTMLArea.Config();" +
241: * "config.toolbar = [[\'fontname\', \'space\',\'fontsize\',
242: * \'space\',\'formatblock\', \'space\',\'bold\',
243: * \'italic\',
244: * \'underline\',\'separator\',\'strikethrough\',
245: * \'subscript\', \'superscript\', \'separator\', \'copy\',
246: * \'cut\', \'paste\', \'space\', \'undo\', \'redo\']," +
247: * "[\'separator\', \'justifyleft\', \'justifycenter\',
248: * \'justifyright\', \'justifyfull\',
249: * \'separator\',\'outdent\',
250: * \'indent\',\'separator\',\'forecolor\', \'hilitecolor\',
251: * \'textindicator\',
252: * \'separator\',\'inserthorizontalrule\', \'createlink\',
253: * \'insertimage\', \'inserttable\', \'htmlmode\',
254: * \'separator\',\'popupeditor\', \'separator\',
255: * \'showhelp\', \'about\' ],"+ "];" + "HTMLArea.replace(\'" +
256: * clientId +"_textarea\',config);" + "\">" + value + "
257: * </textarea>\n");
258: */
259: writer
260: .write("config.toolbar = [[\'fontname\', \'space\',\'fontsize\', \'space\',\'forecolor\', \'space\',\'bold\', \'italic\', \'underline\',\'separator\',\'strikethrough\', \'subscript\', \'superscript\', \'separator\',");
261: writer
262: .write("\'justifyleft\', \'justifycenter\', \'justifyright\', \'justifyfull\', \'separator\'],[\'orderedlist\', \'unorderedlist\',\'outdent\', \'indent\',\'htmlmode\', \'separator\', \'showhelp\', \'about\' ],");
263: writer.write("];\n");
264: } else if (toolbarButtonRows == 3) {
265: /*
266: * writer.write(" <textarea name=\"" + clientId +
267: * "_textarea\" id=\"" + clientId + "_textarea\"" + "
268: * cols=\"" + outCol + "\" rows=\"" + outRow + "\"
269: * onclick=\"var config=new HTMLArea.Config();" +
270: * "config.toolbar = [[\'fontname\', \'space\',\'fontsize\',
271: * \'space\',\'formatblock\', \'space\',\'bold\',
272: * \'italic\', \'underline\']," +
273: * "[\'separator\',\'strikethrough\', \'subscript\',
274: * \'superscript\', \'separator\', \'copy\', \'cut\',
275: * \'paste\', \'space\', \'undo\', \'redo\', \'separator\',
276: * \'justifyleft\', \'justifycenter\', \'justifyright\',
277: * \'justifyfull\', \'separator\',\'outdent\', \'indent\']," +
278: * "[\'separator\',\'forecolor\', \'hilitecolor\',
279: * \'textindicator\',
280: * \'separator\',\'inserthorizontalrule\', \'createlink\',
281: * \'insertimage\', \'inserttable\', \'htmlmode\',
282: * \'separator\',\'popupeditor\', \'separator\',
283: * \'showhelp\', \'about\' ],"+ "];" + "HTMLArea.replace(\'" +
284: * clientId +"_textarea\',config);" + "\">" + value + "
285: * </textarea>\n");
286: */
287: writer
288: .write("config.toolbar = [[\'fontname\', \'space\',\'fontsize\', \'space\',\'formatblock\', \'space\',\'bold\', \'italic\', \'underline\'],");
289: writer
290: .write("[\'separator\',\'strikethrough\', \'subscript\', \'superscript\', \'separator\', \'copy\', \'cut\', \'paste\', \'space\', \'undo\', \'redo\', \'separator\', \'justifyleft\', \'justifycenter\', \'justifyright\', \'justifyfull\', \'separator\',\'outdent\', \'indent\'],");
291: writer
292: .write("[\'separator\',\'forecolor\', \'hilitecolor\', \'textindicator\', \'separator\',\'inserthorizontalrule\', \'createlink\', \'insertimage\', \'inserttable\', \'htmlmode\', \'separator\',\'popupeditor\', \'separator\', \'showhelp\', \'about\' ],");
293: writer.write("];\n");
294: }
295:
296: if (width > 0) {
297: writer.write("config.width=\'");
298: writer.write(String.valueOf(width));
299: writer.write("px");
300: writer.write("\';\n");
301: }
302: if (height > 0) {
303: writer.write("config.height=\'");
304: writer.write(String.valueOf(height));
305: writer.write("px");
306: writer.write("\';\n");
307: }
308:
309: //writer.write("HTMLArea.replace(\'");
310: //writer.write(clientId);
311: //writer.write("_textarea\',config);\n</script>\n");
312:
313: //Jira bug#:SAK-126 returning focus back to first editable field on the web page
314: writer
315: .write("var ta = HTMLArea.getElementById(\"textarea\",\"");
316: writer.write(clientId);
317: writer.write("_textarea\");\n");
318: writer.write("var editor =new HTMLArea(ta, config);\n");
319: writer.write("setTimeout( function(){ \n");
320: writer.write("editor.generate(); \n");
321: //somehow the path: messes up to clear status bar:
322: writer.write("editor._statusBar.innerHTML= \' \'; \n");
323: writer
324: .write("editor._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg[\"Path\"] + \": \")); \n");
325: //writer.write("editor._iframe.style.border = \"1px solid #000000\";\n");
326: //writer.write("editor._iframe.style.height= \"") ;
327: //writer.write(String.valueOf(height));
328: //writer.write("px\";\n");
329: writer.write("if (document.forms.length > 0)\n");
330: writer.write("{\n");
331: writer.write("var allElements = document.forms[0];\n");
332: writer
333: .write("for (i = 0; i < allElements.length; i++) \n");
334: writer.write("{\n");
335: writer
336: .write("if((allElements.elements[i].getAttribute(\"type\") !=null) &&((allElements.elements[i].type == \"text\") || (allElements.elements[i].type == \"textarea\")))\n");
337: writer.write("{\n");
338: writer
339: .write("document.forms[0].elements[i].focus();\n");
340: writer.write("break;\n");
341: writer.write("}\n");
342: writer.write("}\n");
343: writer.write(" }\n");
344: writer.write("}, 400); </script>\n");
345: }
346: //I saw sakai-htmlarea.js already has code to handle focus issue, but it did not work when I tested sakai:rich_text_area tag in jsp's
347: // Jira bug#:SAK-126 returning focus back to first editable field on the web page
348: writer.write("<script type=\"text/javascript\">\n");
349: writer.write("setTimeout(function(){ \n");
350: writer.write("if (document.forms.length > 0)\n");
351: writer.write("{\n");
352: writer.write("var allElements = document.forms[0];\n");
353: writer.write("for (i = 0; i < allElements.length; i++) \n");
354: writer.write("{\n");
355: writer
356: .write("if((allElements.elements[i].getAttribute(\"type\") !=null) &&((allElements.elements[i].type == \"text\") || (allElements.elements[i].type == \"textarea\")))\n");
357: writer.write("{\n");
358: writer.write("document.forms[0].elements[i].focus();\n");
359: writer.write("break;\n");
360: writer.write("}\n");
361: writer.write("}\n");
362: writer.write(" }\n");
363: writer.write("}, 600); </script>\n");
364: } else {
365: String collectionId = ContentHostingService
366: .getSiteCollection(ToolManager
367: .getCurrentPlacement().getContext());
368:
369: //is there a slicker way to get this?
370: String connector = "/sakai-fck-connector/web/editor/filemanager/browser/default/connectors/jsp/connector";
371:
372: writer.write("<table border=\"0\"><tr><td>");
373: writer.write("<textarea name=\"" + textareaId + "\" id=\""
374: + textareaId + "\"");
375: if (columns > 0)
376: writer.write(" cols=\"" + columns + "\"");
377: if (rows > 0)
378: writer.write(" rows=\"" + rows + "\"");
379: writer.write(">");
380: writer.write((String) value);
381: writer.write("</textarea>");
382:
383: RendererUtil.writeExternalJSDependencies(context, writer,
384: "richtextarea.jsf.fckeditor.js",
385: "/library/editor/FCKeditor/fckeditor.js");
386: //writer.write("<script type=\"text/javascript\" src=\"/library/editor/FCKeditor/fckeditor.js\"></script>\n");
387: writer
388: .write("<script type=\"text/javascript\" language=\"JavaScript\">\n");
389: writer
390: .write("function chef_setupformattedtextarea(textarea_id){\n");
391: writer
392: .write("var oFCKeditor = new FCKeditor(textarea_id);\n");
393: writer
394: .write("oFCKeditor.BasePath = \"/library/editor/FCKeditor/\";\n");
395:
396: if (width < 0)
397: width = 600;
398: if (height < 0)
399: height = 400;
400:
401: writer.write("oFCKeditor.Width = \"" + width + "\" ;\n");
402: writer.write("oFCKeditor.Height = \"" + height + "\" ;\n");
403:
404: if ("archival".equals(ServerConfigurationService
405: .getString("tags.focus")))
406: writer
407: .write("\n\toFCKeditor.Config['CustomConfigurationsPath'] = \"/library/editor/FCKeditor/archival_config.js\";\n");
408: else {
409:
410: writer.write("\n\t\tvar courseId = \"" + collectionId
411: + "\";");
412: writer
413: .write("\n\toFCKeditor.Config['ImageBrowserURL'] = oFCKeditor.BasePath + "
414: + "\"editor/filemanager/browser/default/browser.html?Connector="
415: + connector
416: + "&Type=Image&CurrentFolder=\" + courseId;");
417: writer
418: .write("\n\toFCKeditor.Config['LinkBrowserURL'] = oFCKeditor.BasePath + "
419: + "\"editor/filemanager/browser/default/browser.html?Connector="
420: + connector
421: + "&Type=Link&CurrentFolder=\" + courseId;");
422: writer
423: .write("\n\toFCKeditor.Config['FlashBrowserURL'] = oFCKeditor.BasePath + "
424: + "\"editor/filemanager/browser/default/browser.html?Connector="
425: + connector
426: + "&Type=Flash&CurrentFolder=\" + courseId;");
427: writer
428: .write("\n\toFCKeditor.Config['ImageUploadURL'] = oFCKeditor.BasePath + "
429: + "\""
430: + connector
431: + "?Type=Image&Command=QuickUpload&Type=Image&CurrentFolder=\" + courseId;");
432: writer
433: .write("\n\toFCKeditor.Config['FlashUploadURL'] = oFCKeditor.BasePath + "
434: + "\""
435: + connector
436: + "?Type=Flash&Command=QuickUpload&Type=Flash&CurrentFolder=\" + courseId;");
437: writer
438: .write("\n\toFCKeditor.Config['LinkUploadURL'] = oFCKeditor.BasePath + "
439: + "\""
440: + connector
441: + "?Type=File&Command=QuickUpload&Type=Link&CurrentFolder=\" + courseId;");
442:
443: writer
444: .write("\n\n\toFCKeditor.Config['CurrentFolder'] = courseId;");
445:
446: writer
447: .write("\n\toFCKeditor.Config['CustomConfigurationsPath'] = \"/library/editor/FCKeditor/config.js\";\n");
448: }
449:
450: writer.write("oFCKeditor.ReplaceTextarea() ;}\n");
451: writer.write("</script>\n");
452: writer
453: .write("<script type=\"text/javascript\" defer=\"1\">chef_setupformattedtextarea('"
454: + textareaId + "');</script>");
455:
456: writer.write("</td></tr></table>\n");
457: }
458: }
459:
460: public void decode(FacesContext context, UIComponent component) {
461: if (null == context
462: || null == component
463: || !(component instanceof org.sakaiproject.jsf.component.RichTextAreaComponent)) {
464: throw new IllegalArgumentException();
465: }
466:
467: String clientId = component.getClientId(context);
468:
469: Map requestParameterMap = context.getExternalContext()
470: .getRequestParameterMap();
471:
472: String newValue = (String) requestParameterMap.get(clientId
473: + "_textarea");
474:
475: org.sakaiproject.jsf.component.RichTextAreaComponent comp = (org.sakaiproject.jsf.component.RichTextAreaComponent) component;
476: comp.setSubmittedValue(newValue);
477: }
478: }
|