Package org.waveprotocol.wave.client.editor.gwt

Source Code of org.waveprotocol.wave.client.editor.gwt.GwtRenderingMutationHandler

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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 org.waveprotocol.wave.client.editor.gwt;

import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.ui.Widget;

import org.waveprotocol.wave.client.common.util.DomHelper;
import org.waveprotocol.wave.client.common.util.LogicalPanel;
import org.waveprotocol.wave.client.editor.RenderingMutationHandler;
import org.waveprotocol.wave.client.editor.content.ContentElement;
import org.waveprotocol.wave.model.document.util.Property;
import org.waveprotocol.wave.model.util.Preconditions;

/**
* TODO: this logic should be moved to an event handler instead of a renderer
*
*  Renderers for doodads containing GWT widgets MUST extend or delegate to this
* class, if they want to be logically attached (in order to receive dom
* events).
*
*  Subclasses must: - Either implement {@link
* #createGwtWidget(org.waveprotocol.wave.client.editor.content.Renderer.Renderable)}
* and always return a new widget - Or, if null is ever returned from that
* method, for example to delay creating the widget, then when the widget is
* finally created, {@link #receiveNewGwtWidget(ContentElement, Widget)} or its
* variant must be called.
*
*  The mutation handling implementation listens to element removal to perform
* logical widget cleanup - this is also important, and they must be delegated
* to if overridden.
*
* @author danilatos@google.com (Daniel Danilatos)
*/
public abstract class GwtRenderingMutationHandler extends RenderingMutationHandler {

  /**
   * GWT widget associated with element
   */
  private static final Property<Widget> WIDGET = Property.immutable("widget");

  /**
   * Logical GWT parent of the GWT widget
   */
  private static final Property<LogicalPanel> LOGICAL_PANEL = Property.immutable("parent");

  /**
   * The way doodads should be rendered in the document
   */
  public enum Flow {
    /** css inline */
    INLINE {
      @Override Element createContainer() {
        return Document.get().createSpanElement();
      }
    },
    /** css block  */
    BLOCK {
      @Override Element createContainer() {
        return Document.get().createDivElement();
      }
    },
    /** Use the widget directly */
    USE_WIDGET {
      @Override
      Element createContainer() {
        throw new AssertionError("No container for Replace");
      }
    };
    abstract Element createContainer();
  }

  /**
   * How the rendered doodads should flow
   */
  private final Flow flow;

  /**
   * @param flow the GWT widget is by default wrapped by a single html node, and
   *        this parameter indicates what type of flow behaviour the wrapper
   *        html node should exhibit.
   */
  public GwtRenderingMutationHandler(Flow flow) {
    this.flow = flow;
  }


  /////////////////////////////////////////////
  // Public API

  /**
   * Associates a widget with an element, replacing the existing widget if any,
   * and performing necessary GWT book-keeping.
   *
   * Useful for delayed widget attachment (if the widget is added to an element
   * after some time), or to replace an existing widget.
   *
   * If null is passed, the widget is removed and cleanup is performed
   *
   * @param element
   * @param widget
   */
  public final void receiveNewGwtWidget(ContentElement element, Widget widget) {
    Element nodelet = element.getImplNodelet();
    Element parent = flow == Flow.USE_WIDGET ? null : nodelet;
    receiveNewGwtWidget(element, widget, parent);
  }

  /**
   * Same as {@link #receiveNewGwtWidget(ContentElement, Widget)}, but allows
   * specifying an arbitrary physical attach point, in case the default wrapper
   * span was replaced with something else and the widget belongs in a different
   * location within the HTML rendering of the ContentElement
   *
   * @param element
   * @param widget
   * @param physicalParent
   */
  public final void receiveNewGwtWidget(ContentElement element, Widget widget,
      Element physicalParent) {
    maybeLogicalDetach(element);
    disassociateWidget(element);

    if (widget != null) {
      associateWidget(element, widget, physicalParent);
      maybeLogicalAttach(element);
    }
  }


  /////////////////////////////////////////////
  // Subclassing API

  /**
   * Called to create the GWT Widget to be associated with the element, and
   * perform any other miscellaneous initialisation.
   *
   * If null is returned, then it is the responsibility of the subclass to
   * call {@link #associateWidget(Renderable, Widget, Element)}
   *
   * @param element element to associate the widget with
   * @return the newly created widget
   */
  protected abstract Widget createGwtWidget(Renderable element);

