com.appspot.ast.client.editor.doodad.my.MyDoodad.java Source code

Java tutorial

Introduction

Here is the source code for com.appspot.ast.client.editor.doodad.my.MyDoodad.java

Source

/**
 * Copyright 2010 Google Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package com.appspot.ast.client.editor.doodad.my;

import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Widget;
import org.waveprotocol.wave.client.common.util.DomHelper;
import org.waveprotocol.wave.client.common.util.DomHelper.JavaScriptEventListener;
import org.waveprotocol.wave.client.editor.ElementHandlerRegistry;
import org.waveprotocol.wave.client.editor.NodeEventHandler;
import org.waveprotocol.wave.client.editor.RenderingMutationHandler;
import org.waveprotocol.wave.client.editor.content.ContentElement;
import org.waveprotocol.wave.client.editor.content.ContentNode;
import org.waveprotocol.wave.client.editor.content.misc.ChunkyElementHandler;
import org.waveprotocol.wave.client.editor.content.misc.DisplayEditModeHandler;
import org.waveprotocol.wave.client.editor.content.misc.LinoTextEventHandler;
import org.waveprotocol.wave.client.editor.content.misc.UpdateContentEditable;
import org.waveprotocol.wave.client.editor.content.paragraph.ParagraphRenderer;
import org.waveprotocol.wave.client.editor.event.EditorEvent;
import org.waveprotocol.wave.client.editor.gwt.GwtRenderingMutationHandler;
import org.waveprotocol.wave.model.document.util.Point;

/**
 * Demonstration doodad, with a few different variations of rendering and
 * interactive behavior.
 *
 * @author danilatos@google.com (Daniel Danilatos)
 */
public class MyDoodad {
    public static String TAGNAME = "mydoodad";
    public static String CAPTION_TAGNAME = "mycaption";

    public static String REF_ATTR = "ref";

    public static void register(ElementHandlerRegistry registry) {

        //// For <mydoodad> ////

        // Alternatives with different behaviors are commented out.

        SimpleRenderer renderer = new SimpleRenderer();
        //    CaptionedRenderer renderer = new CaptionedRenderer();

        //    NodeEventHandler handler = ChunkyElementHandler.get();
        SimpleEventHandler handler = new SimpleEventHandler();
        //    GwtEventHandler handler = new GwtEventHandler(renderer);
        //    CaptionedEventHandler handler = new CaptionedEventHandler(renderer);

        //// For <mycaption> ////

        // The next line Creates a renderer that uses a "div" for the HTML, and
        // handles all the logic for correctly rendering a line
        // of text. See the implementation for details.
        ParagraphRenderer captionRenderer = ParagraphRenderer.create("div");
        NodeEventHandler captionHandler = new CaptionEventHandler();

        ////

        registry.registerRenderingMutationHandler(TAGNAME, renderer);
        registry.registerRenderingMutationHandler(CAPTION_TAGNAME, captionRenderer);
        registry.registerEventHandler(TAGNAME, handler);
        registry.registerEventHandler(CAPTION_TAGNAME, captionHandler);
    }

    /**
     * A trivial renderer that keeps the image's src attribute up-to-date with the
     * model's ref attribute.
     */
    static class SimpleRenderer extends RenderingMutationHandler {

        @Override
        public Element createDomImpl(Renderable element) {
            Element imgTag = Document.get().createImageElement();
            DomHelper.setContentEditable(imgTag, false, false);
            return imgTag;
        }

        @Override
        public void onActivatedSubtree(ContentElement element) {
            fanoutAttrs(element);
        }

        @Override
        public void onAttributeModified(ContentElement element, String name, String oldValue, String newValue) {

            if (REF_ATTR.equals(name)) {
                element.getImplNodelet().setAttribute("src", newValue);
            }
        }
    }

    /**
     * Simple event handler that allows the user to change the image.
     *
     * This demonstrates adding custom event handling to the doodad.
     *
     * We extend ChunkyElementHandler that takes care of special editor events
     * that make our element have sensible behaviour with respect to arrow keys
     * and delete/backspace.
     */
    static class SimpleEventHandler extends ChunkyElementHandler {

        @Override
        public void onActivated(final ContentElement element) {
            Helper.registerJsHandler(element, element.getImplNodelet(), "click", new JavaScriptEventListener() {
                @Override
                public void onJavaScriptEvent(String name, Event event) {
                    promptNewRef(element);
                }
            });
        }

        @Override
        public void onDeactivated(ContentElement element) {
            // Cleanup
            Helper.removeJsHandlers(element);
        }
    }

    static void promptNewRef(ContentElement element) {
        String newRef = Window.prompt("New Ref", element.getAttribute(REF_ATTR));
        if (newRef != null) {
            // Get the document view for mutating the persistent state, then update it
            element.getMutableDoc().setElementAttribute(element, REF_ATTR, newRef);
        }
    }

    /**
     * Renderer that adds a caption and some fancier DOM rendering.
     *
     *  Demonstrates using a GWT widget for rendering, and fancier features like
     * having a concurrently-editable caption sub-element.
     */
    static class CaptionedRenderer extends GwtRenderingMutationHandler {

        public CaptionedRenderer() {
            super(Flow.INLINE);
        }

        /** Gwt renderer equivalent of {@link #createDomImpl(org.waveprotocol.wave.client.editor.content.Renderer.Renderable)} */
        @Override
        protected CaptionedImageWidget createGwtWidget(Renderable element) {
            return new CaptionedImageWidget();
        }

