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.ade.sitemap.client.control; 029 030import org.opencms.ade.detailpage.CmsDetailPageInfo; 031import org.opencms.ade.sitemap.client.CmsSitemapTreeItem; 032import org.opencms.ade.sitemap.client.CmsSitemapView; 033import org.opencms.ade.sitemap.client.toolbar.CmsSitemapToolbar; 034import org.opencms.ade.sitemap.client.ui.CmsCreatableListItem; 035import org.opencms.ade.sitemap.client.ui.css.I_CmsSitemapLayoutBundle; 036import org.opencms.ade.sitemap.shared.CmsClientSitemapEntry; 037import org.opencms.ade.sitemap.shared.CmsClientSitemapEntry.EntryType; 038import org.opencms.ade.sitemap.shared.CmsNewResourceInfo; 039import org.opencms.gwt.client.dnd.CmsDNDHandler; 040import org.opencms.gwt.client.dnd.CmsDNDHandler.Orientation; 041import org.opencms.gwt.client.dnd.I_CmsDNDController; 042import org.opencms.gwt.client.dnd.I_CmsDraggable; 043import org.opencms.gwt.client.dnd.I_CmsDropTarget; 044import org.opencms.gwt.client.property.CmsReloadMode; 045import org.opencms.gwt.client.ui.tree.CmsTree; 046import org.opencms.gwt.client.util.CmsDebugLog; 047import org.opencms.gwt.client.util.CmsDomUtil; 048import org.opencms.gwt.client.util.CmsDomUtil.Tag; 049import org.opencms.gwt.client.util.I_CmsSimpleCallback; 050import org.opencms.gwt.shared.property.CmsClientProperty; 051import org.opencms.gwt.shared.property.CmsPropertyModification; 052 053import java.util.Collections; 054import java.util.List; 055import java.util.Map; 056 057import com.google.common.collect.Maps; 058import com.google.gwt.core.client.Scheduler; 059import com.google.gwt.core.client.Scheduler.ScheduledCommand; 060import com.google.gwt.dom.client.Element; 061import com.google.gwt.dom.client.Style.Unit; 062 063/** 064 * The sitemap drag and drop controller.<p> 065 * 066 * @since 8.0.0 067 */ 068public class CmsSitemapDNDController implements I_CmsDNDController { 069 070 /** The sitemap controller instance. */ 071 CmsSitemapController m_controller; 072 073 /** The insert position of the draggable. */ 074 int m_insertIndex; 075 076 /** The insert path of the draggable. */ 077 String m_insertPath; 078 079 /** The original position of the draggable. */ 080 private int m_originalIndex; 081 082 /** The original path of the draggable. */ 083 private String m_originalPath; 084 085 /** The sitemap toolbar. */ 086 private CmsSitemapToolbar m_toolbar; 087 088 /** 089 * Constructor.<p> 090 * 091 * @param controller the sitemap controller 092 * @param toolbar the sitemap toolbar 093 */ 094 public CmsSitemapDNDController(CmsSitemapController controller, CmsSitemapToolbar toolbar) { 095 096 m_controller = controller; 097 m_toolbar = toolbar; 098 } 099 100 /** 101 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onAnimationStart(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 102 */ 103 public void onAnimationStart(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 104 105 // nothing to do 106 } 107 108 /** 109 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onBeforeDrop(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 110 */ 111 public boolean onBeforeDrop(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 112 113 if (!(target instanceof CmsTree<?>)) { 114 // only dropping onto the tree allowed in sitemap editor 115 return false; 116 } 117 CmsTree<?> tree = (CmsTree<?>)target; 118 m_insertPath = tree.getPlaceholderPath(); 119 m_insertIndex = tree.getPlaceholderIndex(); 120 if (m_insertPath.equals(m_originalPath) && (m_insertIndex > m_originalIndex)) { 121 // new position has the same path and is below the original position, adjust insert index 122 m_insertIndex -= 1; 123 if (m_insertIndex == m_originalIndex) { 124 return false; 125 } 126 } 127 if (m_insertIndex == -1) { 128 return false; 129 } 130 return true; 131 } 132 133 /** 134 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onDragCancel(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 135 */ 136 public void onDragCancel(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 137 138 if (draggable instanceof CmsSitemapTreeItem) { 139 ((CmsSitemapTreeItem)draggable).resetEntry(); 140 } 141 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 142 143 /** 144 * @see com.google.gwt.user.client.Command#execute() 145 */ 146 public void execute() { 147 148 CmsSitemapView.getInstance().getTree().closeAllEmpty(); 149 } 150 }); 151 } 152 153 /** 154 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onDragStart(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 155 */ 156 public boolean onDragStart(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 157 158 handler.setOrientation(Orientation.VERTICAL); 159 hideItemContent(handler.getPlaceholder()); 160 handler.getDragHelper().getStyle().setOpacity(0.6); 161 m_insertIndex = -1; 162 m_insertPath = null; 163 m_originalIndex = -1; 164 m_originalPath = null; 165 if (draggable instanceof CmsCreatableListItem) { 166 m_toolbar.onButtonActivation(null); 167 168 // fixing placeholder indent not being present in non tree items 169 List<Element> elements = CmsDomUtil.getElementsByClass( 170 org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.floatDecoratedPanelCss().primary(), 171 Tag.div, 172 handler.getPlaceholder()); 173 if ((elements != null) && (elements.size() > 0)) { 174 elements.get(0).getStyle().setMarginLeft(16, Unit.PX); 175 } 176 } else if (draggable instanceof CmsSitemapTreeItem) { 177 CmsSitemapTreeItem treeItem = (CmsSitemapTreeItem)draggable; 178 m_originalPath = treeItem.getParentItem().getPath(); 179 if (treeItem.getParentItem() != null) { 180 m_originalIndex = treeItem.getParentItem().getItemPosition(treeItem); 181 } 182 } 183 CmsDebugLog.getInstance().printLine("Starting path: " + m_originalPath + ", Index: " + m_originalIndex); 184 return true; 185 } 186 187 /** 188 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onDrop(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 189 */ 190 public void onDrop(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 191 192 if (!(target instanceof CmsTree<?>)) { 193 // only dropping onto the tree allowed in sitemap 194 handler.cancel(); 195 return; 196 } 197 CmsClientSitemapEntry parent = CmsSitemapView.getInstance().getController().getEntry(m_insertPath); 198 if (draggable instanceof CmsSitemapTreeItem) { 199 handleDropSitemapEntry((CmsSitemapTreeItem)draggable, target, parent); 200 } 201 if (draggable instanceof CmsCreatableListItem) { 202 handleDropNewEntry((CmsCreatableListItem)draggable, parent); 203 } 204 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 205 206 /** 207 * @see com.google.gwt.user.client.Command#execute() 208 */ 209 public void execute() { 210 211 CmsSitemapView.getInstance().getTree().closeAllEmpty(); 212 } 213 }); 214 } 215 216 /** 217 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onPositionedPlaceholder(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 218 */ 219 public void onPositionedPlaceholder(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 220 221 if (draggable instanceof CmsSitemapTreeItem) { 222 adjustOriginalPositionIndicator((CmsSitemapTreeItem)draggable, target, handler); 223 } 224 } 225 226 /** 227 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onTargetEnter(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 228 */ 229 public boolean onTargetEnter(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 230 231 return true; 232 } 233 234 /** 235 * @see org.opencms.gwt.client.dnd.I_CmsDNDController#onTargetLeave(org.opencms.gwt.client.dnd.I_CmsDraggable, org.opencms.gwt.client.dnd.I_CmsDropTarget, org.opencms.gwt.client.dnd.CmsDNDHandler) 236 */ 237 public void onTargetLeave(I_CmsDraggable draggable, I_CmsDropTarget target, CmsDNDHandler handler) { 238 239 if (target instanceof CmsTree<?>) { 240 ((CmsTree<?>)target).cancelOpenTimer(); 241 } 242 } 243 244 /** 245 * Checks whether the current placeholder position represents a change to the original draggable position within the tree.<p> 246 * 247 * @param draggable the draggable 248 * @param target the current drop target 249 * @param strict if <code>false</code> only the parent path is considered, the index position will be ignored 250 * 251 * @return <code>true</code> if the position changed 252 */ 253 boolean isChangedPosition(I_CmsDraggable draggable, I_CmsDropTarget target, boolean strict) { 254 255 // if draggable is not a sitemap item, any valid position is a changed position 256 if (!((draggable instanceof CmsSitemapTreeItem) && (target instanceof CmsTree<?>))) { 257 return true; 258 } 259 260 String placeholderPath = ((CmsTree<?>)target).getPlaceholderPath(); 261 if ((placeholderPath == null) && !strict) { 262 // first positioning, path has not changed yet 263 return false; 264 } 265 // if the the path differs, the position has changed 266 if ((m_originalPath == null) || !m_originalPath.equals(placeholderPath)) { 267 return true; 268 } 269 // if the new index is not next to the old one, the position has changed 270 if (!((target.getPlaceholderIndex() == (m_originalIndex + 1)) 271 || (target.getPlaceholderIndex() == m_originalIndex)) && strict) { 272 return true; 273 } 274 return false; 275 } 276 277 /** 278 * Adjust the original position indicator by styling the draggable element for this purpose.<p> 279 * 280 * @param draggable the draggable 281 * @param target the current drop target 282 * @param handler the drag and drop handler 283 */ 284 private void adjustOriginalPositionIndicator( 285 CmsSitemapTreeItem draggable, 286 I_CmsDropTarget target, 287 CmsDNDHandler handler) { 288 289 if (!isChangedPosition(draggable, target, true)) { 290 draggable.getElement().addClassName(I_CmsSitemapLayoutBundle.INSTANCE.sitemapItemCss().markUnchanged()); 291 List<Element> itemWidget = CmsDomUtil.getElementsByClass( 292 org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().itemContainer(), 293 handler.getPlaceholder()); 294 if ((itemWidget != null) && (itemWidget.size() > 0)) { 295 CmsDomUtil.addDisablingOverlay(itemWidget.get(0)); 296 } 297 } else { 298 draggable.getElement().removeClassName(I_CmsSitemapLayoutBundle.INSTANCE.sitemapItemCss().markUnchanged()); 299 CmsDomUtil.removeDisablingOverlay(handler.getPlaceholder()); 300 } 301 } 302 303 /** 304 * Handles a dropped detail page.<p> 305 * 306 * @param createItem the detail page which was dropped into the sitemap 307 * @param parent the parent sitemap entry 308 */ 309 private void handleDropNewEntry(CmsCreatableListItem createItem, final CmsClientSitemapEntry parent) { 310 311 final CmsNewResourceInfo typeInfo = createItem.getResourceTypeInfo(); 312 final CmsClientSitemapEntry entry = new CmsClientSitemapEntry(); 313 entry.setNew(true); 314 entry.setVfsPath(null); 315 entry.setPosition(m_insertIndex); 316 entry.setInNavigation(true); 317 Map<String, CmsClientProperty> defaultFileProps = Maps.newHashMap(); 318 entry.setDefaultFileProperties(defaultFileProps); 319 String name; 320 final boolean appendSlash; 321 switch (createItem.getNewEntryType()) { 322 case detailpage: 323 name = CmsDetailPageInfo.removeFunctionPrefix(typeInfo.getResourceType()); 324 appendSlash = true; 325 entry.setDetailpageTypeName(typeInfo.getResourceType()); 326 entry.setVfsModeIcon(typeInfo.getBigIconClasses()); 327 if (typeInfo.isFunction()) { 328 329 CmsClientProperty titleProp = new CmsClientProperty( 330 CmsClientProperty.PROPERTY_TITLE, 331 typeInfo.getTitle(), 332 null); 333 CmsClientProperty navtextProp = new CmsClientProperty( 334 CmsClientProperty.PROPERTY_NAVTEXT, 335 typeInfo.getTitle(), 336 null); 337 entry.getOwnProperties().put(titleProp.getName(), titleProp); 338 entry.getDefaultFileProperties().put(titleProp.getName(), titleProp); 339 entry.getOwnProperties().put(navtextProp.getName(), navtextProp); 340 } 341 entry.setResourceTypeName("folder"); 342 break; 343 case redirect: 344 name = typeInfo.getResourceType(); 345 entry.setEntryType(EntryType.redirect); 346 entry.setVfsModeIcon(typeInfo.getBigIconClasses()); 347 entry.setResourceTypeName(typeInfo.getResourceType()); 348 appendSlash = false; 349 break; 350 default: 351 name = CmsSitemapController.NEW_ENTRY_NAME; 352 appendSlash = true; 353 entry.setResourceTypeName("folder"); 354 entry.setVfsModeIcon(typeInfo.getBigIconClasses()); 355 } 356 357 m_controller.ensureUniqueName(parent, name, new I_CmsSimpleCallback<String>() { 358 359 public void execute(String uniqueName) { 360 361 entry.setName(uniqueName); 362 String sitepath = m_insertPath + uniqueName; 363 if (appendSlash) { 364 sitepath += "/"; 365 } 366 entry.setSitePath(sitepath); 367 m_controller.create( 368 entry, 369 parent.getId(), 370 typeInfo.getId(), 371 typeInfo.getCopyResourceId(), 372 typeInfo.getCreateParameter(), 373 false); 374 } 375 }); 376 377 } 378 379 /** 380 * Handles the drop for a sitemap item which was dragged to a different position.<p> 381 * 382 * @param sitemapEntry the dropped item 383 * @param target the drop target 384 * @param parent the parent sitemap entry 385 */ 386 private void handleDropSitemapEntry( 387 final CmsSitemapTreeItem sitemapEntry, 388 final I_CmsDropTarget target, 389 CmsClientSitemapEntry parent) { 390 391 if (isChangedPosition(sitemapEntry, target, true)) { 392 // moving a tree entry around 393 final CmsClientSitemapEntry entry = sitemapEntry.getSitemapEntry(); 394 m_controller.ensureUniqueName(parent, entry.getName(), new I_CmsSimpleCallback<String>() { 395 396 public void execute(String uniqueName) { 397 398 String suffix = ""; 399 if ((sitemapEntry.getSitePath() != null) && sitemapEntry.getSitePath().endsWith("/")) { 400 suffix = "/"; 401 } 402 if (!uniqueName.equals(entry.getName()) && isChangedPosition(sitemapEntry, target, false)) { 403 m_controller.editAndChangeName( 404 entry, 405 uniqueName, 406 Collections.<CmsPropertyModification> emptyList(), 407 entry.isNew(), 408 CmsReloadMode.none); 409 m_controller.move(entry, m_insertPath + uniqueName + suffix, m_insertIndex); 410 } else { 411 m_controller.move(entry, m_insertPath + entry.getName() + suffix, m_insertIndex); 412 } 413 } 414 }); 415 } else { 416 sitemapEntry.resetEntry(); 417 } 418 } 419 420 /** 421 * Hides the content of list items by setting a specific css class.<p> 422 * 423 * @param element the list item element 424 */ 425 private void hideItemContent(Element element) { 426 427 List<Element> itemWidget = CmsDomUtil.getElementsByClass( 428 org.opencms.gwt.client.ui.css.I_CmsLayoutBundle.INSTANCE.listItemWidgetCss().itemContainer(), 429 element); 430 if ((itemWidget != null) && (itemWidget.size() > 0)) { 431 itemWidget.get(0).addClassName(I_CmsSitemapLayoutBundle.INSTANCE.sitemapItemCss().contentHide()); 432 } 433 } 434}