  /**
   * Default behaviour is to not have a container nodelet, since child elements
   * are probably there for state, not for rendering.
   */
  protected Element getContainerNodelet(Widget w) {
    return null;
  }

  /**
   * @param element
   * @return the GWT Widget for the given element
   */
  @SuppressWarnings("unchecked")
  public static <T extends Widget> T getGwtWidget(ContentElement element) {
    return (T) element.getProperty(WIDGET);
  }


  /////////////////////////////////////////////
  // Methods called by the core

  /**
   * Override {@link #createGwtWidget(Renderable)} to create your widget
   */
  @Override
  public final Element createDomImpl(Renderable element) {
    Widget w = createGwtWidget(element);

    Element implNodelet;
    Element attachNodelet;
    if (flow == Flow.USE_WIDGET) {
      Preconditions.checkState(w != null, "Cannot have null widget with USE_WIDGET");
      implNodelet = w.getElement();
      attachNodelet = null;
    } else {
      implNodelet = flow.createContainer();
      attachNodelet = implNodelet;
    }

    DomHelper.setContentEditable(implNodelet, false, false);
    DomHelper.makeUnselectable(implNodelet);
    if (w != null) {
      associateWidget(element, w, attachNodelet);
    }

    return implNodelet;
  }

  /**
   * Sets the logical panel in the GWT widget hierarchy that should be the
   * parent of the widget associated with the given element.
   *
   * A null parent may be given to indicate the element is no longer rendered
   * in a DOM with GWT widgets.
   *
   * @param element
   * @param parent
   */
  public final void setLogicalPanel(ContentElement element, LogicalPanel parent) {
    LogicalPanel existingParent = element.getProperty(LOGICAL_PANEL);

    if (existingParent == null && parent == null) {
      return; // Nothing to do
    }

    Preconditions.checkState(existingParent == null || parent == null,
        "setLogicalPanel called for an element that already has it");

    if (element.isContentAttached()) {
      if (parent == null) {
        assert existingParent != null && parent == null;
        maybeLogicalDetach(element);
        element.setProperty(LOGICAL_PANEL, null);
      } else {
        // The preconditions check should guard against this being
        // called for already-attached elements.
        assert existingParent == null && parent != null;
        element.setProperty(LOGICAL_PANEL, parent);
        maybeLogicalAttach(element);
      }
    }
  }

  @Override
  public void onActivationStart(ContentElement element) {
    maybeLogicalAttach(element);
  }

  /**
   * Cleans up for when the handler is unattached from an element.
   */
  @Override
  public void onDeactivated(ContentElement element) {
    receiveNewGwtWidget(element, null);
    setLogicalPanel(element, null);
  }

  /////////////////////////////////////////////

  private void maybeLogicalAttach(ContentElement element) {
    Widget w = getGwtWidget(element);
    LogicalPanel p = getLogicalPanel(element);
    if (p != null && w != null) {
      p.doAdopt(w);
    }
  }

  private void maybeLogicalDetach(ContentElement element) {
    Widget w = getGwtWidget(element);
    LogicalPanel p = getLogicalPanel(element);
    if (p != null && w != null && w.getParent() != null) {
      p.doOrphan(w);
    }
  }

  /**
   * Assigns a widget to an element and optionally attaches it to a parent html
   * node. Does not do any GWT logical attachment
   *
   * @param element the element the widget is associated with
   * @param w the widget to insert into the logical hierarchy
   * @param physicalParent the physical place in the dom to attach the widget.
   *        May be null, in which case no physical attachment will take place.
   */
  private void associateWidget(Renderable element, Widget w,
      Element physicalParent) {
    element.setProperty(WIDGET, w);
    if (physicalParent != null) {
      physicalParent.appendChild(w.getElement());
    }
    element.setAutoAppendContainer(getContainerNodelet(w));
  }

  /**
   * The opposite of {@link #associateWidget(Renderable, Widget, Element)}
   *
   * @param element
   */
  private void disassociateWidget(ContentElement element) {
    Widget old = getGwtWidget(element);
    if (old != null) {
      old.getElement().removeFromParent();
      element.setProperty(WIDGET, null);
    }
    assert element.getProperty(WIDGET) == null;
  }

  private LogicalPanel getLogicalPanel(ContentElement element) {
    return element.getProperty(LOGICAL_PANEL);
  }
}
TOP

Related Classes of org.waveprotocol.wave.client.editor.gwt.GwtRenderingMutationHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.