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.contenteditor; 029 030import org.opencms.gwt.client.CmsCoreProvider; 031import org.opencms.gwt.client.I_CmsEditableData; 032import org.opencms.gwt.client.Messages; 033import org.opencms.gwt.client.rpc.CmsRpcAction; 034import org.opencms.gwt.client.ui.CmsAlertDialog; 035import org.opencms.gwt.client.ui.CmsCancelCloseException; 036import org.opencms.gwt.client.ui.CmsConfirmDialog; 037import org.opencms.gwt.client.ui.CmsIFrame; 038import org.opencms.gwt.client.ui.CmsPopup; 039import org.opencms.gwt.client.ui.I_CmsConfirmDialogHandler; 040import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle; 041import org.opencms.gwt.client.util.CmsDebugLog; 042import org.opencms.gwt.client.util.CmsDomUtil; 043import org.opencms.gwt.client.util.CmsDomUtil.Method; 044import org.opencms.util.CmsStringUtil; 045 046import java.util.HashMap; 047import java.util.Map; 048 049import com.google.gwt.core.client.Scheduler; 050import com.google.gwt.core.client.Scheduler.ScheduledCommand; 051import com.google.gwt.dom.client.FormElement; 052import com.google.gwt.event.shared.HandlerRegistration; 053import com.google.gwt.user.client.Command; 054import com.google.gwt.user.client.Window; 055import com.google.gwt.user.client.Window.ClosingEvent; 056import com.google.gwt.user.client.Window.ClosingHandler; 057import com.google.gwt.user.client.ui.RootPanel; 058 059/** 060 * XML content editor dialog.<p> 061 * 062 * @since 8.0.0 063 */ 064public final class CmsContentEditorDialog { 065 066 /** 067 * Additional options for the editor dialog.<p> 068 */ 069 public static class DialogOptions { 070 071 /** Suggested editor width. */ 072 private Integer m_suggestedWidth; 073 074 /** Suggested editor height. */ 075 private Integer m_suggestedHeight; 076 077 /** 078 * Returns the suggestedHeight.<p> 079 * 080 * @return the suggestedHeight 081 */ 082 public Integer getSuggestedHeight() { 083 084 return m_suggestedHeight; 085 } 086 087 /** 088 * Returns the suggestedWidth.<p> 089 * 090 * @return the suggestedWidth 091 */ 092 public Integer getSuggestedWidth() { 093 094 return m_suggestedWidth; 095 } 096 097 /** 098 * Sets the suggestedHeight.<p> 099 * 100 * @param suggestedHeight the suggestedHeight to set 101 */ 102 public void setSuggestedHeight(Integer suggestedHeight) { 103 104 m_suggestedHeight = suggestedHeight; 105 } 106 107 /** 108 * Sets the suggestedWidth.<p> 109 * 110 * @param suggestedWidth the suggestedWidth to set 111 */ 112 public void setSuggestedWidth(Integer suggestedWidth) { 113 114 m_suggestedWidth = suggestedWidth; 115 } 116 117 } 118 119 /** Name of exported dialog close function. */ 120 private static final String CLOSING_METHOD_NAME = "cms_ade_closeEditorDialog"; 121 122 /** Name attribute value for editor iFrame. */ 123 private static final String EDITOR_IFRAME_NAME = "cmsAdvancedDirectEditor"; 124 125 /** The dialog instance. */ 126 private static CmsContentEditorDialog INSTANCE; 127 128 /** The window closing handler registration. */ 129 private HandlerRegistration m_closingHandlerRegistration; 130 131 /** The popup instance. */ 132 private CmsPopup m_dialog; 133 134 /** The currently edit element data. */ 135 private I_CmsEditableData m_editableData; 136 137 /** The editor handler. */ 138 private I_CmsContentEditorHandler m_editorHandler; 139 140 /** The form element. */ 141 private FormElement m_form; 142 143 /** Flag indicating if a new resource needs to created. */ 144 private boolean m_isNew; 145 146 /** The content creation mode. */ 147 private String m_mode; 148 149 /** 150 * Hiding constructor.<p> 151 */ 152 private CmsContentEditorDialog() { 153 154 exportClosingMethod(); 155 } 156 157 /** 158 * Generates the form to post to the editor frame.<p> 159 * 160 * @param editableData the data about the resource which should be edited 161 * @param isNew true if the resource to be edited doesn'T already exist 162 * @param target the target of the form to be created 163 * @param mode the mode for creating new elements 164 * 165 * @return the form element which, when submitted, opens the editor 166 */ 167 public static FormElement generateForm(I_CmsEditableData editableData, boolean isNew, String target, String mode) { 168 169 // create a form to submit a post request to the editor JSP 170 Map<String, String> formValues = new HashMap<String, String>(); 171 if (editableData.getSitePath() != null) { 172 formValues.put("resource", editableData.getSitePath()); 173 } 174 if (editableData.getElementLanguage() != null) { 175 formValues.put("elementlanguage", editableData.getElementLanguage()); 176 } 177 if (editableData.getElementName() != null) { 178 formValues.put("elementname", editableData.getElementName()); 179 } 180 181 // in case the editor is opened within this window, use the current URI as back link 182 String backlink = "_self".equals(target) 183 ? CmsCoreProvider.get().getUri() 184 : CmsCoreProvider.get().getContentEditorBacklinkUrl(); 185 186 formValues.put("backlink", backlink); 187 formValues.put("redirect", "true"); 188 formValues.put("directedit", "true"); 189 formValues.put("editcontext", CmsCoreProvider.get().getUri()); 190 formValues.put("nofoot", "1"); 191 if (isNew) { 192 formValues.put("newlink", editableData.getNewLink()); 193 formValues.put("editortitle", editableData.getNewTitle()); 194 } 195 196 String postCreateHandler = editableData.getPostCreateHandler(); 197 if (postCreateHandler != null) { 198 formValues.put("postCreateHandler", postCreateHandler); 199 } 200 if (mode != null) { 201 formValues.put("mode", mode); 202 } 203 FormElement formElement = CmsDomUtil.generateHiddenForm( 204 CmsCoreProvider.get().link(CmsCoreProvider.get().getContentEditorUrl()), 205 Method.post, 206 target, 207 formValues); 208 return formElement; 209 } 210 211 /** 212 * Returns the dialogs instance.<p> 213 * 214 * @return the dialog instance 215 */ 216 public static CmsContentEditorDialog get() { 217 218 if (INSTANCE == null) { 219 INSTANCE = new CmsContentEditorDialog(); 220 } 221 return INSTANCE; 222 } 223 224 /** 225 * Closes the dialog.<p> 226 */ 227 static void closeEditDialog() { 228 229 get().close(); 230 231 } 232 233 /** 234 * Opens the content editor dialog for the given element.<p> 235 * 236 * @param editableData the editable data 237 * @param isNew <code>true</code> when creating a new resource 238 * @param mode the content creation mode 239 * @param dlgOptions the additional dialog options 240 * @param editorHandler the editor handler 241 */ 242 public void openEditDialog( 243 final I_CmsEditableData editableData, 244 boolean isNew, 245 String mode, 246 final DialogOptions dlgOptions, 247 I_CmsContentEditorHandler editorHandler) { 248 249 m_mode = mode; 250 251 if ((m_dialog != null) && m_dialog.isShowing()) { 252 CmsDebugLog.getInstance().printLine("Dialog is already open, cannot open another one."); 253 return; 254 } 255 m_isNew = isNew; 256 m_editableData = editableData; 257 m_editorHandler = editorHandler; 258 if (m_isNew || (editableData.getStructureId() == null)) { 259 openDialog(dlgOptions); 260 } else { 261 CmsRpcAction<String> action = new CmsRpcAction<String>() { 262 263 @Override 264 public void execute() { 265 266 show(true); 267 CmsCoreProvider.getVfsService().getSitePath(getEditableData().getStructureId(), this); 268 } 269 270 @Override 271 protected void onResponse(String result) { 272 273 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(result)) { 274 getEditableData().setSitePath(result); 275 openDialog(dlgOptions); 276 } else { 277 CmsAlertDialog alert = new CmsAlertDialog( 278 Messages.get().key(Messages.ERR_TITLE_ERROR_0), 279 Messages.get().key(Messages.ERR_RESOURCE_UNAVAILABLE_1, getEditableData().getSitePath())); 280 alert.center(); 281 } 282 stop(false); 283 } 284 }; 285 action.execute(); 286 } 287 } 288 289 /** 290 * Closes the dialog.<p> 291 */ 292 protected void close() { 293 294 if (m_dialog != null) { 295 m_dialog.hide(); 296 m_dialog = null; 297 m_editorHandler.onClose( 298 m_editableData.getSitePath(), 299 m_editableData.getStructureId(), 300 m_isNew, 301 false, 302 false); 303 m_editorHandler = null; 304 } 305 if (m_form != null) { 306 m_form.removeFromParent(); 307 m_form = null; 308 } 309 if (m_closingHandlerRegistration != null) { 310 m_closingHandlerRegistration.removeHandler(); 311 m_closingHandlerRegistration = null; 312 } 313 } 314 315 /** 316 * Returns the editable data.<p> 317 * 318 * @return the editable data 319 */ 320 protected I_CmsEditableData getEditableData() { 321 322 return m_editableData; 323 } 324 325 /** 326 * Execute on window close.<p> 327 * Will ask the user if the edited content should be saved.<p> 328 */ 329 protected void onWindowClose() { 330 331 boolean savePage = Window.confirm( 332 Messages.get().key(Messages.GUI_EDITOR_SAVE_BEFORE_LEAVING_1, m_editableData.getSitePath())); 333 if (savePage) { 334 saveEditorContent(); 335 } 336 } 337 338 /** 339 * Opens the dialog for the given sitepath.<p> 340 * 341 * @param dlgOptions the additional dialog options 342 */ 343 protected void openDialog(DialogOptions dlgOptions) { 344 345 m_dialog = new CmsPopup( 346 Messages.get().key(Messages.GUI_DIALOG_CONTENTEDITOR_TITLE_0) 347 + " - " 348 + (m_isNew ? m_editableData.getNewTitle() : m_editableData.getSitePath())); 349 m_dialog.addStyleName(I_CmsLayoutBundle.INSTANCE.contentEditorCss().contentEditor()); 350 351 // calculate width 352 int width = Window.getClientWidth(); 353 width = (width < 1350) ? width - 50 : 1300; 354 if (dlgOptions.getSuggestedWidth() != null) { 355 width = dlgOptions.getSuggestedWidth().intValue(); 356 } 357 358 m_dialog.setWidth(width); 359 360 // calculate height 361 int height = Window.getClientHeight() - 50; 362 height = (height < 645) ? 645 : height; 363 if (dlgOptions.getSuggestedHeight() != null) { 364 height = dlgOptions.getSuggestedHeight().intValue(); 365 } 366 m_dialog.setHeight(height); 367 368 m_dialog.setGlassEnabled(true); 369 m_dialog.setUseAnimation(false); 370 final CmsIFrame editorFrame = new CmsIFrame(EDITOR_IFRAME_NAME, ""); 371 m_dialog.addDialogClose(new Command() { 372 373 /** 374 * @see com.google.gwt.user.client.Command#execute() 375 */ 376 public void execute() { 377 378 CmsConfirmDialog confirmDlg = new CmsConfirmDialog( 379 Messages.get().key(Messages.GUI_EDITOR_CLOSE_CAPTION_0), 380 Messages.get().key(Messages.GUI_EDITOR_CLOSE_TEXT_0)); 381 confirmDlg.setHandler(new I_CmsConfirmDialogHandler() { 382 383 public void onClose() { 384 385 // do nothing 386 } 387 388 public void onOk() { 389 390 // make sure the onunload event is triggered within the editor frames for ALL browsers 391 editorFrame.setUrl("about:blank"); 392 Scheduler.get().scheduleDeferred(new ScheduledCommand() { 393 394 public void execute() { 395 396 CmsContentEditorDialog.this.close(); 397 } 398 }); 399 } 400 }); 401 confirmDlg.center(); 402 // Let the confirm dialog handle the closing 403 throw new CmsCancelCloseException(); 404 } 405 }); 406 407 m_dialog.add(editorFrame); 408 m_dialog.setPositionFixed(); 409 m_dialog.center(); 410 m_form = generateForm(m_editableData, m_isNew, EDITOR_IFRAME_NAME, m_mode); 411 RootPanel.getBodyElement().appendChild(m_form); 412 m_form.submit(); 413 414 // adding on close handler 415 m_closingHandlerRegistration = Window.addWindowClosingHandler(new ClosingHandler() { 416 417 public void onWindowClosing(ClosingEvent event) { 418 419 onWindowClose(); 420 } 421 }); 422 } 423 424 /** 425 * Exports the close method to the window object, so it can be accessed from within the content editor iFrame.<p> 426 */ 427 private native void exportClosingMethod() /*-{ 428 $wnd[@org.opencms.gwt.client.ui.contenteditor.CmsContentEditorDialog::CLOSING_METHOD_NAME] = function() { 429 @org.opencms.gwt.client.ui.contenteditor.CmsContentEditorDialog::closeEditDialog()(); 430 }; 431 }-*/; 432 433 /** 434 * Saves the current editor content synchronously.<p> 435 */ 436 private native void saveEditorContent() /*-{ 437 var iFrame = $wnd.frames[@org.opencms.gwt.client.ui.contenteditor.CmsContentEditorDialog::EDITOR_IFRAME_NAME]; 438 if (iFrame != null) { 439 var editFrame = iFrame["edit"]; 440 if (editFrame != null) { 441 var editorFrame = editFrame.frames["editform"]; 442 if (editorFrame != null) { 443 var editForm = editorFrame.$("#EDITOR"); 444 editForm.find("input[name='action']").attr("value", 445 "saveexit"); 446 if (editForm != null) { 447 var data = editForm.serializeArray(editForm); 448 editorFrame.$.ajax({ 449 type : 'POST', 450 async : false, 451 url : editForm.attr("action"), 452 data : data, 453 success : function(result) { 454 // nothing to do 455 }, 456 dataType : "html" 457 }); 458 } 459 } 460 } 461 } 462 }-*/; 463}