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