/*
 * 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.google.gwt.user.client.ui.impl;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Element;

/**
 * Implementation of {@link com.google.gwt.user.client.ui.impl.FocusImpl} that
 * uses a hidden input element to serve as a 'proxy' for accesskeys, which are
 * only supported on form elements in most browsers.
 */
public class FocusImplStandard extends FocusImpl {

  /**
   * Single focusHandler shared by all focusable.
   */
  static JavaScriptObject focusHandler;

  private static native Element createFocusable0(JavaScriptObject focusHandler) /*-{
    // Divs are focusable in all browsers, but only IE supports the accessKey
    // property on divs. We use the infamous 'hidden input' trick to add an
    // accessKey to the focusable div. Note that the input is only used to
    // capture focus when the accessKey is pressed. Focus is forwarded to the
    // div immediately.
    var div = $doc.createElement('div');
    div.tabIndex = 0;

    var input = $doc.createElement('input');
    input.type = 'text';
    input.tabIndex = -1;
    input.setAttribute('aria-hidden', 'true');
    var style = input.style;
    style.opacity = 0;
    style.height = '1px';
    style.width = '1px';
    style.zIndex = -1;
    style.overflow = 'hidden';
    style.position = 'absolute';

    // Note that we're using isolated lambda methods as the event listeners
    // to avoid creating a memory leaks. (Lambdas here would create cycles
    // involving the div and input).  This also allows us to share a single
    // set of handlers among every focusable item.
    input.addEventListener('focus', focusHandler, false);

    div.appendChild(input);
    return div;
  }-*/;

  @Override
  public Element createFocusable() {
    return createFocusable0(ensureFocusHandler());
  }

  @Override
  public native void setAccessKey(Element elem, char key) /*-{
    elem.firstChild.accessKey = String.fromCharCode(key);
  }-*/;

  /**
   * Use an isolated method call to create the handler to avoid creating memory
   * leaks via handler-closures-element.
   */
  private native JavaScriptObject createFocusHandler() /*-{
    return function(evt) {
      // This function is called directly as an event handler, so 'this' is
      // set up by the browser to be the input on which the event is fired. We
      // call focus() in a timeout or the element may be blurred when this event
      // ends.
      var div = this.parentNode;
      if (div.onfocus) {
        $wnd.setTimeout(function() {
          div.focus();
        }, 0);
      } 
    };
  }-*/;

  private JavaScriptObject ensureFocusHandler() {
    return focusHandler != null ? focusHandler : (focusHandler = createFocusHandler());
  }
}
