View Javadoc
1   /*
2    * Copyright (c) 2011-2024 PrimeFaces Extensions
3    *
4    *  Permission is hereby granted, free of charge, to any person obtaining a copy
5    *  of this software and associated documentation files (the "Software"), to deal
6    *  in the Software without restriction, including without limitation the rights
7    *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    *  copies of the Software, and to permit persons to whom the Software is
9    *  furnished to do so, subject to the following conditions:
10   *
11   *  The above copyright notice and this permission notice shall be included in
12   *  all copies or substantial portions of the Software.
13   *
14   *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20   *  THE SOFTWARE.
21   */
22  package org.primefaces.extensions.component.monacoeditor;
23  
24  import java.io.IOException;
25  import java.util.AbstractMap;
26  import java.util.Arrays;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Map.Entry;
30  
31  import javax.faces.component.UIComponent;
32  import javax.faces.context.FacesContext;
33  import javax.faces.context.ResponseWriter;
34  
35  import org.primefaces.config.PrimeConfiguration;
36  import org.primefaces.context.PrimeApplicationContext;
37  import org.primefaces.util.WidgetBuilder;
38  
39  /**
40   * Base renderer for both the {@link MonacoEditorFramed framed} and {@link MonacoEditorInline inline} monaco editor.
41   *
42   * @since 11.1.0
43   */
44  abstract class MonacoDiffEditorBaseRenderer<TEditor extends MonacoDiffEditorBase>
45                                             extends
46                                             MonacoEditorCommonRenderer<TEditor, org.primefaces.extensions.model.monacoeditor.DiffEditorOptions> {
47      private static final String INPUT_SUFFIX = "_input";
48      private static final String INPUT_ORIGINAL_SUFFIX = "_input_original";
49  
50      private static final List<String> PASSTHROUGH_ATTRS = Arrays.asList(//
51                  "alt", "accesskey", "autocomplete", //
52                  "cols", "dir", "lang", //
53                  "maxlength", "placeholder", "rows", //
54                  "size", "title" //
55      );
56  
57      protected MonacoDiffEditorBaseRenderer(final Class<TEditor> clazz) {
58          super(clazz);
59      }
60  
61      @Override
62      public final void decode(final FacesContext context, final UIComponent component) {
63          final TEditor monacoEditor = componentClass.cast(component);
64  
65          final String clientId = monacoEditor.getClientId() + INPUT_SUFFIX;
66          final String originalClientId = monacoEditor.getClientId() + INPUT_ORIGINAL_SUFFIX;
67          final Map<String, String> params = context.getExternalContext().getRequestParameterMap();
68  
69          boolean update = false;
70          String originalValue = null;
71          String modifiedValue = null;
72  
73          // Do not allow modifications if component is not allowed to submit values.
74          // Read-only is fine, we should still accept the submitted value when read-only
75          if (!monacoEditor.isDisabled()) {
76              if (params.containsKey(clientId)) {
77                  modifiedValue = params.get(clientId);
78                  update = true;
79              }
80          }
81          if (!monacoEditor.isOriginalDisabled()) {
82              if (params.containsKey(originalClientId)) {
83                  originalValue = params.get(originalClientId);
84                  update = true;
85              }
86          }
87  
88          if (update) {
89              final Map.Entry<String, String> entry = new AbstractMap.SimpleEntry<>(originalValue, modifiedValue);
90              monacoEditor.setSubmittedValue(entry);
91          }
92  
93          // Decode behaviors
94          decodeBehaviors(context, component);
95      }
96  
97      @Override
98      protected final void encodeHiddenInput(final FacesContext context, final TEditor monacoEditor) throws IOException {
99          final ResponseWriter writer = context.getResponseWriter();
100         final String clientId = monacoEditor.getClientId();
101 
102         writer.startElement("div", null);
103         writer.writeAttribute("class", "ui-helper-hidden-accessible", null);
104         renderPassThruAttributes(context, monacoEditor, PASSTHROUGH_ATTRS);
105 
106         final org.primefaces.extensions.model.monaco.MonacoDiffEditorModel valueToRender;
107         valueToRender = (org.primefaces.extensions.model.monaco.MonacoDiffEditorModel) getValueToRender(
108                     context, monacoEditor);
109 
110         writer.startElement("textarea", null);
111         writer.writeAttribute("id", clientId + INPUT_SUFFIX, null);
112         writer.writeAttribute("name", clientId + INPUT_SUFFIX, null);
113         writer.writeAttribute("autocomplete", "off", null);
114         if (monacoEditor.isReadonly()) {
115             writer.writeAttribute("readonly", "readonly", null);
116         }
117         if (monacoEditor.isDisabled()) {
118             writer.writeAttribute("disabled", "disabled", null);
119         }
120         if (valueToRender != null && valueToRender.getModifiedValue() != null) {
121             writer.writeText(valueToRender.getModifiedValue(), null);
122         }
123         writer.endElement("textarea");
124 
125         writer.startElement("textarea", null);
126         writer.writeAttribute("id", clientId + INPUT_ORIGINAL_SUFFIX, null);
127         writer.writeAttribute("name", clientId + INPUT_ORIGINAL_SUFFIX, null);
128         writer.writeAttribute("autocomplete", "off", null);
129         if (monacoEditor.isOriginalDisabled()) {
130             writer.writeAttribute("disabled", "disabled", null);
131         }
132         if (monacoEditor.isOriginalReadonly()) {
133             writer.writeAttribute("readonly", "readonly", null);
134         }
135         if (valueToRender != null && valueToRender.getOriginalValue() != null) {
136             writer.writeText(valueToRender.getOriginalValue(), null);
137         }
138         writer.endElement("textarea");
139 
140         writer.endElement("div");
141     }
142 
143     @Override
144     public final Object getConvertedValue(final FacesContext context, final UIComponent component,
145                 final Object submittedValue) {
146         return convertedSubmittedValue(component, submittedValue);
147     }
148 
149     @Override
150     protected final void addBaseWidgetProperties(final FacesContext context, final WidgetBuilder wb,
151                 final TEditor monacoEditor)
152                 throws IOException {
153         wb.attr("originalDisabled", monacoEditor.isOriginalDisabled(), MonacoDiffEditorBase.DEFAULT_ORIGINAL_DISABLED);
154         wb.attr("originalReadonly", monacoEditor.isOriginalReadonly(), MonacoDiffEditorBase.DEFAULT_ORIGINAL_READONLY);
155         wb.attr("originalLanguage", monacoEditor.getOriginalLanguage(), MonacoDiffEditorBase.DEFAULT_ORIGINAL_LANGUAGE);
156         wb.attr("originalRequired", monacoEditor.isOriginalRequired(), MonacoDiffEditorBase.DEFAULT_ORIGINAL_REQUIRED);
157 
158         wb.attr("originalScheme", monacoEditor.getScheme(), MonacoDiffEditorBase.DEFAULT_ORIGINAL_SCHEME);
159         wb.attr("originalDirectory", monacoEditor.getDirectory(), MonacoDiffEditorBase.DEFAULT_ORIGINAL_DIRECTORY);
160         wb.attr("originalBasename", monacoEditor.getBasename(), MonacoDiffEditorBase.DEFAULT_ORIGINAL_BASENAME);
161         wb.attr("originalExtension", monacoEditor.getExtension(), MonacoDiffEditorBase.DEFAULT_ORIGINAL_EXTENSION);
162 
163         wb.callback("onoriginalblur", CALLBACK_SIGNATURE, monacoEditor.getOnoriginalblur());
164         wb.callback("onoriginalchange", CALLBACK_SIGNATURE, monacoEditor.getOnoriginalchange());
165         wb.callback("onoriginalfocus", CALLBACK_SIGNATURE, monacoEditor.getOnoriginalfocus());
166         wb.callback("onoriginalkeyup", CALLBACK_SIGNATURE, monacoEditor.getOnoriginalkeyup());
167         wb.callback("onoriginalmousedown", CALLBACK_SIGNATURE, monacoEditor.getOnoriginalmousedown());
168         wb.callback("onoriginalmousemove", CALLBACK_SIGNATURE, monacoEditor.getOnoriginalmousemove());
169         wb.callback("onoriginalmouseup", CALLBACK_SIGNATURE, monacoEditor.getOnoriginalmouseup());
170         wb.callback("onoriginalkeydown", CALLBACK_SIGNATURE, monacoEditor.getOnoriginalkeydown());
171         wb.callback("onoriginalpaste", CALLBACK_SIGNATURE, monacoEditor.getOnoriginalpaste());
172 
173         addWidgetProperties(context, wb, monacoEditor);
174     }
175 
176     protected abstract void addWidgetProperties(FacesContext context, WidgetBuilder wb, TEditor monacoEditor)
177                 throws IOException;
178 
179     @Override
180     protected String getLanguage(final TEditor monacoEditor) {
181         return monacoEditor.getLanguage();
182     }
183 
184     @Override
185     protected boolean isEntireEditorDisabled(final TEditor monacoEditor) {
186         return monacoEditor.isDisabled() && monacoEditor.isOriginalDisabled();
187     }
188 
189     private static Object getValueToRender(final FacesContext context, final MonacoDiffEditorBase component) {
190         final Object submittedValue = component.getSubmittedValue();
191         final PrimeConfiguration config = PrimeApplicationContext.getCurrentInstance(context).getConfig();
192         if (config.isInterpretEmptyStringAsNull() && submittedValue == null && !component.isLocalValueSet() &&
193                     context.isValidationFailed()
194                     && !component.isValid()) {
195             return null;
196         }
197         else if (submittedValue != null) {
198             return convertedSubmittedValue(component, submittedValue);
199         }
200         else {
201             final Object value = component.getValue();
202             return value instanceof org.primefaces.extensions.model.monaco.MonacoDiffEditorModel
203                         ? (org.primefaces.extensions.model.monaco.MonacoDiffEditorModel) value
204                         : org.primefaces.extensions.model.monaco.MonacoDiffEditorModel.empty();
205         }
206     }
207 
208     public static org.primefaces.extensions.model.monaco.MonacoDiffEditorModel convertedSubmittedValue(
209                 final UIComponent component, final Object submittedValue) {
210         if (submittedValue == null) {
211             return org.primefaces.extensions.model.monaco.MonacoDiffEditorModel.empty();
212         }
213         if (submittedValue instanceof org.primefaces.extensions.model.monaco.MonacoDiffEditorModel) {
214             return (org.primefaces.extensions.model.monaco.MonacoDiffEditorModel) submittedValue;
215         }
216         final MonacoDiffEditorBase editor = (MonacoDiffEditorBase) component;
217         final org.primefaces.extensions.model.monaco.MonacoDiffEditorModel currentModel = editor.getValue() != null
218                     //
219                     ? (org.primefaces.extensions.model.monaco.MonacoDiffEditorModel) editor.getValue()
220                     //
221                     : org.primefaces.extensions.model.monaco.MonacoDiffEditorModel.empty();
222         @SuppressWarnings("unchecked")
223         final Entry<String, String> value = (Map.Entry<String, String>) submittedValue;
224         final String originalValue = value.getKey() != null ? value.getKey() : currentModel.getOriginalValue();
225         final String modifiedValue = value.getValue() != null ? value.getValue() : currentModel.getModifiedValue();
226         return new org.primefaces.extensions.model.monaco.MonacoDiffEditorModel(originalValue, modifiedValue);
227     }
228 }