001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://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: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://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    /**
178     * Constructs an CmsComboWidget with the in XSD schema declared configuration.<p>
179     * @param config The configuration string given from OpenCms XSD
180     */
181    public CmsCategoryWidget(String config) {
182
183        m_categoryField = new CmsCategoryField();
184        m_selected = new HashSet<String>();
185        //parse configuration string and set member variables
186        parseConfiguration(config);
187        m_categoryField.setParentSelection(m_children);
188        m_categoryField.getScrollPanel().addStyleName(I_CmsWidgetsLayoutBundle.INSTANCE.widgetCss().categoryPanel());
189        m_categoryField.addDomHandler(new ClickHandler() {
190
191            public void onClick(ClickEvent event) {
192
193                if ((m_cmsPopup == null) || !m_cmsPopup.isShowing()) {
194                    openPopup();
195                } else {
196                    closePopup();
197                }
198
199            }
200        }, ClickEvent.getType());
201        initWidget(m_categoryField);
202
203    }
204
205    /**
206     * @see com.google.gwt.event.dom.client.HasFocusHandlers#addFocusHandler(com.google.gwt.event.dom.client.FocusHandler)
207     */
208    public HandlerRegistration addFocusHandler(FocusHandler handler) {
209
210        return addDomHandler(handler, FocusEvent.getType());
211    }
212
213    /**
214     * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler)
215     */
216    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) {
217
218        return addHandler(handler, ValueChangeEvent.getType());
219    }
220
221    /**
222     * Represents a value change event.<p>
223     */
224    public void fireChangeEvent() {
225
226        ValueChangeEvent.fire(this, getValue());
227
228    }
229
230    /**
231     * @see com.google.gwt.user.client.ui.HasValue#getValue()
232     */
233    public String getValue() {
234
235        String result = "";
236        int y = 0;
237        if (m_isSingleValue) {
238            if (m_selected.size() != 0) {
239                result = m_selected.iterator().next();
240            } else {
241                result = "";
242            }
243        } else {
244            Iterator<String> i = m_categoryField.getAllSitePath().iterator();
245            while (i.hasNext()) {
246                if (y != 0) {
247                    result += ",";
248
249                }
250                result += i.next();
251                y++;
252            }
253        }
254        return result;
255    }
256
257    /**
258     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#isActive()
259     */
260    public boolean isActive() {
261
262        return m_active;
263    }
264
265    /**
266     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#onAttachWidget()
267     */
268    public void onAttachWidget() {
269
270        super.onAttach();
271    }
272
273    /**
274     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#owns(com.google.gwt.dom.client.Element)
275     */
276    public boolean owns(Element element) {
277
278        return getElement().isOrHasChild(element);
279    }
280
281    /**
282     * @see org.opencms.gwt.client.I_CmsHasResizeOnShow#resizeOnShow()
283     */
284    public void resizeOnShow() {
285
286        m_categoryField.resizeOnShow();
287    }
288
289    /**
290     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#setActive(boolean)
291     */
292    public void setActive(boolean active) {
293
294        if (m_active == active) {
295            return;
296        }
297        m_active = active;
298        // only fire change if the widget was activated
299        if (m_active) {
300            fireChangeEvent();
301        }
302    }
303
304    /**
305     * @see org.opencms.acacia.client.widgets.I_CmsEditWidget#setName(java.lang.String)
306     */
307    public void setName(String name) {
308
309        // no input field so nothing to do.
310    }
311
312    /**
313     * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object)
314     */
315    public void setValue(String value) {
316
317        setValue(value, false);
318
319    }
320
321    /**
322     * @see com.google.gwt.user.client.ui.HasValue#setValue(java.lang.Object, boolean)
323     */
324    public void setValue(String value, boolean fireEvents) {
325
326        String[] selectedArray = value.split(",");
327        m_selected.clear();
328        for (String selectedCat : selectedArray) {
329            m_selected.add(selectedCat);
330        }
331
332        displayValue();
333
334        if (fireEvents) {
335            fireChangeEvent();
336        }
337    }
338
339    /**
340     * Is called to close the popup and show the new values.<p>
341     */
342    protected void closePopup() {
343
344        List<String> result;
345
346        if (m_previewHandlerRegistration != null) {
347            m_previewHandlerRegistration.removeHandler();
348            m_previewHandlerRegistration = null;
349        }
350        if (m_isSingleValue) {
351            result = m_cmsCategoryTree.getSelected();
352        } else {
353            result = m_cmsCategoryTree.getAllSelectedSitePath();
354        }
355        m_selected.clear();
356        m_selected.addAll(result);
357        displayValue();
358        m_cmsPopup.hide();
359        fireChangeEvent();
360
361    }
362
363    /**
364     * Is called to open the popup.<p>
365     */
366    protected void openPopup() {
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
379            m_cmsCategoryTree = new CmsCategoryTree(
380                m_selected,
381                spaceForTree,
382                m_isSingleValue,
383                m_resultList,
384                m_collapsed);
385            m_cmsPopup.add(m_cmsCategoryTree);
386            m_cmsPopup.setModal(false);
387            m_cmsPopup.setAutoHideEnabled(true);
388            m_cmsPopup.addCloseHandler(new CloseHandler<PopupPanel>() {
389
390                public void onClose(CloseEvent<PopupPanel> event) {
391
392                    closePopup();
393
394                }
395            });
396            m_cmsPopup.addDialogClose(new Command() {
397
398                public void execute() {
399
400                    // do nothing all will done in onClose();
401                }
402            });
403        }
404        if (m_previewHandlerRegistration != null) {
405            m_previewHandlerRegistration.removeHandler();
406        }
407        m_previewHandlerRegistration = Event.addNativePreviewHandler(new CloseEventPreviewHandler());
408        m_cmsCategoryTree.truncate("CATEGORIES", CmsPopup.WIDE_WIDTH - 20);
409        if (center) {
410            m_cmsPopup.center();
411        } else {
412            m_cmsPopup.showRelativeTo(m_categoryField);
413        }
414    }
415
416    /**
417     * Generates the right height for the view.<p>
418     * */
419    protected void setHeight() {
420
421        if (m_categoryField.getValuesSet() > 0) {
422            m_height = (m_categoryField.getValuesSet() * 26) + 4;
423
424            if (m_height > MAX_HEIGHT) {
425                m_height = MAX_HEIGHT;
426                m_categoryField.getScrollPanel().setResizable(true);
427            } else {
428                m_categoryField.getScrollPanel().setResizable(false);
429            }
430        } else {
431            m_height = DEFAULT_HEIGHT;
432
433            m_categoryField.getScrollPanel().setResizable(false);
434        }
435
436        m_categoryField.setHeight(m_height);
437
438    }
439
440    /**
441     * Displays the current value.<p>
442     */
443    private void displayValue() {
444
445        if (m_resultList == null) {
446            if (!m_loadingCategoryTree) {
447                m_loadingCategoryTree = true;
448                // generate a list of all configured categories.
449                final String category = m_category;
450                final String refPath = m_refPath;
451                final boolean showWithRepository = m_showWithRepository;
452                CmsRpcAction<List<CmsCategoryTreeEntry>> action = new CmsRpcAction<List<CmsCategoryTreeEntry>>() {
453
454                    /**
455                     * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
456                     */
457                    @Override
458                    public void execute() {
459
460                        CmsCoreProvider.getService().getCategories(
461                            category,
462                            true,
463                            refPath,
464                            showWithRepository,
465                            m_selected,
466                            this);
467
468                    }
469
470                    /**
471                     * @see org.opencms.gwt.client.rpc.CmsRpcAction#onResponse(java.lang.Object)
472                     */
473                    @Override
474                    protected void onResponse(List<CmsCategoryTreeEntry> result) {
475
476                        // copy the result to the global variable.
477                        m_resultList = result;
478                        m_loadingCategoryTree = false;
479                        // start to generate the tree view.
480                        m_categoryField.buildCategoryTree(m_resultList, m_selected);
481                        setHeight();
482                    }
483
484                };
485                action.execute();
486            }
487        } else {
488            m_categoryField.buildCategoryTree(m_resultList, m_selected);
489            setHeight();
490        }
491    }
492
493    /**
494     * Help function to parse the configuration.<p>
495     * @param configuration the value to be parsed.
496     *
497     * */
498    private void parseConfiguration(String configuration) {
499
500        Map<String, String> configOptions = CmsWidgetUtil.parsePipeSeparatedConfigString(configuration);
501        if (!configOptions.isEmpty()) {
502            m_category = CmsWidgetUtil.getStringOption(configOptions, CONFIGURATION_CATEGORY, m_category);
503            m_isSingleValue = !("multi".equals(
504                CmsWidgetUtil.getStringOption(configOptions, CONFIGURATION_SELECTIONTYPE, "single")));
505            m_children = CmsWidgetUtil.getBooleanOption(configOptions, CONFIGURATION_PARENTSELECTION);
506            m_collapsed = CmsWidgetUtil.getBooleanOption(configOptions, CONFIGURATION_COLLAPSED);
507            m_showWithRepository = CmsWidgetUtil.getBooleanOption(configOptions, CONFIGURATION_SHOW_WITH_REPOSITORY);
508            m_refPath = CmsWidgetUtil.getStringOption(configOptions, CONFIGURATION_REFPATH, m_refPath);
509        }
510    }
511}