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.gwt.client.ui.input; 029 030import org.opencms.gwt.client.CmsCoreProvider; 031import org.opencms.gwt.client.I_CmsHasInit; 032import org.opencms.gwt.client.I_CmsHasResizeOnShow; 033import org.opencms.gwt.client.ui.CmsPushButton; 034import org.opencms.gwt.client.ui.CmsScrollPanel; 035import org.opencms.gwt.client.ui.I_CmsAutoHider; 036import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle; 037import org.opencms.gwt.client.ui.input.category.CmsDataValue; 038import org.opencms.gwt.client.ui.input.form.CmsWidgetFactoryRegistry; 039import org.opencms.gwt.client.ui.input.form.I_CmsFormWidgetFactory; 040import org.opencms.gwt.client.ui.tree.CmsTreeItem; 041import org.opencms.gwt.shared.CmsCategoryBean; 042import org.opencms.gwt.shared.CmsCategoryTreeEntry; 043import org.opencms.util.CmsStringUtil; 044 045import java.util.ArrayList; 046import java.util.Collection; 047import java.util.List; 048import java.util.Map; 049 050import com.google.common.base.Optional; 051import com.google.gwt.core.client.GWT; 052import com.google.gwt.dom.client.Style.Unit; 053import com.google.gwt.event.dom.client.ClickEvent; 054import com.google.gwt.event.dom.client.ClickHandler; 055import com.google.gwt.event.dom.client.DoubleClickEvent; 056import com.google.gwt.event.dom.client.DoubleClickHandler; 057import com.google.gwt.user.client.ui.Composite; 058import com.google.gwt.user.client.ui.FlowPanel; 059import com.google.gwt.user.client.ui.Panel; 060 061/** 062 * Basic category widget for forms.<p> 063 * 064 * @since 8.0.0 065 * 066 */ 067public class CmsCategoryField extends Composite implements I_CmsFormWidget, I_CmsHasInit, I_CmsHasResizeOnShow { 068 069 /** Selection handler to handle check box click events and double clicks on the list items. */ 070 protected abstract class A_SelectionHandler implements ClickHandler, DoubleClickHandler { 071 072 /** The reference to the checkbox. */ 073 private CmsCheckBox m_checkBox; 074 075 /** The the select button, can be used instead of a double click to select and search. */ 076 private CmsPushButton m_selectButton; 077 078 /** 079 * Constructor.<p> 080 * 081 * @param checkBox the item check box 082 */ 083 protected A_SelectionHandler(CmsCheckBox checkBox) { 084 085 m_checkBox = checkBox; 086 } 087 088 /** 089 * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) 090 */ 091 public void onClick(ClickEvent event) { 092 093 if (event.getSource().equals(m_selectButton)) { 094 m_checkBox.setChecked(true); 095 onSelectionChange(); 096 } else if (event.getSource().equals(m_checkBox)) { 097 onSelectionChange(); 098 } 099 } 100 101 /** 102 * @see com.google.gwt.event.dom.client.DoubleClickHandler#onDoubleClick(com.google.gwt.event.dom.client.DoubleClickEvent) 103 */ 104 public void onDoubleClick(DoubleClickEvent event) { 105 106 m_checkBox.setChecked(true); 107 onSelectionChange(); 108 event.stopPropagation(); 109 event.preventDefault(); 110 } 111 112 /** 113 * Sets the select button, can be used instead of a double click to select and search.<p> 114 * 115 * @param button the select button 116 */ 117 public void setSelectButton(CmsPushButton button) { 118 119 m_selectButton = button; 120 } 121 122 /** 123 * Returns the check box.<p> 124 * 125 * @return the check box 126 */ 127 protected CmsCheckBox getCheckBox() { 128 129 return m_checkBox; 130 } 131 132 /** 133 * Executed on selection change. Either when the check box was clicked or on double click on a list item.<p> 134 */ 135 protected abstract void onSelectionChange(); 136 } 137 138 /** The widget type identifier for this widget. */ 139 private static final String WIDGET_TYPE = "categoryField"; 140 141 /** The panel contains all the categories. */ 142 FlowPanel m_categories = new FlowPanel(); 143 144 /** The default rows set. */ 145 int m_defaultHeight; 146 147 /** The root panel containing the other components of this widget. */ 148 Panel m_panel = new FlowPanel(); 149 150 /** The container for the text area. */ 151 CmsScrollPanel m_scrollPanel = GWT.create(CmsScrollPanel.class); 152 153 /** The sife pathes of all added categories. */ 154 private List<String> m_allSidePath = new ArrayList<String>(); 155 156 /** The error display for this widget. */ 157 private CmsErrorWidget m_error = new CmsErrorWidget(); 158 159 /** The value if the parent should be selected with the children. */ 160 private boolean m_selectParent; 161 162 /** The side path of the last added category. */ 163 private String m_singleSidePath = ""; 164 165 /** Count the numbers of values shown. */ 166 private int m_valuesSet; 167 168 /** 169 * Category field widgets for ADE forms.<p> 170 */ 171 public CmsCategoryField() { 172 173 super(); 174 initWidget(m_panel); 175 m_panel.add(m_scrollPanel); 176 m_scrollPanel.getElement().getStyle().setHeight(50, Unit.PX); 177 m_scrollPanel.add(m_categories); 178 179 m_panel.add(m_error); 180 m_scrollPanel.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().cornerAll()); 181 } 182 183 /** 184 * Initializes this class.<p> 185 */ 186 public static void initClass() { 187 188 // registers a factory for creating new instances of this widget 189 CmsWidgetFactoryRegistry.instance().registerFactory(WIDGET_TYPE, new I_CmsFormWidgetFactory() { 190 191 /** 192 * @see org.opencms.gwt.client.ui.input.form.I_CmsFormWidgetFactory#createWidget(java.util.Map, com.google.common.base.Optional) 193 */ 194 public I_CmsFormWidget createWidget(Map<String, String> widgetParams, Optional<String> defaultValue) { 195 196 return new CmsCategoryField(); 197 } 198 }); 199 } 200 201 /** 202 * Checks if the given category is a parent category of any element of the given selection.<p> 203 * 204 * The selection might contain either category paths or site paths of categories. 205 * 206 * @param category a category path 207 * @param selection a set containing either category paths or category site paths 208 * @return true if the category is a parent category of any element of the given selection 209 */ 210 public static boolean isParentCategoryOfSelected(String category, Collection<String> selection) { 211 212 category = normalizePath(category); 213 for (String selected : selection) { 214 selected = normalizePath(removeCategoryPrefix(selected)); 215 if (selected.startsWith(category)) { 216 return true; 217 } 218 } 219 return false; 220 } 221 222 /** 223 * Adds leading/trailing slashes to a path if it doesn't already have them. 224 * 225 * @param path the path to normalize 226 * @return the normalized path 227 */ 228 private static String normalizePath(String path) { 229 230 if (!path.startsWith("/")) { 231 path = "/" + path; 232 } 233 if (!path.endsWith("/")) { 234 path = path + "/"; 235 } 236 return path; 237 } 238 239 /** 240 * Removes the category folder portion from the path of a category. 241 * 242 * <p>If the argument doesn't have a category folder portion, it will be returned unchanged. 243 * 244 * @param selected a category site path (or in some cases just a category path) 245 * @return the category path (with the category folder stripped) 246 */ 247 private static String removeCategoryPrefix(String selected) { 248 249 String globalFolder = "/system/categories/"; 250 String localName = normalizePath(CmsCoreProvider.get().getCategoryBaseFolder()); 251 String result = selected; 252 if (selected.startsWith(globalFolder)) { 253 result = selected.substring(globalFolder.length() - 1); // keep the slash 254 } else { 255 int namePos = selected.indexOf(localName); 256 if (namePos != -1) { 257 result = selected.substring((namePos + localName.length()) - 1); 258 } 259 } 260 return result; 261 } 262 263 /** 264 * Builds and shows the category tree.<p> 265 * 266 * @param treeEntries List of category entries 267 * @param selectedCategories a list of all selected categories 268 */ 269 public void buildCategoryTree(List<CmsCategoryTreeEntry> treeEntries, Collection<String> selectedCategories) { 270 271 m_valuesSet = 0; 272 m_allSidePath.clear(); 273 m_categories.removeFromParent(); 274 m_categories.clear(); 275 276 if ((treeEntries != null) && !treeEntries.isEmpty()) { 277 // add the first level and children 278 for (CmsCategoryTreeEntry category : treeEntries) { 279 // set the category tree item and add to list 280 CmsTreeItem treeItem; 281 boolean hasSelectedChildren = hasSelectedChildren(category.getChildren(), selectedCategories); 282 if (!category.getPath().isEmpty() || hasSelectedChildren) { 283 if (m_selectParent || !hasSelectedChildren) { 284 treeItem = buildTreeItem(category, selectedCategories, false); 285 if (treeItem.isOpen()) { 286 m_allSidePath.add(category.getSitePath()); 287 } 288 } else { 289 treeItem = buildTreeItem(category, selectedCategories, true); 290 } 291 if (treeItem.isOpen()) { 292 m_singleSidePath = category.getSitePath(); 293 294 m_valuesSet++; 295 addChildren(treeItem, category.getChildren(), selectedCategories); 296 } 297 } 298 } 299 } 300 m_scrollPanel.add(m_categories); 301 m_scrollPanel.onResizeDescendant(); 302 303 } 304 305 /** 306 * Returns the site path of all shown categories.<p> 307 * 308 * @return the site path of all shown categories 309 */ 310 public List<String> getAllSitePath() { 311 312 return m_allSidePath; 313 } 314 315 /** 316 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getApparentValue() 317 */ 318 public String getApparentValue() { 319 320 // TODO: Auto-generated method stub 321 return null; 322 } 323 324 /** 325 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFieldType() 326 */ 327 public FieldType getFieldType() { 328 329 return I_CmsFormWidget.FieldType.STRING; 330 } 331 332 /** 333 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValue() 334 */ 335 public Object getFormValue() { 336 337 // TODO: Auto-generated method stub 338 return null; 339 } 340 341 /** 342 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValueAsString() 343 */ 344 public String getFormValueAsString() { 345 346 // TODO: Auto-generated method stub 347 return null; 348 } 349 350 /** 351 * Returns the scroll panel of this widget.<p> 352 * 353 * @return the scroll panel 354 */ 355 public CmsScrollPanel getScrollPanel() { 356 357 return m_scrollPanel; 358 } 359 360 /** 361 * Returns the site path of the last category.<p> 362 * 363 * @return the site path of the last category 364 */ 365 public String getSingelSitePath() { 366 367 return m_singleSidePath; 368 } 369 370 /** 371 * Returns the count of values set to show.<p> 372 * @return the count of values set to show 373 */ 374 public int getValuesSet() { 375 376 return m_valuesSet; 377 } 378 379 /** 380 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#isEnabled() 381 */ 382 public boolean isEnabled() { 383 384 return false; 385 } 386 387 /** 388 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#reset() 389 */ 390 public void reset() { 391 392 //TODO implement reset(); 393 } 394 395 /** 396 * @see org.opencms.gwt.client.I_CmsHasResizeOnShow#resizeOnShow() 397 */ 398 public void resizeOnShow() { 399 400 m_scrollPanel.onResizeDescendant(); 401 } 402 403 /** 404 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setAutoHideParent(org.opencms.gwt.client.ui.I_CmsAutoHider) 405 */ 406 public void setAutoHideParent(I_CmsAutoHider autoHideParent) { 407 408 // nothing to do 409 } 410 411 /** 412 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setEnabled(boolean) 413 */ 414 public void setEnabled(boolean enabled) { 415 416 //TODO implement setEnabled; 417 } 418 419 /** 420 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setErrorMessage(java.lang.String) 421 */ 422 public void setErrorMessage(String errorMessage) { 423 424 m_error.setText(errorMessage); 425 } 426 427 /** 428 * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setFormValueAsString(java.lang.String) 429 */ 430 public void setFormValueAsString(String value) { 431 432 // TODO: Auto-generated method stub 433 434 } 435 436 /** 437 * Sets the height of this category field.<p> 438 * 439 * @param height the height of this category field 440 */ 441 public void setHeight(int height) { 442 443 m_defaultHeight = height; 444 m_scrollPanel.setHeight(m_defaultHeight + "px"); 445 m_scrollPanel.setDefaultHeight(m_defaultHeight); 446 m_scrollPanel.onResizeDescendant(); 447 } 448 449 /** 450 * Sets if the parent category should be selected with the child or not. 451 * 452 * @param value if the parent categories should be selected or not 453 * */ 454 public void setParentSelection(boolean value) { 455 456 m_selectParent = value; 457 } 458 459 /** 460 * Sets the value of the widget.<p> 461 * 462 * @param value the new value 463 */ 464 public void setSelected(Object value) { 465 466 // nothing to do 467 } 468 469 /** 470 * Set the selected categories.<p> 471 * 472 * @param newValue String of selected categories separated by '|' 473 */ 474 public void setSelectedAsString(String newValue) { 475 476 setSelected(newValue); 477 } 478 479 /** 480 * @see com.google.gwt.user.client.ui.Composite#onAttach() 481 */ 482 @Override 483 protected void onAttach() { 484 485 super.onAttach(); 486 } 487 488 /** 489 * Adds children item to the category tree and select the categories.<p> 490 * 491 * @param parent the parent item 492 * @param children the list of children 493 * @param selectedCategories the list of categories to select 494 */ 495 private void addChildren( 496 CmsTreeItem parent, 497 List<CmsCategoryTreeEntry> children, 498 Collection<String> selectedCategories) { 499 500 if (children != null) { 501 for (CmsCategoryTreeEntry child : children) { 502 // set the category tree item and add to parent tree item 503 CmsTreeItem treeItem; 504 boolean isPartofPath = false; 505 isPartofPath = isParentCategoryOfSelected(child.getPath(), selectedCategories); 506 if (isPartofPath) { 507 m_singleSidePath = child.getSitePath(); 508 m_valuesSet++; 509 if (m_selectParent || !hasSelectedChildren(child.getChildren(), selectedCategories)) { 510 m_allSidePath.add(child.getSitePath()); 511 treeItem = buildTreeItem(child, selectedCategories, false); 512 } else { 513 treeItem = buildTreeItem(child, selectedCategories, true); 514 } 515 addChildren(treeItem, child.getChildren(), selectedCategories); 516 parent.addChild(treeItem); 517 } 518 519 } 520 } 521 } 522 523 /** 524 * Builds a tree item for the given category.<p> 525 * 526 * @param category the category 527 * @param selectedCategories the selected categories 528 * @param inactive true if the value should be displayed inactive 529 * 530 * @return the tree item widget 531 */ 532 private CmsTreeItem buildTreeItem( 533 CmsCategoryTreeEntry category, 534 Collection<String> selectedCategories, 535 boolean inactive) { 536 537 CmsDataValue categoryTreeItem = new CmsDataValue( 538 500, 539 4, 540 CmsCategoryBean.SMALL_ICON_CLASSES, 541 category.getTitle(), 542 category.getPath()); 543 if (inactive) { 544 categoryTreeItem.setInactive(); 545 } 546 categoryTreeItem.setTitle( 547 CmsStringUtil.isNotEmptyOrWhitespaceOnly(category.getDescription()) 548 ? category.getDescription() 549 : category.getPath()); 550 CmsTreeItem treeItem = new CmsTreeItem(false, categoryTreeItem); 551 treeItem.setId(category.getPath()); 552 boolean isPartofPath = false; 553 isPartofPath = isParentCategoryOfSelected(category.getPath(), selectedCategories); 554 if (isPartofPath) { 555 m_categories.add(treeItem); 556 treeItem.setOpen(true); 557 } 558 return treeItem; 559 } 560 561 /** 562 * Checks if it has selected children.<p> 563 * 564 * @param children the children to check 565 * @param selectedCategories list of all selected categories 566 * 567 * @return true if it has selected children 568 * */ 569 private boolean hasSelectedChildren(List<CmsCategoryTreeEntry> children, Collection<String> selectedCategories) { 570 571 boolean result = false; 572 if (children == null) { 573 return false; 574 } 575 for (CmsCategoryTreeEntry child : children) { 576 result = selectedCategories.contains(child.getSitePath()); 577 if (result || hasSelectedChildren(child.getChildren(), selectedCategories)) { 578 return true; 579 } 580 } 581 582 return result; 583 } 584}