001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.acacia.client.widgets;
029
030import org.opencms.acacia.client.css.I_CmsLayoutBundle;
031import org.opencms.acacia.client.widgets.CmsTypografUtil.Typograf;
032import org.opencms.gwt.client.ui.input.form.I_CmsHasPlaceholder;
033import org.opencms.gwt.client.util.CmsDomUtil;
034import org.opencms.gwt.shared.CmsGwtConstants;
035import org.opencms.gwt.shared.CmsGwtLog;
036
037import java.util.Objects;
038
039import com.google.gwt.core.client.GWT;
040import com.google.gwt.dom.client.Element;
041import com.google.gwt.event.dom.client.BlurEvent;
042import com.google.gwt.event.dom.client.ClickEvent;
043import com.google.gwt.event.dom.client.FocusEvent;
044import com.google.gwt.event.dom.client.FocusHandler;
045import com.google.gwt.event.logical.shared.ValueChangeEvent;
046import com.google.gwt.event.logical.shared.ValueChangeHandler;
047import com.google.gwt.event.shared.HandlerRegistration;
048import com.google.gwt.uibinder.client.UiBinder;
049import com.google.gwt.uibinder.client.UiField;
050import com.google.gwt.uibinder.client.UiHandler;
051import com.google.gwt.user.client.ui.Composite;
052import com.google.gwt.user.client.ui.FocusPanel;
053import com.google.gwt.user.client.ui.HTMLPanel;
054import com.google.gwt.user.client.ui.TextBox;
055
056import elemental2.core.Global;
057import elemental2.dom.HTMLInputElement;
058import jsinterop.base.Js;
059import jsinterop.base.JsPropertyMap;
060
061/**
062 * Provides a display only widget, for use on a widget dialog.<p>
063 *
064 */
065public class CmsTextboxWidget extends Composite implements I_CmsEditWidget, I_CmsHasPlaceholder {
066
067    /**
068     * The UI binder interface.<p>
069     */
070    interface I_CmsTextboxWidgetUiBinder extends UiBinder<HTMLPanel, CmsTextboxWidget> {
071        // nothing to do
072    }
073
074    /** The UI binder instance. */
075    private static I_CmsTextboxWidgetUiBinder uiBinder = GWT.create(I_CmsTextboxWidgetUiBinder.class);
076
077    /** The fader of this widget. */
078    @UiField
079    FocusPanel m_fadePanel;
080
081    /**The main panel of this widget. */
082    HTMLPanel m_mainPanel;
083
084    /** The input test area.*/
085    @UiField
086    TextBox m_textbox;
087
088    /** The token to control activation. */
089    private boolean m_active = true;
090
091    /** The previous value. */
092    private String m_previousValue;
093
094    /** Typograf instance used for typography, if configured. */
095    private Typograf m_typograf;
096
097    /** The value changed handler initialized flag. */
098    private boolean m_valueChangeHandlerInitialized;
099
100    /**
101     * Creates a new display widget.<p>
102     *
103     * @param config the widget configuration
104     */
105    public CmsTextboxWidget(String config) {
106
107        m_mainPanel = uiBinder.createAndBindUi(this);
108        if (config != null) {
109            try {
110                JsPropertyMap<String> configMap = Js.cast(Global.JSON.parse(config));
111                String locale = configMap.get(CmsGwtConstants.JSON_INPUT_LOCALE);
112                String typograf = configMap.get(CmsGwtConstants.JSON_INPUT_TYPOGRAF);
113                boolean typografEnabled = "auto".equals(typograf);
114                if ((locale != null) && typografEnabled && Typograf.hasLocale(locale)) {
115                    m_typograf = CmsTypografUtil.createLiveInstance(locale);
116                }
117            } catch (Exception e) {
118                CmsGwtLog.log(e.getMessage());
119            }
120        }
121
122        initWidget(m_mainPanel);
123    }
124
125    /**
126     * @see com.google.gwt.event.dom.client.HasFocusHandlers#addFocusHandler(com.google.gwt.event.dom.client.FocusHandler)
127     */
128    public HandlerRegistration addFocusHandler(FocusHandler handler) {
129
130        return addDomHandler(handler, FocusEvent.getType());
131    }
132
133    /**
134     * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler)
135     */
136    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) {
137
138        // Initialization code
139        if (!m_valueChangeHandlerInitialized) {
140            m_valueChangeHandlerInitialized = true;
141            HTMLInputElement inputElem = Js.cast(m_textbox.getElement());
142            inputElem.addEventListener("input", event -> {
143                if (m_typograf != null) {
144                    try {
145                        String oldValue = inputElem.value;
146                        String newValue = CmsTypografUtil.transform(m_typograf, oldValue);
147                        if (!Objects.equals(oldValue, newValue)) {
148                            int pos = inputElem.selectionStart;
149                            inputElem.value = newValue;
150                            inputElem.setSelectionRange(pos, pos);
151                        }
152                    } catch (Exception e) {
153                        CmsGwtLog.log(e.getMessage());
154                    }
155                }
156                fireValueChange(false);
157            });
158        }
159        return addHandler(handler, ValueChangeEvent.getType());
160    }
161
162    /**
163     * Represents a value change event.<p>
164     *
165     */
166    public void fireChangeEvent() {
167
168        String result = "";
169        if (m_textbox.getText() != null) {
170            result = m_textbox.getText();
171        }
172
173        ValueChangeEvent.fire(this, result);
174    }
175
176    /**
177     * @see com.google.gwt.user.client.ui.HasValue#getValue()
178     */
179    public String getValue() {
180
181        return m_textbox.getText();
182    }
183
184    /**
185     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#isActive()
186     */
187    public boolean isActive() {
188
189        return m_active;
190    }
191
192    /**
193     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#onAttachWidget()
194     */
195    public void onAttachWidget() {
196
197        super.onAttach();
198    }
199
200    /**
201     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#owns(com.google.gwt.dom.client.Element)
202     */
203    public boolean owns(Element element) {
204
205        // TODO implement this in case we want the delete behavior for optional fields
206        return false;
207
208    }
209
210    /**
211     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#setActive(boolean)
212     */
213    public void setActive(boolean active) {
214
215        if (m_active == active) {
216            return;
217        }
218
219        m_active = active;
220        if (m_active) {
221            getElement().removeClassName(I_CmsLayoutBundle.INSTANCE.form().inActive());
222            getElement().focus();
223        } else {
224            getElement().addClassName(I_CmsLayoutBundle.INSTANCE.form().inActive());
225        }
226        if (active) {
227            fireChangeEvent();
228        }
229    }
230
231    /**
232     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#setName(java.lang.String)
233     */
234    public void setName(String name) {
235
236        m_textbox.setName(name);
237
238    }
239
240    /**
241     * @see org.opencms.gwt.client.ui.input.form.I_CmsHasPlaceholder#setPlaceholder(java.lang.String)
242     */
243    @Override
244    public void setPlaceholder(String placeholder) {
245
246        if (placeholder == null) {
247            placeholder = "";
248        }
249        // This relies on the TextBox element being *just* a text input field
250        HTMLInputElement input = Js.cast(m_textbox.getElement());
251        input.placeholder = placeholder;
252    }
253
254    /**
255     * @see com.google.gwt.user.client.ui.UIObject#setTitle(java.lang.String)
256     */
257    @Override
258    public void setTitle(String title) {
259
260        if ((title.length() * 6.88) > m_mainPanel.getOffsetWidth()) {
261            m_mainPanel.getElement().setTitle(title);
262        } else {
263            m_mainPanel.getElement().setTitle("");
264        }
265    }
266
267    /**
268     * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object)
269     */
270    public void setValue(String value) {
271
272        m_textbox.setText(value);
273    }
274
275    /**
276     * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object, boolean)
277     */
278    public void setValue(String value, boolean fireEvents) {
279
280        // set the saved value to the textArea
281        m_textbox.setText(value);
282        m_previousValue = value;
283        if (fireEvents) {
284            fireChangeEvent();
285        }
286    }
287
288    /**
289     * Fires the value change event, if the value has changed.<p>
290     *
291     * @param force <code>true</code> to force firing the event, not regarding an actually changed value
292     */
293    protected void fireValueChange(boolean force) {
294
295        String currentValue = getValue();
296        if (force || !currentValue.equals(m_previousValue)) {
297            m_previousValue = currentValue;
298            ValueChangeEvent.fire(this, currentValue);
299        }
300    }
301
302    /**
303     * Handles fade panel clicks.<p>
304     *
305     * @param event the click event
306     */
307    @UiHandler("m_fadePanel")
308    void onFadeClick(ClickEvent event) {
309
310        m_textbox.setFocus(true);
311        m_textbox.setCursorPos(m_textbox.getText().length());
312    }
313
314    /**
315     * Handles text box blur.<p>
316     *
317     * @param event the blur event
318     */
319    @UiHandler("m_textbox")
320    void onTextboxBlur(BlurEvent event) {
321
322        m_mainPanel.add(m_fadePanel);
323        setTitle(m_textbox.getText());
324    }
325
326    /**
327     * Handles text box focus.<p>
328     *
329     * @param event the focus event
330     */
331    @UiHandler("m_textbox")
332    void onTextboxFocus(FocusEvent event) {
333
334        m_mainPanel.remove(m_fadePanel);
335        setTitle("");
336        CmsDomUtil.fireFocusEvent(this);
337    }
338
339    /**
340     * Handles text box value change.<p>
341     *
342     * @param event the value change event
343     */
344    @UiHandler("m_textbox")
345    void onTextboxValueChange(ValueChangeEvent<String> event) {
346
347        fireValueChange(false);
348    }
349
350}