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.editor.dom.CmsNode;
38 import info.magnolia.ui.vaadin.gwt.client.editor.dom.Comment;
39 import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlComponent;
40 import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlElement;
41 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.AbstractMgnlElementProcessor;
42 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.CommentProcessor;
43 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.ElementProcessor;
44 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.MgnlElementProcessorFactory;
45 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.ProcessException;
46 import info.magnolia.ui.vaadin.gwt.client.editor.event.ComponentActionEvent;
47 import info.magnolia.ui.vaadin.gwt.client.editor.event.ComponentActionEventHandler;
48 import info.magnolia.ui.vaadin.gwt.client.editor.event.ComponentStartMoveEvent;
49 import info.magnolia.ui.vaadin.gwt.client.editor.event.ComponentStopMoveEvent;
50 import info.magnolia.ui.vaadin.gwt.client.editor.event.EditAreaEvent;
51 import info.magnolia.ui.vaadin.gwt.client.editor.event.EditAreaEventHandler;
52 import info.magnolia.ui.vaadin.gwt.client.editor.event.EditComponentEvent;
53 import info.magnolia.ui.vaadin.gwt.client.editor.event.EditComponentEventHandler;
54 import info.magnolia.ui.vaadin.gwt.client.editor.event.FrameNavigationEvent;
55 import info.magnolia.ui.vaadin.gwt.client.editor.event.FrameNavigationEventHandler;
56 import info.magnolia.ui.vaadin.gwt.client.editor.event.NewAreaEvent;
57 import info.magnolia.ui.vaadin.gwt.client.editor.event.NewAreaEventHandler;
58 import info.magnolia.ui.vaadin.gwt.client.editor.event.NewComponentEvent;
59 import info.magnolia.ui.vaadin.gwt.client.editor.event.NewComponentEventHandler;
60 import info.magnolia.ui.vaadin.gwt.client.editor.event.SelectElementEvent;
61 import info.magnolia.ui.vaadin.gwt.client.editor.event.SelectElementEventHandler;
62 import info.magnolia.ui.vaadin.gwt.client.editor.event.SortComponentEvent;
63 import info.magnolia.ui.vaadin.gwt.client.editor.event.SortComponentEventHandler;
64 import info.magnolia.ui.vaadin.gwt.client.editor.jsni.event.FrameLoadedEvent;
65 import info.magnolia.ui.vaadin.gwt.client.editor.model.Model;
66 import info.magnolia.ui.vaadin.gwt.client.editor.model.ModelImpl;
67 import info.magnolia.ui.vaadin.gwt.client.editor.model.focus.FocusModel;
68 import info.magnolia.ui.vaadin.gwt.client.editor.model.focus.FocusModelImpl;
69 import info.magnolia.ui.vaadin.gwt.client.rpc.PageEditorClientRpc;
70 import info.magnolia.ui.vaadin.gwt.client.rpc.PageEditorServerRpc;
71 import info.magnolia.ui.vaadin.gwt.client.shared.AbstractElement;
72 import info.magnolia.ui.vaadin.gwt.client.shared.AreaElement;
73 import info.magnolia.ui.vaadin.gwt.client.shared.ComponentElement;
74 import info.magnolia.ui.vaadin.gwt.client.shared.PageEditorParameters;
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.DOM;
93 import com.google.gwt.user.client.ui.Widget;
94 import com.vaadin.client.BrowserInfo;
95 import com.vaadin.client.communication.RpcProxy;
96 import com.vaadin.client.communication.StateChangeEvent;
97 import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
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 PAGE_EDITOR_CSS = "/VAADIN/themes/ui-app-pages/page-editor.css";
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 @Override
125 protected void init() {
126 super.init();
127 this.model = new ModelImpl();
128 this.focusModel = new FocusModelImpl(eventBus, model);
129 this.elementProcessor = new ElementProcessor(eventBus, model);
130 this.commentProcessor = new CommentProcessor();
131
132 addStateChangeHandler(new StateChangeHandler() {
133 @Override
134 public void onStateChanged(StateChangeEvent stateChangeEvent) {
135 PageEditorParameters params = getState().parameters;
136 view.setUrl(params.getUrl());
137 if (params.isPreview()) {
138 view.getFrame().addStyleName("iframe-preloader");
139 }
140 }
141 });
142
143 registerRpc(PageEditorClientRpc.class, new PageEditorClientRpc() {
144
145 @Override
146 public void refresh() {
147 view.reload();
148 }
149
150 @Override
151 public void startMoveComponent() {
152 MgnlComponent component = focusModel.getSelectedComponent();
153 if (component != null) {
154 component.doStartMove(false);
155 model.setMoving(true);
156 if (!BrowserInfo.get().isTouchDevice()) {
157 Element element = DOM.clone(focusModel.getSelectedComponent().getControlBar().getElement(), true);
158 moveWidget = new MoveWidget(element);
159 moveWidget.attach(view.getFrame(), component.getWidth(), component.getHeight());
160 }
161 }
162 }
163
164 @Override
165 public void cancelMoveComponent() {
166 eventBus.fireEvent(new ComponentStopMoveEvent(null, true));
167 }
168 });
169
170 eventBus.addHandler(FrameLoadedEvent.TYPE, new FrameLoadedEvent.Handler() {
171 @Override
172 public void handle(FrameLoadedEvent event) {
173 model.reset();
174 Document document = null;
175 try {
176 document = event.getFrame().getContentDocument();
177 } catch (JavaScriptException e) {
178 GWT.log("Error getting content document from iframe: " + e.getMessage());
179 }
180 if (document == null) {
181 rpc.selectExternalPage();
182 } else {
183 process(document);
184
185 view.initKeyEventListeners();
186
187 if (!getState().parameters.isPreview()) {
188 view.initDomEventListeners();
189 focusModel.init();
190 } else {
191 focusModel.clearSelection();
192 }
193 }
194 }
195 });
196
197 eventBus.addHandler(FrameNavigationEvent.TYPE, new FrameNavigationEventHandler() {
198 @Override
199 public void onFrameUrlChanged(FrameNavigationEvent frameUrlChangedEvent) {
200 String path = frameUrlChangedEvent.getPath();
201
202 final String platformId = getState().parameters.getPlatformType().getId();
203 path += path.indexOf('?') == -1 ? "?" : "&";
204 path += "mgnlChannel=" + platformId;
205
206 final boolean isPreview = getState().parameters.isPreview();
207 path += "&mgnlPreview=" + isPreview;
208
209 view.setUrl(path);
210 }
211 });
212
213 eventBus.addHandler(SelectElementEvent.TYPE, new SelectElementEventHandler() {
214 @Override
215 public void onSelectElement(SelectElementEvent selectElementEvent) {
216 AbstractElement selectedElement = selectElementEvent.getElement();
217 if (selectedElement instanceof PageElement) {
218 rpc.selectPage((PageElement) selectedElement);
219 } else if (selectedElement instanceof AreaElement) {
220 rpc.selectArea((AreaElement) selectedElement);
221 } else if (selectedElement instanceof ComponentElement) {
222 rpc.selectComponent((ComponentElement) selectedElement);
223 }
224 view.resetScrollTop();
225 }
226 });
227
228 eventBus.addHandler(NewAreaEvent.TYPE, new NewAreaEventHandler() {
229 @Override
230 public void onNewArea(NewAreaEvent newAreaEvent) {
231 rpc.newArea(newAreaEvent.getAreaElement());
232 }
233 });
234
235 eventBus.addHandler(NewComponentEvent.TYPE, new NewComponentEventHandler() {
236 @Override
237 public void onNewComponent(NewComponentEvent newComponentEvent) {
238 rpc.newComponent(newComponentEvent.getParentAreaElement());
239 }
240 });
241
242 eventBus.addHandler(EditAreaEvent.TYPE, new EditAreaEventHandler() {
243 @Override
244 public void onEditArea(EditAreaEvent editAreaEvent) {
245 rpc.editArea(editAreaEvent.getAreaElement());
246 }
247 });
248
249 eventBus.addHandler(EditComponentEvent.TYPE, new EditComponentEventHandler() {
250 @Override
251 public void onEditComponent(EditComponentEvent editComponentEvent) {
252 rpc.editComponent(editComponentEvent.getComponentElement());
253 }
254 });
255
256 eventBus.addHandler(SortComponentEvent.TYPE, new SortComponentEventHandler() {
257 @Override
258 public void onSortComponent(SortComponentEvent sortComponentEvent) {
259 rpc.sortComponent(sortComponentEvent.getAreaElement());
260 }
261 });
262
263 eventBus.addHandler(ComponentStartMoveEvent.TYPE, new ComponentStartMoveEvent.CompnentStartMoveEventHandler() {
264 @Override
265 public void onStart(ComponentStartMoveEvent componentStartMoveEvent) {
266 rpc.startMoveComponent();
267 }
268 });
269
270 eventBus.addHandler(ComponentStopMoveEvent.TYPE, new ComponentStopMoveEvent.ComponentStopMoveEventHandler() {
271 @Override
272 public void onStop(ComponentStopMoveEvent componentStopMoveEvent) {
273 if (!componentStopMoveEvent.isServerSide()) {
274 rpc.stopMoveComponent();
275 }
276 if (moveWidget != null && moveWidget.isAttached()) {
277 moveWidget.detach();
278 }
279 model.setMoving(false);
280 }
281 });
282
283 eventBus.addHandler(ComponentActionEvent.TYPE, new ComponentActionEventHandler() {
284 @Override
285 public void onAction(ComponentActionEvent actionEvent) {
286 rpc.onComponentAction(actionEvent.getElement(), actionEvent.getActionName(), actionEvent.getArgs());
287 }
288 });
289 }
290
291 @Override
292 protected Widget createWidget() {
293 this.view = new PageEditorViewImpl(eventBus);
294 this.view.setListener(this);
295 return view.asWidget();
296 }
297
298 @Override
299 public PageEditorState getState() {
300 return (PageEditorState) super.getState();
301 }
302
303 @Override
304 public void selectElement(Element element) {
305 focusModel.selectElement(model.getMgnlElement(element));
306 }
307
308 private void process(final Document document) {
309 try {
310 injectEditorStyles(document);
311 long startTime = System.currentTimeMillis();
312 processDocument(document, null);
313 processMgnlElements();
314 GWT.log("Time spent to process cms comments: " + (System.currentTimeMillis() - startTime) + "ms");
315 } catch (ProcessException e) {
316 rpc.onError(e.getErrorType(), e.getTagName());
317 GWT.log("Error while processing comment: " + e.getTagName() + " due to " + e.getErrorType());
318 consoleLog("Error while processing comment: " + e.getTagName() + " due to " + e.getErrorType());
319 }
320 }
321
322 private void injectEditorStyles(final Document document) {
323 HeadElement head = HeadElement.as(document.getElementsByTagName("head").getItem(0));
324 LinkElement cssLink = document.createLinkElement();
325 cssLink.setType("text/css");
326 cssLink.setRel("stylesheet");
327 cssLink.setHref(getState().parameters.getContextPath() + PAGE_EDITOR_CSS);
328 head.insertFirst(cssLink);
329 }
330
331 private void processDocument(Node node, MgnlElement mgnlElement) throws ProcessException {
332 if (mgnlElement == null && model.getRootPage() != null) {
333 mgnlElement = model.getRootPage();
334 }
335 for (int i = 0; i < node.getChildCount(); i++) {
336 Node childNode = node.getChild(i);
337 if (childNode.getNodeType() == Comment.COMMENT_NODE) {
338 try {
339 mgnlElement = commentProcessor.process(model, eventBus, childNode, mgnlElement);
340 } catch (ProcessException e) {
341 throw e;
342 } catch (IllegalArgumentException e) {
343 GWT.log("Not CMSComment element, skipping: " + e.toString());
344 } catch (Exception e) {
345 GWT.log("Caught undefined exception: " + e.toString());
346 consoleLog("Caught undefined exception: " + e.toString());
347 }
348 } else if (childNode.getNodeType() == Node.ELEMENT_NODE) {
349 Element element = childNode.cast();
350 elementProcessor.process(element, mgnlElement, getState().parameters.isPreview());
351 }
352 processDocument(childNode, mgnlElement);
353 }
354 }
355
356 private void processMgnlElements() {
357 CmsNode root = model.getRootPage();
358 if (model.getRootPage() == null) {
359 log.warning("Could not find any Magnolia cms:page tag, this might be a static page; not injecting page-editor bars.");
360 return;
361 }
362
363 List<CmsNode> elements = root.getDescendants();
364 elements.add(root);
365 for (CmsNode element : elements) {
366 try {
367 AbstractMgnlElementProcessor processor = MgnlElementProcessorFactory.getProcessor(model, element.asMgnlElement());
368 processor.process();
369 } catch (IllegalArgumentException e) {
370 GWT.log("MgnlFactory could not instantiate class. The element is neither an area nor component.");
371 } catch (Exception e) {
372 final String errorMessage = "Error when processing editor components for '" + element.asMgnlElement().getAttribute("path") +
373 "'. 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"
374 + e.toString();
375 GWT.log(errorMessage);
376 consoleLog(errorMessage);
377 }
378 }
379 }
380
381 void consoleLog(String message) {
382 log.info("PageEditor: " + message);
383 }
384 }