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.ui.apps; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsProject; 032import org.opencms.i18n.CmsEncoder; 033import org.opencms.main.CmsException; 034import org.opencms.main.OpenCms; 035import org.opencms.ui.A_CmsUI; 036import org.opencms.ui.components.CmsBreadCrumb; 037import org.opencms.ui.components.CmsToolLayout; 038import org.opencms.util.CmsStringUtil; 039 040import java.util.HashMap; 041import java.util.Iterator; 042import java.util.LinkedHashMap; 043import java.util.List; 044import java.util.Map; 045 046import com.vaadin.server.Resource; 047import com.vaadin.ui.Button; 048import com.vaadin.ui.Button.ClickEvent; 049import com.vaadin.ui.Button.ClickListener; 050import com.vaadin.ui.Component; 051import com.vaadin.ui.HorizontalLayout; 052import com.vaadin.ui.Label; 053import com.vaadin.ui.UI; 054 055/** 056 * Super class for workplace apps to help implementing the app navigation and layout.<p> 057 */ 058public abstract class A_CmsWorkplaceApp implements I_CmsWorkplaceApp { 059 060 /** 061 * An app navigation entry.<p> 062 */ 063 public static class NavEntry { 064 065 /** The entry description. */ 066 private String m_description; 067 068 /** The entry icon. */ 069 private Resource m_icon; 070 071 /** The localized entry name. */ 072 private String m_name; 073 074 /** The target state. */ 075 private String m_targetState; 076 077 /** 078 * Constructor.<p> 079 * 080 * @param name the entry name 081 * @param description the description 082 * @param icon the icon 083 * @param targetState the target state 084 */ 085 public NavEntry(String name, String description, Resource icon, String targetState) { 086 087 m_name = name; 088 m_description = description; 089 m_icon = icon; 090 m_targetState = targetState; 091 } 092 093 /** 094 * Returns the description.<p> 095 * 096 * @return the description 097 */ 098 public String getDescription() { 099 100 return m_description; 101 } 102 103 /** 104 * Returns the icon.<p> 105 * 106 * @return the icon 107 */ 108 public Resource getIcon() { 109 110 return m_icon; 111 } 112 113 /** 114 * Returns the entry name.<p> 115 * 116 * @return the entry name 117 */ 118 public String getName() { 119 120 return m_name; 121 } 122 123 /** 124 * Returns the target state.<p> 125 * 126 * @return the target state 127 */ 128 public String getTargetState() { 129 130 return m_targetState; 131 } 132 } 133 134 /** State parameter value separator. */ 135 public static final String PARAM_ASSIGN = "::"; 136 137 /** State parameter separator. */ 138 public static final String PARAM_SEPARATOR = "!!"; 139 140 /** The app info layout containing the bread crumb navigation as first component. */ 141 protected HorizontalLayout m_infoLayout; 142 143 /** The root layout. */ 144 protected CmsToolLayout m_rootLayout; 145 146 /** The app UI context. */ 147 protected I_CmsAppUIContext m_uiContext; 148 149 /** The bread crumb navigation. */ 150 private CmsBreadCrumb m_breadCrumb; 151 152 /** 153 * Constructor.<p> 154 */ 155 protected A_CmsWorkplaceApp() { 156 157 m_rootLayout = new CmsToolLayout(); 158 m_rootLayout.setSizeFull(); 159 } 160 161 /** 162 * Adds a parameter value to the given state.<p> 163 * 164 * @param state the state 165 * @param paramName the parameter name 166 * @param value the parameter value 167 * 168 * @return the state 169 */ 170 public static String addParamToState(String state, String paramName, String value) { 171 172 return state + PARAM_SEPARATOR + paramName + PARAM_ASSIGN + CmsEncoder.encode(value, CmsEncoder.ENCODING_UTF_8); 173 } 174 175 /** 176 * Parses the requested parameter from the given state.<p> 177 * 178 * @param state the state 179 * @param paramName the parameter name 180 * 181 * @return the parameter value 182 */ 183 public static String getParamFromState(String state, String paramName) { 184 185 String prefix = PARAM_SEPARATOR + paramName + PARAM_ASSIGN; 186 if (state.contains(prefix)) { 187 String result = state.substring(state.indexOf(prefix) + prefix.length()); 188 if (result.contains(PARAM_SEPARATOR)) { 189 result = result.substring(0, result.indexOf(PARAM_SEPARATOR)); 190 } 191 return CmsEncoder.decode(result, CmsEncoder.ENCODING_UTF_8); 192 } 193 return null; 194 } 195 196 /** 197 * Returns the parameters contained in the state string.<p> 198 * 199 * @param state the state 200 * 201 * @return the parameters 202 */ 203 public static Map<String, String> getParamsFromState(String state) { 204 205 Map<String, String> result = new HashMap<String, String>(); 206 int separatorIndex = state.indexOf(PARAM_SEPARATOR); 207 while (separatorIndex >= 0) { 208 state = state.substring(separatorIndex + 2); 209 int assignIndex = state.indexOf(PARAM_ASSIGN); 210 if (assignIndex > 0) { 211 String key = state.substring(0, assignIndex); 212 state = state.substring(assignIndex + 2); 213 separatorIndex = state.indexOf(PARAM_SEPARATOR); 214 String value = null; 215 if (separatorIndex < 0) { 216 value = state; 217 } else if (separatorIndex > 0) { 218 value = state.substring(0, separatorIndex); 219 } 220 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)) { 221 result.put(key, CmsEncoder.decode(value, CmsEncoder.ENCODING_UTF_8)); 222 } 223 } else { 224 separatorIndex = -1; 225 } 226 } 227 return result; 228 } 229 230 /** 231 * Gets an offline version of the cms object.<p> 232 * 233 * @param cms initial CmsObject 234 * @return CmsObject adjusted to offline project (cloned) 235 */ 236 public CmsObject getOfflineCmsObject(CmsObject cms) { 237 238 CmsObject res = null; 239 try { 240 if (!cms.getRequestContext().getCurrentProject().isOnlineProject()) { 241 return OpenCms.initCmsObject(cms); 242 } 243 res = OpenCms.initCmsObject(cms); 244 List<CmsProject> projects = OpenCms.getOrgUnitManager().getAllAccessibleProjects(res, "/", true); 245 Iterator<CmsProject> projIterator = projects.iterator(); 246 boolean offFound = false; 247 while (projIterator.hasNext() & !offFound) { 248 CmsProject offP = projIterator.next(); 249 if (!offP.isOnlineProject()) { 250 res.getRequestContext().setCurrentProject(offP); 251 offFound = true; 252 } 253 } 254 } catch (CmsException e) { 255 return cms; 256 } 257 return res; 258 } 259 260 /** 261 * @see org.opencms.ui.apps.I_CmsWorkplaceApp#initUI(org.opencms.ui.apps.I_CmsAppUIContext) 262 */ 263 public void initUI(I_CmsAppUIContext context) { 264 265 m_uiContext = context; 266 m_uiContext.showInfoArea(true); 267 m_breadCrumb = new CmsBreadCrumb(); 268 m_infoLayout = new HorizontalLayout(); 269 m_infoLayout.setSizeFull(); 270 m_infoLayout.setSpacing(true); 271 m_infoLayout.setMargin(true); 272 m_uiContext.setAppInfo(m_infoLayout); 273 m_infoLayout.addComponent(m_breadCrumb); 274 m_infoLayout.setExpandRatio(m_breadCrumb, 2); 275 m_uiContext.setAppContent(m_rootLayout); 276 } 277 278 /** 279 * @see org.opencms.ui.apps.I_CmsWorkplaceApp#onStateChange(java.lang.String) 280 */ 281 public void onStateChange(String state) { 282 283 openSubView(state, false); 284 } 285 286 /** 287 * Opens the requested sub view.<p> 288 * 289 * @param state the state 290 * @param updateState <code>true</code> to update the state URL token 291 */ 292 public void openSubView(String state, boolean updateState) { 293 294 if (updateState) { 295 CmsAppWorkplaceUi.get().changeCurrentAppState(state); 296 } 297 Component comp = getComponentForState(state); 298 if (comp != null) { 299 comp.setSizeFull(); 300 m_rootLayout.setMainContent(comp); 301 } else { 302 m_rootLayout.setMainContent(new Label("Malformed path, tool not available for path: " + state)); 303 } 304 updateSubNav(getSubNavEntries(state)); 305 updateBreadCrumb(getBreadCrumbForState(state)); 306 } 307 308 /** 309 * Adds a navigation entry.<p> 310 * 311 * @param navEntry the navigation entry 312 */ 313 protected void addSubNavEntry(final NavEntry navEntry) { 314 315 Button button = m_rootLayout.addSubNavEntry(navEntry); 316 button.addClickListener(new ClickListener() { 317 318 private static final long serialVersionUID = 1L; 319 320 public void buttonClick(ClickEvent event) { 321 322 openSubView(navEntry.getTargetState(), true); 323 } 324 }); 325 } 326 327 /** 328 * Returns the current bread crumb entries in an ordered map.<p> 329 * 330 * @param state the current state 331 * 332 * @return bread crumb entry name by state, in case the state is empty, the entry will be disabled 333 */ 334 protected abstract LinkedHashMap<String, String> getBreadCrumbForState(String state); 335 336 /** 337 * Returns the app component for the given state.<p> 338 * 339 * @param state the state to render 340 * 341 * @return the app component 342 */ 343 protected abstract Component getComponentForState(String state); 344 345 /** 346 * Returns the last path level.<p> 347 * 348 * @param path the path 349 * 350 * @return the last path level 351 */ 352 protected String getLastPathLevel(String path) { 353 354 path = path.trim(); 355 if (path.endsWith("/")) { 356 path = path.substring(0, path.length() - 1); 357 } 358 if (path.contains("/")) { 359 path = path.substring(path.lastIndexOf("/")); 360 } 361 return path; 362 } 363 364 /** 365 * Returns the sub navigation entries.<p> 366 * 367 * @param state the state 368 * 369 * @return the sub navigation entries 370 */ 371 protected abstract List<NavEntry> getSubNavEntries(String state); 372 373 /** 374 * Method to set bread crumb entries.<p> 375 * 376 * @param entries to be set 377 */ 378 protected void setBreadCrumbEntries(LinkedHashMap<String, String> entries) { 379 380 m_breadCrumb.setEntries(entries); 381 } 382 383 /** 384 * Updates the bread crumb navigation.<p> 385 * 386 * @param breadCrumbEntries the bread crumb entries 387 */ 388 protected void updateBreadCrumb(Map<String, String> breadCrumbEntries) { 389 390 LinkedHashMap<String, String> entries = new LinkedHashMap<String, String>(); 391 I_CmsWorkplaceAppConfiguration launchpadConfig = OpenCms.getWorkplaceAppManager().getAppConfiguration( 392 CmsAppHierarchyConfiguration.APP_ID); 393 if (launchpadConfig.getVisibility(A_CmsUI.getCmsObject()).isActive()) { 394 entries.put(CmsAppHierarchyConfiguration.APP_ID, launchpadConfig.getName(UI.getCurrent().getLocale())); 395 } 396 if ((breadCrumbEntries != null) && !breadCrumbEntries.isEmpty()) { 397 entries.putAll(breadCrumbEntries); 398 } else { 399 entries.put( 400 "", 401 OpenCms.getWorkplaceAppManager().getAppConfiguration(m_uiContext.getAppId()).getName( 402 UI.getCurrent().getLocale())); 403 } 404 m_breadCrumb.setEntries(entries); 405 } 406 407 /** 408 * Updates the sub navigation with the given entries.<p> 409 * 410 * @param subEntries the sub navigation entries 411 */ 412 protected void updateSubNav(List<NavEntry> subEntries) { 413 414 m_rootLayout.clearSubNav(); 415 if ((subEntries == null) || subEntries.isEmpty()) { 416 m_rootLayout.setSubNavVisible(false); 417 } else { 418 m_rootLayout.setSubNavVisible(true); 419 for (NavEntry entry : subEntries) { 420 addSubNavEntry(entry); 421 } 422 } 423 } 424}