<?xml version="1.0"?>

<!-- ***** BEGIN LICENSE BLOCK *****
   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
   -
   - The contents of this file are subject to the Mozilla Public License Version
   - 1.1 (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.mozilla.org/MPL/
   -
   - Software distributed under the License is distributed on an "AS IS" basis,
   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
   - for the specific language governing rights and limitations under the
   - License.
   -
   - The Original Code is Mozilla XForms support.
   -
   - The Initial Developer of the Original Code is
   - Novell, Inc.
   - Portions created by the Initial Developer are Copyright (C) 2005
   - the Initial Developer. All Rights Reserved.
   -
   - Contributor(s):
   -  Allan Beaufour <abeaufour@novell.com>
   -  Olli Pettay <Olli.Pettay@helsinki.fi>
   -  Alexander Surkov <surkov@dc.baikal.ru>
   -
   - Alternatively, the contents of this file may be used under the terms of
   - either the GNU General Public License Version 2 or later (the "GPL"), or
   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
   - in which case the provisions of the GPL or the LGPL are applicable instead
   - of those above. If you wish to allow use of your version of this file only
   - under the terms of either the GPL or the LGPL, and not to allow others to
   - use your version of this file under the terms of the MPL, indicate your
   - decision by deleting the provisions above and replace them with the notice
   - and other provisions required by the GPL or the LGPL. If you do not delete
   - the provisions above, a recipient may use your version of this file under
   - the terms of any one of the MPL, the GPL or the LGPL.
   -
   - ***** END LICENSE BLOCK ***** -->


<!--
  This file contains xforms input, secret and textarea controls implementation
  for XHTML. All controls are inherited from interface bindings realized in 
  input.xml.
--> 

<!DOCTYPE bindings [
<!ENTITY % xformsDTD SYSTEM "chrome://xforms/locale/xforms.dtd">
  %xformsDTD;
]>

<bindings id="xformsInputBindingsForXHTML"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:html="http://www.w3.org/1999/xhtml"
          xmlns:xbl="http://www.mozilla.org/xbl"
          xmlns:mozType="http://www.mozilla.org/projects/xforms/2005/type">


  <!-- INPUT: base widget for input: <default>, textarea: <default> and
              secret: <default> controls.

    Binding assumes getControlElement returns object that has properties
    required by 'xformswidget-input-base' binding plus 'editor' property to
    get nsIEditor for embedded editor. You shouldn't use this binding if xforms
    input element isn't supposed to have an editor.
  -->
  <binding id="xformswidget-input-base"
           extends="chrome://xforms/content/input.xml#xformswidget-input-base">

    <implementation implements="nsIXFormsNSEditableElement">
      <property name="editor" readonly="true"
                onget="return this.control.editor;"/>
    </implementation>
  </binding>


  <!-- INPUT: <DEFAULT> -->
  <binding id="xformswidget-input"
           extends="#xformswidget-input-base">
    <content>
      <children includes="label"/>
      <html:input class="xf-value" anonid="control"
                  xbl:inherits="tabindex"/>
      <children/>
    </content>

    <implementation>
      <method name="getControlElement">
        <body>
          return {
            __proto__: this.ownerDocument.
              getAnonymousElementByAttribute(this, 'anonid', 'control'),

            set readonly(val) {
              if (val) {
                this.setAttribute('readonly', 'readonly');
              } else {
                this.removeAttribute('readonly');
              }
            }
          };
        </body>
      </method>

    </implementation>

    <handlers>
      <handler event="focus" phase="capturing">
        if (event.originalTarget == this.control) {
          this.dispatchDOMUIEvent("DOMFocusIn");
        }
      </handler>

      <handler event="blur" phase="capturing">
        if (event.originalTarget == this.control) {
          this.updateInstanceData();
          this.dispatchDOMUIEvent("DOMFocusOut");
        }
      </handler>

      <handler event="keyup">
        <![CDATA[
        if (event.originalTarget == this.control &&
            event.keyCode != event.DOM_VK_TAB) {
          this.updateInstanceData(true);
        }
        ]]>
      </handler>

      <handler event="keypress" keycode="VK_RETURN">
        <![CDATA[
        if (event.originalTarget == this.control) {
          this.dispatchDOMUIEvent("DOMActivate");
        }
        ]]>
      </handler>
    </handlers>
  </binding>


  <!-- INPUT: BOOLEAN -->
  <binding id="xformswidget-input-boolean"
           extends="chrome://xforms/content/input.xml#xformswidget-input-boolean-base">
    <content>
      <children includes="label"/>
      <html:input anonid="control" type="checkbox" class="xf-value"
                  xbl:inherits="tabindex"/>
      <children/>
    </content>

    <implementation>
      <method name="getControlElement">
        <body>
          return {
            __proto__: this.ownerDocument.
              getAnonymousElementByAttribute(this, 'anonid', 'control'),

            get value() {
              return this.checked;
            },
            set value(val) {
              this.checked = val;
            },
            set readonly(val) {
              if (val) {
                this.setAttribute('disabled', 'disabled');
              } else {
                this.removeAttribute('disabled');
              }
            }
          };
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="click">
        if (event.originalTarget == this.control)
          this.updateInstanceData(true);
      </handler>

      <handler event="focus" phase="capturing">
        if (event.originalTarget == this.control) {
          this.dispatchDOMUIEvent("DOMFocusIn");
        }
      </handler>

      <handler event="blur" phase="capturing">
        if (event.originalTarget == this.control) {
          this.updateInstanceData();
          this.dispatchDOMUIEvent("DOMFocusOut");
        }
      </handler>

      <handler event="keyup" keycode="VK_SPACE">
        <![CDATA[
        if (event.originalTarget == this.control)
          this.updateInstanceData(true);
        ]]>
      </handler>
    </handlers>
  </binding>


  <!-- INPUT: DATE -->
  <binding id="xformswidget-input-date"
           extends="#xformswidget-input-base">
    <content>
      <children includes="label"/>
      <html:span class="-moz-date-container">
        <html:input anonid="control" size="10"
                    class="-moz-xforms-date-input xf-value"
                    xbl:inherits="tabindex"/>
        <html:input mozType:dropmarker="true"
                    type="button" anonid="dropmarker"
                    title="&xforms.datepicker.title;"/>
      </html:span>
      <html:span mozType:calendar="true" anonid="picker"
                 internaltabhandling="true"
                 style="position:absolute; display:none;"/>
      <children/>
    </content>

    <implementation implements="nsIXFormsComboboxUIWidget, nsIAccessibleProvider">
      <!-- nsIXFormsComboboxUIWidget -->
      <property name="open">
        <getter>
          return this._isPickerVisible;
        </getter>
        <setter>
          if (val)
            this.showPicker();
          else
            this.hidePicker();
        </setter>
      </property>

    <!-- nsIAccessibleProvider -->
      <property name="accessibleType" readonly="true">
        <getter>
          return Components.interfaces.nsIAccessibleProvider.XFormsInputDate;
        </getter>
      </property>

      <method name="getControlElement">
        <body>
          return {
            __proto__: this.inputField,
            pickerButton: this.dropmarker,

            set readonly(val) {
              if (val) {
                this.setAttribute('disabled', 'disabled');
                this.pickerButton.setAttribute('disabled', 'disabled');
              } else {
                this.removeAttribute('disabled');
                this.pickerButton.removeAttribute('disabled');
              }
            }
          };
        </body>
      </method>

      <!-- Show date picker when it is hidden and hide when it is shown. -->
      <method name="togglePicker">
        <body>
          if (this._isPickerVisible)
            this.hidePicker(true);
          else
            this.showPicker();
        </body>
      </method>

      <!-- Show date picker -->
      <method name="showPicker">
        <body>
        <![CDATA[
          if (this._isPickerVisible || this.accessors.isReadonly()) {
            return;
          }

          // show the picker
          this.picker.style.display = "block";
          this._isPickerVisible = true;

          // refresh the picker
          this.picker.value = this.inputField.value;

          // move the picker, aligning it's right side with the calendar
          // button's right side
          var dropmarkerBox = this.ownerDocument.
            getBoxObjectFor(this.dropmarker);
          var pickerWidth = this.ownerDocument.
            getBoxObjectFor(this.picker).width;

          // we use window.innerWidth because XHTML documents are not always
          // 100% width, and innerWidth will always give use the browser size
          var windowWidth = this.ownerDocument.defaultView.innerWidth;

          var p = this.control.offsetParent;
          var compStyle =
            this.ownerDocument.defaultView.getComputedStyle(p, null);
          var relative = compStyle.getPropertyValue("position") == "relative";
          if (!relative) {
            relative = compStyle.getPropertyValue("position") == "absolute";
          }

          var position;
          if (relative) {
            position = this.dropmarker.offsetLeft - pickerWidth + dropmarkerBox.width;
          } else {
            position = dropmarkerBox.x - pickerWidth + dropmarkerBox.width;
          }

          // Reset the position if it will bleed to the left or right.
          if (position < 0) {
            position = 0;
          } else if ((position + pickerWidth) > windowWidth) {
            if (relative) {
              // Can't base our position on the window width since our position
              // will be set relative to the offsetParent.  So let's set the
              // position to be the left-most part of the button that triggers
              // the dropdown.  We'll assume that at least that much of the
              // button is visible since the user navigated to it and triggered
              // it.
              position = this.dropmarker.offsetLeft - pickerWidth;
            } else {
              position = windowWidth - pickerWidth;
            }
          }

          this.picker.style.left = position + "px";
          this.picker.focus();

          this.ownerDocument.
            addEventListener("blur", this.hidePickerHandler, true);
          this.ownerDocument.
            addEventListener("focus", this.hidePickerHandler, true);
        ]]>
        </body>
      </method>

      <!-- Hide date picker -->
      <method name="hidePicker">
        <parameter name="aFocusInput"/>
        <body>
        <![CDATA[
          if (!this._isPickerVisible)
            return;

          this.ownerDocument.
            removeEventListener("blur", this.hidePickerHandler, true);
          this.ownerDocument.
            removeEventListener("focus", this.hidePickerHandler, true);

          this.picker.style.display = "none";
          this._isPickerVisible = false;

          if (aFocusInput)
            this.inputField.focus();
        ]]>
        </body>
      </method>

      <!--
        'hidePickerHandler' object serves to hide date picker when date
        picker looses a focus. When date picker is shown then
        'hidePickerHandler' object is attached to the window to handle 'focus'
        and 'blur' events as event listener. When date picker is hidden then
        event listener 'hidePickerHandler' is removed.
      -->
      <property name="hidePickerHandler" readonly="true">
        <getter>
        <![CDATA[
          if (!this._hidePickerHandler) {
            this._hidePickerHandler = {
              inputElm: this,
              dropmarkerElm: this.dropmarker,
              pickerContent: this.ownerDocument.getAnonymousNodes(this.picker)[0],
              inputContentNodes: this.ownerDocument.getAnonymousNodes(this),
              ownerDocument: this.ownerDocument,

              shouldHandleFocus: false,

              handleEvent: function(aEvent) {
                var target = aEvent.originalTarget;
                switch (aEvent.type) {
                case "blur":
                  if (this.ownerDocument == target) {
                    this.inputElm.hidePicker();
                  } else {
                    this.shouldHandleFocus =
                      this.isPickerNode(target) || this.isInputNode(target);
                  }
                  break;
                case "focus":
                  if (this.shouldHandleFocus && target != this.dropmarkerElm &&
                      !this.isPickerNode(target)) {
                    this.inputElm.hidePicker();
                  }
                  break;
                }
              },

              // Return true if aNode is a child of anonymous content of date
              // picker.
              isPickerNode: function(aNode) {
                var walker = this.ownerDocument.createTreeWalker(
                  this.pickerContent, NodeFilter.SHOW_ELEMENT, null, false);
                var child = null;
                while ((child = walker.nextNode())) {
                  if (child == aNode)
                    return true;
                }
                return false;
              },
              // Return true if aNode is a child of anonymous content of input.
              isInputNode: function(aNode) {
                for (var i = 0; i < this.inputContentNodes.length; i++) {
                  if (this.inputContentNodes[i] == aNode)
                    return true;
                }
                return false;
              }
            };
          }
          return this._hidePickerHandler;
        ]]>
        </getter>
      </property>
      <field name="_hidePickerHandler">null</field>

      <constructor>
        // Add event handlers to update instance data when date picker value is
        // changed.

        // Add event handler on 'change' event.
        var changePickerValueHandlerOnChange = {
          inputElm: this,
          handleEvent: function(event) {
            this.inputElm.inputField.value = this.inputElm.picker.value;
            this.inputElm.hidePicker(true);
            this.inputElm.updateInstanceData(true);
          }
        };
        this.picker.addEventListener("change", changePickerValueHandlerOnChange,
                                     false);

        // Add event handler on 'enter' key pressing.
        var changePickerValueHandlerOnKeypress = {
          inputElm: this,
          pickerElm: this.picker,
          handleEvent: function(event) {
            if (event.keyCode != event.DOM_VK_RETURN ||
                !this.pickerElm.isDayControl(event.originalTarget))
              return;

            var date = this.pickerElm.getDate();
            if (date) {
              this.inputElm.inputField.value = date.toLocaleFormat('%Y-%m-%d');
              this.inputElm.hidePicker(true);
              this.inputElm.updateInstanceData(true);
            }
          }
        };
        this.picker.addEventListener("keypress",
                                     changePickerValueHandlerOnKeypress, false);
      </constructor>

      <!-- Input field control -->
      <property name="inputField" readonly="true">
        <getter>
          if (!this._inputField) {
            this._inputField =
              document.getAnonymousElementByAttribute(this, "anonid", "control");
          }
          return this._inputField;
        </getter>
      </property>
      <field name="_inputField">null</field>

      <!-- Date picker control -->
      <property name="picker" readonly="true">
        <getter>
          if (!this._picker) {
            this._picker =
              document.getAnonymousElementByAttribute(this, "anonid", "picker");
          }
          return this._picker;
        </getter>
      </property>
      <field name="_picker">null</field>

      <!-- Dropmarker control used to open/hide date picker -->
      <property name="dropmarker" readonly="true">
        <getter>
          if (!this._dropmarker) {
            this._dropmarker =
              document.getAnonymousElementByAttribute(this, "anonid", "dropmarker");
          }
          return this._dropmarker;
        </getter>
      </property>
      <field name="_dropmarker">null</field>

      <field name="_isPickerVisible">false</field>
    </implementation>

    <handlers>
      <handler event="keypress" keycode="VK_ESCAPE">
        this.hidePicker(true);
      </handler>

      <handler event="keypress" keycode="VK_RETURN">
        var target = event.originalTarget;
        if (target == this.inputField) {
          this.dispatchDOMUIEvent('DOMActivate');
        }
      </handler>

      <handler event="keypress">
      <![CDATA[
        if (event.keyCode == event.DOM_VK_F4) {
          this.togglePicker();
        } else if (event.altKey && (event.keyCode == event.DOM_VK_DOWN ||
                   event.keyCode == event.DOM_VK_UP)) {
          this.togglePicker();
          event.preventDefault();
        }
      ]]>
      </handler>

      <handler event="input">
        this.updateInstanceData(true);
      </handler>

      <handler event="click">
        if (event.originalTarget == this.dropmarker) {
          this.togglePicker();
        }
      </handler>

      <handler event="focus" phase="capturing">
        if (event.originalTarget == this.inputField) {
          this.dispatchDOMUIEvent('DOMFocusIn');
        }
      </handler>

      <handler event="blur" phase="capturing">
        if (event.originalTarget == this.inputField) {
          this.updateInstanceData(false);
          this.dispatchDOMUIEvent('DOMFocusOut');
        }
      </handler>
    </handlers>
  </binding>


  <!-- INPUT: <DATE, APPEARANCE='FULL' -->
  <binding id="xformswidget-input-date-full"
           extends="chrome://xforms/content/input.xml#xformswidget-input-date-calendar-base">
    <content>
      <children includes="label"/>
      <html:span mozType:calendar="true" anonid="control"/>
      <children/>
    </content>

    <implementation>
      <method name="getControlElement">
        <body>
          return this.ownerDocument.
            getAnonymousElementByAttribute(this, "anonid", "control");
        </body>
      </method>

      <constructor>
        var changeHandler = {
          inputControl: this,
          handleEvent: function() {
            this.inputControl.updateInstanceData(false);
          }
        };
        this.addEventListener("change", changeHandler, false);
      </constructor>
    </implementation>
  </binding>


  <!-- INPUT: Month -->
  <binding id="xformswidget-input-month"
           extends="chrome://xforms/content/input.xml#xformswidget-input-month-base">
    <content>
      <children includes="label"/>
      <html:select anonid="control"
                   xbl:inherits="style, tabindex">
        <html:option value=""></html:option>
      </html:select>
      <children/>
    </content>

    <implementation>
      <method name="getControlElement">
        <body>
          return {
            __proto__: this.ownerDocument.
              getAnonymousElementByAttribute(this, 'anonid', 'control'),

            XHTML_NS: 'http://www.w3.org/1999/xhtml',

            set readonly(val) {
              this.disabled = val;
            },
            appendMonth: function(name, value) {
              var option = this.ownerDocument.
                createElementNS(this.XHTML_NS, 'option');
              option.textContent = name;
              option.setAttribute('value', value);
              this.appendChild(option);
            }
          };
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="blur" phase="capturing">
        if (event.originalTarget == this.control)
          this.updateInstanceData(false);
      </handler>

      <handler event="change">
        if (event.originalTarget == this.control)
          this.updateInstanceData(true);
      </handler>
    </handlers>
  </binding>


  <!-- INPUT: DAY -->
  <binding id="xformswidget-input-day"
           extends="chrome://xforms/content/input.xml#xformswidget-input-day-base">
    <content>
      <children includes="label"/>
      <html:select anonid="control"
                   xbl:inherits="style, tabindex">
        <html:option value=""></html:option>
      </html:select>
      <children/>
    </content>

    <implementation>
      <method name="getControlElement">
        <body>
          return {
            __proto__: this.ownerDocument.
              getAnonymousElementByAttribute(this, 'anonid', 'control'),

            XHTML_NS: 'http://www.w3.org/1999/xhtml',

            set readonly(val) {
              this.disabled = val;
            },
            appendDay: function(name, value) {
              var option = this.ownerDocument.
                createElementNS(this.XHTML_NS, 'option');
              option.textContent = name;
              option.setAttribute('value', value);
              this.appendChild(option);
            }
          };
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="blur" phase="capturing">
        if (event.originalTarget == this.control)
          this.updateInstanceData(false);
      </handler>

      <handler event="change">
        if (event.originalTarget == this.control)
          this.updateInstanceData(true);
      </handler>
    </handlers>
  </binding> 


  <!-- SECRET: <DEFAULT> -->
  <binding id="xformswidget-secret"
           extends="#xformswidget-input">
    <content>
      <children includes="label"/>
      <html:input anonid="control" type="password"
                  xbl:inherits="tabindex"/>
      <children/>
    </content>

    <implementation implements="nsIAccessibleProvider">
      <property name="accessibleType" readonly="true">
        <getter>
          return Components.interfaces.nsIAccessibleProvider.XFormsSecret;
        </getter>
      </property>
    </implementation>
  </binding>


  <!-- TEXTAREA: <DEFAULT> -->
  <binding id="xformswidget-textarea"
           extends="#xformswidget-input-base">
    <content>
       <children includes="label"/>
       <html:textarea class="xf-value" anonid="control"
                      xbl:inherits="tabindex"/>
      <children/>
    </content>

    <implementation>
      <method name="getControlElement">
        <body>
          return {
            __proto__: this.ownerDocument.
              getAnonymousElementByAttribute(this, 'anonid', 'control'),

            set readonly(val) {
              if (val) {
                this.setAttribute('readonly', 'readonly');
              } else {
                this.removeAttribute('readonly');
              }
            }
          };
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="focus" phase="capturing">
        if (event.originalTarget == this.control) {
          this.dispatchDOMUIEvent("DOMFocusIn");
        }
      </handler>

      <handler event="blur" phase="capturing">
        if (event.originalTarget == this.control) {
          this.updateInstanceData();
          this.dispatchDOMUIEvent("DOMFocusOut");
        }
      </handler>

      <handler event="keyup">
        <![CDATA[
        if (event.originalTarget == this.control)
          this.updateInstanceData(true);
        ]]>
      </handler>
    </handlers>
  </binding>

</bindings>
