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}