/**
* 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 org.waveprotocol.wave.client.doodad.form.input;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.dom.client.StyleInjector;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.user.client.Event;
import org.waveprotocol.wave.client.common.util.DomHelper;
import org.waveprotocol.wave.client.common.util.DomHelper.HandlerReference;
import org.waveprotocol.wave.client.common.util.DomHelper.JavaScriptEventListener;
import org.waveprotocol.wave.client.editor.ElementHandlerRegistry;
import org.waveprotocol.wave.client.editor.NodeEventHandlerImpl;
import org.waveprotocol.wave.client.editor.RenderingMutationHandler;
import org.waveprotocol.wave.client.editor.content.ContentElement;
import org.waveprotocol.wave.client.editor.event.EditorEvent;
import org.waveprotocol.wave.model.document.util.Property;
/**
* Password form input field
*
* Note that this does not allow concurrent editing of content - the password is stored in
* a value attribute
*
* @author danilatos@google.com (Daniel Danilatos)
*/
public class Password {
public interface Resources extends ClientBundle {
@Source("Password.css")
Css css();
interface Css extends CssResource {
String password();
}
}
/***/
public static final String TAGNAME = "password";
/***/
public static final String VALUE = "value";
/** The singleton instance of our CSS resources. */
private static final Resources.Css css = GWT.<Resources>create(Resources.class).css();
/**
* Registers paragraph handlers for any provided tag names / type attributes.
*/
public static void register(ElementHandlerRegistry registry) {
PasswordEventHandler eventHandler = new PasswordEventHandler();
PasswordRenderer renderer = new PasswordRenderer(eventHandler);
registry.registerEventHandler(TAGNAME, eventHandler);
registry.registerRenderingMutationHandler(TAGNAME, renderer);
}
/**
* Register schema + inject stylesheet
*/
static {
// For unit testing using mockito all Gwt.Create() returns mocks.
// The mock for Resources.class returns null css by default.
if (css != null) {
StyleInjector.inject(css.getText(), true);
}
}
static class PasswordEventHandler extends NodeEventHandlerImpl {
boolean isMutatingLocallyToMatchUserInput = false;
private static final Property<HandlerReference> HANDLE = Property.mutable("handle");
@Override
public void onActivated(final ContentElement element) {
// Since typing in a password field updates its value attribute, not its content, our regular
// typing extraction stuff does nothing. So here is a poor man's typing extractor just
// for password fields...
// NOTE(danilatos): Handling keyup isn't perfect, but should hold for now.
element.setProperty(HANDLE, DomHelper.registerEventHandler(element.getImplNodelet(), "keyup",
new JavaScriptEventListener() {
@Override
public void onJavaScriptEvent(String name, Event event) {
handleTyping(element);
}
}));
}
@Override
public void onDeactivated(ContentElement element) {
// Clean up
element.getProperty(HANDLE).unregister();
}
/**
* Prevent pressing enter
*/
@Override
public boolean handleEnter(ContentElement element, EditorEvent event) {
return true;
}
/**
* Cancels backspace at beginning of line.
*
* {@inheritDoc}
*/
@Override
public boolean handleBackspaceAtBeginning(ContentElement p, EditorEvent event) {
return true;
}
/**
* Cancels delete at end of line.
*
* {@inheritDoc}
*/
@Override
public boolean handleDeleteAtEnd(ContentElement p, EditorEvent event) {
return true;
}
/**
* TODO(danilatos): Have this be called by the central event routing, not the hack
* dom listeners below?
*
* @param p
*/
public void handleTyping(ContentElement p) {
isMutatingLocallyToMatchUserInput = true;
try {
p.getMutableDoc().setElementAttribute(p, "value",
p.getImplNodelet().<InputElement>cast().getValue());
} finally {
isMutatingLocallyToMatchUserInput = false;
}
}
}
static class PasswordRenderer extends RenderingMutationHandler {
private final PasswordEventHandler eventHandler;
public PasswordRenderer(PasswordEventHandler eventHandler) {
this.eventHandler = eventHandler;
}
@Override
public Element createDomImpl(Renderable element) {
Element passwordElement = Document.get().createPasswordInputElement();
passwordElement.addClassName(css.password());
return passwordElement;
}
@Override
public void onActivationStart(ContentElement element) {
fanoutAttrs(element);
}
@Override
public void onAttributeModified(ContentElement element, String name, String oldValue,
String newValue) {
if (VALUE.equals(name) && !eventHandler.isMutatingLocallyToMatchUserInput) {
element.getImplNodelet().<InputElement>cast().setValue(newValue);
}
}
}
}