<?xml version="1.0"?>

<bindings id="numberboxBindings"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:html="http://www.w3.org/1999/xhtml"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="numberbox"
           extends="chrome://global/content/bindings/textbox.xml#textbox">

    <resources>
      <stylesheet src="chrome://global/skin/numberbox.css"/>
    </resources>

    <content>
      <xul:hbox class="textbox-input-box numberbox-input-box" flex="1" xbl:inherits="context,disabled,focused">
        <html:input class="numberbox-input textbox-input" anonid="input"
                    xbl:inherits="value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey"/>
      </xul:hbox>
      <xul:spinbuttons anonid="buttons" xbl:inherits="disabled,hidden=hidespinbuttons"/>
    </content>

    <implementation>
      <field name="_valueEntered">false</field>
      <field name="_spinButtons">null</field>
      <field name="_value">0</field>
      <field name="decimalSymbol">"."</field>

      <property name="spinButtons" readonly="true">
        <getter>
          <![CDATA[
            if (!this._spinButtons)
              this._spinButtons = document.getAnonymousElementByAttribute(this, "anonid", "buttons");
            return this._spinButtons;
          ]]>
        </getter>
      </property>

      <property name="value" onget="return '' + this.valueNumber"
                             onset="return this.valueNumber = val;"/>

      <property name="valueNumber">
        <getter>
          if (this._valueEntered) {
            var newval = this.inputField.value;
            newval = newval.replace(this.decimalSymbol, ".");
            this._validateValue(newval, false);
          }
          return this._value;
        </getter>
        <setter>
          this._validateValue(val, false);
          return val;
        </setter>
      </property>

      <property name="wrapAround">
        <getter>
        <![CDATA[
          return (this.getAttribute('wraparound') == 'true')
        ]]>
        </getter>
        <setter>
        <![CDATA[
          if (val)
            this.setAttribute('wraparound', 'true');
          else
            this.removeAttribute('wraparound');
          this._enableDisableButtons();
          return val;
        ]]>
        </setter>
      </property>

      <property name="min">
        <getter>
          var min = this.getAttribute("min");
          return min ? Number(min) : 0;
        </getter>
        <setter>
        <![CDATA[
          if (typeof val == "number") {
            this.setAttribute("min", val);
            if (this.valueNumber < val)
              this._validateValue(val, false);
          }
          return val;
        ]]>
        </setter>
      </property>

      <property name="max">
        <getter>
          var max = this.getAttribute("max");
          return max ? Number(max) : Infinity;
        </getter>
        <setter>
        <![CDATA[
          if (typeof val != "number")
            return val;
          var min = this.min;
          if (val < min)
            val = min;
          this.setAttribute("max", val);
          if (this.valueNumber > val)
            this._validateValue(val, false);
          return val;
        ]]>
        </setter>
      </property>

      <property name="decimalPlaces">
        <getter>
          var places = this.getAttribute("decimalplaces");
          return places ? Number(places) : 0;
        </getter>
        <setter>
          if (typeof val == "number") {
            this.setAttribute("decimalplaces", val);
            this._validateValue(this.valueNumber, false);
          }
          return val;
        </setter>
      </property>

      <property name="increment">
        <getter>
          var increment = this.getAttribute("increment");
          return increment ? Number(increment) : 1;
        </getter>
        <setter>
        <![CDATA[
          if (typeof val == "number")
            this.setAttribute("increment", val);
          return val;
        ]]>
        </setter>
      </property>

      <method name="decrease">
        <body>
          return this._validateValue(this.valueNumber - this.increment, true);
        </body>
      </method>

      <method name="increase">
        <body>
          return this._validateValue(this.valueNumber + this.increment, true);
        </body>
      </method>

      <method name="_modifyUp">
        <body>
          <![CDATA[
            if (this.disabled || this.readOnly)
              return;
            var oldval = this.valueNumber;
            var newval = this.increase();
            this.inputField.select();
            if (oldval != newval)
              this._fireChange();
          ]]>
        </body>
      </method>
      <method name="_modifyDown">
        <body>
          <![CDATA[
            if (this.disabled || this.readOnly)
              return;
            var oldval = this.valueNumber;
            var newval = this.decrease();
            this.inputField.select();
            if (oldval != newval)
              this._fireChange();
          ]]>
        </body>
      </method>

      <method name="_enableDisableButtons">
        <body>
          <![CDATA[
            var buttons = this.spinButtons;
            if (this.wrapAround) {
              buttons.decreaseDisabled = buttons.increaseDisabled = false;
            }
            else if (this.disabled || this.readOnly) {
              buttons.decreaseDisabled = buttons.increaseDisabled = true;
            }
            else {
              buttons.decreaseDisabled = (this.valueNumber <= this.min);
              buttons.increaseDisabled = (this.valueNumber >= this.max);
            }
          ]]>
        </body>
      </method>

      <method name="_validateValue">
        <parameter name="aValue"/>
        <parameter name="aIsIncDec"/>
        <body>
          <![CDATA[
            aValue = Number(aValue) || 0;

            var min = this.min;
            var max = this.max;
            var wrapAround = this.wrapAround &&
                             min != -Infinity && max != Infinity;
            if (aValue < min)
              aValue = (aIsIncDec && wrapAround ? max : min);
            else if (aValue > max)
              aValue = (aIsIncDec && wrapAround ? min : max);

            var places = this.decimalPlaces;
            aValue = (places == Infinity) ? "" + aValue : aValue.toFixed(places);

            this._valueEntered = false;
            this._value = Number(aValue);
            this.inputField.value = aValue.replace(/\./, this.decimalSymbol);

            if (!wrapAround)
              this._enableDisableButtons();

            return aValue;
          ]]>
        </body>
      </method>

      <method name="_fireChange">
        <body>
          var evt = document.createEvent("Events");
          evt.initEvent("change", true, true);
          this.dispatchEvent(evt);
        </body>
      </method>

      <constructor><![CDATA[
        if (this.max < this.min)
          this.max = this.min;

        var dsymbol = (Number(5.4)).toLocaleString().match(/\D/);
        if (dsymbol != null)
          this.decimalSymbol = dsymbol[0];

        var value = this.inputField.value || 0;
        this._validateValue(value, false);
      ]]></constructor>

    </implementation>

    <handlers>
      <handler event="input" phase="capturing">
        this._valueEntered = true;
      </handler>

      <handler event="keypress">
        <![CDATA[
          if (!event.ctrlKey && !event.metaKey && !event.altKey && event.charCode) {
            if (event.charCode == this.decimalSymbol.charCodeAt(0) &&
                this.decimalPlaces &&
                String(this.inputField.value).indexOf(this.decimalSymbol) == -1)
              return;

            if (event.charCode == 45 && this.min < 0)
              return;

            if (event.charCode < 48 || event.charCode > 57)
              event.preventDefault();
          }
        ]]>
      </handler>

      <handler event="keypress" keycode="VK_UP">
        this._modifyUp();
      </handler>

      <handler event="keypress" keycode="VK_DOWN">
        this._modifyDown();
      </handler>

      <handler event="up" preventdefault="true">
        this._modifyUp();
      </handler>

      <handler event="down" preventdefault="true">
        this._modifyDown();
      </handler>

      <handler event="change">
        if (event.originalTarget == this.inputField) {
          var newval = this.inputField.value;
          newval = newval.replace(this.decimalSymbol, ".");
          this._validateValue(newval, false);
        }
      </handler>
    </handlers>

  </binding>

</bindings>
