Created
December 20, 2016 06:09
-
-
Save ailabs-software/7e87da2bc4ed7664246d8260f89f3a7b to your computer and use it in GitHub Desktop.
Events and life cycle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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