1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.vaadin.gwt.client.connector;
35
36 import info.magnolia.ui.vaadin.editor.PageEditor;
37 import info.magnolia.ui.vaadin.gwt.client.css.PageEditorCssProvider;
38 import info.magnolia.ui.vaadin.gwt.client.editor.dom.CmsNode;
39 import info.magnolia.ui.vaadin.gwt.client.editor.dom.Comment;
40 import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlComponent;
41 import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlElement;
42 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.AbstractMgnlElementProcessor;
43 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.CommentProcessor;
44 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.ElementProcessor;
45 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.MgnlElementProcessorFactory;
46 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.ProcessException;
47 import info.magnolia.ui.vaadin.gwt.client.editor.event.ComponentActionEvent;
48 import info.magnolia.ui.vaadin.gwt.client.editor.event.ComponentActionEventHandler;
49 import info.magnolia.ui.vaadin.gwt.client.editor.event.ComponentStartMoveEvent;
50 import info.magnolia.ui.vaadin.gwt.client.editor.event.ComponentStopMoveEvent;
51 import info.magnolia.ui.vaadin.gwt.client.editor.event.EditAreaEvent;
52 import info.magnolia.ui.vaadin.gwt.client.editor.event.EditAreaEventHandler;
53 import info.magnolia.ui.vaadin.gwt.client.editor.event.EditComponentEvent;
54 import info.magnolia.ui.vaadin.gwt.client.editor.event.EditComponentEventHandler;
55 import info.magnolia.ui.vaadin.gwt.client.editor.event.FrameNavigationEvent;
56 import info.magnolia.ui.vaadin.gwt.client.editor.event.FrameNavigationEventHandler;
57 import info.magnolia.ui.vaadin.gwt.client.editor.event.NewAreaEvent;
58 import info.magnolia.ui.vaadin.gwt.client.editor.event.NewAreaEventHandler;
59 import info.magnolia.ui.vaadin.gwt.client.editor.event.NewComponentEvent;
60 import info.magnolia.ui.vaadin.gwt.client.editor.event.NewComponentEventHandler;
61 import info.magnolia.ui.vaadin.gwt.client.editor.event.SelectElementEvent;
62 import info.magnolia.ui.vaadin.gwt.client.editor.event.SelectElementEventHandler;
63 import info.magnolia.ui.vaadin.gwt.client.editor.event.SortComponentEvent;
64 import info.magnolia.ui.vaadin.gwt.client.editor.event.SortComponentEventHandler;
65 import info.magnolia.ui.vaadin.gwt.client.editor.jsni.event.FrameLoadedEvent;
66 import info.magnolia.ui.vaadin.gwt.client.editor.model.Model;
67 import info.magnolia.ui.vaadin.gwt.client.editor.model.ModelImpl;
68 import info.magnolia.ui.vaadin.gwt.client.editor.model.focus.FocusModel;
69 import info.magnolia.ui.vaadin.gwt.client.editor.model.focus.FocusModelImpl;
70 import info.magnolia.ui.vaadin.gwt.client.rpc.PageEditorClientRpc;
71 import info.magnolia.ui.vaadin.gwt.client.rpc.PageEditorServerRpc;
72 import info.magnolia.ui.vaadin.gwt.client.shared.AbstractElement;
73 import info.magnolia.ui.vaadin.gwt.client.shared.AreaElement;
74 import info.magnolia.ui.vaadin.gwt.client.shared.ComponentElement;
75 import info.magnolia.ui.vaadin.gwt.client.shared.PageElement;
76 import info.magnolia.ui.vaadin.gwt.client.widget.PageEditorView;
77 import info.magnolia.ui.vaadin.gwt.client.widget.PageEditorViewImpl;
78 import info.magnolia.ui.vaadin.gwt.client.widget.dnd.MoveWidget;
79
80 import java.util.List;
81 import java.util.logging.Logger;
82
83 import com.google.gwt.core.client.GWT;
84 import com.google.gwt.core.client.JavaScriptException;
85 import com.google.gwt.dom.client.Document;
86 import com.google.gwt.dom.client.Element;
87 import com.google.gwt.dom.client.HeadElement;
88 import com.google.gwt.dom.client.LinkElement;
89 import com.google.gwt.dom.client.Node;
90 import com.google.gwt.event.shared.EventBus;
91 import com.google.gwt.event.shared.SimpleEventBus;
92 import com.google.gwt.user.client.ui.Widget;
93 import com.vaadin.client.BrowserInfo;
94 import com.vaadin.client.communication.RpcProxy;
95 import com.vaadin.client.communication.StateChangeEvent;
96 import com.vaadin.client.ui.AbstractComponentConnector;
97 import com.vaadin.shared.ui.Connect;
98
99
100
101
102 @Connect(PageEditor.class)
103 public class PageEditorConnector extends AbstractComponentConnector implements PageEditorView.Listener {
104
105 private Logger log = Logger.getLogger(getClass().getName());
106
107 private static final String IFRAME_PRELOADER_STYLE = "iframe-preloader";
108
109 private final PageEditorServerRpc rpc = RpcProxy.create(PageEditorServerRpc.class, this);
110
111 private final EventBus eventBus = new SimpleEventBus();
112
113 private PageEditorView view;
114 private MoveWidget moveWidget;
115
116 private Model model;
117
118 private FocusModel focusModel;
119 private ElementProcessor elementProcessor;
120 private CommentProcessor commentProcessor;
121
122
123
124
125
126
127 private boolean pageChange = false;
128
129 @Override
130 protected void init() {
131 super.init();
132 this.model = new ModelImpl();
133 this.focusModel = new FocusModelImpl(eventBus, model);
134 this.elementProcessor = new ElementProcessor(eventBus, model);
135 this.commentProcessor = new CommentProcessor();
136
137 addStateChangeHandler("url", new StateChangeEvent.StateChangeHandler() {
138
139 @Override
140 public void onStateChanged(StateChangeEvent stateChangeEvent) {
141 view.setUrl(getState().getUrl());
142 if (getState().isPreview()) {
143 view.getFrame().addStyleName(IFRAME_PRELOADER_STYLE);
144 }
145 }
146 });
147 registerRpc(PageEditorClientRpc.class, new PageEditorClientRpc() {
148
149 @Override
150 public void refresh() {
151 view.reload();
152 }
153
154 @Override
155 public void startMoveComponent() {
156 MgnlElement selectedElement = model.getMgnlElement(getState().getSelectedElement());
157 if (selectedElement != null && selectedElement.isComponent()) {
158 MgnlComponent component = (MgnlComponent) selectedElement;
159 component.doStartMove(false);
160 model.setMoving(true);
161 if (!BrowserInfo.get().isTouchDevice()) {
162 PageEditorConnector.this.moveWidget = component.getMoveWidget();
163 moveWidget.attach(view.getFrame());
164 }
165 }
166 }
167
168 @Override
169 public void cancelMoveComponent() {
170 eventBus.fireEvent(new ComponentStopMoveEvent(null, true));
171 }
172 });
173
174
175
176
177
178 eventBus.addHandler(FrameLoadedEvent.TYPE, new FrameLoadedEvent.Handler() {
179 @Override
180 public void handle(FrameLoadedEvent event) {
181 model.reset();
182 focusModel.resetFocus();
183 Document document = null;
184 try {
185 document = event.getFrame().getContentDocument();
186 } catch (JavaScriptException e) {
187 GWT.log("Error getting content document from iframe: " + e.getMessage());
188 }
189 if (document == null) {
190 rpc.selectExternalPage();
191 } else {
192 process(document);
193
194 view.initKeyEventListeners();
195
196 if (!getState().isPreview()) {
197 view.initDomEventListeners();
198 focusModel.init();
199 }
200 AbstractElement elementToSelect = pageChange ? null : getState().getSelectedElement();
201 focusModel.selectElement(elementToSelect);
202 }
203 PageEditorConnector.this.pageChange = false;
204
205 if (event.getFrame() != null) {
206 rpc.onNavigation(cleanupSpecialParameters(event.getFrame().getUrl()));
207 }
208 }
209 });
210
211 eventBus.addHandler(FrameNavigationEvent.TYPE, new FrameNavigationEventHandler() {
212 @Override
213 public void onFrameUrlChanged(FrameNavigationEvent frameUrlChangedEvent) {
214 String path = frameUrlChangedEvent.getPath();
215
216 final String platformId = getState().getPlatformType().getId();
217 path += path.indexOf('?') == -1 ? "?" : "&";
218 path += "mgnlChannel=" + platformId;
219
220 final boolean isPreview = getState().isPreview();
221 path += "&mgnlPreview=" + isPreview;
222
223 PageEditorConnector.this.pageChange = true;
224 view.setUrl(path);
225 }
226 });
227
228 eventBus.addHandler(SelectElementEvent.TYPE, new SelectElementEventHandler() {
229 @Override
230 public void onSelectElement(SelectElementEvent selectElementEvent) {
231 AbstractElement selectedElement = selectElementEvent.getElement();
232 if (selectedElement instanceof PageElement) {
233 rpc.selectPage((PageElement) selectedElement);
234 } else if (selectedElement instanceof AreaElement) {
235 rpc.selectArea((AreaElement) selectedElement);
236 } else if (selectedElement instanceof ComponentElement) {
237 rpc.selectComponent((ComponentElement) selectedElement);
238 }
239 view.resetScrollTop();
240 }
241 });
242
243 eventBus.addHandler(NewAreaEvent.TYPE, new NewAreaEventHandler() {
244 @Override
245 public void onNewArea(NewAreaEvent newAreaEvent) {
246 rpc.newArea(newAreaEvent.getAreaElement());
247 }
248 });
249
250 eventBus.addHandler(NewComponentEvent.TYPE, new NewComponentEventHandler() {
251 @Override
252 public void onNewComponent(NewComponentEvent newComponentEvent) {
253 rpc.newComponent(newComponentEvent.getParentAreaElement());
254 }
255 });
256
257 eventBus.addHandler(EditAreaEvent.TYPE, new EditAreaEventHandler() {
258 @Override
259 public void onEditArea(EditAreaEvent editAreaEvent) {
260 rpc.editArea(editAreaEvent.getAreaElement());
261 }
262 });
263
264 eventBus.addHandler(EditComponentEvent.TYPE, new EditComponentEventHandler() {
265 @Override
266 public void onEditComponent(EditComponentEvent editComponentEvent) {
267 rpc.editComponent(editComponentEvent.getComponentElement());
268 }
269 });
270
271 eventBus.addHandler(SortComponentEvent.TYPE, new SortComponentEventHandler() {
272 @Override
273 public void onSortComponent(SortComponentEvent sortComponentEvent) {
274 rpc.sortComponent(sortComponentEvent.getAreaElement());
275 }
276 });
277
278 eventBus.addHandler(ComponentStartMoveEvent.TYPE, new ComponentStartMoveEvent.CompnentStartMoveEventHandler() {
279 @Override
280 public void onStart(ComponentStartMoveEvent componentStartMoveEvent) {
281 rpc.startMoveComponent();
282 }
283 });
284
285 eventBus.addHandler(ComponentStopMoveEvent.TYPE, new ComponentStopMoveEvent.ComponentStopMoveEventHandler() {
286 @Override
287 public void onStop(ComponentStopMoveEvent componentStopMoveEvent) {
288 if (!componentStopMoveEvent.isServerSide()) {
289 rpc.stopMoveComponent();
290 }
291 if (moveWidget != null && moveWidget.isAttached()) {
292 moveWidget.detach();
293 }
294 model.setMoving(false);
295 }
296 });
297
298 eventBus.addHandler(ComponentActionEvent.TYPE, new ComponentActionEventHandler() {
299 @Override
300 public void onAction(ComponentActionEvent actionEvent) {
301 rpc.onClientAction(actionEvent.getElement(), actionEvent.getActionName(), actionEvent.getParameters());
302 }
303 });
304 }
305
306 @Override
307 protected Widget createWidget() {
308 this.view = new PageEditorViewImpl(eventBus);
309 this.view.setListener(this);
310 return view.asWidget();
311 }
312
313 @Override
314 public PageEditorState getState() {
315 return (PageEditorState) super.getState();
316 }
317
318 @Override
319 public void selectElement(Element element) {
320 focusModel.selectElement(element);
321 }
322
323 private void process(final Document document) {
324 try {
325 injectEditorStyles(document);
326 long startTime = System.currentTimeMillis();
327 processDocument(document, null);
328 processMgnlElements();
329 GWT.log("Time spent to process cms comments: " + (System.currentTimeMillis() - startTime) + "ms");
330 } catch (ProcessException e) {
331 rpc.onError(e.getErrorType(), e.getTagName());
332 GWT.log("Error while processing comment: " + e.getTagName() + " due to " + e.getErrorType());
333 consoleLog("Error while processing comment: " + e.getTagName() + " due to " + e.getErrorType());
334 }
335 }
336
337 private void injectEditorStyles(final Document document) {
338 HeadElement head = HeadElement.as(document.getElementsByTagName("head").getItem(0));
339 for (String uri: PageEditorCssProvider.INSTANCE.getCssLinks()) {
340 LinkElement cssLink = createLinkElement(document, getState().getContextPath() + uri);
341 head.insertFirst(cssLink);
342 }
343 }
344
345 private LinkElement createLinkElement(final Document document, final String href) {
346 LinkElement cssLink = document.createLinkElement();
347 cssLink.setType("text/css");
348 cssLink.setRel("stylesheet");
349 cssLink.setHref(href);
350 return cssLink;
351 }
352
353 private void processDocument(Node node, MgnlElement mgnlElement) throws ProcessException {
354 if (mgnlElement == null && model.getRootPage() != null) {
355 mgnlElement = model.getRootPage();
356 }
357 for (int i = 0; i < node.getChildCount(); i++) {
358 Node childNode = node.getChild(i);
359 if (childNode.getNodeType() == Comment.COMMENT_NODE) {
360 try {
361 mgnlElement = commentProcessor.process(model, eventBus, childNode, mgnlElement);
362 } catch (ProcessException e) {
363 throw e;
364 } catch (IllegalArgumentException e) {
365 GWT.log("Not CMSComment element, skipping: " + e.toString());
366 } catch (Exception e) {
367 GWT.log("Caught undefined exception: " + e.toString());
368 consoleLog("Caught undefined exception: " + e.toString());
369 }
370 } else if (childNode.getNodeType() == Node.ELEMENT_NODE) {
371 Element element = childNode.cast();
372 elementProcessor.process(element, mgnlElement, getState().isPreview());
373 }
374 processDocument(childNode, mgnlElement);
375 }
376 }
377
378 private void processMgnlElements() {
379 CmsNode root = model.getRootPage();
380 if (model.getRootPage() == null) {
381 log.warning("Could not find any Magnolia cms:page tag, this might be a static page; not injecting page-editor bars.");
382 return;
383 }
384
385 List<CmsNode> elements = root.getDescendants();
386 elements.add(root);
387 for (CmsNode element : elements) {
388 try {
389 AbstractMgnlElementProcessor processor = MgnlElementProcessorFactory.getProcessor(model, element.asMgnlElement());
390 processor.process();
391 } catch (IllegalArgumentException e) {
392 GWT.log("MgnlFactory could not instantiate class. The element is neither an area nor component.");
393 } catch (Exception e) {
394 final String errorMessage = "Error when processing editor components for '" + element.asMgnlElement().getAttribute("path") +
395 "'. It's possible that the template script for this area or for some subcomponent is incorrect. Please check that all HTML tags are closed properly.\n"
396 + e.toString();
397 GWT.log(errorMessage);
398 consoleLog(errorMessage);
399 }
400 }
401 }
402
403 void consoleLog(String message) {
404 log.info("PageEditor: " + message);
405 }
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421 private String cleanupSpecialParameters(String url) {
422 return url.replaceAll("(\\&{0,1})(mgnlChannel|mgnlPreview)\\=([^&]+)", "").replaceAll("(\\?$)", "").replaceAll("(\\?\\&)", "?");
423 }
424 }