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}