        /**
         * Specify where the HTML DOM of child XML elements goes. Our widget's
         * getContainer() method returns the inner 'div' where we would like to put
         * the caption. We use this as the "container nodelet" so that when the
         * 'mycaption' element gets added to 'mydoodad' (in the model XML), the
         * caption's main 'div' nodelet automatically gets added to our doodad's
         * inner container nodelet (in the render HTML).
         *
         * So our DOM will end up looking like this:
         *
         * <pre>{@literal
         *
         * <div class='top'>           <!-- this is <mydoodad>'s top level "impl nodelet" -->
         *   <img src='...'/>          <!-- the image inside the tag -->
         *   <div class='container>    <!-- this is the container nodelet -->
         *
         *     <div>                   <!-- this is <mycaption>'s top level impl nodelet -->
         *       caption text
         *       <br/>                 <!-- This br gets inserted by the paragraph renderer
         *     </div>                       and is needed on some browsers. we don't have to
         *                                  worry about it, it's taken care of for us -->
         *   </div>
         * </div>
         *
         * }</pre>
         */
        @Override
        protected Element getContainerNodelet(Widget w) {
            return ((CaptionedImageWidget) w).getContainer();
        }

        @Override
        public void onActivatedSubtree(ContentElement element) {
            super.onActivatedSubtree(element);
            fanoutAttrs(element);
        }

        @Override
        public void onAttributeModified(ContentElement element, String name, String oldValue, String newValue) {
            super.onAttributeModified(element, name, oldValue, newValue);

            if (REF_ATTR.equals(name)) {
                getWidget(element).setImageSrc(newValue);
            }
        }

        /** Convenience getter */
        CaptionedImageWidget getWidget(ContentElement e) {
            return ((CaptionedImageWidget) getGwtWidget(e));
        }
    }

    static class GwtEventHandler extends ChunkyElementHandler {
        private final CaptionedRenderer renderer;

        GwtEventHandler(CaptionedRenderer renderer) {
            this.renderer = renderer;
        }

        @Override
        public void onActivated(final ContentElement element) {
            renderer.getWidget(element).setListener(new CaptionedImageWidget.Listener() {
                @Override
                public void onClickImage() {
                    promptNewRef(element);
                }
            });
        }
    }

    static class CaptionedEventHandler extends GwtEventHandler {
        CaptionedEventHandler(CaptionedRenderer renderer) {
            super(renderer);
        }

        /**
         * Handles a left arrow that occurred with the caret immediately
         * after this node, by moving caret to end of caption
         */
        @Override
        public boolean handleLeftAfterNode(ContentElement element, EditorEvent event) {
            ContentElement caption = getCaption(element);

            if (caption != null) {
                // If we have a caption, move the selection into the caption
                element.getSelectionHelper().setCaret(Point.<ContentNode>end(getCaption(element)));
                return true;
            } else {
                // If we don't have a caption, use the default behavior
                return super.handleLeftAfterNode(element, event);
            }
        }

        /**
         * Similar to {@link #handleLeftAfterNode(org.waveprotocol.wave.client.editor.content.ContentElement, org.waveprotocol.wave.client.editor.event.EditorEvent)}
         */
        @Override
        public boolean handleRightBeforeNode(ContentElement element, EditorEvent event) {
            ContentElement caption = getCaption(element);

            if (caption != null) {
                // If we have a caption, move the selection into the caption
                element.getSelectionHelper().setCaret(Point.start(element.getRenderedContentView(), caption));
                return true;
            } else {
                // If we don't have a caption, use the default behavior
                return super.handleRightBeforeNode(element, event);
            }
        }

        /**
         * Handles a left arrow at the beginning of the caption, moving the
         * selection out of the whole doodad. We receive this event because the
         * caption doesn't handle it and it bubbles outwards to our handler here.
         */
        @Override
        public boolean handleLeftAtBeginning(ContentElement element, EditorEvent event) {
            // NOTE: The use of location mapper will normalise into text nodes.
            element.getSelectionHelper().setCaret(element.getLocationMapper()
                    .getLocation(Point.before(element.getRenderedContentView(), element)));
            return true;
        }

        /**
         * Similar to {@link #handleLeftAtBeginning(org.waveprotocol.wave.client.editor.content.ContentElement, org.waveprotocol.wave.client.editor.event.EditorEvent)}
         */
        @Override
        public boolean handleRightAtEnd(ContentElement element, EditorEvent event) {
            // NOTE: The use of location mapper will normalise into text nodes.
            element.getSelectionHelper().setCaret(element.getLocationMapper()
                    .getLocation(Point.after(element.getRenderedContentView(), element)));
            return true;
        }

        private ContentElement getCaption(ContentElement element) {
            return (ContentElement) element.getFirstChild();
        }
    }

    /**
     * Event handler for our caption. Demonstrates two things:
     * 1. Subclassing LinoTextEventHandler, which provides sane behavior for,
     *    well, a line-of-text. (See its code for details)
     * 2. Use of utility to synchronise editability of caption region with main
     *    editor region.
     */
    static class CaptionEventHandler extends LinoTextEventHandler {
        @Override
        public void onActivated(ContentElement element) {
            super.onActivated(element);

            // Add a listener to edit mode changes.
            // We use an existing one that does exactly what we want: updates the editability of
            // our element's container as a result.
            DisplayEditModeHandler.setEditModeListener(element, UpdateContentEditable.get());
        }
    }
}