Skip to content

Instantly share code, notes, and snippets.

@ailabs-software
Created December 20, 2016 06:09
Show Gist options
  • Save ailabs-software/7e87da2bc4ed7664246d8260f89f3a7b to your computer and use it in GitHub Desktop.
Save ailabs-software/7e87da2bc4ed7664246d8260f89f3a7b to your computer and use it in GitHub Desktop.
Events and life cycle
package com.google.gwt.sample.contentcentral.client;
import com.google.gwt.core.client.EntryPoint;
// package disposable;
/** Interface for an object which observes when we have been disposed */
interface IDisposeCallback
{
void disposeCallback();
}
/**@interface {goog.disposable.IDisposable}
* Interface for a disposable object. If a instance requires cleanup
* (references COM objects, DOM notes, or other disposable objects), it should
* implement this interface (it may subclass goog.Disposable).
* @interface
*/
interface IDisposable
{
/**
* Disposes of the object and its resources.
* @return {void} Nothing.
*/
void dispose();
/**
* @return {boolean} Whether the object has been disposed of.
*/
boolean isDisposed();
/** Used by EventTarget to dispose listeners when listening object is disposed */
void addOnDisposeCallback(IDisposeCallback callback);
}
/**@class {goog.disposable.Disposable}
* Class that provides the basic implementation for disposable objects. If your
* class holds one or more references to COM objects, DOM nodes, or other
* disposable objects, it should extend this class or implement the disposable
* interface (defined in goog.disposable.IDisposable).
* @constructor
* @implements {goog.disposable.IDisposable}
*/
class Disposable implements IDisposable
{
/**
* Whether the object has been disposed of.
* @type {boolean}
* @private
*/
private boolean disposed_ = false;
/**
* @return {boolean} Whether the object has been disposed of.
* @override
*/
public boolean isDisposed()
{
return this.disposed_;
}
/**
* Disposes of the object. If the object hasn't already been disposed of, calls
* {@link #disposeInternal}. Classes that extend {@code goog.Disposable} should
* override {@link #disposeInternal} in order to delete references to COM
* objects, DOM nodes, and other disposable objects. Reentrant.
*
* @return {void} Nothing.
* @override
*/
public void dispose()
{
if (!this.disposed_) {
// Set disposed_ to true first, in case during the chain of disposal this
// gets disposed recursively.
this.disposed_ = true;
this.disposeInternal();
// Run callbacks from other objects on dispose
Window.alert("EventTarget: I should let them know this listening object is dead");
}
}
// Overridable
protected void disposeInternal()
{
// Default implementation is no-op
}
/**
* Invokes a callback function when this object is disposed. Callbacks are
* invoked in the order in which they were added. If a callback is added to
* an already disposed Disposable, it will be called immediately.
* @param {function(this:T):?} callback The callback function.
* @param {T=} opt_scope An optional scope to call the callback in.
* @template T
*/
public void addOnDisposeCallback(IDisposeCallback callback)
{
// TODO unimplemented
}
}
// CONVENTIONS: Trailing underscore if method is JSNI and wrapped.
// TODO: DisposableObject class
// TODO: Track event listener count like Closure
// Package events;
/** @class {goog.events.EventTarget} */
abstract class EventTarget extends Disposable
{
@FunctionalInterface
public interface EventHandlerFunc {
void apply();
}
/** (simular to Closure Library, but also tracks synthetic event listeners)
* Estimated count of total listeners in the event system.
* @private {number}
*/
private static int listenerCountEstimate_ = 0;
public final void listen(String eventType, IDisposable handler, EventHandlerFunc handlerMethod)
{
listen(eventType, handler, handlerMethod, false); // Capturing false default
}
public final void listen(String eventType, IDisposable handler, EventHandlerFunc handlerMethod, boolean capturing)
{
listenerCountEstimate_++;
handler.addOnDisposeCallback( new EventTargetListenerDisposer(this, eventType, handler, handlerMethod, capturing) );
listenImpl(eventType, handlerMethod, capturing);
}
public final void unlisten(String eventType, IDisposable handler, EventHandlerFunc handlerMethod, boolean capturing)
{
listenerCountEstimate_--;
unlistenImpl(eventType, handlerMethod, capturing);
}
// TODO unlisten
// TODO String bad choice for eventType
protected abstract void listenImpl(String eventType, EventHandlerFunc handlerMethod, boolean capturing);
protected abstract void unlistenImpl(String eventType, EventHandlerFunc handlerMethod, boolean capturing);
/** @internal Protects against memory leaks by ensuring all listeners are cleaned up when observing object is disposed. */
private class EventTargetListenerDisposer implements IDisposeCallback
{
private EventTarget eventTarget;
private String eventType;
private IDisposable handler;
private EventHandlerFunc handlerMethod;
private boolean capturing;
EventTargetListenerDisposer(EventTarget eventTarget, String eventType, IDisposable handler, EventHandlerFunc handlerMethod, boolean capturing)
{
this.eventTarget = eventTarget;
this.eventType = eventType;
this.handler = handler;
this.handlerMethod = handlerMethod;
this.capturing = capturing;
}
public void disposeCallback()
{
this.eventTarget.unlisten(this.eventType, this.handler, this.handlerMethod, this.capturing);
}
}
}
class SyntheticEventTarget extends EventTarget
{
@Override
protected void listenImpl(String eventType, EventHandlerFunc handler, boolean capturing)
{
throw new Error("Not Yet Implemented");
}
@Override
protected void unlistenImpl(String eventType, EventHandlerFunc handler, boolean capturing)
{
throw new Error("Not Yet Implemented");
}
}
// Package dom;
class Element extends EventTarget
{
private Object wrappedDomContext;
Element(Object wrappedDomContext)
{
this.wrappedDomContext = wrappedDomContext;
}
/* Implement inherited abstract methods */
@Override
protected native void listenImpl(String eventType, EventHandlerFunc handler, boolean capturing) /*-{
var wrappedDomContext = [email protected]::wrappedDomContext;
function handlerFunc() // TODO ELIMINATE THIS CLOSURE
{
handler.@com.google.gwt.sample.contentcentral.client.EventTarget.EventHandlerFunc::apply()();
}
wrappedDomContext.addEventListener(eventType, handlerFunc, capturing);
}-*/;
@Override
protected void unlistenImpl(String eventType, EventHandlerFunc handler, boolean capturing)
{
throw new Error("Not Yet Implemented");
}
/* Query DOM and return Element */
public Element querySelector(String query)
{
return new Element( this.querySelector_(query) );
}
private native Object querySelector_(String query) /*-{
var wrappedDomContext = [email protected]::wrappedDomContext;
return wrappedDomContext.querySelector(query);
}-*/;
/* Append child */
public native void appendChild(Element element) /*-{
var parentNode = [email protected]::wrappedDomContext;
var childNode = [email protected]::wrappedDomContext;
parentNode.appendChild(childNode);
}-*/;
/* Set DOM text content */
public native void setTextContent(String textContent) /*-{
var wrappedDomContext = [email protected]::wrappedDomContext;
return wrappedDomContext.textContent = textContent;
}-*/;
/* Set cssName */
public native void setCssName(String cssName) /*-{
var wrappedDomContext = [email protected]::wrappedDomContext;
return wrappedDomContext.className = cssName;
}-*/;
}
// Package dom;
// TODO: Allow for multiple documents to coexist.
class Document
{
/* Provides access to document associated with current Window */
public static Element getDocumentElement()
{
return new Element( getDocumentDomContext_() );
}
private native static Object getDocumentDomContext_() /*-{
return $wnd.document.documentElement;
}-*/;
public static Element createElement(String tagName)
{
return new Element( createElement_(tagName) );
}
private native static Object createElement_(String tagName) /*-{
return $wnd.document.createElement(tagName);
}-*/;
}
// Package dom;
class TagName
{
public static String DIV = "div";
public static String LABEL = "label";
public static String INPUT = "input";
public static String SELECT = "select";
public static String TEXTAREA = "textarea";
public static String BUTTON = "button";
}
// Package dom;
class Window
{
public native static void alert(String message) /*-{
$wnd.alert(message);
}-*/;
public native static void log(Object arg1) /*-{
$wnd.console.log(arg1);
}-*/;
}
/* Similar to goog.ui.Component in Closure Library */
class Component extends SyntheticEventTarget
{
/** Default cssName */
private static final String COMPONENT_CSS_NAME = "goog-component";
/**
* Error when the component is already rendered and another render attempt is
* made.
*/
private static final String ERROR_ALREADY_RENDERED = "Component already rendered";
private Element element;
/**
* Gets the component's element.
* @return {Element} The element for the component.
*/
public Element getElement()
{
return this.element;
}
/**
* Sets the component's root element to the given element. Considered
* protected and final.
*
* This should generally only be called during createDom. Setting the element
* does not actually change which element is rendered, only the element that is
* associated with this UI component.
*
* This should only be used by subclasses and its associated renderers.
*
* @param {Element} element Root element for the component.
*/
protected void setElementInternal(Element element)
{
this.element = element;
}
/**
* Renders the component. If a parent element is supplied, the component's
* element will be appended to it. If there is no optional parent element and
* the element doesn't have a parentNode then it will be appended to the
* document body.
*
* Throws an Error if the component is already rendered.
*
* @param {Element=} opt_parentElement Optional parent element to render the
* component into.
*/
public void render(Element parentElement)
{
if (this.element != null) {
throw new Error(Component.ERROR_ALREADY_RENDERED);
}
this.createDom();
parentElement.appendChild(this.element);
}
/**
* Creates the initial DOM representation for the component. The default
* implementation is to set this.element = div.
*/
protected void createDom()
{
this.element = Document.createElement(TagName.DIV);
this.element.setCssName( this.getCssName() );
}
/** Default implementation */
protected String getCssName()
{
return Component.COMPONENT_CSS_NAME;
}
}
class ContentEditor extends Component
{
@Override
protected String getCssName()
{
return super.getCssName() + " content-editor";
}
@Override
protected void createDom()
{
super.createDom();
Element elem = this.getElement();
// TODO ui.Grid!
Element lbl1 = Document.createElement(TagName.LABEL);
lbl1.setTextContent("Headline");
elem.appendChild( lbl1 );
Element headlineInput = Document.createElement(TagName.INPUT);
elem.appendChild( headlineInput );
Element lbl2 = Document.createElement(TagName.LABEL);
lbl2.setTextContent("Subheading is great");
elem.appendChild( lbl2 );
Element subheadingInput = Document.createElement(TagName.INPUT);
elem.appendChild( subheadingInput );
subheadingInput.listen("change", this, this::handleChange);
Element lbl3 = Document.createElement(TagName.LABEL);
lbl3.setTextContent("Text");
elem.appendChild( lbl3 );
Element descriptionTextarea = Document.createElement(TagName.TEXTAREA);
elem.appendChild( descriptionTextarea );
descriptionTextarea.listen("keyup", this, this::handleChange);
Element button = Document.createElement(TagName.BUTTON);
button.setTextContent("Create Content Box");
elem.appendChild(button);
button.listen("click", this, this::handleClick);
}
private void handleChange()
{
Window.alert("AI Labs jclosure.events EventSystem alive!");
}
private void handleClick()
{
Window.alert("Mommy Test");
}
}
/**
* HelloWorld application.
*/
public class ContentCentral implements EntryPoint
{
public void onModuleLoad()
{
ContentEditor contentEditor = new ContentEditor();
contentEditor.render( Document.getDocumentElement() );
String name = "Hello, ContentBuilder";
Window.alert(name);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment