View Javadoc

1   /**
2    * This file Copyright (c) 2011 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.templating.editor.client;
35  
36  
37  import info.magnolia.templating.editor.client.jsni.LegacyJavascript;
38  
39  import com.google.gwt.core.client.EntryPoint;
40  import com.google.gwt.core.client.GWT;
41  import com.google.gwt.dom.client.Document;
42  import com.google.gwt.dom.client.Element;
43  import com.google.gwt.dom.client.MetaElement;
44  import com.google.gwt.dom.client.Node;
45  import com.google.gwt.dom.client.NodeList;
46  import com.google.gwt.http.client.Request;
47  import com.google.gwt.http.client.RequestBuilder;
48  import com.google.gwt.http.client.RequestCallback;
49  import com.google.gwt.http.client.RequestException;
50  import com.google.gwt.http.client.Response;
51  import com.google.gwt.http.client.URL;
52  import com.google.gwt.http.client.UrlBuilder;
53  import com.google.gwt.i18n.client.Dictionary;
54  import com.google.gwt.user.client.Event;
55  import com.google.gwt.user.client.EventListener;
56  import com.google.gwt.user.client.Window;
57  import com.google.gwt.user.client.ui.HTML;
58  
59  /**
60   * Client side implementation of the page editor. Outputs ui widgets inside document element (typically the {@code <html>} element).
61   *
62   * @version $Id$
63   */
64  public class PageEditor extends HTML implements EventListener, EntryPoint {
65  
66      public static final String MARKER_EDIT = "cms:edit";
67      public static final String MARKER_AREA = "cms:area";
68  
69      private boolean pageEditBarAlreadyProcessed = false;
70      private String locale;
71      private static Dictionary dictionary;
72  
73      @Override
74      public void onModuleLoad() {
75          Element documentElement = Document.get().getDocumentElement();
76  
77          final NodeList<Element> edits = documentElement.getOwnerDocument().getElementsByTagName(MARKER_EDIT);
78          GWT.log("found " + edits.getLength() + " cms:edit tags");
79  
80          final NodeList<Element> areas = documentElement.getOwnerDocument().getElementsByTagName(MARKER_AREA);
81          GWT.log("found " + areas.getLength() + " cms:area tags");
82  
83          locale = detectCurrentLocale(documentElement);
84          //TODO move messages we need to this module?
85          LegacyJavascript.exposeMgnlMessagesToGwtDictionary("info.magnolia.module.admininterface.messages");
86          dictionary = Dictionary.getDictionary("mgnlGwtMessages");
87  
88          processCmsTags(documentElement, null, edits, areas);
89  
90      }
91  
92      @Override
93      public void onBrowserEvent(Event event) {
94          super.onBrowserEvent(event);
95      }
96  
97      /**
98       * TODO: rename and/or remove arguments no longer needed (collectionName, nodeName).
99       */
100     public void openDialog(String dialog, String workspace, String path, String collectionName, String nodeName) {
101         if (collectionName == null) {
102             collectionName = "";
103         }
104         if (nodeName == null) {
105             nodeName = "";
106         }
107 
108         LegacyJavascript.mgnlOpenDialog(path, collectionName, nodeName, dialog, workspace, "", "", "", locale);
109     }
110 
111     public void moveComponentStart(String id) {
112         LegacyJavascript.mgnlMoveNodeStart(id);
113     }
114 
115     public void moveComponentEnd(AbstractBarWidget source, String path) {
116         LegacyJavascript.mgnlMoveNodeEnd(source.getElement(), path);
117     }
118 
119     public void moveComponentOver(AbstractBarWidget source) {
120         LegacyJavascript.mgnlMoveNodeHigh(source.getElement());
121     }
122 
123     public void moveComponentOut(AbstractBarWidget source) {
124         LegacyJavascript.mgnlMoveNodeReset(source.getElement());
125     }
126 
127     public void deleteComponent(String path) {
128         LegacyJavascript.mgnlDeleteNode(path);
129     }
130 
131     public void addComponent(String workspace, String path, String collectionName, String nodeName, String availableComponents) {
132         if (collectionName == null) {
133             collectionName = "";
134         }
135         if (nodeName == null) {
136             nodeName = "mgnlNew";
137         }
138         if (availableComponents == null) {
139             availableComponents = "";
140         }
141         LegacyJavascript.mgnlOpenDialog(path, collectionName, nodeName, availableComponents, workspace, ".magnolia/dialogs/selectParagraph.html", "", "", locale);
142     }
143 
144     public void preview(boolean isPreview) {
145         LegacyJavascript.mgnlPreview(isPreview);
146     }
147 
148     public void showTree(String workspace, String path) {
149         LegacyJavascript.showTree(workspace, path);
150 
151     }
152 
153     public void createComponent(String workspace, String parent, String relPath, String itemType) {
154         GWT.log("Creating [" + itemType + "] in workspace [" + workspace + "] at path [" + parent + "/" + relPath + "]");
155 
156         final StringBuilder url = new StringBuilder();
157         url.append(LegacyJavascript.getContextPath() + ".magnolia/pageeditor/PageEditorServlet");
158         url.append("?action=create");
159         url.append("&workspace=" + workspace);
160         url.append("&parent=" + parent);
161         url.append("&relPath=" + relPath);
162         url.append("&itemType=" + itemType);
163 
164         RequestBuilder req = new RequestBuilder(RequestBuilder.GET, URL.encode(url.toString()));
165         req.setCallback(new RequestCallback() {
166 
167             @Override
168             public void onResponseReceived(Request request, Response response) {
169                 int status = response.getStatusCode();
170                 String responseText = "";
171                 boolean reload = false;
172 
173                 switch (status) {
174                     case Response.SC_OK:
175                         reload = true;
176                         break;
177                     case Response.SC_UNAUTHORIZED:
178                         responseText = "Is your session expired? Please, try to login again.";
179                         break;
180                     default:
181                         responseText = "See logs for more details.";
182                 }
183 
184                 if (reload) {
185                     UrlBuilder urlBuilder = Window.Location.createUrlBuilder();
186 
187                     urlBuilder.removeParameter("mgnlIntercept");
188                     urlBuilder.removeParameter("mgnlPath");
189 
190                     Window.Location.replace(urlBuilder.buildString());
191                 } else {
192                     Window.alert("An error occurred on the server: response status code is " + status + "\n" + responseText);
193                 }
194             }
195 
196             @Override
197             public void onError(Request request, Throwable exception) {
198                 Window.alert(exception.getMessage());
199             }
200         });
201         try {
202             req.send();
203         } catch (RequestException e) {
204             Window.alert("An error occurred while trying to send a request to the server: " + e.getMessage());
205         }
206 
207     }
208 
209     /**
210      * Provides dynamic string lookup of key/value string pairs defined in a module's host HTML page.
211      */
212     public static Dictionary getDictionary() {
213         return dictionary;
214     }
215 
216     /**
217      * A String representing the value for the GWT meta property whose content is <em>locale</em>.
218      * See also <a href='http://code.google.com/webtoolkit/doc/latest/DevGuideI18nLocale.html#LocaleSpecifying'>GWT Dev Guide to i18n</a>
219      */
220     private String detectCurrentLocale(Element element) {
221         final NodeList<Element> meta = element.getOwnerDocument().getElementsByTagName("meta");
222         for (int i = 0; i < meta.getLength(); i++) {
223             MetaElement metaTag = ((MetaElement) meta.getItem(i));
224             if ("gwt:property".equals(metaTag.getName()) && metaTag.getContent().contains("locale")) {
225                 String[] split = metaTag.getContent().split("=");
226                 locale = split.length == 2 ? split[1] : "en";
227                 GWT.log("Detected Locale " + locale);
228                 break;
229             }
230         }
231         return locale;
232     }
233 
234     private void processCmsTags(Element element, AreaBarWidget parentBar, NodeList<Element> edits, NodeList<Element> areas) {
235         for (int i = 0; i < element.getChildCount(); i++) {
236             Node childNode = element.getChild(i);
237             if (childNode.getNodeType() == Element.ELEMENT_NODE) {
238                 Element child = (Element) childNode;
239                 if (child.getTagName().equalsIgnoreCase(MARKER_EDIT)) {
240                     GWT.log("processing element " + child);
241                     //We assume the first cms:edit we encounter in DOM is the page edit bar.
242                     if (!pageEditBarAlreadyProcessed) {
243                         GWT.log("element was detected as page edit bar. Injecting it...");
244                         PageBarWidget pageBarWidget = new PageBarWidget(this, child);
245                         pageBarWidget.attach(child);
246                         pageEditBarAlreadyProcessed = true;
247 
248                         if (pageBarWidget.isPreviewMode()) {
249                             //we just need the preview bar here
250                             GWT.log("We're in preview mode, stop processing DOM.");
251                             break;
252                         }
253                         //avoid processing cms:edit marker twice if this is an area
254                     } else if (!isAreaEditBar(child, areas)) {
255                         GWT.log("element is a plain edit bar. Injecting it...");
256                         EditBarWidget editBarWidget = new EditBarWidget(parentBar, this, child);
257                         editBarWidget.attach(child);
258                     }
259                 } else if (child.getTagName().equalsIgnoreCase(MARKER_AREA)) {
260                     GWT.log("processing element " + child);
261                     Element edit = findCmsEditMarkerForArea(child, edits);
262                     if (edit != null) {
263                         GWT.log("element was detected as area edit bar. Injecting it...");
264                         AreaBarWidget areaBarWidget = new AreaBarWidget(parentBar, this, child);
265                         areaBarWidget.attach(edit);
266                         parentBar = areaBarWidget;
267                     }
268                 }
269                 processCmsTags(child, parentBar, edits, areas);
270             }
271         }
272     }
273 
274     /**
275      * Tries to find a match between the provided edit bar tag and the area tags found in DOM. Best match is when area and edit tags have the exact same <code>content</code> attribute value.
276      * However there might be the case where an optional area is in DOM but still needs to be created (manually via the UI). In that case content will be null,
277      * therefore we rely on name and optional attributes on having the same values in area and edit tags.
278      */
279     private boolean isAreaEditBar(Element edit, NodeList<Element> areas) {
280 
281         String content = edit.getAttribute("content");
282         String name = edit.getAttribute("name");
283         boolean optional = Boolean.valueOf(edit.getAttribute("optional"));
284         String bestMatch = optional ? name : content;
285 
286         for (int j = 0; j < areas.getLength(); j++) {
287 
288             Element area = areas.getItem(j);
289 
290             String areaContent = area.getAttribute("content");
291             String areaName = area.getAttribute("name");
292             boolean areaOptional = Boolean.valueOf(area.getAttribute("optional"));
293             boolean created = Boolean.valueOf(area.getAttribute("created"));
294 
295             String areaMatch = areaContent + (LegacyJavascript.isNotEmpty(areaName) ? "/" + areaName : "");
296 
297             if (areaOptional && !created) {
298                 areaMatch = areaName;
299             }
300 
301             if (bestMatch.equals(areaMatch)) {
302                 GWT.log("element is an area edit bar (matched with [" + areaMatch + "])");
303                 return true;
304             }
305         }
306         return false;
307     }
308 
309     /**
310      * Tries to find a match between the provided edit area tag and the edit tags found in DOM. Best match is when area and edit tags have the exact same <code>content</code> attribute value.
311      * However there might be the case where an optional area is in DOM but still needs to be created (manually via the UI). In that case content will be null,
312      * therefore we rely on name and optional attributes on having the same values in area and edit tags.
313      */
314     private Element findCmsEditMarkerForArea(Element area, NodeList<Element> edits) {
315         String content = area.getAttribute("content");
316         String name = area.getAttribute("name");
317         boolean optional = Boolean.valueOf(area.getAttribute("optional"));
318         boolean created = Boolean.valueOf(area.getAttribute("created"));
319         //if area is optional and not yet created, best match is its name, else is content + name
320         String bestMatch = optional && !created ? name : content + (LegacyJavascript.isNotEmpty(name) ? "/" + name : "");
321 
322         GWT.log("Best match for " + (optional ? "optional" : "required") + " area and edit bar is [" + bestMatch + "]");
323 
324         for (int i = 0; i < edits.getLength(); i++) {
325             Element edit = edits.getItem(i);
326             String toMatch = edit.getAttribute("content");
327             String editName = edit.getAttribute("name");
328             toMatch += (LegacyJavascript.isNotEmpty(editName) ? "/" + editName : "");
329             boolean editOptional = Boolean.valueOf(edit.getAttribute("optional"));
330 
331             if (toMatch.equals(bestMatch) || (optional && editOptional && bestMatch.equals(editName))) {
332                 GWT.log("found match with element " + edit);
333                 return edit;
334             }
335         }
336         GWT.log("No match found. Area won't have an edit bar associated.");
337         return null;
338     }
339 }