allan%beaufour.dk 007c381428 [XForms] Expose abstract interface for input[type="gMonth"] and input[type="gDay"]. Bug 327234, r=doronr+me, patch by surkov@dc.baikal.ru
git-svn-id: svn://10.0.0.236/trunk@190816 18797224-902f-48f8-a5cc-f745e15eee43
2006-02-22 09:22:19 +00:00

878 lines
28 KiB
XML

<?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>
-
- 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 implements the "abstract" UI classes for XForms input, secret and
textarea controls. They all have "pure virtual" functions that they expect to
be implemented by concrete application and returned in the getElementControl()
call. An example is the controls for XHTML in input-xhtml.xml.
-->
<!DOCTYPE bindings [
<!ENTITY % xformsDTD SYSTEM "chrome://xforms/locale/xforms.dtd">
%xformsDTD;
]>
<bindings id="xformsInputBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xbl="http://www.mozilla.org/xbl">
<!-- INPUT: <DEFAULT>
The input widget assumes successors bindings implement getElementControl()
method what returns the object:
{
set value(); // set "string" value
get value(); // return "string" value
set readonly(); // set readonly state
focus(); // set focus
}
-->
<binding id="xformswidget-input-base"
extends="chrome://xforms/content/xforms.xml#xformswidget-base">
<implementation implements="nsIXFormsUIWidget">
<method name="refresh">
<body>
this.control.readonly = this.accessors.isReadonly();
// If the value has not changed, no need to update the
// value of the control, because f.x. that makes the textarea widget
// to scroll up.
if (this.control.value != this.stringValue) {
this.control.value = this.stringValue;
}
return true;
</body>
</method>
<method name="focus">
<body>
this.control.focus();
return true;
</body>
</method>
<method name="updateInstanceData">
<parameter name="aIncremental"/>
<body>
if (!aIncremental || this.getAttribute("incremental") == "true")
this.accessors.setValue(this.control.value);
</body>
</method>
</implementation>
</binding>
<!-- INPUT: BOOLEAN
The input[type="xsd:boolean"] widget assumes successors bindings implement
getElementControl() method what returns the object:
{
set value(); // set "boolean" value
get value(); // return "boolean" value
set readonly(); // set readonly state
focus(); // set focus
}
-->
<binding id="xformswidget-input-boolean-base"
extends="chrome://xforms/content/xforms.xml#xformswidget-base">
<implementation>
<method name="refresh">
<body>
var value = this.stringValue;
if (value == "true" || value == "1") {
this.control.value = true;
} else {
this.control.value = false;
}
this.control.readonly = this.accessors.isReadonly();
return true;
</body>
</method>
<method name="focus">
<body>
this.control.focus();
return true;
</body>
</method>
<method name="updateInstanceData">
<parameter name="aIncremental"/>
<body>
<![CDATA[
if (!aIncremental || this.getAttribute("incremental") != "false") {
if (this.control.value) {
this.accessors.setValue("true");
} else {
this.accessors.setValue("false");
}
}
]]>
</body>
</method>
</implementation>
</binding>
<!-- INPUT: DATE
XXX: The widget doesn't support interface based on getElementControl()
method (see a bug https://bugzilla.mozilla.org/show_bug.cgi?id=323845).
-->
<binding id="xformswidget-input-date"
extends="chrome://xforms/content/xforms.xml#xformswidget-base">
<content>
<children/>
<html:input anonid="control"
onblur="this.parentNode.accessors.setValue(this.value); this.parentNode.dispatchDOMUIEvent('DOMFocusOut');"
onfocus="this.parentNode.dispatchDOMUIEvent('DOMFocusIn'); this.parentNode._hidePicker(true);"
onclick="this.parentNode._change(); this.parentNode._hidePicker(true);"
onkeypress="if (event.keyCode == event.DOM_VK_RETURN) this.parentNode.dispatchDOMUIEvent('DOMActivate');"
xbl:inherits="accesskey" size="10"/>
<html:input type="button" anonid="dropmarker" title="&xforms.datepicker.title;"
onkeypress="if (event.keyCode == event.DOM_VK_ENTER) this.parentNode._togglePicker();"
onclick="this.parentNode._togglePicker();"/>
<html:div style="position:absolute; display:none;" anonid="picker">
<html:table>
<html:tbody anonid="tbody">
<html:tr>
<html:td colspan="1">
<html:input type="button" anonid="back-button"
class="-moz-date-back-button" title="&xforms.datepicker.prevMonth.title;"
onclick="this.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.goBack(true);"/>
</html:td>
<html:td colspan="5" align="center">
<html:span anonid="date"/>
</html:td>
<html:td colspan="1">
<html:input type="button" anonid="fwd-button"
class="-moz-date-fwd-button" title="&xforms.datepicker.nextMonth.title;"
onclick="this.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.goForward(true);"/>
</html:td>
</html:tr>
</html:tbody>
</html:table>
</html:div>
</content>
<implementation>
<method name="refresh">
<body>
this.inputField.value = this.stringValue;
if (this.accessors.isReadonly()) {
this.inputField.setAttribute("readonly", "readonly");
this.dropmarker.setAttribute("disabled", "true");
} else {
this.inputField.removeAttribute("readonly");
this.dropmarker.removeAttribute("disabled");
}
return true;
</body>
</method>
<method name="_change">
<body>
if (this.getAttribute("incremental") == "true") {
this.accessors.setValue(this.inputField.value);
}
return true;
</body>
</method>
<field name="_inputField">null</field>
<property name="inputField" readonly="true">
<getter>
if (!this._inputField) {
this._inputField =
document.getAnonymousElementByAttribute(this, "anonid", "control");
}
return this._inputField;
</getter>
</property>
<field name="_picker">null</field>
<property name="picker" readonly="true">
<getter>
if (!this._picker) {
this._picker =
document.getAnonymousElementByAttribute(this, "anonid", "picker");
}
return this._picker;
</getter>
</property>
<field name="_dropmarker">null</field>
<property name="dropmarker" readonly="true">
<getter>
if (!this._dropmarker) {
this._dropmarker =
document.getAnonymousElementByAttribute(this, "anonid", "dropmarker");
}
return this._dropmarker;
</getter>
</property>
<field name="_dateField">null</field>
<property name="dateField" readonly="true">
<getter>
if (!this._dateField) {
this._dateField =
document.getAnonymousElementByAttribute(this, "anonid", "date");
}
return this._dateField;
</getter>
</property>
<field name="_uibuilt">false</field>
<field name="_isPickerVisible">false</field>
<field name="_cells">null</field>
<field name="_date">null</field>
<field name="_currentCellIndex">-1</field>
<method name="_togglePicker">
<body>
<![CDATA[
if (this._isPickerVisible)
this._hidePicker(true);
else
this._showPicker();
]]>
</body>
</method>
<method name="_showPicker">
<body>
<![CDATA[
if (this._isPickerVisible || this.accessors.isReadonly()) {
return;
}
// show the picker
var picker = this.picker;
picker.style.display = "block";
this._isPickerVisible = true;
var value = this.inputField.value;
// js date likes YYYY/MM/DD, schema's is YYYY-MM-DD
value = value.replace(/-/g, "/");
// we check if the delgate is valid since javascript Date()
// returns a valid date for 2005-04-56.
var tmpDate = new Date(value);
if (!this.accessors.isValid() || tmpDate == "Invalid Date")
this._date = new Date();
else
this._date = tmpDate;
if (!this._uibuilt)
this._buildUI(this._date);
// position the dropdown, aligning it's right side with the calendar
// button's right side
var dropmarker = document.getAnonymousElementByAttribute(this, "anonid", "dropmarker");
var dropmarkerBox = document.getBoxObjectFor(dropmarker);
var width = document.getBoxObjectFor(picker).width;
var position = dropmarkerBox.x - width + dropmarkerBox.width;
// reset position if it will bleed to the left or right
if (position < 0) {
position = 0;
} else if ((position + width) > window.innerWidth) {
// we use window.innerWidth because XHTML documents are not always
// 100% width, and innerWidth will always give use the browser size.
position = window.innerWidth - width;
}
picker.style.left = position + "px";
this._refreshCells(this._date, this._date.getDate());
]]>
</body>
</method>
<method name="_hidePicker">
<parameter name="aFocusInput"/>
<body>
<![CDATA[
if (!this._isPickerVisible)
return;
this._cells[this._currentCellIndex].node.setAttribute("tabindex", "-1");
this._currentCellIndex = -1;
this.picker.style.display = "none";
this._isPickerVisible = false;
if (aFocusInput)
this.inputField.focus();
]]>
</body>
</method>
<method name="_buildUI">
<parameter name="aDate"/>
<body>
<![CDATA[
var xhtmlNS = "http://www.w3.org/1999/xhtml";
// shortname defaults
var dayShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
// try to get localized short names.
// May 2005's first day is a Sunday - also, month is 0-indexed in JS
var day;
for (var i = 0; i < 7; i++) {
day = new Date(2005, 4, i+1).toLocaleFormat("%a");
if (day)
dayShort[i] = day;
}
var month = aDate.getMonth();
var year = aDate.getFullYear();
// clear the cells array
this._cells = new Array();
var table = document.getAnonymousElementByAttribute(this, "anonid", "tbody");
var row, cell, header, caption;
// create the table headers
row = document.createElementNS(xhtmlNS, "tr");
for (var i = 0; i < 7; i++) {
header = document.createElementNS(xhtmlNS, "th");
// day shorthands
header.textContent = dayShort[i];
row.appendChild(header);
}
table.appendChild(row);
// create a table of 7 columns, 6 rows
for (var i = 0; i < 6; i++) {
row = document.createElementNS(xhtmlNS, "tr");
for (var y = 0; y < 7; y++) {
cell = document.createElementNS(xhtmlNS, "td");
cell.setAttribute("num", this._cells.length);
this._cells[this._cells.length] = {row:i, col:y, node: cell};
row.appendChild(cell);
}
table.appendChild(row);
}
this._uibuilt = true;
]]>
</body>
</method>
<method name="_refreshCells">
<parameter name="aDate"/>
<parameter name="aDayToSelect"/>
<parameter name="aSkipFocus"/>
<body>
<![CDATA[
var month = aDate.getMonth();
var year = aDate.getFullYear();
var totaldays = this._getDaysInMonth(month, year);
// first day of month is?
var firstDay = new Date(year, month, 1).getDay();
// get the previous month's date so we can prefill that section. The
// next month is easy, we go from 1..x as far as we need
var prevDate = this._getPrevDate(month, year);
var showsPrevDays = 0;
// init cells
for (var i = 0; i < this._cells.length; i++) {
if (i < firstDay || i >= (firstDay + totaldays)) {
// either previous or next month
if (i < firstDay) {
// previous month
var prevyear = prevDate.getFullYear();
var prevmonth = prevDate.getMonth();
var maxprev = this._getDaysInMonth(prevmonth, prevyear);
this._cells[i].node.textContent = maxprev - firstDay + i + 1;
this._cells[i].node.className = "prevMonth";
showsPrevDays++;
} else {
// next month
this._cells[i].node.textContent = i - (firstDay + totaldays) + 1;
this._cells[i].node.className = "nextMonth";
}
} else {
// current month
// this._cells add one since the first day is 1, not 0!
this._cells[i].node.textContent = i - firstDay + 1;
this._cells[i].node.className = "currentMonth";
}
this._cells[i].node.setAttribute("tabindex", "-1");
}
// first time
if (this._currentCellIndex == -1) {
// select the current day
this._currentCellIndex = aDate.getDate() + showsPrevDays - 1;
} else {
// if the day is larger that the total days in this month
if (aDayToSelect > totaldays)
aDayToSelect = totaldays;
this._currentCellIndex = aDayToSelect + showsPrevDays - 1;
}
this._cells[this._currentCellIndex].node.setAttribute("tabindex", "0");
if (!aSkipFocus)
this._cells[this._currentCellIndex].node.focus();
// update the month year heading
this.dateField.textContent = this._date.toLocaleFormat("%B %Y");
]]>
</body>
</method>
<method name="_getPrevDate">
<parameter name="aMonth"/>
<parameter name="aYear"/>
<body>
var month, year = aYear;
if (aMonth == 0) {
month = 11;
year--;
} else {
month = aMonth - 1;
}
return new Date(year, month);
</body>
</method>
<method name="_getNextDate">
<parameter name="aMonth"/>
<parameter name="aYear"/>
<body>
var month, year = aYear;
if (aMonth == 11) {
month = 0;
year++;
} else {
month = aMonth + 1;
}
return new Date(year, month);
</body>
</method>
<method name="_getDaysInMonth">
<parameter name="aMonth"/>
<parameter name="aYear"/>
<body>
<![CDATA[
var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
// check for leap year
if ((aYear % 4 == 0 && aYear % 100 != 0) || aYear % 400 == 0)
days[1] = 29;
return days[aMonth];
]]>
</body>
</method>
<method name="goBack">
<parameter name="aCalledFromButton"/>
<body>
<![CDATA[
var day = parseInt(this._cells[this._currentCellIndex].node.textContent);
this._date = this._getPrevDate(this._date.getMonth(), this._date.getFullYear());
this._refreshCells(this._date, day, aCalledFromButton);
]]>
</body>
</method>
<method name="goForward">
<parameter name="aCalledFromButton"/>
<body>
<![CDATA[
var day = parseInt(this._cells[this._currentCellIndex].node.textContent);
this._date = this._getNextDate(this._date.getMonth(), this._date.getFullYear());
this._refreshCells(this._date, day, aCalledFromButton);
]]>
</body>
</method>
<method name="selectCell">
<parameter name="aCellNum"/>
<body>
<![CDATA[
if (aCellNum == this._currentCellIndex) {
// make sure it is focused. We keep the currentCellIndex even if we
// switch to the prev/next buttons, so we just need to refocus the
// cell.
this._cells[this._currentCellIndex].node.focus();
} else {
this._cells[this._currentCellIndex].node.setAttribute("tabindex", "-1");
this._currentCellIndex = aCellNum;
this._cells[this._currentCellIndex].node.setAttribute("tabindex", "0");
this._cells[this._currentCellIndex].node.focus();
}
]]>
</body>
</method>
<method name="_valueSet">
<body>
<![CDATA[
// called when a cell is choosen (enter or mouse click)
var value = this._cells[this._currentCellIndex].node.textContent;
var date = new Date(this._date.getFullYear(), this._date.getMonth(),
parseInt(value));
this.inputField.value = date.toLocaleFormat("%Y-%m-%d");
// check if we should update the instance data
this._change();
this._hidePicker(true);
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="keypress">
<![CDATA[
// first we handle events that will always toggle the picker dropdown -
// F4 and alt-down/up
if (event.keyCode == event.DOM_VK_F4 ||
event.altKey && (event.keyCode == event.DOM_VK_DOWN ||
event.keyCode == event.DOM_VK_UP)) {
// first set the accessor value, since the input's value hasn't
// been validated yet, and forcing this will.
this.accessors.setValue(this.inputField.value);
this._togglePicker();
} else if (this._isPickerVisible) {
// handle events if the picker dropdown is visible - we always
// hide the picker if focus returns to the input
var index = this._currentCellIndex;
var currentElement = event.originalTarget;
if (event.keyCode == event.DOM_VK_DOWN) {
if (currentElement.localName == "input") {
// if we are on the button, down should focus the current selected
// cell
this.selectCell(index);
} else if ((index + 7) < this._cells.length) {
this.selectCell(index + 7);
}
} else if (event.keyCode == event.DOM_VK_UP) {
// td means we are on a cell
if (currentElement.localName == "td" && (index - 7) >= 0) {
this.selectCell(index - 7);
} else {
// focus the back button
document.getAnonymousElementByAttribute(this, "anonid", "back-button").focus();
}
} else if (event.keyCode == event.DOM_VK_LEFT) {
// ctrl-left goes back a month
if (event.ctrlKey) {
this.goBack();
} else if (currentElement.localName == "input") {
// input means we are on one of the back/fwd buttons
document.getAnonymousElementByAttribute(this, "anonid", "back-button").focus();
} else if ((index - 1) >= 0) {
this.selectCell(index - 1);
}
} else if (event.keyCode == event.DOM_VK_RIGHT) {
// ctrl-right goes forward a month
if (event.ctrlKey) {
this.goForward();
} else if (currentElement.localName == "input") {
// input means we are on one of the back/fwd buttons
document.getAnonymousElementByAttribute(this, "anonid", "fwd-button").focus();
} else if ((index + 1) < this._cells.length) {
this.selectCell(index + 1);
}
} else if (event.keyCode == event.DOM_VK_RETURN &&
event.originalTarget.localName == "td") {
var type = event.originalTarget.className;
if (type == "currentMonth") {
this.selectCell(event.originalTarget.getAttribute("num"));
this._valueSet();
} else if (type == "prevMonth") {
this.goBack();
} else if (type == "nextMonth") {
this.goForward();
}
} else if (event.keyCode == event.DOM_VK_ESCAPE) {
this._hidePicker(true);
}
} else {
// pressing down if the picker is hidden will show it
if (event.keyCode == event.DOM_VK_DOWN) {
// first set the accessor value, since the input's value hasn't
// been validated yet, and forcing this will.
this.accessors.setValue(this.inputField.value);
this._showPicker();
} else {
// check if something changed
this._change();
}
}
]]>
</handler>
<handler event="keyup">
<![CDATA[
if (!this._isPickerVisible) {
// check if something changed
this._change();
}
]]>
</handler>
<handler event="mousedown">
<![CDATA[
if (event.originalTarget.localName == "td") {
var type = event.originalTarget.className;
var cell = parseInt(event.originalTarget.getAttribute("num"));
if (type == "currentMonth") {
this.selectCell(cell);
this._valueSet();
} else if (type == "prevMonth") {
this._currentCellIndex = cell;
this.goBack();
} else if (type == "nextMonth") {
this._currentCellIndex = cell;
this.goForward();
}
}
]]>
</handler>
<handler event="xforms-next">
<![CDATA[
this._hidePicker(false);
]]>
</handler>
<handler event="xforms-previous">
<![CDATA[
this._hidePicker(false);
]]>
</handler>
</handlers>
</binding>
<!-- INPUT: Month
The input[type="xsd:gMonth"] widget assumes successors bindings implement
getElementControl() method what returns the object:
{
set value(); // set "string" value
get value(); // return "string" value
set readonly(); // set readonly state
focus(); // set focus
appendMonth(name, value); // append a month
}
-->
<binding id="xformswidget-input-month-base"
extends="chrome://xforms/content/xforms.xml#xformswidget-base">
<implementation implements="nsIXFormsUIWidget">
<method name="refresh">
<body>
<![CDATA[
this.control.readonly = this.accessors.isReadonly();
if (this.accessors.isValid())
this.control.value = this.stringValue.substr(2,2);
else
this.control.value = "";
return true;
]]>
</body>
</method>
<method name="focus">
<body>
this.control.focus();
return true;
</body>
</method>
<method name="updateInstanceData">
<parameter name="aIncremental"/>
<body>
if (!aIncremental || this.getAttribute("incremental") == "true") {
if (this.control.value != "") {
this.accessors.setValue("--" + this.control.value);
} else {
this.accessors.setValue("");
}
}
</body>
</method>
<constructor>
<![CDATA[
var date = new Date();
for (var i = 0; i < 12; i++) {
var value = i + 1;
if (value < 10)
value = "0" + value;
date.setMonth(i);
this.control.appendMonth(date.toLocaleFormat("%B"), value);
}
this.refresh();
]]>
</constructor>
</implementation>
</binding>
<!-- INPUT: Day
The input[type="xsd:gDay"] widget assumes successors bindings implement
getElementControl() method what returns the object:
{
set value(); // set "string" value
get value(); // return "string" value
set readonly(); // set readonly state
focus(); // set focus
appendDay(name, value); // append a day
}
-->
<binding id="xformswidget-input-day-base"
extends="chrome://xforms/content/xforms.xml#xformswidget-base">
<implementation implements="nsIXFormsUIWidget">
<method name="refresh">
<body>
<![CDATA[
this.control.readonly = this.accessors.isReadonly();
if (this.accessors.isValid())
this.control.value = this.stringValue.substr(3,2);
else
this.control.value = "";
return true;
]]>
</body>
</method>
<method name="focus">
<body>
this.control.focus();
return true;
</body>
</method>
<method name="updateInstanceData">
<parameter name="aIncremental"/>
<body>
if (!aIncremental || this.getAttribute("incremental") == "true") {
if (this.control.value != "") {
this.accessors.setValue("---" + this.control.value);
} else {
this.accessors.setValue("");
}
}
</body>
</method>
<constructor>
<![CDATA[
var date = new Date();
for (var i = 0; i < 31; i++) {
var value = i + 1;
if (value < 10)
value = "0" + value;
this.control.appendDay(i + 1, value);
}
this.refresh();
]]>
</constructor>
</implementation>
</binding>
<!-- SECRET: <DEFAULT>
We don't need in any special base binding for secret widget. All
successors bindings should be extended from base binding for input widget.
-->
<!-- TEXTAREA: <DEFAULT>
We don't need in any special base binding for textarea widget. All
successors bindings should be extended from base binding for input widget.
-->
</bindings>