001: /***************************************************************
002: * This file is part of the [fleXive](R) project.
003: *
004: * Copyright (c) 1999-2007
005: * UCS - unique computing solutions gmbh (http://www.ucs.at)
006: * All rights reserved
007: *
008: * The [fleXive](R) project is free software; you can redistribute
009: * it and/or modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation;
011: * either version 2 of the License, or (at your option) any
012: * later version.
013: *
014: * The GNU General Public License can be found at
015: * http://www.gnu.org/copyleft/gpl.html.
016: * A copy is found in the textfile GPL.txt and important notices to the
017: * license from the author are found in LICENSE.txt distributed with
018: * these libraries.
019: *
020: * This library is distributed in the hope that it will be useful,
021: * but WITHOUT ANY WARRANTY; without even the implied warranty of
022: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: * GNU General Public License for more details.
024: *
025: * For further information about UCS - unique computing solutions gmbh,
026: * please see the company website: http://www.ucs.at
027: *
028: * For further information about [fleXive](R), please see the
029: * project website: http://www.flexive.org
030: *
031: *
032: * This copyright notice MUST APPEAR in all copies of the file!
033: ***************************************************************/package com.flexive.faces.components.input;
034:
035: import com.flexive.faces.FxJsfUtils;
036: import com.flexive.faces.components.DeferredWriterTag;
037: import com.flexive.shared.CacheAdmin;
038: import com.flexive.shared.FxFormatUtils;
039: import com.flexive.shared.FxLanguage;
040: import com.flexive.shared.XPathElement;
041: import com.flexive.shared.content.FxPK;
042: import com.flexive.shared.structure.FxPropertyAssignment;
043: import com.flexive.shared.structure.FxSelectList;
044: import com.flexive.shared.value.*;
045: import com.flexive.war.servlet.ThumbnailServlet;
046: import org.apache.commons.lang.StringUtils;
047: import org.apache.myfaces.custom.date.HtmlInputDate;
048: import org.apache.myfaces.custom.fileupload.HtmlInputFileUpload;
049:
050: import javax.faces.component.UIComponent;
051: import javax.faces.component.UIInput;
052: import javax.faces.component.UISelectItems;
053: import javax.faces.component.html.*;
054: import javax.faces.context.ResponseWriter;
055: import javax.faces.context.FacesContext;
056: import javax.faces.model.SelectItem;
057: import java.io.IOException;
058: import java.text.MessageFormat;
059: import java.util.ArrayList;
060: import java.util.Collections;
061: import java.util.Date;
062: import java.util.List;
063:
064: /**
065: * Renders an FxValueInput component in edit mode.
066: *
067: * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
068: * @version $Rev: 184 $
069: */
070: class EditModeHelper extends RenderHelper {
071: private static final String REQUEST_EDITORINIT = "REQUEST_EDITORINIT";
072: private static final String JS_OBJECT = "fxValue";
073:
074: public EditModeHelper(ResponseWriter writer,
075: FxValueInput component, String clientId, FxValue value) {
076: super (writer, component, clientId, value);
077: }
078:
079: /**
080: * Renders a multi langugae input field.
081: *
082: * @throws java.io.IOException if the component could not be rendered
083: */
084: @Override
085: protected void encodeMultiLanguageField() throws IOException {
086: final List<FxLanguage> languages = FxValueInputRenderer
087: .getLanguages();
088: ensureDefaultLanguageExists(value, languages);
089: final String radioName = clientId
090: + FxValueInputRenderer.DEFAULT_LANGUAGE;
091:
092: new DeferredWriterTag(component, new ContainerWriter());
093:
094: final List<String> rowIds = new ArrayList<String>(languages
095: .size());
096: for (final FxLanguage language : languages) {
097: final String containerId = clientId
098: + FxValueInputRenderer.LANG_CONTAINER
099: + language.getId();
100: final String inputId = clientId
101: + FxValueInputRenderer.INPUT + language.getId();
102: rowIds.add("'" + containerId + "'");
103:
104: new DeferredWriterTag(component,
105: new DeferredWriterTag.DeferredWriter() {
106: public void render(ResponseWriter out)
107: throws IOException {
108: writer.startElement("div", null);
109: writer.writeAttribute("id", containerId,
110: null);
111: writer
112: .writeAttribute(
113: "class",
114: FxValueInputRenderer.CSS_LANG_CONTAINER,
115: null);
116: if (language.getId() != value
117: .getDefaultLanguage()) {
118: writer.writeAttribute("style",
119: "display:none", null);
120: }
121: }
122: });
123:
124: encodeField(inputId, language);
125: encodeDefaultLanguageRadio(radioName, language);
126:
127: new DeferredWriterTag(component,
128: new DeferredWriterTag.DeferredWriter() {
129: public void render(ResponseWriter out)
130: throws IOException {
131: writer.endElement("div");
132: }
133: });
134:
135: }
136:
137: // language select
138: final String languageSelectId = clientId
139: + FxValueInputRenderer.LANG_SELECT;
140: new DeferredWriterTag(component,
141: new DeferredWriterTag.DeferredWriter() {
142: public void render(ResponseWriter out)
143: throws IOException {
144: writer.startElement("select", null);
145: writer.writeAttribute("name", languageSelectId,
146: null);
147: writer.writeAttribute("class", "languages",
148: null);
149: writer.writeAttribute("onchange",
150: "document.getElementById('" + clientId
151: + "')." + JS_OBJECT
152: + ".onLanguageChanged(this)",
153: null);
154: for (FxLanguage language : languages) {
155: writer.startElement("option", null);
156: writer.writeAttribute("value", language
157: .getId(), null);
158: if (language.getId() == value
159: .getDefaultLanguage()) {
160: writer.writeAttribute("selected",
161: "selected", null);
162: }
163: writer.writeText(language.getLabel()
164: .getBestTranslation(), null);
165: writer.endElement("option");
166: }
167: writer.endElement("select");
168:
169: writer.startElement("br", null);
170: writer.writeAttribute("clear", "all", null);
171: writer.endElement("br");
172: writer.write(" ");
173: writer.endElement("div");
174:
175: // attach JS handler object to container div
176: writer
177: .write(MessageFormat
178: .format(
179: "<script language=\"javascript\">\n"
180: + "<!--\n"
181: + " document.getElementById(''{0}'')."
182: + JS_OBJECT
183: + " = new FxMultiLanguageValueInput(''{0}'', ''{1}'', [{2}]);\n"
184: + "//-->\n"
185: + "</script>",
186: clientId,
187: clientId
188: + FxValueInputRenderer.LANG_CONTAINER,
189: StringUtils.join(rowIds
190: .iterator(),
191: ',')));
192: }
193: });
194: }
195:
196: /**
197: * Ensure that the default language of the given value exists in languages.
198: * If it does not exist and languages is not empty, the first language is chosen
199: * as default language.
200: *
201: * @param value the FxValue to be checked
202: * @param languages the available languages
203: */
204: private void ensureDefaultLanguageExists(FxValue value,
205: List<FxLanguage> languages) {
206: boolean defaultLanguageExists = false;
207: for (FxLanguage language : languages) {
208: if (language.getId() == value.getDefaultLanguage()) {
209: defaultLanguageExists = true;
210: break;
211: }
212: }
213: if (!defaultLanguageExists && languages.size() > 0) {
214: value.setDefaultLanguage((int) languages.get(0).getId(),
215: true);
216: }
217: }
218:
219: /**
220: * Render the default language radiobutton for the given language.
221: *
222: * @param radioName name of the radio input control
223: * @param language the language for which this input should be rendered
224: * @throws IOException if a io error occured
225: */
226: private void encodeDefaultLanguageRadio(final String radioName,
227: final FxLanguage language) throws IOException {
228: new DeferredWriterTag(component,
229: new DeferredWriterTag.DeferredWriter() {
230: public void render(ResponseWriter out)
231: throws IOException {
232: writer.startElement("input", null);
233: writer.writeAttribute("type", "radio", null);
234: writer.writeAttribute("name", radioName, null);
235: writer.writeAttribute("value",
236: language.getId(), null);
237: writer.writeAttribute("class",
238: "fxValueDefaultLanguageRadio", null);
239: if (language.getId() == value
240: .getDefaultLanguage()) {
241: writer.writeAttribute("checked", "true",
242: null);
243: }
244: writer.endElement("input");
245: }
246: });
247: }
248:
249: /**
250: * {@inheritDoc}
251: */
252: @Override
253: protected void encodeField(String inputId, FxLanguage language)
254: throws IOException {
255: boolean multiLine = false;
256: if (language == null) {
257: new DeferredWriterTag(component, new ContainerWriter());
258: }
259: if (value != null && StringUtils.isNotBlank(value.getXPath())
260: && value instanceof FxString) {
261: multiLine = ((FxPropertyAssignment) CacheAdmin
262: .getEnvironment().getAssignment(value.getXPath()))
263: .isMultiLine();
264: }
265: if (value instanceof FxHTML || multiLine) {
266: renderTextArea(inputId, language);
267: } else if (value instanceof FxSelectOne) {
268: renderSelectOne(inputId, language);
269: } else if (value instanceof FxSelectMany) {
270: renderSelectMany(inputId, language);
271: } else if (value instanceof FxDate) {
272: renderDateInput(inputId, language);
273: } else if (value instanceof FxDateTime) {
274: renderDateTimeInput(inputId, language);
275: } else if (value instanceof FxReference) {
276: renderReferenceSelect(inputId, language);
277: } else if (value instanceof FxBinary) {
278: renderBinary(inputId, language);
279: } else if (value instanceof FxBoolean) {
280: renderCheckbox(inputId, language);
281: } else {
282: renderTextInput(inputId, language);
283: }
284: if (language == null) {
285: new DeferredWriterTag(component,
286: new DeferredWriterTag.DeferredWriter() {
287: public void render(ResponseWriter out)
288: throws IOException {
289: writer.endElement("div");
290: }
291: });
292: }
293: }
294:
295: private void renderTextInput(final String inputId,
296: final FxLanguage language) throws IOException {
297: new DeferredWriterTag(component,
298: new DeferredWriterTag.DeferredWriter() {
299: public void render(ResponseWriter out)
300: throws IOException {
301: writer.startElement("input", null);
302: writeHtmlAttributes();
303: writer.writeAttribute("type", "text", null);
304: writer.writeAttribute("name", inputId, null);
305: writer.writeAttribute("id", inputId, null);
306: if (value.getMaxInputLength() > 0) {
307: writer.writeAttribute("maxlength", value
308: .getMaxInputLength(), null);
309: }
310: writer.writeAttribute("value",
311: getTextValue(language), null);
312: writer.writeAttribute("class",
313: FxValueInputRenderer.CSS_TEXT_INPUT,
314: null);
315: writer.endElement("input");
316: }
317: });
318: }
319:
320: private void renderTextArea(final String inputId,
321: final FxLanguage language) throws IOException {
322: new DeferredWriterTag(component,
323: new DeferredWriterTag.DeferredWriter() {
324: public void render(ResponseWriter out)
325: throws IOException {
326: if (component.isForceLineInput()) {
327: renderTextInput(inputId, language);
328: return;
329: }
330: writer.startElement("textarea", null);
331: writer.writeAttribute("id", inputId, null);
332: writeHtmlAttributes();
333: if (value instanceof FxHTML) {
334: // render tinyMCE editor
335: // if (!FxJsfUtils.isAjaxRequest()) {
336: // when the page is updated via ajax, tinyMCE generates an additional hidden input
337: // and the text area is essential an anonymous div container (not sure why)
338: writer
339: .writeAttribute("name", inputId,
340: null);
341: // }
342: writer
343: .writeAttribute(
344: "class",
345: FxValueInputRenderer.CSS_TEXTAREA_HTML,
346: null);
347: writer.writeText(getTextValue(language),
348: null);
349: writer.endElement("textarea");
350: writer.startElement("script", null);
351: writer.writeAttribute("type",
352: "text/javascript", null);
353: if (FxJsfUtils.isAjaxRequest()
354: && FxJsfUtils.getRequest()
355: .getAttribute(
356: REQUEST_EDITORINIT) == null) {
357: // reset tinyMCE to avoid getDoc() error messages
358: writer
359: .write("tinyMCE.idCounter = 0;\n");
360: FxJsfUtils.getRequest().setAttribute(
361: REQUEST_EDITORINIT, true);
362: }
363: writer
364: .write("tinyMCE.execCommand('mceAddControl', false, '"
365: + inputId + "');\n");
366: if (FxJsfUtils.isAjaxRequest()) {
367: // explicitly set content for firefox, since it messes up HTML markup
368: // when populated directly from the textarea content
369: writer
370: .write("if (tinyMCE.isGecko) {\n");
371: writer
372: .write(" tinyMCE.execInstanceCommand('"
373: + inputId
374: + "', 'mceSetContent', false, '"
375: + FxFormatUtils
376: .escapeForJavaScript(
377: getTextValue(language),
378: false,
379: false)
380: + "');\n");
381: writer.write("}\n");
382: }
383: writer.endElement("script");
384: } else {
385: // render standard text area
386: writer
387: .writeAttribute("name", inputId,
388: null);
389: writer.writeAttribute("class",
390: FxValueInputRenderer.CSS_TEXTAREA,
391: null);
392: writer.writeText(getTextValue(language),
393: null);
394: writer.endElement("textarea");
395: }
396: }
397: });
398: }
399:
400: /**
401: * Render additional HTML attributes passed to the FxValueInput component.
402: *
403: * @throws IOException if the output could not be written
404: */
405: private void writeHtmlAttributes() throws IOException {
406: if (StringUtils.isNotBlank(component.getOnchange())) {
407: writer.writeAttribute("onchange", component.getOnchange(),
408: null);
409: }
410: }
411:
412: private void renderSelectOne(String inputId, FxLanguage language)
413: throws IOException {
414: final FxSelectOne selectValue = (FxSelectOne) value;
415: // create selectone component
416: final HtmlSelectOneListbox listbox = (HtmlSelectOneListbox) createUISelect(
417: inputId, HtmlSelectOneListbox.COMPONENT_TYPE);
418: listbox.setSize(1);
419: listbox
420: .setStyleClass(FxValueInputRenderer.CSS_INPUTELEMENTWIDTH);
421: // update posted value
422: listbox.setValue(String.valueOf(selectValue.getTranslation(
423: language).getId()));
424: storeSelectItems(listbox, selectValue.getSelectList());
425: }
426:
427: private void renderSelectMany(String inputId, FxLanguage language) {
428: final FxSelectMany selectValue = (FxSelectMany) value;
429: final SelectMany sm = selectValue.getTranslation(language) != null ? selectValue
430: .getTranslation(language)
431: : new SelectMany(selectValue.getSelectList());
432: final String[] selected = new String[sm.getSelected().size()];
433: for (int i = 0; i < selected.length; i++) {
434: selected[i] = String.valueOf(sm.getSelected().get(i)
435: .getId());
436: }
437: if (component.isForceLineInput()) {
438: // render a single line dropdown
439: final HtmlSelectOneListbox listbox = (HtmlSelectOneListbox) createUISelect(
440: inputId, HtmlSelectOneListbox.COMPONENT_TYPE);
441: listbox.setSize(1);
442: listbox
443: .setStyleClass(FxValueInputRenderer.CSS_INPUTELEMENTWIDTH);
444: if (selected.length > 0) {
445: // choose first selected element - other selections get discarded
446: listbox.setValue(selected[0]);
447: }
448: storeSelectItems(listbox, selectValue.getSelectList());
449: } else {
450: // render a "multiple" select list
451: final HtmlSelectManyListbox listbox = (HtmlSelectManyListbox) createUISelect(
452: inputId, HtmlSelectManyListbox.COMPONENT_TYPE);
453: listbox
454: .setStyleClass(FxValueInputRenderer.CSS_INPUTELEMENTWIDTH);
455: listbox.setSelectedValues(selected);
456: storeSelectItems(listbox, selectValue.getSelectList());
457: }
458: }
459:
460: private void addHtmlAttributes(UIComponent target) {
461: if (component.getOnchange() != null) {
462: //noinspection unchecked
463: target.getAttributes().put("onchange",
464: component.getOnchange());
465: }
466: }
467:
468: private UIInput createUISelect(String inputId, String componentType) {
469: final UIInput listbox = (UIInput) FxJsfUtils.addChildComponent(
470: component, componentType);
471: listbox.setId(stripForm(inputId));
472: addHtmlAttributes(listbox);
473: return listbox;
474: }
475:
476: private void storeSelectItems(UIInput listbox,
477: FxSelectList selectList) {
478: // store available items in select component
479: final UISelectItems selectItems = (UISelectItems) FxJsfUtils
480: .createComponent(UISelectItems.COMPONENT_TYPE);
481: final List<SelectItem> items = FxJsfUtils
482: .asSelectList(selectList);
483: Collections.sort(items, new FxJsfUtils.SelectItemSorter());
484: selectItems.setValue(items);
485: listbox.setConverter(FxJsfUtils.getApplication()
486: .createConverter(Long.class));
487: listbox.getChildren().add(selectItems);
488: }
489:
490: private void renderDateInput(String inputId, FxLanguage language) {
491: createInputDate(inputId, language);
492: }
493:
494: private void renderDateTimeInput(String inputId, FxLanguage language) {
495: createInputDate(inputId, language).setType("both");
496: }
497:
498: @SuppressWarnings({"unchecked"})
499: private HtmlInputDate createInputDate(String inputId,
500: FxLanguage language) {
501: final HtmlInputDate inputDate = (HtmlInputDate) FxJsfUtils
502: .addChildComponent(component,
503: HtmlInputDate.COMPONENT_TYPE);
504: inputDate.setId(stripForm(inputId));
505: if (!value.isTranslationEmpty(language)) {
506: inputDate.setValue(((FxValue<Date, ?>) value)
507: .getBestTranslation(language));
508: }
509: inputDate.setPopupCalendar(false);
510: return inputDate;
511: }
512:
513: private void renderReferenceSelect(String inputId,
514: FxLanguage language) throws IOException {
515: FxReference referenceValue = (FxReference) value;
516: final String popupLink = "javascript:openReferenceQueryPopup('"
517: + value.getXPath() + "', '" + inputId + "', '"
518: + getForm(inputId) + "')";
519: // render hidden input that contains the actual reference
520: final HtmlInputHidden hidden = (HtmlInputHidden) FxJsfUtils
521: .addChildComponent(component,
522: HtmlInputHidden.COMPONENT_TYPE);
523: hidden.setId(stripForm(inputId));
524: // render image container (we need this since the image id attribute does not get rendered)
525: final HtmlOutputLink imageContainer = (HtmlOutputLink) FxJsfUtils
526: .addChildComponent(component,
527: HtmlOutputLink.COMPONENT_TYPE);
528: imageContainer.setId(stripForm(inputId) + "_preview");
529: imageContainer.setValue(popupLink);
530: // render the image itself
531: final HtmlGraphicImage image = (HtmlGraphicImage) FxJsfUtils
532: .addChildComponent(imageContainer,
533: HtmlGraphicImage.COMPONENT_TYPE);
534: image.setStyle("border:0");
535: if (!value.isEmpty()
536: && ((language != null && !value
537: .isTranslationEmpty(language)) || language == null)) {
538: // render preview image
539: final FxPK translation = referenceValue
540: .getTranslation(language);
541: image.setUrl(ThumbnailServlet.getLink(translation,
542: BinaryDescriptor.PreviewSizes.PREVIEW2));
543: hidden.setValue(translation);
544: } else {
545: image.setUrl("/pub/images/empty.gif");
546: }
547: // render popup button
548: final HtmlOutputLink link = (HtmlOutputLink) FxJsfUtils
549: .addChildComponent(component,
550: HtmlOutputLink.COMPONENT_TYPE);
551: link.setValue(popupLink);
552: final HtmlGraphicImage button = (HtmlGraphicImage) FxJsfUtils
553: .addChildComponent(link,
554: HtmlGraphicImage.COMPONENT_TYPE);
555: button.setUrl("/adm/images/contentEditor/findReferences.png");
556: button.setStyle("border:0");
557: }
558:
559: private void renderBinary(String inputId, FxLanguage language)
560: throws IOException {
561: if (!value.isEmpty()
562: && (language == null || value
563: .translationExists((int) language.getId()))) {
564: final HtmlGraphicImage image = (HtmlGraphicImage) FxJsfUtils
565: .addChildComponent(component,
566: HtmlGraphicImage.COMPONENT_TYPE);
567: final BinaryDescriptor descriptor = ((FxBinary) value)
568: .getTranslation(language);
569: image.setUrl(ThumbnailServlet.getLink(XPathElement
570: .getPK(value.getXPath()),
571: BinaryDescriptor.PreviewSizes.PREVIEW2, value
572: .getXPath(), descriptor.getCreationTime(),
573: language));
574: }
575: final HtmlInputFileUpload upload = (HtmlInputFileUpload) FxJsfUtils
576: .addChildComponent(component,
577: HtmlInputFileUpload.COMPONENT_TYPE);
578: addHtmlAttributes(upload);
579: upload.setId(stripForm(inputId));
580: upload.setStyleClass("fxValueFileInput");
581: }
582:
583: private void renderCheckbox(String inputId, FxLanguage language)
584: throws IOException {
585: final HtmlSelectBooleanCheckbox checkbox = (HtmlSelectBooleanCheckbox) FxJsfUtils
586: .addChildComponent(component,
587: HtmlSelectBooleanCheckbox.COMPONENT_TYPE);
588: checkbox.setId(stripForm(inputId));
589: checkbox.setValue(value.getTranslation(language));
590: addHtmlAttributes(checkbox);
591: }
592:
593: private String getTextValue(FxLanguage language) {
594: if (value.isEmpty()) {
595: return "";
596: }
597: final Object writeValue = getWriteValue(language);
598: //noinspection unchecked
599: return value.isValid() ? value.getStringValue(writeValue)
600: : (writeValue != null ? writeValue.toString() : "");
601: }
602:
603: private Object getWriteValue(FxLanguage language) {
604: final Object writeValue;
605: if (language != null) {
606: //noinspection unchecked
607: writeValue = value.isTranslationEmpty(language) ? ""
608: : value.getTranslation(language);
609: } else {
610: //noinspection unchecked
611: writeValue = value.getDefaultTranslation();
612: }
613: return writeValue;
614: }
615:
616: private String stripForm(String inputId) {
617: return inputId.substring(inputId.lastIndexOf(':') + 1);
618: }
619:
620: private String getForm(String inputId) {
621: return inputId.substring(0, inputId.indexOf(':'));
622: }
623:
624: private class ContainerWriter implements
625: DeferredWriterTag.DeferredWriter {
626: public void render(ResponseWriter out) throws IOException {
627: writer.startElement("div", null);
628: writer.writeAttribute("id", clientId, null);
629: writer.writeAttribute("class",
630: FxValueInputRenderer.CSS_CONTAINER + " "
631: + value.getClass().getSimpleName()
632: + "Input", null);
633: }
634: }
635: }
|