001/*
002 * #%L
003 * GwtMaterial
004 * %%
005 * Copyright (C) 2015 - 2017 GwtMaterialDesign
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 * 
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 * 
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package gwt.material.design.addins.client.richeditor;
021
022import com.google.gwt.dom.client.Document;
023import com.google.gwt.dom.client.Element;
024import com.google.gwt.event.dom.client.BlurEvent;
025import com.google.gwt.event.dom.client.FocusEvent;
026import com.google.gwt.event.dom.client.KeyDownEvent;
027import com.google.gwt.event.dom.client.KeyUpEvent;
028import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
029import com.google.gwt.event.logical.shared.ValueChangeEvent;
030import com.google.gwt.event.shared.HandlerRegistration;
031import com.google.gwt.safehtml.shared.SafeHtmlUtils;
032import com.google.gwt.user.client.ui.HasHTML;
033import gwt.material.design.addins.client.MaterialAddins;
034import gwt.material.design.addins.client.base.constants.AddinsCssName;
035import gwt.material.design.addins.client.richeditor.base.HasPasteHandlers;
036import gwt.material.design.addins.client.richeditor.base.ToolBarManager;
037import gwt.material.design.addins.client.richeditor.base.constants.RichEditorEvents;
038import gwt.material.design.addins.client.richeditor.base.constants.ToolbarButton;
039import gwt.material.design.addins.client.richeditor.events.PasteEvent;
040import gwt.material.design.addins.client.richeditor.js.JsRichEditor;
041import gwt.material.design.addins.client.richeditor.js.JsRichEditorOptions;
042import gwt.material.design.client.MaterialDesignBase;
043import gwt.material.design.client.base.AbstractValueWidget;
044import gwt.material.design.client.base.HasPlaceholder;
045import gwt.material.design.client.base.JsLoader;
046import gwt.material.design.client.ui.MaterialModal;
047import gwt.material.design.client.ui.MaterialModalContent;
048import gwt.material.design.jquery.client.api.JQueryElement;
049
050import static gwt.material.design.addins.client.richeditor.js.JsRichEditor.$;
051
052//@formatter:off
053
054/**
055 * Provides a great Rich Editor with amazing options built with Material Design Look and Feel
056 * <p>
057 * <h3>XML Namespace Declaration</h3>
058 * <pre>
059 * {@code
060 * xmlns:ma='urn:import:gwt.material.design.addins.client'
061 * }
062 * </pre>
063 * <p>
064 * <h3>UiBinder Usage:</h3>
065 * <pre>
066 * {@code
067 * <ma:MaterialRichEditor placeholder="Type anything in here..."/>
068 * }
069 * </pre>
070 *
071 * @author kevzlou7979
072 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#richeditor">Material Rich Editor</a>
073 * @see <a href="https://github.com/Cerealkillerway/materialNote">1.2.1</a>
074 */
075//@formatter:on
076public class MaterialRichEditor extends AbstractValueWidget<String> implements JsLoader, HasValueChangeHandlers<String>, HasPasteHandlers, HasPlaceholder, HasHTML  {
077
078    static {
079        if (MaterialAddins.isDebug()) {
080            MaterialDesignBase.injectDebugJs(MaterialRichEditorDebugClientBundle.INSTANCE.richEditorDebugJs());
081            MaterialDesignBase.injectCss(MaterialRichEditorDebugClientBundle.INSTANCE.richEditorDebugCss());
082        } else {
083            MaterialDesignBase.injectJs(MaterialRichEditorClientBundle.INSTANCE.richEditorJs());
084            MaterialDesignBase.injectCss(MaterialRichEditorClientBundle.INSTANCE.richEditorCss());
085        }
086    }
087
088
089    private String html;
090    private ToolBarManager manager = new ToolBarManager();
091    private boolean toggleFullScreen = true;
092    private JsRichEditorOptions options = JsRichEditorOptions.create();
093
094    private HandlerRegistration handlerRegistration;
095
096    public MaterialRichEditor() {
097        super(Document.get().createDivElement(), AddinsCssName.EDITOR);
098    }
099
100    public MaterialRichEditor(String placeholder) {
101        this();
102        setPlaceholder(placeholder);
103    }
104
105    public MaterialRichEditor(String placeholder, String value) {
106        this(placeholder);
107        setValue(value);
108    }
109
110    @Override
111    protected void onLoad() {
112        super.onLoad();
113
114        load();
115
116        setHTML(html);
117    }
118
119    @Override
120    public void load() {
121
122        JsRichEditor jsRichEditor = $(getElement());
123
124        options.toolbar = manager.getToolbars();
125        options.placeholder = getPlaceholder();
126        options.height = getHeight();
127
128        jsRichEditor.materialnote(options);
129
130        // Events
131        jsRichEditor.on(RichEditorEvents.MATERIALNOTE_BLUR, event -> {
132            fireEvent(new BlurEvent() {});
133            return true;
134        });
135        jsRichEditor.on(RichEditorEvents.MATERIALNOTE_FOCUS, event -> {
136            fireEvent(new FocusEvent() {});
137            return true;
138        });
139        jsRichEditor.on(RichEditorEvents.MATERIALNOTE_KEYUP, event -> {
140            fireEvent(new KeyUpEvent() {});
141            return true;
142        });
143        jsRichEditor.on(RichEditorEvents.MATERIALNOTE_KEYDOWN, event -> {
144            fireEvent(new KeyDownEvent() {});
145            return true;
146        });
147        jsRichEditor.on(RichEditorEvents.MATERIALNOTE_PASTE, event -> {
148            fireEvent(new PasteEvent() {});
149            return true;
150        });
151        jsRichEditor.on(RichEditorEvents.MATERIALNOTE_CHANGE, event -> {
152            ValueChangeEvent.fire(MaterialRichEditor.this, getHTMLCode(getElement()));
153            return true;
154        });
155
156        checkContainer();
157    }
158
159    @Override
160    protected void onUnload() {
161        super.onUnload();
162
163        unload();
164    }
165
166    @Override
167    public void unload() {
168        JsRichEditor jsRichEditor = $(getElement());
169        jsRichEditor.off(RichEditorEvents.MATERIALNOTE_BLUR);
170        jsRichEditor.off(RichEditorEvents.MATERIALNOTE_FOCUS);
171        jsRichEditor.off(RichEditorEvents.MATERIALNOTE_KEYUP);
172        jsRichEditor.off(RichEditorEvents.MATERIALNOTE_KEYDOWN);
173        jsRichEditor.off(RichEditorEvents.MATERIALNOTE_PASTE);
174        jsRichEditor.off(RichEditorEvents.MATERIALNOTE_CHANGE);
175        jsRichEditor.destroy();
176    }
177
178    @Override
179    public void reload() {
180        unload();
181        load();
182    }
183
184    public ToolbarButton[] getStyleOptions() {
185        return manager.getStyleOptions();
186    }
187
188    public void setStyleOptions(ToolbarButton... styleOptions) {
189        manager.setStyleOptions(styleOptions);
190    }
191
192    public ToolbarButton[] getFontOptions() {
193        return manager.getFontOptions();
194    }
195
196    public void setFontOptions(ToolbarButton... fontOptions) {
197        manager.setFontOptions(fontOptions);
198    }
199
200    public ToolbarButton[] getColorOptions() {
201        return manager.getColorOptions();
202    }
203
204    public void setColorOptions(ToolbarButton... colorOptions) {
205        manager.setColorOptions(colorOptions);
206    }
207
208    public ToolbarButton[] getUndoOptions() {
209        return manager.getUndoOptions();
210    }
211
212    public void setUndoOptions(ToolbarButton... undoOptions) {
213        manager.setUndoOptions(undoOptions);
214    }
215
216    public ToolbarButton[] getCkMediaOptions() {
217        return manager.getCkMediaOptions();
218    }
219
220    public void setCkMediaOptions(ToolbarButton... ckMediaOptions) {
221        manager.setCkMediaOptions(ckMediaOptions);
222    }
223
224    public ToolbarButton[] getMiscOptions() {
225        return manager.getMiscOptions();
226    }
227
228    public void setMiscOptions(ToolbarButton... miscOptions) {
229        manager.setMiscOptions(miscOptions);
230    }
231
232    public ToolbarButton[] getParaOptions() {
233        return manager.getParaOptions();
234    }
235
236    public void setParaOptions(ToolbarButton... paraOptions) {
237        manager.setParaOptions(paraOptions);
238    }
239
240    public ToolbarButton[] getHeightOptions() {
241        return manager.getHeightOptions();
242    }
243
244    public void setHeightOptions(ToolbarButton... heightOptions) {
245        manager.setHeightOptions(heightOptions);
246    }
247
248    protected void checkContainer() {
249        if (getParent() instanceof MaterialModal) {
250            MaterialModal modal = (MaterialModal) getParent();
251            adjustFullScreen(modal);
252            adjustNestedModals(modal);
253        } else if (getParent() instanceof MaterialModalContent) {
254            MaterialModal modal = (MaterialModal) getParent().getParent();
255            adjustFullScreen(modal);
256            adjustNestedModals(modal);
257        }
258    }
259
260    protected void adjustNestedModals(MaterialModal modal) {
261        registerHandler(modal.addOpenHandler(openEvent -> modal.setDepth(9999)));
262    }
263
264    protected void adjustFullScreen(MaterialModal modal) {
265        getEditor().find("div[data-event='fullscreen']").off("click").on("click", (e, param1) -> {
266            modal.setFullscreen(toggleFullScreen);
267            if (toggleFullScreen) {
268                toggleFullScreen = false;
269            } else {
270                toggleFullScreen = true;
271            }
272            return true;
273        });
274    }
275
276    public JQueryElement getEditor() {
277        return $(getElement()).next(".note-editor");
278    }
279
280    /**
281     * Insert custom text inside the note zone.
282     */
283    public void insertText(String text) {
284        insertText(getElement(), text);
285    }
286
287    /**
288     * Insert custom text inside the note zone.
289     */
290    protected void insertText(Element e, String text) {
291        $(e).materialnote("insertText", SafeHtmlUtils.fromString(text).asString());
292    }
293
294    /**
295     * Insert custom HTML inside the note zone.
296     */
297    public void pasteHTML(String html) {
298        pasteHTML(getElement(), html);
299    }
300
301    /**
302     * Insert custom HTML inside the note zone.
303     */
304    protected void pasteHTML(Element e, String html) {
305        $(e).materialnote("pasteHTML", html);
306    }
307
308    /**
309     * Reset the Rich Editor component
310     */
311    public void reset() {
312        $(getElement()).materialnote("reset");
313    }
314
315    @Override
316    public void clear() {
317        super.clear();
318        reset();
319    }
320
321    public boolean isAirMode() {
322        return options.airMode;
323    }
324
325    public void setAirMode(boolean airMode) {
326        options.airMode = airMode;
327    }
328
329    /**
330     * Check if the dnd for rich editor is enabled / disabled
331     */
332    public boolean isDisableDragAndDrop() {
333        return options.disableDragAndDrop;
334    }
335
336    /**
337     * If true, disable the ability to drag and drop items to rich editor
338     */
339    public void setDisableDragAndDrop(boolean disableDragAndDrop) {
340        options.disableDragAndDrop = disableDragAndDrop;
341    }
342
343    @Override
344    public String getPlaceholder() {
345        return options.placeholder;
346    }
347
348    @Override
349    public void setPlaceholder(String placeholder) {
350        options.placeholder = placeholder;
351    }
352
353    public String getHeight() {
354        String height = getElement().getStyle().getHeight();
355        if (height == null || height.isEmpty()) {
356            height = "550px";
357        }
358        return height;
359    }
360
361    @Override
362    public String getHTML() {
363        return getHTMLCode(getElement());
364    }
365
366    @Override
367    public void setHTML(final String html) {
368        this.html = html;
369
370        if (handlerRegistration != null) {
371            handlerRegistration.removeHandler();
372            handlerRegistration = null;
373        }
374
375        if (!isAttached() && html != null) {
376            handlerRegistration = registerHandler(addAttachHandler(e -> setHTMLCode(getElement(), html)));
377        } else {
378            setHTMLCode(getElement(), html);
379        }
380    }
381
382    @Override
383    public String getText() {
384        return getHTML();
385    }
386
387    @Override
388    public void setText(String text) {
389        setHTML(text);
390    }
391
392    protected String getHTMLCode(Element e) {
393        return $(e).code();
394    }
395
396    protected void setHTMLCode(Element e, String html) {
397        $(e).code(html);
398    }
399
400    @Override
401    public String getValue() {
402        return getHTML();
403    }
404
405    @Override
406    public void setValue(String value, boolean fireEvents) {
407        setHTML(value);
408    }
409
410    @Override
411    public HandlerRegistration addPasteHandler(final PasteEvent.PasteHandler handler) {
412        return addHandler(handler, PasteEvent.TYPE);
413    }
414}