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_CmsWidgetsLayoutBundle;
031import org.opencms.acacia.shared.CmsWidgetUtil;
032import org.opencms.gwt.client.CmsCoreProvider;
033import org.opencms.gwt.client.I_CmsHasResizeOnShow;
034import org.opencms.gwt.client.Messages;
035import org.opencms.gwt.client.rpc.CmsRpcAction;
036import org.opencms.gwt.client.ui.CmsPopup;
037import org.opencms.gwt.client.ui.input.CmsCategoryField;
038import org.opencms.gwt.client.ui.input.category.CmsCategoryTree;
039import org.opencms.gwt.shared.CmsCategoryTreeEntry;
040
041import java.util.HashSet;
042import java.util.Iterator;
043import java.util.List;
044import java.util.Map;
045import java.util.Set;
046
047import com.google.gwt.dom.client.Element;
048import com.google.gwt.event.dom.client.ClickEvent;
049import com.google.gwt.event.dom.client.ClickHandler;
050import com.google.gwt.event.dom.client.FocusEvent;
051import com.google.gwt.event.dom.client.FocusHandler;
052import com.google.gwt.event.logical.shared.CloseEvent;
053import com.google.gwt.event.logical.shared.CloseHandler;
054import com.google.gwt.event.logical.shared.ValueChangeEvent;
055import com.google.gwt.event.logical.shared.ValueChangeHandler;
056import com.google.gwt.event.shared.HandlerRegistration;
057import com.google.gwt.user.client.Command;
058import com.google.gwt.user.client.DOM;
059import com.google.gwt.user.client.Event;
060import com.google.gwt.user.client.Event.NativePreviewEvent;
061import com.google.gwt.user.client.Event.NativePreviewHandler;
062import com.google.gwt.user.client.ui.Composite;
063import com.google.gwt.user.client.ui.PopupPanel;
064
065import elemental2.dom.DOMRect;
066import elemental2.dom.DomGlobal;
067import jsinterop.base.Js;
068
069/**
070 * Provides a standard HTML form category widget, for use on a widget dialog.<p>
071 */
072public class CmsCategoryWidget extends Composite implements I_CmsEditWidget, I_CmsHasResizeOnShow {
073
074    /**
075     * Drag and drop event preview handler.<p>
076     *
077     * To be used while dragging.<p>
078     */
079    protected class CloseEventPreviewHandler implements NativePreviewHandler {
080
081        /**
082         * @see com.google.gwt.user.client.Event.NativePreviewHandler#onPreviewNativeEvent(com.google.gwt.user.client.Event.NativePreviewEvent)
083         */
084        public void onPreviewNativeEvent(NativePreviewEvent event) {
085
086            Event nativeEvent = Event.as(event.getNativeEvent());
087            switch (DOM.eventGetType(nativeEvent)) {
088                case Event.ONMOUSEWHEEL:
089                    int x = nativeEvent.getClientX();
090                    int y = nativeEvent.getClientY();
091                    elemental2.dom.Element popupElem = Js.cast(m_cmsPopup.getElement());
092                    DOMRect rect = popupElem.getBoundingClientRect();
093                    if ((x < rect.left) || (x > rect.right) || (y < rect.top) || (y > rect.bottom)) {
094                        closePopup();
095                    }
096                    break;
097                default:
098                    // do nothing
099            }
100        }
101
102    }
103
104    /** Configuration parameter to set the category to display. */
105    private static final String CONFIGURATION_CATEGORY = "category";
106
107    /** Configuration parameter to set the collapsing state when opening the selection. */
108    private static final String CONFIGURATION_COLLAPSED = "collapsed";
109
110    /** Configuration parameter to set the 'selection type' parameter. */
111    private static final String CONFIGURATION_PARENTSELECTION = "parentselection";
112
113    /** Set the reference url relative to which category repositories are shown. */
114    private static final String CONFIGURATION_REFPATH = "refpath";
115
116    /** Configuration parameter to set the 'selection type' parameter. */
117    private static final String CONFIGURATION_SELECTIONTYPE = "selectiontype";
118
119    /** Configuration parameter to set flag, indicating if categories should be shown separated by repository. */
120    private static final String CONFIGURATION_SHOW_WITH_REPOSITORY = "showwithrepository";
121
122    /** Configuration parameter to set the default height. */
123    private static final int DEFAULT_HEIGHT = 30;
124
125    /** Configuration parameter to set the maximal height. */
126    private static final int MAX_HEIGHT = 242;
127
128    /** Category widget. */
129    protected CmsCategoryField m_categoryField;
130
131    /** The priview handler. */
132    protected HandlerRegistration m_previewHandlerRegistration;
133
134    /** List of all category folder. */
135    protected List<CmsCategoryTreeEntry> m_resultList;
136
137    /** The category field. */
138    CmsCategoryTree m_cmsCategoryTree;
139
140    /** The popup panel. */
141    CmsPopup m_cmsPopup;
142
143    /** Height of the display field. */
144    int m_height = DEFAULT_HEIGHT;
145
146    /** Flag indicating the category tree is being loaded. */
147    boolean m_loadingCategoryTree;
148
149    /** List of all selected categories. */
150    Set<String> m_selected;
151
152    /** Map of selected Values in relation to the select level. */
153    String m_selectedValue;
154
155    /** Value of the activation. */
156    private boolean m_active = true;
157
158    /** Single category folder. */
159    private String m_category = "";
160
161    /** Sets the value if the parent should be selected with the children. */
162    private boolean m_children;
163
164    /** If true, the category selection opens with collapsed category trees. */
165    private boolean m_collapsed;
166
167    /** Is true if only one value is set in xml. */
168    private boolean m_isSingleValue;
169
170    /** List of all possible category folder. */
171    private String m_refPath;
172
173    /** If true, the categories are shown separate for each repository. */
174    private boolean m_showWithRepository;
175
176    /**
177     * Constructs an CmsComboWidget with the in XSD schema declared configuration.<p>
178     * @param config The configuration string given from OpenCms XSD
179     */
180    public CmsCategoryWidget(String config) {
181
182        m_categoryField = new CmsCategoryField();
183        m_selected = new HashSet<String>();
184        //parse configuration string and set member variables
185        parseConfiguration(config);
186        m_categoryField.setParentSelection(m_children);
187        m_categoryField.getScrollPanel().addStyleName(I_CmsWidgetsLayoutBundle.INSTANCE.widgetCss().categoryPanel());
188        m_categoryField.addDomHandler(new ClickHandler() {
189
190            public void onClick(ClickEvent event) {
191
192                if ((m_cmsPopup == null) || !m_cmsPopup.isShowing()) {
193                    openPopup();
194                } else {
195                    closePopup();
196                }
197
198            }
199        }, ClickEvent.getType());
200        initWidget(m_categoryField);
201
202    }
203
204    /**
205     * @see com.google.gwt.event.dom.client.HasFocusHandlers#addFocusHandler(com.google.gwt.event.dom.client.FocusHandler)
206     */
207    public HandlerRegistration addFocusHandler(FocusHandler handler) {
208
209        return addDomHandler(handler, FocusEvent.getType());
210    }
211
212    /**
213     * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler)
214     */
215    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) {
216
217        return addHandler(handler, ValueChangeEvent.getType());
218    }
219
220    /**
221     * Represents a value change event.<p>
222     */
223    public void fireChangeEvent() {
224
225        ValueChangeEvent.fire(this, getValue());
226
227    }
228
229    /**
230     * @see com.google.gwt.user.client.ui.HasValue#getValue()
231     */
232    public String getValue() {
233
234        String result = "";
235        int y = 0;
236        if (m_isSingleValue) {
237            if (m_selected.size() != 0) {
238                result = m_selected.iterator().next();
239            } else {
240                result = "";
241            }
242        } else {
243            Iterator<String> i = m_categoryField.getAllSitePath().iterator();
244            while (i.hasNext()) {
245                if (y != 0) {
246                    result += ",";
247
248                }
249                result += i.next();
250                y++;
251            }
252        }
253        return result;
254    }
255
256    /**
257     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#isActive()
258     */
259    public boolean isActive() {
260
261        return m_active;
262    }
263
264    /**
265     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#onAttachWidget()
266     */
267    public void onAttachWidget() {
268
269        super.onAttach();
270    }
271
272    /**
273     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#owns(com.google.gwt.dom.client.Element)
274     */
275    public boolean owns(Element element) {
276
277        return getElement().isOrHasChild(element);
278    }
279
280    /**
281     * @see org.opencms.gwt.client.I_CmsHasResizeOnShow#resizeOnShow()
282     */
283    public void resizeOnShow() {
284
285        m_categoryField.resizeOnShow();
286    }
287
288    /**
289     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#setActive(boolean)
290     */
291    public void setActive(boolean active) {
292
293        if (m_active == active) {
294            return;
295        }
296        m_active = active;
297        // only fire change if the widget was activated
298        if (m_active) {
299            fireChangeEvent();
300        }
301    }
302
303    /**
304     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#setName(java.lang.String)
305     */
306    public void setName(String name) {
307
308        // no input field so nothing to do.
309    }
310
311    /**
312     * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object)
313     */
314    public void setValue(String value) {
315
316        setValue(value, false);
317
318    }
319
320    /**
321     * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object, boolean)
322     */
323    public void setValue(String value, boolean fireEvents) {
324
325        String[] selectedArray = value.split(",");
326        m_selected.clear();
327        for (String selectedCat : selectedArray) {
328            m_selected.add(selectedCat);
329        }
330
331        displayValue();
332
333        if (fireEvents) {
334            fireChangeEvent();
335        }
336    }
337
338    /**
339     * Is called to close the popup and show the new values.<p>
340     */
341    protected void closePopup() {
342
343        List<String> result;
344
345        if (m_previewHandlerRegistration != null) {
346            m_previewHandlerRegistration.removeHandler();
347            m_previewHandlerRegistration = null;
348        }
349        if (m_isSingleValue) {
350            result = m_cmsCategoryTree.getSelected();
351        } else {
352            result = m_cmsCategoryTree.getAllSelectedSitePath();
353        }
354        m_selected.clear();
355        m_selected.addAll(result);
356        displayValue();
357        m_cmsPopup.hide();
358        fireChangeEvent();
359
360    }
361
362    /**
363     * Is called to open the popup.<p>
364     */
365    protected void openPopup() {
366
367        elemental2.dom.Element myElem = Js.cast(getElement());
368        boolean center = false;
369        int spaceForTree = (int)(DomGlobal.window.innerHeight - myElem.getBoundingClientRect().bottom) - 115;
370        if (spaceForTree < 300) {
371            spaceForTree = 300;
372            center = true;
373        }
374        if (m_cmsPopup == null) {
375            int width = Math.max(getOffsetWidth(), CmsPopup.WIDE_WIDTH);
376            m_cmsPopup = new CmsPopup(Messages.get().key(Messages.GUI_DIALOG_CATEGORIES_TITLE_0), width);
377
378            m_cmsCategoryTree = new CmsCategoryTree(
379                m_selected,
380                spaceForTree,
381                m_isSingleValue,
382                m_resultList,
383                m_collapsed);
384            m_cmsPopup.add(m_cmsCategoryTree);
385            m_cmsPopup.setModal(false);
386            m_cmsPopup.setAutoHideEnabled(true);
387            m_cmsPopup.addCloseHandler(new CloseHandler<PopupPanel>() {
388
389                public void onClose(CloseEvent<PopupPanel> event) {
390
391                    closePopup();
392
393                }
394            });
395            m_cmsPopup.addDialogClose(new Command() {
396
397                public void execute() {
398
399                    // do nothing all will done in onClose();
400                }
401            });
402        }
403        if (m_previewHandlerRegistration != null) {
404            m_previewHandlerRegistration.removeHandler();
405        }
406        m_previewHandlerRegistration = Event.addNativePreviewHandler(new CloseEventPreviewHandler());
407        m_cmsCategoryTree.truncate("CATEGORIES", CmsPopup.WIDE_WIDTH - 20);
408        if (center) {
409            m_cmsPopup.center();
410        } else {
411            m_cmsPopup.showRelativeTo(m_categoryField);
412        }
413    }
414
415    /**
416     * Generates the right height for the view.<p>
417     */
418    protected void setHeight() {
419
420        if (m_categoryField.getValuesSet() > 0) {
421            m_height = (m_categoryField.getValuesSet() * 26) + 4;
422
423            if (m_height > MAX_HEIGHT) {
424                m_height = MAX_HEIGHT;
425                m_categoryField.getScrollPanel().setResizable(true);
426            } else {
427                m_categoryField.getScrollPanel().setResizable(false);
428            }
429        } else {
430            m_height = DEFAULT_HEIGHT;
431
432            m_categoryField.getScrollPanel().setResizable(false);
433        }
434
435        m_categoryField.setHeight(m_height);
436
437    }
438
439    /**
440     * Displays the current value.<p>
441     */
442    private void displayValue() {
443
444        if (m_resultList == null) {
445            if (!m_loadingCategoryTree) {
446                m_loadingCategoryTree = true;
447                // generate a list of all configured categories.
448                final String category = m_category;
449                final String refPath = m_refPath;
450                final boolean showWithRepository = m_showWithRepository;
451                CmsRpcAction<List<CmsCategoryTreeEntry>> action = new CmsRpcAction<List<CmsCategoryTreeEntry>>() {
452
453                    /**
454                     * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
455                     */
456                    @Override
457                    public void execute() {
458
459                        CmsCoreProvider.getService().getCategories(
460                            category,
461                            true,
462                            refPath,
463                            showWithRepository,
464                            m_selected,
465                            this);
466
467                    }
468
469                    /**
470                     * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
471                     */
472                    @Override
473                    protected void onResponse(List<CmsCategoryTreeEntry> result) {
474
475                        // copy the result to the global variable.
476                        m_resultList = result;
477                        m_loadingCategoryTree = false;
478                        // start to generate the tree view.
479                        m_categoryField.buildCategoryTree(m_resultList, m_selected);
480                        setHeight();
481                    }
482
483                };
484                action.execute();
485            }
486        } else {
487            m_categoryField.buildCategoryTree(m_resultList, m_selected);
488            setHeight();
489        }
490    }
491
492    /**
493     * Help function to parse the configuration.<p>
494     * @param configuration the value to be parsed.
495     *
496     */
497    private void parseConfiguration(String configuration) {
498
499        Map<String, String> configOptions = CmsWidgetUtil.parsePipeSeparatedConfigString(configuration);
500        if (!configOptions.isEmpty()) {
501            m_category = CmsWidgetUtil.getStringOption(configOptions, CONFIGURATION_CATEGORY, m_category);
502            m_isSingleValue = !("multi".equals(
503                CmsWidgetUtil.getStringOption(configOptions, CONFIGURATION_SELECTIONTYPE, "single")));
504            m_children = CmsWidgetUtil.getBooleanOption(configOptions, CONFIGURATION_PARENTSELECTION);
505            m_collapsed = CmsWidgetUtil.getBooleanOption(configOptions, CONFIGURATION_COLLAPSED);
506            m_showWithRepository = CmsWidgetUtil.getBooleanOption(configOptions, CONFIGURATION_SHOW_WITH_REPOSITORY);
507            m_refPath = CmsWidgetUtil.getStringOption(configOptions, CONFIGURATION_REFPATH, m_refPath);
508        }
509    }
510}