зеркало из https://github.com/mozilla/gecko-dev.git
3050 строки
115 KiB
XML
3050 строки
115 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 Sun Microsystems code.
|
|
-
|
|
- The Initial Developer of the Original Code is Sun Microsystems.
|
|
- Portions created by the Initial Developer are Copyright (C) 2006
|
|
- the Initial Developer. All Rights Reserved.
|
|
-
|
|
- Contributor(s):
|
|
- Michael Buettner <michael.buettner@sun.com>
|
|
-
|
|
- 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 *****
|
|
-->
|
|
|
|
<!DOCTYPE dialog
|
|
[
|
|
<!ENTITY % dtd1 SYSTEM "chrome://calendar/locale/global.dtd" > %dtd1;
|
|
<!ENTITY % dtd2 SYSTEM "chrome://calendar/locale/calendar.dtd" > %dtd2;
|
|
<!ENTITY % dtd3 SYSTEM "chrome://calendar/locale/sun-calendar-event-dialog.dtd" > %dtd3;
|
|
<!ENTITY % calendar-event-dialogDTD SYSTEM "chrome://calendar/locale/calendar-event-dialog.dtd">
|
|
%calendar-event-dialogDTD;
|
|
]>
|
|
|
|
<bindings xmlns="http://www.mozilla.org/xbl"
|
|
xmlns:xbl="http://www.mozilla.org/xbl"
|
|
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
|
|
|
<!-- ############################################################################# -->
|
|
<!-- 'attendees-list'-binding -->
|
|
<!-- ############################################################################# -->
|
|
|
|
<!-- id's are evil, use anonid -->
|
|
<binding id="attendees-list">
|
|
<content>
|
|
|
|
<xul:listbox anonid="listbox" seltype="multiple" rows="-1" flex="1">
|
|
<xul:listcols>
|
|
<xul:listcol/>
|
|
<xul:listcol flex="1"/>
|
|
</xul:listcols>
|
|
<xul:listitem anonid="item" class="addressingWidgetItem" allowevents="true">
|
|
<xul:listcell class="addressingWidgetCell" align="center" pack="center">
|
|
<xul:image id="attendeeCol1#1" anonid="icon"/>
|
|
</xul:listcell>
|
|
<xul:listcell class="addressingWidgetCell">
|
|
<xul:textbox id="attendeeCol2#1" anonid="input" class="plain textbox-addressingWidget uri-element"
|
|
type="autocomplete" flex="1"
|
|
searchSessions="addrbook" timeout="300" maxrows="4"
|
|
autoFill="true" autoFillAfterMatch="true" forceComplete="true"
|
|
minResultsForPopup="1" ignoreBlurWhileSearching="true"
|
|
oninput="this.setAttribute('dirty','true');">
|
|
<xul:image class="person-icon" onclick="this.parentNode.select();"/>
|
|
</xul:textbox>
|
|
</xul:listcell>
|
|
</xul:listitem>
|
|
</xul:listbox>
|
|
|
|
</content>
|
|
|
|
<implementation>
|
|
|
|
<field name="mMaxAttendees">0</field>
|
|
<field name="mContentHeight">0</field>
|
|
<field name="mRowHeight">0</field>
|
|
<field name="mNumColumns">0</field>
|
|
<field name="mIOService">null</field>
|
|
<field name="mDirectoryServerObserver">null</field>
|
|
<field name="mHeaderParser">null</field>
|
|
<field name="mPrefs">null</field>
|
|
<field name="mPrefBranchInternal">null</field>
|
|
<field name="mIsOffline">0</field>
|
|
<field name="mLDAPSession">null</field>
|
|
<field name="mSessionAdded">0</field>
|
|
<field name="mOwnerID">null</field>
|
|
<field name="mUserID">null</field>
|
|
<field name="mOrganizerID">null</field>
|
|
<field name="mIsReadOnly">false</field>
|
|
<field name="mIsOrganizer">false</field>
|
|
<field name="mPopupOpen">false</field>
|
|
|
|
<constructor>
|
|
<![CDATA[
|
|
this.mMaxAttendees = 0;
|
|
|
|
var self = this;
|
|
var load = function loadHandler() { self.onLoad(); };
|
|
window.addEventListener("load", load, true);
|
|
var unload = function unloadHandler() { self.onUnload(); };
|
|
window.addEventListener("unload", unload, true);
|
|
|
|
var observer = {
|
|
observe: function(subject, topic, value) {
|
|
// catch the exception and ignore it, so that if LDAP setup
|
|
// fails, the entire window doesn't get horked
|
|
try {
|
|
self.setupAutocomplete();
|
|
}
|
|
catch (ex) {}
|
|
}
|
|
}
|
|
|
|
this.mDirectoryServerObserver = observer;
|
|
|
|
this.mHeaderParser = Components.classes["@mozilla.org/messenger/headerparser;1"].getService(Components.interfaces.nsIMsgHeaderParser);
|
|
|
|
// First get the preferences service
|
|
try {
|
|
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
|
|
.getService(Components.interfaces.nsIPrefService);
|
|
this.mPrefs = prefService.getBranch(null);
|
|
this.mPrefBranchInternal = this.mPrefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
|
|
}
|
|
catch (ex) {}
|
|
|
|
this.mIOService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
|
|
this.mIsOffline = this.mIOService.offline;
|
|
|
|
]]>
|
|
</constructor>
|
|
|
|
<method name="onLoad">
|
|
<body>
|
|
<![CDATA[
|
|
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
|
|
var template = document.getAnonymousElementByAttribute(this, "anonid", "item");
|
|
|
|
// we need to enfore several layout constraints which can't be modelled
|
|
// with plain xul and css, at least as far as i know.
|
|
const kStylesheet = "chrome://calendar/content/sun-calendar-event-dialog.css";
|
|
for each(var stylesheet in document.styleSheets) {
|
|
if (stylesheet.href == kStylesheet) {
|
|
|
|
// the height of the text blocks contained in the grid items needs
|
|
// to have the same height as the items of the attendee-list.
|
|
var height = template.boxObject.height-1;
|
|
stylesheet.insertRule(".freebusy-grid { min-height: "+height+"px; }", 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
this.onInitialize();
|
|
|
|
// this trigger the continous update chain, which
|
|
// effectively calls this.onModify() on predefined
|
|
// time intervals [each second].
|
|
var self = this;
|
|
var callback = function func() {
|
|
setTimeout(callback, 1000);
|
|
self.onModify();
|
|
}
|
|
callback();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
<method name="onInitialize">
|
|
<body>
|
|
<![CDATA[
|
|
var args = window.arguments[0];
|
|
var organizer = args.organizer;
|
|
var attendees = args.attendees;
|
|
var calendar = args.calendar;
|
|
|
|
// set 'mIsReadOnly' if the calendar is read-only
|
|
if (calendar && calendar.readOnly)
|
|
this.mIsReadOnly = true;
|
|
|
|
// assume we're the organizer [in case that the calendar
|
|
// does not support the concept of identities].
|
|
this.mIsOrganizer = true;
|
|
|
|
try {
|
|
this.mUserID = "";
|
|
this.mOwnerID = "";
|
|
this.mOrganizerID = "";
|
|
|
|
var provider = calendar.QueryInterface(Components.interfaces.calIWcapCalendar);
|
|
this.mOwnerID = provider.ownerId;
|
|
this.mUserID = provider.session.userId;
|
|
this.mOrganizerID = ((organizer == null || organizer.id == null)
|
|
? this.mOwnerID // sensible default
|
|
: organizer.id);
|
|
|
|
// set 'mIsOrganizer' if the current calid originally scheduled this event.
|
|
this.mIsOrganizer = false;
|
|
if(this.mOwnerID == this.mOrganizerID)
|
|
this.mIsOrganizer = true;
|
|
}
|
|
catch(e) {}
|
|
|
|
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
|
|
var template = document.getAnonymousElementByAttribute(this, "anonid", "item");
|
|
template.focus();
|
|
|
|
if(this.mIsReadOnly || !this.mIsOrganizer)
|
|
listbox.setAttribute("disabled","true");
|
|
|
|
// TODO: the organizer should show up in the attendee list, but this information
|
|
// should be based on the organizer contained in the appropriate field of calIItemBase.
|
|
// This is currently not supported, since we're still missing calendar identities.
|
|
if(this.mOrganizerID && this.mOrganizerID != "") {
|
|
if(!organizer) {
|
|
organizer = this.createAttendee();
|
|
organizer.id = this.mOrganizerID;
|
|
organizer.role = "CHAIR";
|
|
organizer.participationStatus = "ACCEPTED";
|
|
} else {
|
|
if(!organizer.id)
|
|
organizer.id = this.mOrganizerID;
|
|
if(!organizer.role)
|
|
organizer.role = "CHAIR";
|
|
if(!organizer.participationStatus)
|
|
organizer.participationStatus = "ACCEPTED";
|
|
}
|
|
try {
|
|
var provider = calendar.QueryInterface(Components.interfaces.calIWcapCalendar);
|
|
var organizerCalendar = provider.session.getCalendarByCalId(this.mOrganizerID);
|
|
var props = organizerCalendar.getCalendarProperties("X-S1CS-CALPROPS-COMMON-NAME",{});
|
|
if(props.length > 0)
|
|
organizer.commonName = props[0];
|
|
}
|
|
catch(e) {}
|
|
this.appendAttendee(organizer,listbox,template,true);
|
|
}
|
|
|
|
var numRowsAdded = 0;
|
|
if(attendees.length > 0) {
|
|
for each(var attendee in attendees) {
|
|
this.appendAttendee(attendee,listbox,template,false);
|
|
numRowsAdded++;
|
|
}
|
|
}
|
|
if(numRowsAdded == 0) {
|
|
this.appendAttendee(null,listbox,template,false);
|
|
}
|
|
|
|
// detach the template item from the listbox, but hold the reference.
|
|
// until this function returns we add at least a single copy of this template back again.
|
|
listbox.removeChild(template);
|
|
|
|
this.addDirectoryServerObserver();
|
|
|
|
this.setFocus(this.mMaxAttendees);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onUnload">
|
|
<body>
|
|
<![CDATA[
|
|
|
|
this.removeDirectoryServerObserver();
|
|
this.releaseAutoCompleteState();
|
|
this.mIOService = null;
|
|
this.mLDAPSession = null;
|
|
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- appends a new row using an existing attendee structure -->
|
|
<method name="appendAttendee">
|
|
<parameter name="aAttendee"/>
|
|
<parameter name="aParentNode"/>
|
|
<parameter name="aTemplateNode"/>
|
|
<parameter name="aDisableIfOrganizer"/>
|
|
<body>
|
|
<![CDATA[
|
|
// create a new listbox item and append it to our parent control.
|
|
var newNode = aTemplateNode.cloneNode(true);
|
|
|
|
var input = document.getAnonymousElementByAttribute(newNode, "anonid", "input");
|
|
var icon = document.getAnonymousElementByAttribute(newNode, "anonid", "icon");
|
|
|
|
// We always clone the first row. The problem is that the first row
|
|
// could be focused. When we clone that row, we end up with a cloned
|
|
// XUL textbox that has a focused attribute set. Therefore we think
|
|
// we're focused and don't properly refocus. The best solution to this
|
|
// would be to clone a template row that didn't really have any presentation,
|
|
// rather than using the real visible first row of the listbox.
|
|
// For now we'll just put in a hack that ensures the focused attribute
|
|
// is never copied when the node is cloned.
|
|
if (input.getAttribute('focused') != '')
|
|
input.removeAttribute('focused');
|
|
|
|
aParentNode.appendChild(newNode);
|
|
|
|
// the template could have its fields disabled,
|
|
// that's why we need to reset their status.
|
|
input.removeAttribute("disabled");
|
|
icon.removeAttribute("disabled");
|
|
|
|
if(this.mIsReadOnly || !this.mIsOrganizer) {
|
|
input.setAttribute("disabled","true");
|
|
icon.setAttribute("disabled","true");
|
|
}
|
|
|
|
// disable the input-field [name <email>] if this attendee
|
|
// appears to be the organizer.
|
|
if (aDisableIfOrganizer) {
|
|
if (aAttendee) {
|
|
if (this.mOrganizerID && this.mOrganizerID != "") {
|
|
if (aAttendee.id == this.mOrganizerID) {
|
|
input.setAttribute("disabled","true");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.mMaxAttendees++;
|
|
var rowNumber = this.mMaxAttendees;
|
|
if (rowNumber >= 0)
|
|
{
|
|
icon.setAttribute("id", "attendeeCol1#" + rowNumber);
|
|
input.setAttribute("id", "attendeeCol2#" + rowNumber);
|
|
}
|
|
|
|
if(!aAttendee) {
|
|
aAttendee = this.createAttendee();
|
|
}
|
|
|
|
// construct the display string from common name and/or email address.
|
|
var inputValue = aAttendee.commonName;
|
|
if(inputValue) {
|
|
var email = aAttendee.id;
|
|
if(email && email.length) {
|
|
if (email.indexOf("mailto:") == 0)
|
|
email = email.split("mailto:")[1]
|
|
inputValue += ' <'+email+'>';
|
|
}
|
|
} else {
|
|
var email = aAttendee.id;
|
|
if(email && email.length) {
|
|
if (email.indexOf("mailto:") == 0)
|
|
email = email.split("mailto:")[1]
|
|
inputValue = email;
|
|
}
|
|
}
|
|
|
|
// remove leading spaces
|
|
while (inputValue && inputValue[0] == " " )
|
|
inputValue = inputValue.substring(1, inputValue.length);
|
|
|
|
input.setAttribute("value", inputValue);
|
|
input.value = inputValue;
|
|
input.attendee = aAttendee;
|
|
input.setAttribute("dirty","true");
|
|
|
|
if (aAttendee) {
|
|
if (this.mOrganizerID && this.mOrganizerID != "") {
|
|
if (aAttendee.id == this.mOrganizerID) {
|
|
|
|
icon.setAttribute("class","status-icon");
|
|
icon.setAttribute("status",aAttendee.participationStatus);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
icon.setAttribute("class","role-icon");
|
|
icon.setAttribute("role",aAttendee.role);
|
|
|
|
return true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- appends a new empty row -->
|
|
<method name="appendNewRow">
|
|
<parameter name="aSetFocus"/>
|
|
<body>
|
|
<![CDATA[
|
|
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
|
|
var listitem1 = this.getListItem(1);
|
|
|
|
if (listbox && listitem1) {
|
|
|
|
var newAttendee = this.createAttendee();
|
|
var nextDummy = this.getNextDummyRow();
|
|
var newNode = listitem1.cloneNode(true);
|
|
|
|
var input = document.getAnonymousElementByAttribute(newNode, "anonid", "input");
|
|
var icon = document.getAnonymousElementByAttribute(newNode, "anonid", "icon");
|
|
|
|
// the template could have its fields disabled,
|
|
// that's why we need to reset their status.
|
|
input.removeAttribute("disabled");
|
|
icon.removeAttribute("disabled");
|
|
|
|
if(this.mIsReadOnly || !this.mIsOrganizer) {
|
|
input.setAttribute("disabled","true");
|
|
icon.setAttribute("disabled","true");
|
|
}
|
|
|
|
this.mMaxAttendees++;
|
|
var rowNumber = this.mMaxAttendees;
|
|
if (rowNumber >= 0)
|
|
{
|
|
icon.setAttribute("id", "attendeeCol1#" + rowNumber);
|
|
input.setAttribute("id", "attendeeCol2#" + rowNumber);
|
|
}
|
|
|
|
input.value = null;
|
|
input.removeAttribute("value");
|
|
input.attendee = newAttendee;
|
|
|
|
// this copies the autocomplete sessions list from first attendee
|
|
if (this.mLDAPSession && this.mSessionAdded) {
|
|
input.syncSessions(document.getElementById('attendeeCol2#1'));
|
|
}
|
|
|
|
// set role and participation status
|
|
//status.setAttribute("status",newAttendee.participationStatus);
|
|
//role.setAttribute("role",newAttendee.role);
|
|
icon.setAttribute("class","role-icon");
|
|
icon.setAttribute("role","REQ-PARTICIPANT");
|
|
|
|
// We always clone the first row. The problem is that the first row
|
|
// could be focused. When we clone that row, we end up with a cloned
|
|
// XUL textbox that has a focused attribute set. Therefore we think
|
|
// we're focused and don't properly refocus. The best solution to this
|
|
// would be to clone a template row that didn't really have any presentation,
|
|
// rather than using the real visible first row of the listbox.
|
|
// For now we'll just put in a hack that ensures the focused attribute
|
|
// is never copied when the node is cloned.
|
|
if (input.getAttribute('focused') != '')
|
|
input.removeAttribute('focused');
|
|
|
|
if (nextDummy)
|
|
listbox.replaceChild(newNode, nextDummy);
|
|
else
|
|
listbox.appendChild(newNode);
|
|
|
|
// focus on new input widget
|
|
if(aSetFocus)
|
|
this.setFocus(this.mMaxAttendees);
|
|
|
|
this.onModify();
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<property name="attendees">
|
|
<getter>
|
|
<![CDATA[
|
|
var attendees = [];
|
|
|
|
var i=1;
|
|
var inputField;
|
|
while ((inputField = this.getInputElement(i))) {
|
|
var fieldValue = inputField.value;
|
|
if (fieldValue == null)
|
|
fieldValue = inputField.getAttribute("value");
|
|
|
|
if (fieldValue != "") {
|
|
|
|
// the inputfield already has a reference to the attendee
|
|
// object, we just need to fill in the name.
|
|
var attendee = inputField.attendee.clone();
|
|
|
|
attendee.role = this.getRoleElement(i).getAttribute("role");
|
|
//attendee.participationStatus = this.getStatusElement(i).getAttribute("status");
|
|
|
|
// break the list of potentially many attendees back into individual names
|
|
var emailAddresses = {};
|
|
var names = {};
|
|
var fullNames = {};
|
|
var numAddresses = this.mHeaderParser.parseHeadersWithArray(fieldValue,emailAddresses,names,fullNames);
|
|
|
|
if(emailAddresses.value.length > 0) {
|
|
|
|
// if the new address has no 'mailto'-prefix but seems
|
|
// to look like an email-address, we prepend the prefix.
|
|
// this also allows for non-email-addresses.
|
|
var email = emailAddresses.value[0];
|
|
if (email.indexOf("mailto:") != 0)
|
|
if (email.indexOf("@") >= 0)
|
|
email = "mailto:" + email;
|
|
attendee.id = email;
|
|
}
|
|
if(names.value.length > 0) {
|
|
attendee.commonName = names.value[0];
|
|
}
|
|
|
|
var addAttendee = true;
|
|
if (this.mOrganizerID && this.mOrganizerID != "") {
|
|
if (attendee.id == this.mOrganizerID) {
|
|
if (i == 1) {
|
|
addAttendee = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// append the attendee object to the list of attendees.
|
|
if (addAttendee)
|
|
attendees.push(attendee);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
return attendees;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="organizer">
|
|
<getter>
|
|
<![CDATA[
|
|
var i=1;
|
|
var inputField;
|
|
while ((inputField = this.getInputElement(i))) {
|
|
var fieldValue = inputField.value;
|
|
if (fieldValue == null)
|
|
fieldValue = inputField.getAttribute("value");
|
|
|
|
if (fieldValue != "") {
|
|
|
|
// the inputfield already has a reference to the attendee
|
|
// object, we just need to fill in the name.
|
|
var attendee = inputField.attendee.clone();
|
|
|
|
//attendee.role = this.getRoleElement(i).getAttribute("role");
|
|
attendee.participationStatus = this.getStatusElement(i).getAttribute("status");
|
|
|
|
// break the list of potentially many attendees back into individual names
|
|
var emailAddresses = {};
|
|
var names = {};
|
|
var fullNames = {};
|
|
var numAddresses = this.mHeaderParser.parseHeadersWithArray(fieldValue,emailAddresses,names,fullNames);
|
|
|
|
if(emailAddresses.value.length > 0) {
|
|
|
|
// if the new address has no 'mailto'-prefix but seems
|
|
// to look like an email-address, we prepend the prefix.
|
|
// this also allows for non-email-addresses.
|
|
var email = emailAddresses.value[0];
|
|
if (email.indexOf("mailto:") != 0)
|
|
if (email.indexOf("@") >= 0)
|
|
email = "mailto:" + email;
|
|
attendee.id = email;
|
|
}
|
|
if(names.value.length > 0) {
|
|
attendee.commonName = names.value[0];
|
|
}
|
|
|
|
if (this.mOrganizerID && this.mOrganizerID != "") {
|
|
if (attendee.id == this.mOrganizerID) {
|
|
return attendee;
|
|
}
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
return null;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<method name="onModify">
|
|
<body>
|
|
<![CDATA[
|
|
var list = [];
|
|
for (var i=1; i<=this.mMaxAttendees; i++)
|
|
{
|
|
// retrieve the string from the appropriate row
|
|
var input = this.getInputElement(i);
|
|
var fieldValue = input.value;
|
|
|
|
// parse the string to break this down to individual names and addresses
|
|
var email = "";
|
|
var emailAddresses = {};
|
|
var names = {};
|
|
var fullNames = {};
|
|
var numAddresses = this.mHeaderParser.parseHeadersWithArray(fieldValue,emailAddresses,names,fullNames);
|
|
|
|
if(emailAddresses.value.length > 0) {
|
|
|
|
// if the new address has no 'mailto'-prefix but seems
|
|
// to look like an email-address, we prepend the prefix.
|
|
// this also allows for non-email-addresses.
|
|
email = emailAddresses.value[0];
|
|
if (email.indexOf("mailto:") != 0)
|
|
if (email.indexOf("@") >= 0)
|
|
email = "mailto:" + email;
|
|
}
|
|
|
|
var isdirty = false;
|
|
if(input.hasAttribute("dirty"))
|
|
isdirty = input.getAttribute("dirty");
|
|
input.removeAttribute("dirty");
|
|
var entry = { dirty: isdirty, calid: email };
|
|
list.push(entry);
|
|
}
|
|
|
|
var event = document.createEvent('Events');
|
|
event.initEvent('modify', true, false);
|
|
event.details = list;
|
|
this.dispatchEvent(event);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<property name="documentSize">
|
|
<getter>
|
|
<![CDATA[
|
|
return this.mRowHeight * this.mMaxAttendees;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<method name="fitDummyRows">
|
|
<body>
|
|
<![CDATA[
|
|
var self = this;
|
|
var func = function func() {
|
|
self.calcContentHeight();
|
|
self.createOrRemoveDummyRows();
|
|
}
|
|
setTimeout(func,0);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="calcContentHeight">
|
|
<body>
|
|
<![CDATA[
|
|
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
|
|
var items = listbox.getElementsByTagNameNS('*','listitem');
|
|
this.mContentHeight = 0;
|
|
if (items.length > 0) {
|
|
var i = 0;
|
|
do {
|
|
this.mRowHeight = items[i].boxObject.height;
|
|
++i;
|
|
} while (i < items.length && !this.mRowHeight);
|
|
this.mContentHeight = this.mRowHeight*items.length;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="createOrRemoveDummyRows">
|
|
<body>
|
|
<![CDATA[
|
|
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
|
|
var listboxHeight = listbox.boxObject.height;
|
|
|
|
// remove rows to remove scrollbar
|
|
var kids = listbox.childNodes;
|
|
for (var i = kids.length-1; this.mContentHeight > listboxHeight && i >= 0; --i) {
|
|
if (kids[i].hasAttribute("_isDummyRow")) {
|
|
this.mContentHeight -= this.mRowHeight;
|
|
listbox.removeChild(kids[i]);
|
|
}
|
|
}
|
|
|
|
// add rows to fill space
|
|
if (this.mRowHeight) {
|
|
while (this.mContentHeight+this.mRowHeight < listboxHeight) {
|
|
this.createDummyItem(listbox);
|
|
this.mContentHeight += this.mRowHeight;
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="createDummyCell">
|
|
<parameter name="aParent"/>
|
|
<body>
|
|
<![CDATA[
|
|
var cell = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","listcell");
|
|
cell.setAttribute("class", "addressingWidgetCell dummy-row-cell");
|
|
if (aParent)
|
|
aParent.appendChild(cell);
|
|
return cell;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="createDummyItem">
|
|
<parameter name="aParent"/>
|
|
<body>
|
|
<![CDATA[
|
|
var titem = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","listitem");
|
|
titem.setAttribute("_isDummyRow", "true");
|
|
titem.setAttribute("class", "dummy-row");
|
|
for (var i=this.numColumns; i>0; i--)
|
|
this.createDummyCell(titem);
|
|
if (aParent)
|
|
aParent.appendChild(titem);
|
|
return titem;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- gets the next row from the top down -->
|
|
<method name="getNextDummyRow">
|
|
<body>
|
|
<![CDATA[
|
|
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
|
|
var kids = listbox.childNodes;
|
|
for (var i = 0; i < kids.length; ++i) {
|
|
if (kids[i].hasAttribute("_isDummyRow"))
|
|
return kids[i];
|
|
}
|
|
return null;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- This method returns the <xul:listitem> at row numer 'aRow' -->
|
|
<method name="getListItem">
|
|
<parameter name="aRow"/>
|
|
<body>
|
|
<![CDATA[
|
|
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
|
|
if (listbox && aRow > 0) {
|
|
var listitems = listbox.getElementsByTagNameNS('*','listitem');
|
|
if (listitems && listitems.length >= aRow)
|
|
return listitems[aRow-1];
|
|
}
|
|
return 0;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="getRowByInputElement">
|
|
<parameter name="aElement"/>
|
|
<body>
|
|
<![CDATA[
|
|
var row = 0;
|
|
while(aElement && aElement.localName != "listitem")
|
|
aElement = aElement.parentNode;
|
|
if (aElement) {
|
|
while (aElement) {
|
|
if (aElement.localName == "listitem")
|
|
++row;
|
|
aElement = aElement.previousSibling;
|
|
}
|
|
}
|
|
return row;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- This method returns the <xul:textbox> that contains
|
|
the name of the attendee at row number 'aRow' -->
|
|
<method name="getInputElement">
|
|
<parameter name="aRow"/>
|
|
<body>
|
|
<![CDATA[
|
|
return document.getElementById("attendeeCol2#" + aRow);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="getRoleElement">
|
|
<parameter name="aRow"/>
|
|
<body>
|
|
<![CDATA[
|
|
return document.getElementById("attendeeCol1#" + aRow);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="getStatusElement">
|
|
<parameter name="aRow"/>
|
|
<body>
|
|
<![CDATA[
|
|
return document.getElementById("attendeeCol1#" + aRow);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="setFocus">
|
|
<parameter name="aRow"/>
|
|
<body>
|
|
<![CDATA[
|
|
var self = this;
|
|
var set_focus = function func() {
|
|
// do we need to scroll in order to see the selected row?
|
|
var node = self.getListItem(aRow);
|
|
var listbox = document.getAnonymousElementByAttribute(self, "anonid", "listbox");
|
|
var firstVisibleRow = listbox.getIndexOfFirstVisibleRow();
|
|
var numOfVisibleRows = listbox.getNumberOfVisibleRows();
|
|
if (aRow <= firstVisibleRow)
|
|
listbox.scrollToIndex(aRow-1);
|
|
else
|
|
if (aRow-1 >= (firstVisibleRow+numOfVisibleRows))
|
|
listbox.scrollToIndex(aRow-numOfVisibleRows);
|
|
|
|
var input = document.getAnonymousElementByAttribute(node, "anonid", "input");
|
|
input.focus();
|
|
}
|
|
setTimeout(set_focus,0);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<property name="firstVisibleRow">
|
|
<getter>
|
|
<![CDATA[
|
|
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
|
|
return listbox.getIndexOfFirstVisibleRow();
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<method name="createAttendee">
|
|
<body>
|
|
<![CDATA[
|
|
var attendee = createAttendee();
|
|
attendee.id = "";
|
|
attendee.rsvp = true;
|
|
attendee.role = "REQ-PARTICIPANT";
|
|
attendee.participationStatus = "NEEDS-ACTION";
|
|
return attendee;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<property name="numColumns">
|
|
<getter>
|
|
<![CDATA[
|
|
if (!this.mNumColumns) {
|
|
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
|
|
var listCols = listbox.getElementsByTagNameNS('*','listcol');
|
|
this.mNumColumns = listCols.length;
|
|
if (!this.mNumColumns)
|
|
this.mNumColumns = 1;
|
|
}
|
|
return this.mNumColumns;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="ratio">
|
|
<setter>
|
|
<![CDATA[
|
|
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
|
|
var rowcount = listbox.getRowCount();
|
|
listbox.scrollToIndex(Math.floor(rowcount*val));
|
|
return val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<method name="arrowHit">
|
|
<parameter name="aElement"/>
|
|
<parameter name="aDirection"/>
|
|
<body>
|
|
<![CDATA[
|
|
var row = this.getRowByInputElement(aElement) + aDirection;
|
|
if (row) {
|
|
if(row > this.mMaxAttendees) {
|
|
this.appendNewRow(true);
|
|
} else {
|
|
var input = this.getInputElement(row);
|
|
if(input.hasAttribute("disabled"))
|
|
return;
|
|
this.setFocus(row);
|
|
}
|
|
var event = document.createEvent('Events');
|
|
event.initEvent('rowchange', true, false);
|
|
event.details = row;
|
|
this.dispatchEvent(event);
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="deleteHit">
|
|
<parameter name="aElement"/>
|
|
<body>
|
|
<![CDATA[
|
|
// don't delete the row if it's the last one remaining
|
|
if (this.mMaxAttendees <= 1)
|
|
return;
|
|
var row = this.getRowByInputElement(aElement);
|
|
this.deleteRow(row);
|
|
if(row > 1)
|
|
row = row-1;
|
|
|
|
this.setFocus(row);
|
|
this.onModify();
|
|
|
|
var event = document.createEvent('Events');
|
|
event.initEvent('rowchange', true, false);
|
|
event.details = row;
|
|
this.dispatchEvent(event);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="deleteRow">
|
|
<parameter name="aRow"/>
|
|
<body>
|
|
<![CDATA[
|
|
// reset id's in order to not break the sequence
|
|
var maxAttendees = this.mMaxAttendees;
|
|
this.removeRow(aRow);
|
|
var numberOfCols = this.numColumns;
|
|
for (var row = aRow+1; row<=maxAttendees; row++) {
|
|
for (var col=1; col<=numberOfCols; col++) {
|
|
var colID = "attendeeCol" + col + "#" + row;
|
|
var elem = document.getElementById(colID);
|
|
if(elem)
|
|
elem.setAttribute("id", "attendeeCol" + (col) + "#" + (row-1));
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="removeRow">
|
|
<parameter name="aRow"/>
|
|
<body>
|
|
<![CDATA[
|
|
var listbox = document.getAnonymousElementByAttribute(this, "anonid", "listbox");
|
|
var nodeToRemove = this.getListItem(aRow)
|
|
nodeToRemove.parentNode.removeChild(nodeToRemove);
|
|
this.fitDummyRows();
|
|
this.mMaxAttendees--;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- ############################################################################# -->
|
|
<!-- LDAP support -->
|
|
<!-- ############################################################################# -->
|
|
|
|
<method name="setupAutocomplete">
|
|
<body>
|
|
<![CDATA[
|
|
var autocompleteLdap = false;
|
|
var autocompleteDirectory = null;
|
|
var prevAutocompleteDirectory = this.mCurrentAutocompleteDirectory;
|
|
var i;
|
|
|
|
autocompleteLdap = this.mPrefs.getBoolPref("ldap_2.autoComplete.useDirectory");
|
|
if (autocompleteLdap)
|
|
autocompleteDirectory = this.mPrefs.getCharPref(
|
|
"ldap_2.autoComplete.directoryServer");
|
|
|
|
// use a temporary to do the setup so that we don't overwrite the
|
|
// global, then have some problem and throw an exception, and leave the
|
|
// global with a partially setup session. we'll assign the temp
|
|
// into the global after we're done setting up the session
|
|
var LDAPSession;
|
|
if (this.mLDAPSession) {
|
|
LDAPSession = this.mLDAPSession;
|
|
} else {
|
|
LDAPSession = Components.classes[
|
|
"@mozilla.org/autocompleteSession;1?type=ldap"].createInstance()
|
|
.QueryInterface(Components.interfaces.nsILDAPAutoCompleteSession);
|
|
}
|
|
|
|
if (autocompleteDirectory && !this.mIsOffline) {
|
|
// Add observer on the directory server we are autocompleting against
|
|
// only if current server is different from previous.
|
|
// Remove observer if current server is different from previous
|
|
this.mCurrentAutocompleteDirectory = autocompleteDirectory;
|
|
if (prevAutocompleteDirectory) {
|
|
if (prevAutocompleteDirectory != this.mCurrentAutocompleteDirectory) {
|
|
this.removeDirectorySettingsObserver(prevAutocompleteDirectory);
|
|
this.addDirectorySettingsObserver();
|
|
}
|
|
}
|
|
else
|
|
this.addDirectorySettingsObserver();
|
|
|
|
// fill in the session params if there is a session
|
|
//
|
|
if (LDAPSession) {
|
|
var serverURL = Components.classes[
|
|
"@mozilla.org/network/ldap-url;1"].
|
|
createInstance().QueryInterface(
|
|
Components.interfaces.nsILDAPURL);
|
|
|
|
try {
|
|
serverURL.spec = this.mPrefs.getComplexValue(autocompleteDirectory +".uri",
|
|
Components.interfaces.nsISupportsString).data;
|
|
} catch (ex) {
|
|
dump("ERROR: " + ex + "\n");
|
|
}
|
|
LDAPSession.serverURL = serverURL;
|
|
|
|
// get the login to authenticate as, if there is one
|
|
//
|
|
var login = "";
|
|
try {
|
|
login = this.mPrefs.getComplexValue(
|
|
autocompleteDirectory + ".auth.dn",
|
|
Components.interfaces.nsISupportsString).data;
|
|
} catch (ex) {
|
|
// if we don't have this pref, no big deal
|
|
}
|
|
|
|
// set the LDAP protocol version correctly
|
|
var protocolVersion;
|
|
try {
|
|
protocolVersion = this.mPrefs.getCharPref(autocompleteDirectory +
|
|
".protocolVersion");
|
|
} catch (ex) {
|
|
// if we don't have this pref, no big deal
|
|
}
|
|
if (protocolVersion == "2") {
|
|
LDAPSession.version =
|
|
Components.interfaces.nsILDAPConnection.VERSION2;
|
|
}
|
|
|
|
// find out if we need to authenticate, and if so, tell the LDAP
|
|
// autocomplete session how to prompt for a password. This window
|
|
// is being used to parent the authprompter.
|
|
//
|
|
LDAPSession.login = login;
|
|
if (login != "") {
|
|
var windowWatcherSvc = Components.classes[
|
|
"@mozilla.org/embedcomp/window-watcher;1"]
|
|
.getService(Components.interfaces.nsIWindowWatcher);
|
|
var domWin =
|
|
window.QueryInterface(Components.interfaces.nsIDOMWindow);
|
|
var authPrompter =
|
|
windowWatcherSvc.getNewAuthPrompter(domWin);
|
|
|
|
LDAPSession.authPrompter = authPrompter;
|
|
}
|
|
|
|
// don't search on non-CJK strings shorter than this
|
|
//
|
|
try {
|
|
LDAPSession.minStringLength = this.mPrefs.getIntPref(
|
|
autocompleteDirectory + ".autoComplete.minStringLength");
|
|
} catch (ex) {
|
|
// if this pref isn't there, no big deal. just let
|
|
// nsLDAPAutoCompleteSession use its default.
|
|
}
|
|
|
|
// don't search on CJK strings shorter than this
|
|
//
|
|
try {
|
|
LDAPSession.cjkMinStringLength = this.mPrefs.getIntPref(
|
|
autocompleteDirectory + ".autoComplete.cjkMinStringLength");
|
|
} catch (ex) {
|
|
// if this pref isn't there, no big deal. just let
|
|
// nsLDAPAutoCompleteSession use its default.
|
|
}
|
|
|
|
// we don't try/catch here, because if this fails, we're outta luck
|
|
//
|
|
var ldapFormatter = Components.classes[
|
|
"@mozilla.org/ldap-autocomplete-formatter;1?type=addrbook"]
|
|
.createInstance().QueryInterface(
|
|
Components.interfaces.nsIAbLDAPAutoCompFormatter);
|
|
|
|
// override autocomplete name format?
|
|
//
|
|
try {
|
|
ldapFormatter.nameFormat =
|
|
this.mPrefs.getComplexValue(autocompleteDirectory +
|
|
".autoComplete.nameFormat",
|
|
Components.interfaces.nsISupportsString).data;
|
|
} catch (ex) {
|
|
// if this pref isn't there, no big deal. just let
|
|
// nsAbLDAPAutoCompFormatter use its default.
|
|
}
|
|
|
|
// override autocomplete mail address format?
|
|
//
|
|
try {
|
|
ldapFormatter.addressFormat =
|
|
this.mPrefs.getComplexValue(autocompleteDirectory +
|
|
".autoComplete.addressFormat",
|
|
Components.interfaces.nsISupportsString).data;
|
|
} catch (ex) {
|
|
// if this pref isn't there, no big deal. just let
|
|
// nsAbLDAPAutoCompFormatter use its default.
|
|
}
|
|
|
|
try {
|
|
// figure out what goes in the comment column, if anything
|
|
//
|
|
// 0 = none
|
|
// 1 = name of addressbook this card came from
|
|
// 2 = other per-addressbook format
|
|
//
|
|
var showComments = 0;
|
|
showComments = this.mPrefs.getIntPref(
|
|
"mail.autoComplete.commentColumn");
|
|
|
|
switch (showComments) {
|
|
|
|
case 1:
|
|
// use the name of this directory
|
|
//
|
|
ldapFormatter.commentFormat = this.mPrefs.getComplexValue(
|
|
autocompleteDirectory + ".description",
|
|
Components.interfaces.nsISupportsString).data;
|
|
break;
|
|
|
|
case 2:
|
|
// override ldap-specific autocomplete entry?
|
|
//
|
|
try {
|
|
ldapFormatter.commentFormat =
|
|
this.mPrefs.getComplexValue(autocompleteDirectory +
|
|
".autoComplete.commentFormat",
|
|
Components.interfaces.nsISupportsString).data;
|
|
} catch (innerException) {
|
|
// if nothing has been specified, use the ldap
|
|
// organization field
|
|
ldapFormatter.commentFormat = "[o]";
|
|
}
|
|
break;
|
|
|
|
case 0:
|
|
default:
|
|
// do nothing
|
|
}
|
|
} catch (ex) {
|
|
// if something went wrong while setting up comments, try and
|
|
// proceed anyway
|
|
}
|
|
|
|
// set the session's formatter, which also happens to
|
|
// force a call to the formatter's getAttributes() method
|
|
// -- which is why this needs to happen after we've set the
|
|
// various formats
|
|
//
|
|
LDAPSession.formatter = ldapFormatter;
|
|
|
|
// override autocomplete entry formatting?
|
|
//
|
|
try {
|
|
LDAPSession.outputFormat =
|
|
this.mPrefs.getComplexValue(autocompleteDirectory +
|
|
".autoComplete.outputFormat",
|
|
Components.interfaces.nsISupportsString).data;
|
|
|
|
} catch (ex) {
|
|
// if this pref isn't there, no big deal. just let
|
|
// nsLDAPAutoCompleteSession use its default.
|
|
}
|
|
|
|
// override default search filter template?
|
|
//
|
|
try {
|
|
LDAPSession.filterTemplate = this.mPrefs.getComplexValue(
|
|
autocompleteDirectory + ".autoComplete.filterTemplate",
|
|
Components.interfaces.nsISupportsString).data;
|
|
|
|
} catch (ex) {
|
|
// if this pref isn't there, no big deal. just let
|
|
// nsLDAPAutoCompleteSession use its default
|
|
}
|
|
|
|
// override default maxHits (currently 100)
|
|
//
|
|
try {
|
|
// XXXdmose should really use .autocomplete.maxHits,
|
|
// but there's no UI for that yet
|
|
//
|
|
LDAPSession.maxHits =
|
|
this.mPrefs.getIntPref(autocompleteDirectory + ".maxHits");
|
|
} catch (ex) {
|
|
// if this pref isn't there, or is out of range, no big deal.
|
|
// just let nsLDAPAutoCompleteSession use its default.
|
|
}
|
|
|
|
if (!this.mSessionAdded) {
|
|
// if we make it here, we know that session initialization has
|
|
// succeeded; add the session for all recipients, and
|
|
// remember that we've done so
|
|
var autoCompleteWidget;
|
|
for (i=1; i <= this.mMaxAttendees; i++)
|
|
{
|
|
autoCompleteWidget = this.getInputElement(i);
|
|
if (autoCompleteWidget)
|
|
{
|
|
autoCompleteWidget.addSession(LDAPSession);
|
|
// ldap searches don't insert a default entry with the default domain appended to it
|
|
// so reduce the minimum results for a popup to 2 in this case.
|
|
autoCompleteWidget.minResultsForPopup = 2;
|
|
|
|
}
|
|
}
|
|
this.mSessionAdded = true;
|
|
}
|
|
}
|
|
} else {
|
|
if (this.mCurrentAutocompleteDirectory) {
|
|
// Remove observer on the directory server since we are not doing Ldap
|
|
// autocompletion.
|
|
this.removeDirectorySettingsObserver(this.mCurrentAutocompleteDirectory);
|
|
this.mCurrentAutocompleteDirectory = null;
|
|
}
|
|
if (this.mLDAPSession && this.mSessionAdded) {
|
|
for (i=1; i <= this.mMaxAttendees; i++)
|
|
this.getInputElement(i).removeSession(this.mLDAPSession);
|
|
this.mSessionAdded = false;
|
|
}
|
|
}
|
|
|
|
this.mLDAPSession = LDAPSession;
|
|
//gSetupLdapAutocomplete = true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="addDirectoryServerObserver">
|
|
<body>
|
|
<![CDATA[
|
|
this.mPrefBranchInternal.addObserver("ldap_2.autoComplete.useDirectory",
|
|
this.mDirectoryServerObserver, false);
|
|
this.mPrefBranchInternal.addObserver("ldap_2.autoComplete.directoryServer",
|
|
this.mDirectoryServerObserver, false);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="removeDirectoryServerObserver">
|
|
<body>
|
|
<![CDATA[
|
|
this.mPrefBranchInternal.removeObserver("ldap_2.autoComplete.useDirectory", this.mDirectoryServerObserver);
|
|
this.mPrefBranchInternal.removeObserver("ldap_2.autoComplete.directoryServer", this.mDirectoryServerObserver);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="addDirectorySettingsObserver">
|
|
<body>
|
|
<![CDATA[
|
|
this.mPrefBranchInternal.addObserver(this.mCurrentAutocompleteDirectory, this.mDirectoryServerObserver, false);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="removeDirectorySettingsObserver">
|
|
<parameter name="aPrefString"/>
|
|
<body>
|
|
<![CDATA[
|
|
this.mPrefBranchInternal.removeObserver(aPrefString, this.mDirectoryServerObserver);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="releaseAutoCompleteState">
|
|
<body>
|
|
<![CDATA[
|
|
if (this.mLDAPSession && this.mSessionAdded) {
|
|
for (i=1; i <= this.mMaxAttendees; i++) {
|
|
this.getInputElement(i).removeSession(this.mLDAPSession);
|
|
}
|
|
}
|
|
|
|
this.mSessionAdded = false;
|
|
this.mLDAPSession = null;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- ############################################################################# -->
|
|
<!-- End of implementation for the 'attendees-list'-binding -->
|
|
<!-- ############################################################################# -->
|
|
|
|
</implementation>
|
|
|
|
<handlers>
|
|
|
|
<handler event="click" button="0">
|
|
<![CDATA[
|
|
if (event.originalTarget.hasAttribute("role")) {
|
|
if (event.originalTarget.hasAttribute("disabled")) {
|
|
if (event.originalTarget.getAttribute("disabled")) {
|
|
return;
|
|
}
|
|
}
|
|
var role = event.originalTarget.getAttribute("role");
|
|
if (role == "CHAIR") {
|
|
event.originalTarget.setAttribute("role","REQ-PARTICIPANT");
|
|
} else if (role == "REQ-PARTICIPANT") {
|
|
event.originalTarget.setAttribute("role","OPT-PARTICIPANT");
|
|
} else if (role == "OPT-PARTICIPANT") {
|
|
event.originalTarget.setAttribute("role","CHAIR");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (event.originalTarget.hasAttribute("status")) {
|
|
if (event.originalTarget.hasAttribute("disabled")) {
|
|
if (event.originalTarget.getAttribute("disabled")) {
|
|
return;
|
|
}
|
|
}
|
|
var status = event.originalTarget.getAttribute("status");
|
|
if (status == "NEEDS-ACTION") {
|
|
event.originalTarget.setAttribute("status","ACCEPTED");
|
|
} else if (status == "ACCEPTED") {
|
|
event.originalTarget.setAttribute("status","DECLINED");
|
|
} else if (status == "DECLINED") {
|
|
event.originalTarget.setAttribute("status","TENTATIVE");
|
|
} else if (status == "TENTATIVE") {
|
|
event.originalTarget.setAttribute("status","ACCEPTED");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(this.mIsReadOnly || !this.mIsOrganizer)
|
|
return;
|
|
|
|
if (event.originalTarget == null ||
|
|
(event.originalTarget.localName != "listboxbody" &&
|
|
event.originalTarget.localName != "listcell" &&
|
|
event.originalTarget.localName != "listitem"))
|
|
return;
|
|
|
|
var lastInput = this.getInputElement(this.mMaxAttendees);
|
|
if (lastInput && lastInput.value)
|
|
this.appendNewRow(true);
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="popupshown">
|
|
<![CDATA[
|
|
this.mPopupOpen = true;
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="popuphidden">
|
|
<![CDATA[
|
|
this.mPopupOpen = false;
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="keydown">
|
|
<![CDATA[
|
|
if(this.mIsReadOnly || !this.mIsOrganizer)
|
|
return;
|
|
if(event.originalTarget.localName == "input") {
|
|
switch(event.keyCode) {
|
|
case 46:
|
|
case 8:
|
|
if (!event.originalTarget.value)
|
|
this.deleteHit(event.originalTarget);
|
|
event.stopPropagation();
|
|
break;
|
|
case 13:
|
|
this.arrowHit(event.originalTarget, 1);
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
break;
|
|
}
|
|
}
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="keypress" phase="capturing">
|
|
<![CDATA[
|
|
// in case we're currently showing the autocompletion popup
|
|
// don't care about keypress-events and let them go. otherwise
|
|
// this event indicates the user wants to travel between
|
|
// the different attendees. in this case we set the focus
|
|
// appropriately and stop the event propagation.
|
|
if(this.mPopupOpen)
|
|
return;
|
|
if(this.mIsReadOnly || !this.mIsOrganizer)
|
|
return;
|
|
if(event.originalTarget.localName == "input") {
|
|
switch(event.keyCode) {
|
|
case KeyEvent.DOM_VK_UP:
|
|
this.arrowHit(event.originalTarget, -1);
|
|
event.stopPropagation();
|
|
break;
|
|
case KeyEvent.DOM_VK_DOWN:
|
|
this.arrowHit(event.originalTarget, 1);
|
|
event.stopPropagation();
|
|
break;
|
|
case KeyEvent.DOM_VK_TAB:
|
|
this.arrowHit(event.originalTarget, event.shiftKey ? -1 : +1);
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
break;
|
|
case KeyEvent.DOM_VK_RETURN:
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
break;
|
|
}
|
|
}
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="input">
|
|
<![CDATA[
|
|
this.setupAutocomplete();
|
|
]]>
|
|
</handler>
|
|
|
|
</handlers>
|
|
|
|
</binding>
|
|
|
|
<!-- the 'selection-bar' binding implements the vertical bar that provides
|
|
a visual indication for the time range the event is configured for. -->
|
|
<binding id="selection-bar">
|
|
|
|
<content>
|
|
<xul:scrollbox anonid="scrollbox" width="0" orient="horizontal" flex="1">
|
|
<xul:box class="selection-bar" anonid="selection-bar">
|
|
<xul:box width="3" style="cursor: e-resize"/>
|
|
<xul:spacer flex="1" style="cursor: -moz-grab"/>
|
|
<xul:box width="3" style="cursor: e-resize"/>
|
|
</xul:box>
|
|
</xul:scrollbox>
|
|
</content>
|
|
|
|
<implementation>
|
|
|
|
<field name="mRange">0</field>
|
|
<field name="mStartHour">8</field>
|
|
<field name="mEndHour">19</field>
|
|
<field name="mStartHourDefault">8</field>
|
|
<field name="mEndHourDefault">19</field>
|
|
<field name="mContentWidth">0</field>
|
|
<field name="mHeaderHeight">0</field>
|
|
<field name="mRatio">0</field>
|
|
<field name="mBaseDate">null</field>
|
|
<field name="mStartDate">null</field>
|
|
<field name="mEndDate">null</field>
|
|
<field name="mMouseX">0</field>
|
|
<field name="mMouseY">0</field>
|
|
<field name="mDragState">0</field>
|
|
<field name="mMargin">0</field>
|
|
<field name="mWidth">0</field>
|
|
<field name="mForce24Hours">false</field>
|
|
<field name="mZoomFactor">100</field>
|
|
|
|
<property name="zoomFactor">
|
|
<getter>
|
|
<![CDATA[
|
|
return this.mZoomFactor;
|
|
]]>
|
|
</getter>
|
|
<setter>
|
|
<![CDATA[
|
|
this.mZoomFactor = val;
|
|
return val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<property name="force24Hours">
|
|
<getter>
|
|
<![CDATA[
|
|
return this.mForce24Hours;
|
|
]]>
|
|
</getter>
|
|
<setter>
|
|
<![CDATA[
|
|
this.mForce24Hours = val;
|
|
this.initTimeRange();
|
|
this.update();
|
|
return val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<property name="ratio">
|
|
<setter>
|
|
<![CDATA[
|
|
this.mRatio = val;
|
|
this.update();
|
|
return val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<constructor>
|
|
<![CDATA[
|
|
this.initTimeRange();
|
|
|
|
// the basedate is the date/time from which the display
|
|
// of the timebar starts. the range is the number of days
|
|
// we should be able to show. the start- and enddate
|
|
// is the time the event is scheduled for.
|
|
this.mRange = Number(this.getAttribute("range"));
|
|
]]>
|
|
</constructor>
|
|
|
|
<property name="baseDate">
|
|
<setter>
|
|
<![CDATA[
|
|
// we need to convert the date/time in question in
|
|
// order to calculate with hours that are aligned
|
|
// with our timebar display.
|
|
var kDefaultTimezone = calendarDefaultTimezone();
|
|
this.mBaseDate = val.getInTimezone(kDefaultTimezone);
|
|
this.mBaseDate.isDate = true;
|
|
this.mBaseDate.makeImmutable();
|
|
return val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<property name="startDate">
|
|
<setter>
|
|
<![CDATA[
|
|
// currently we *always* set the basedate to be
|
|
// equal to the startdate. we'll most probably
|
|
// want to change this later.
|
|
this.baseDate = val;
|
|
// we need to convert the date/time in question in
|
|
// order to calculate with hours that are aligned
|
|
// with our timebar display.
|
|
var kDefaultTimezone = calendarDefaultTimezone();
|
|
this.mStartDate = val.getInTimezone(kDefaultTimezone);
|
|
this.mStartDate.makeImmutable();
|
|
return val;
|
|
]]>
|
|
</setter>
|
|
<getter>
|
|
<![CDATA[
|
|
return this.mStartDate;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="endDate">
|
|
<setter>
|
|
<![CDATA[
|
|
// we need to convert the date/time in question in
|
|
// order to calculate with hours that are aligned
|
|
// with our timebar display.
|
|
var kDefaultTimezone = calendarDefaultTimezone();
|
|
this.mEndDate = val.getInTimezone(kDefaultTimezone);
|
|
if(this.mEndDate.isDate)
|
|
this.mEndDate.day += 1;
|
|
this.mEndDate.normalize();
|
|
this.mEndDate.makeImmutable();
|
|
return val;
|
|
]]>
|
|
</setter>
|
|
<getter>
|
|
<![CDATA[
|
|
return this.mEndDate;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<method name="init">
|
|
<parameter name="width"/>
|
|
<parameter name="height"/>
|
|
<body>
|
|
<![CDATA[
|
|
this.mContentWidth = width;
|
|
this.mHeaderHeight = height + 2;
|
|
var num_hours = this.mEndHour - this.mStartHour;
|
|
var hour_width = this.mContentWidth / num_hours;
|
|
this.mWidth = hour_width*1;
|
|
this.mMargin = 0;
|
|
this.update();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- given some specific date this method calculates the
|
|
corrposonding offset in fractional hours -->
|
|
<method name="date2offset">
|
|
<parameter name="date"/>
|
|
<body>
|
|
<![CDATA[
|
|
var num_hours = this.mEndHour - this.mStartHour;
|
|
var diff = date.subtractDate(this.mBaseDate);
|
|
var offset = diff.days * num_hours;
|
|
var hours = (diff.hours - this.mStartHour) + (diff.minutes / 60.0);
|
|
if(hours < 0)
|
|
hours = 0;
|
|
if(hours > num_hours)
|
|
hours = num_hours;
|
|
offset += hours;
|
|
return offset;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="update">
|
|
<body>
|
|
<![CDATA[
|
|
if(!this.mStartDate || !this.mEndDate)
|
|
return;
|
|
|
|
// calculate the relation of startdate/basedate and enddate/startdate.
|
|
var offset = this.mStartDate.subtractDate(this.mBaseDate);
|
|
var duration = this.mEndDate.subtractDate(this.mStartDate);
|
|
|
|
// calculate how much pixels a single hour and a single day take up.
|
|
var num_hours = this.mEndHour - this.mStartHour;
|
|
var hour_width = this.mContentWidth / num_hours;
|
|
|
|
// calculate the offset in fractional hours that corrospond
|
|
// to our start- and end-time.
|
|
var start_offset_in_hours = this.date2offset(this.mStartDate);
|
|
var end_offset_in_hours = this.date2offset(this.mEndDate);
|
|
var duration_in_hours = end_offset_in_hours - start_offset_in_hours;
|
|
|
|
// calculate width & margin for the selection bar based on the
|
|
// relation of startdate/basedate and enddate/startdate.
|
|
// this is a simple conversion from hours to pixels.
|
|
this.mWidth = duration_in_hours*hour_width;
|
|
this.mMargin = start_offset_in_hours*hour_width;
|
|
|
|
// calculate the difference between content and container in pixels.
|
|
// the container is the window showing this control, the content is the
|
|
// total number of pixels the selection bar can theoretically take up.
|
|
var total_width = this.mContentWidth * this.mRange - this.parentNode.boxObject.width;
|
|
|
|
// calculate the current scroll offset.
|
|
var offset = Math.floor(total_width * this.mRatio);
|
|
|
|
// the final margin is the difference between
|
|
// the date-based margin and the scroll-based margin.
|
|
this.mMargin -= offset;
|
|
|
|
// set the styles based on the calculations above for the 'selection-bar'.
|
|
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
|
|
var style = "width: "+this.mWidth+"px; margin-left: "+this.mMargin+"px; margin-top: "+this.mHeaderHeight+"px;";
|
|
selectionbar.setAttribute("style",style);
|
|
|
|
var event = document.createEvent('Events');
|
|
event.initEvent('timechange', true, false);
|
|
event.startDate = this.mStartDate;
|
|
event.endDate = this.mEndDate.clone();
|
|
if(event.endDate.isDate)
|
|
event.endDate.day -= 1;
|
|
event.endDate.makeImmutable();
|
|
this.dispatchEvent(event);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="setWidth">
|
|
<parameter name="width"/>
|
|
<body>
|
|
<![CDATA[
|
|
var scrollbox = document.getAnonymousElementByAttribute(this, "anonid", "scrollbox");
|
|
scrollbox.setAttribute("width",width);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="initTimeRange">
|
|
<body>
|
|
<![CDATA[
|
|
this.mStartHour = this.mStartHourDefault;
|
|
this.mEndHour = this.mEndHourDefault;
|
|
|
|
var pb2 = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch2);
|
|
|
|
// get default start/end times from prefs and set on the
|
|
// view. if we hit an error (eg because sunbird's pref
|
|
// infrastructure hasn't created the pref yet), the
|
|
// defaults will do
|
|
try {
|
|
this.mStartHour = pb2.getIntPref("calendar.view.defaultstarthour");
|
|
this.mEndHour = pb2.getIntPref("calendar.view.defaultendhour");
|
|
} catch (ex) {}
|
|
|
|
if(this.force24Hours) {
|
|
this.mStartHour = 0;
|
|
this.mEndHour = 24;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="moveTime">
|
|
<parameter name="time"/>
|
|
<parameter name="delta"/>
|
|
<body>
|
|
<![CDATA[
|
|
var num_hours = this.mEndHour - this.mStartHour;
|
|
var hour_width = this.mContentWidth / num_hours;
|
|
var minutes_per_pixel = 60 / hour_width;
|
|
var newTime = time.clone();
|
|
var minute_shift = minutes_per_pixel * delta;
|
|
var clip_minutes = 60 * this.zoomFactor / 100;
|
|
if(delta > 0) {
|
|
if(time.isDate) {
|
|
if(minute_shift >= clip_minutes) {
|
|
newTime.day++;
|
|
newTime.normalize();
|
|
}
|
|
} else {
|
|
if(minute_shift >= clip_minutes) {
|
|
newTime.minute -= newTime.minute % clip_minutes;
|
|
newTime.minute += clip_minutes;
|
|
newTime.normalize();
|
|
}
|
|
}
|
|
|
|
} else if(delta < 0) {
|
|
if(time.isDate) {
|
|
if(-minute_shift >= clip_minutes) {
|
|
newTime.day--;
|
|
newTime.normalize();
|
|
}
|
|
} else {
|
|
if(-minute_shift >= clip_minutes) {
|
|
newTime.minute -= newTime.minute % clip_minutes;
|
|
newTime.minute -= clip_minutes;
|
|
newTime.normalize();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!newTime.isDate) {
|
|
if(newTime.hour < this.mStartHour) {
|
|
newTime.hour = this.mEndHour-1;
|
|
newTime.day--;
|
|
}
|
|
if(newTime.hour >= this.mEndHour) {
|
|
newTime.hour = this.mStartHour;
|
|
newTime.day++;
|
|
}
|
|
}
|
|
|
|
newTime.normalize();
|
|
|
|
return newTime;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
</implementation>
|
|
|
|
<handlers>
|
|
|
|
<handler event="mousedown">
|
|
<![CDATA[
|
|
var element = event.target;
|
|
this.mMouseX = event.clientX - element.boxObject.x;
|
|
this.mMouseY = event.clientY - element.boxObject.y;
|
|
if(this.mMouseX >= this.mMargin) {
|
|
if(this.mMouseX < (this.mMargin+this.mWidth)) {
|
|
this.mDragState = 1;
|
|
window.setCursor("-moz-grab");
|
|
if(this.mMouseX <= (this.mMargin+3)) {
|
|
window.setCursor("e-resize");
|
|
this.mDragState = 2;
|
|
} else if(this.mMouseX >= (this.mMargin+this.mWidth-3)) {
|
|
window.setCursor("e-resize");
|
|
this.mDragState = 3;
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="mousemove">
|
|
<![CDATA[
|
|
if(this.mDragState == 1) {
|
|
var element = event.target;
|
|
var mouseX = event.clientX - element.boxObject.x;
|
|
var mouseY = event.clientY - element.boxObject.y;
|
|
var delta = mouseX - this.mMouseX;
|
|
var newStart = this.moveTime(this.mStartDate,delta);
|
|
if(newStart.compare(this.mStartDate) != 0) {
|
|
newEnd = this.moveTime(this.mEndDate,delta);
|
|
|
|
// we need to adapt this date in case we're dealing with
|
|
// an all-day event. this is because setting 'endDate' will
|
|
// automatically add one day extra for all-day events.
|
|
if(newEnd.isDate) {
|
|
newEnd.day -= 1;
|
|
newEnd.normalize();
|
|
}
|
|
|
|
this.startDate = newStart;
|
|
this.endDate = newEnd;
|
|
this.mMouseX = mouseX;
|
|
this.mMouseY = mouseY;
|
|
this.update();
|
|
}
|
|
} else if(this.mDragState == 2) {
|
|
var element = event.target;
|
|
var mouseX = event.clientX - element.boxObject.x;
|
|
var mouseY = event.clientY - element.boxObject.y;
|
|
var delta = mouseX - this.mMouseX;
|
|
var newStart = this.moveTime(this.mStartDate,delta);
|
|
if(newStart.compare(this.mStartDate) != 0) {
|
|
this.startDate = newStart;
|
|
this.mMouseX = mouseX;
|
|
this.mMouseY = mouseY;
|
|
this.update();
|
|
}
|
|
} else if(this.mDragState == 3) {
|
|
var element = event.target;
|
|
var mouseX = event.clientX - element.boxObject.x;
|
|
var mouseY = event.clientY - element.boxObject.y;
|
|
var delta = mouseX - this.mMouseX;
|
|
var newEnd = this.moveTime(this.mEndDate,delta);
|
|
if(newEnd.compare(this.mEndDate) != 0) {
|
|
|
|
// we need to adapt this date in case we're dealing with
|
|
// an all-day event. this is because setting 'endDate' will
|
|
// automatically add one day extra for all-day events.
|
|
if(newEnd.isDate) {
|
|
newEnd.day -= 1;
|
|
newEnd.normalize();
|
|
}
|
|
|
|
// don't allow all-day events to be shorter than a single day.
|
|
if(!newEnd.isDate ||
|
|
(newEnd.compare(this.startDate) >= 0)) {
|
|
|
|
this.endDate = newEnd;
|
|
this.mMouseX = mouseX;
|
|
this.mMouseY = mouseY;
|
|
this.update();
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="mouseup">
|
|
<![CDATA[
|
|
this.mDragState = 0;
|
|
window.setCursor("auto");
|
|
]]>
|
|
</handler>
|
|
|
|
</handlers>
|
|
|
|
</binding>
|
|
|
|
|
|
<!-- id's are evil, use anonid -->
|
|
<binding id="attendees-page">
|
|
<content>
|
|
|
|
<xul:box orient="vertical" flex="1">
|
|
|
|
<xul:box orient="horizontal" align="center" pack="end">
|
|
|
|
<xul:spacer flex="1"/>
|
|
|
|
<xul:label value="&event.freebusy.suggest.slot;"/>
|
|
<xul:button label="&event.freebusy.previous.slot;" dir="normal" class="left-icon" anonid="previous-slot" oncommand="onPreviousSlot();"/>
|
|
<xul:button label="&event.freebusy.next.slot;" dir="reverse" class="right-icon" anonid="next-slot" oncommand="onNextSlot();"/>
|
|
|
|
<xul:spacer style="width: 10em"/>
|
|
|
|
<xul:label value="&event.freebusy.zoom;"/>
|
|
<xul:image class="zoom-out-icon"/>
|
|
<xul:menulist anonid="zoom-menulist" oncommand="onZoomFactor(this.value);">
|
|
<xul:menupopup>
|
|
<xul:menuitem label="400%" value="25"/>
|
|
<xul:menuitem label="200%" value="50"/>
|
|
<xul:menuitem label="100%" value="100"/>
|
|
<xul:menuitem label="50%" value="200"/>
|
|
<xul:menuitem label="25%" value="400"/>
|
|
</xul:menupopup>
|
|
</xul:menulist>
|
|
<xul:image class="zoom-in-icon"/>
|
|
|
|
</xul:box>
|
|
|
|
<xul:box orient="horizontal" flex="1">
|
|
<xul:box orient="vertical" flex="1">
|
|
<xul:box class="attendee-spacer-top"/>
|
|
<xul:attendees-list flex="1" anonid="attendees-list"/>
|
|
<xul:box class="attendee-spacer-bottom"/>
|
|
</xul:box>
|
|
<xul:splitter anonid="splitter"/>
|
|
<xul:box orient="vertical">
|
|
|
|
<xul:stack flex="1">
|
|
<xul:box orient="vertical" flex="1">
|
|
<xul:freebusy-timebar anonid="timebar" xbl:inherits="range"/>
|
|
<xul:freebusy-grid flex="1" anonid="freebusy-grid" xbl:inherits="range"/>
|
|
</xul:box>
|
|
<xul:selection-bar anonid="selection-bar" xbl:inherits="range"/>
|
|
</xul:stack>
|
|
|
|
|
|
<xul:scrollbar orient="horizontal" anonid="horizontal-scrollbar" maxpos="100"/>
|
|
</xul:box>
|
|
<xul:box orient="vertical" anonid="vertical-scrollbar-box" collapsed="true">
|
|
<xul:box class="attendee-spacer-top"/>
|
|
<xul:scrollbar orient="vertical" flex="1" anonid="vertical-scrollbar" maxpos="100"/>
|
|
<xul:box class="attendee-spacer-bottom"/>
|
|
</xul:box>
|
|
</xul:box>
|
|
|
|
<xul:box orient="horizontal">
|
|
<xul:box orient="vertical" flex="1">
|
|
<xul:box orient="horizontal" align="center">
|
|
<xul:box class="legend" status="FREE"/>
|
|
<xul:label value="&event.freebusy.legend.free;"/>
|
|
</xul:box>
|
|
<xul:box orient="horizontal" align="center">
|
|
<xul:box class="legend" status="BUSY"/>
|
|
<xul:label value="&event.freebusy.legend.busy;"/>
|
|
</xul:box>
|
|
<xul:box orient="horizontal" align="center">
|
|
<xul:box class="legend" status="UNKNOWN"/>
|
|
<xul:label value="&event.freebusy.legend.unknown;"/>
|
|
</xul:box>
|
|
<xul:box orient="horizontal">
|
|
<xul:button label="&event.freebusy.minus;" oncommand="onMinus();"/>
|
|
<xul:button label="&event.freebusy.plus;" oncommand="onPlus();"/>
|
|
</xul:box>
|
|
<xul:box orient="horizontal" collapsed="true">
|
|
<xul:label value="&event.organizer.label;" disabled="true"/>
|
|
<xul:textbox anonid="event-organizer" disabled="true" flex="true"/>
|
|
</xul:box>
|
|
</xul:box>
|
|
<xul:grid>
|
|
<xul:columns>
|
|
<xul:column/>
|
|
<xul:column flex="1"/>
|
|
</xul:columns>
|
|
<xul:rows>
|
|
<xul:row align="center">
|
|
<xul:spacer/>
|
|
<xul:checkbox anonid="all-day" oncommand="changeAllDay();" label="&event.alldayevent.label;"/>
|
|
</xul:row>
|
|
<xul:row align="center">
|
|
<xul:label value="&newevent.from.label;"/>
|
|
<xul:datetimepicker anonid="event-starttime" onchange="updateStartTime();"/>
|
|
<xul:label anonid="timezone-starttime"
|
|
crop="right"
|
|
class="text-link"
|
|
flex="1"
|
|
collapsed="true"
|
|
hyperlink="true"
|
|
onclick="editStartTimezone()"/>
|
|
</xul:row>
|
|
<xul:row align="center">
|
|
<xul:label value="&newevent.to.label;"/>
|
|
<xul:datetimepicker anonid="event-endtime" onchange="updateEndTime();"/>
|
|
<xul:label anonid="timezone-endtime"
|
|
crop="right"
|
|
class="text-link"
|
|
flex="1"
|
|
collapsed="true"
|
|
hyperlink="true"
|
|
onclick="editEndTimezone()"/>
|
|
</xul:row>
|
|
</xul:rows>
|
|
</xul:grid>
|
|
</xul:box>
|
|
|
|
<xul:separator class="groove"/>
|
|
|
|
</xul:box>
|
|
|
|
</content>
|
|
<implementation>
|
|
|
|
<field name="mStartDate">null</field>
|
|
<field name="mEndDate">null</field>
|
|
<field name="mStartTimezone">null</field>
|
|
<field name="mEndTimezone">null</field>
|
|
<field name="mDuration">null</field>
|
|
<field name="mStartHour">8</field>
|
|
<field name="mEndHour">19</field>
|
|
<field name="mStartHourDefault">8</field>
|
|
<field name="mEndHourDefault">19</field>
|
|
|
|
<field name="mOwnerID">null</field>
|
|
<field name="mUserID">null</field>
|
|
<field name="mOrganizerID">null</field>
|
|
<field name="mIsReadOnly">false</field>
|
|
<field name="mIsOrganizer">false</field>
|
|
<field name="mIgnoreUpdate">false</field>
|
|
<field name="mDisplayTimezone">true</field>
|
|
|
|
<field name="mUndoStack">[]</field>
|
|
|
|
<field name="mForce24Hours">false</field>
|
|
<field name="mZoomFactor">100</field>
|
|
|
|
<property name="zoomFactor">
|
|
<getter>
|
|
<![CDATA[
|
|
return this.mZoomFactor;
|
|
]]>
|
|
</getter>
|
|
<setter>
|
|
<![CDATA[
|
|
if(this.mZoomFactor == val)
|
|
return val;
|
|
|
|
this.mZoomFactor = val;
|
|
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
timebar.zoomFactor = this.mZoomFactor;
|
|
|
|
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
|
|
selectionbar.zoomFactor = this.mZoomFactor;
|
|
|
|
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
|
|
grid.zoomFactor = this.mZoomFactor;
|
|
|
|
// calling onResize() will update the scrollbars and everything else
|
|
// that needs to adopt the previously made changes. we need to call
|
|
// this after the changes have actually been made...
|
|
this.onResize();
|
|
|
|
var scrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
|
|
if (scrollbar.hasAttribute("maxpos")) {
|
|
var curpos = scrollbar.getAttribute("curpos");
|
|
var maxpos = scrollbar.getAttribute("maxpos");
|
|
var ratio = curpos/maxpos;
|
|
timebar.scroll = ratio;
|
|
grid.scroll = ratio;
|
|
selectionbar.ratio = ratio;
|
|
}
|
|
|
|
return val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<property name="force24Hours">
|
|
<getter>
|
|
<![CDATA[
|
|
return this.mForce24Hours;
|
|
]]>
|
|
</getter>
|
|
<setter>
|
|
<![CDATA[
|
|
if(this.mForce24Hours == val)
|
|
return val;
|
|
|
|
this.mForce24Hours = val;
|
|
|
|
this.initTimeRange();
|
|
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
timebar.force24Hours = this.mForce24Hours;
|
|
|
|
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
|
|
selectionbar.force24Hours = this.mForce24Hours;
|
|
|
|
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
|
|
grid.force24Hours = this.mForce24Hours;
|
|
|
|
// calling onResize() will update the scrollbars and everything else
|
|
// that needs to adopt the previously made changes. we need to call
|
|
// this after the changes have actually been made...
|
|
this.onResize();
|
|
|
|
var scrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
|
|
if (!scrollbar.hasAttribute("maxpos"))
|
|
return;
|
|
var curpos = scrollbar.getAttribute("curpos");
|
|
var maxpos = scrollbar.getAttribute("maxpos");
|
|
var ratio = curpos/maxpos;
|
|
timebar.scroll = ratio;
|
|
grid.scroll = ratio;
|
|
selectionbar.ratio = ratio;
|
|
|
|
return val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<constructor>
|
|
<![CDATA[
|
|
var args = window.arguments[0];
|
|
|
|
this.initTimeRange();
|
|
|
|
var self = this;
|
|
var load = function loadHandler() { self.onLoad(); };
|
|
window.addEventListener("load", load, true);
|
|
var resize = function resizeHandler() { self.onResize(); };
|
|
window.addEventListener("resize", resize, true);
|
|
|
|
var freebusy = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
|
|
var modify = function modifyHandler(event) {
|
|
self.onResize();
|
|
freebusy.onModify(event);
|
|
};
|
|
this.addEventListener('modify', modify, true);
|
|
|
|
var focus = function focusHandler(event) {
|
|
var scrollbar = document.getAnonymousElementByAttribute(self, "anonid", "vertical-scrollbar");
|
|
var attendees = document.getAnonymousElementByAttribute(self, "anonid", "attendees-list");
|
|
var maxpos = scrollbar.getAttribute("maxpos");
|
|
scrollbar.setAttribute("curpos",event.details/attendees.mMaxAttendees*maxpos);
|
|
};
|
|
this.addEventListener('rowchange', focus, true);
|
|
]]>
|
|
</constructor>
|
|
|
|
<method name="onZoomFactor">
|
|
<parameter name="aValue"/>
|
|
<body>
|
|
<![CDATA[
|
|
this.zoomFactor = parseInt(aValue);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="initTimeRange">
|
|
<body>
|
|
<![CDATA[
|
|
this.mStartHour = this.mStartHourDefault;
|
|
this.mEndHour = this.mEndHourDefault;
|
|
|
|
var pb2 = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch2);
|
|
|
|
// get default start/end times from prefs and set on the
|
|
// view. if we hit an error (eg because sunbird's pref
|
|
// infrastructure hasn't created the pref yet), the
|
|
// defaults will do
|
|
try {
|
|
this.mStartHour = pb2.getIntPref("calendar.view.defaultstarthour");
|
|
this.mEndHour = pb2.getIntPref("calendar.view.defaultendhour");
|
|
} catch (ex) {}
|
|
|
|
if(this.force24Hours) {
|
|
this.mStartHour = 0;
|
|
this.mEndHour = 24;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="loadDateTime">
|
|
<parameter name="aStartDate"/>
|
|
<parameter name="aEndDate"/>
|
|
<body>
|
|
<![CDATA[
|
|
this.mDuration = aEndDate.subtractDate(aStartDate);
|
|
var kDefaultTimezone = calendarDefaultTimezone();
|
|
this.mStartTimezone = aStartDate.timezone;
|
|
this.mEndTimezone = aEndDate.timezone;
|
|
this.mStartDate = aStartDate.getInTimezone(kDefaultTimezone);
|
|
this.mEndDate = aEndDate.getInTimezone(kDefaultTimezone);
|
|
this.mStartDate.normalize();
|
|
this.mEndDate.normalize();
|
|
this.mStartDate.makeImmutable();
|
|
this.mEndDate.makeImmutable();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="saveDateTime">
|
|
<body>
|
|
<![CDATA[
|
|
var kDefaultTimezone = calendarDefaultTimezone();
|
|
|
|
var startTime = this.mStartDate.getInTimezone(this.mStartTimezone);
|
|
var endTime = this.mEndDate.getInTimezone(this.mEndTimezone);
|
|
|
|
this.mStartDate = startTime;
|
|
this.mEndDate = endTime;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="propagateDateTime">
|
|
<body>
|
|
<![CDATA[
|
|
// fill the controls
|
|
this.updateDateTime();
|
|
|
|
// tell the timebar about the new start/enddate
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
timebar.startDate = this.mStartDate;
|
|
timebar.endDate = this.mEndDate;
|
|
timebar.refresh();
|
|
|
|
// tell the selection-bar about the new start/enddate
|
|
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
|
|
selectionbar.startDate = this.mStartDate;
|
|
selectionbar.endDate = this.mEndDate;
|
|
selectionbar.update();
|
|
|
|
// tell the freebusy grid about the new start/enddate
|
|
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
|
|
|
|
var refresh = (grid.startDate == null) ||
|
|
(grid.startDate.compare(this.mStartDate) != 0) ||
|
|
(grid.endDate == null) ||
|
|
(grid.endDate.compare(this.mEndDate) != 0);
|
|
grid.startDate = this.mStartDate;
|
|
grid.endDate = this.mEndDate;
|
|
if(refresh)
|
|
grid.forceRefresh();
|
|
|
|
// expand to 24hrs if the new range is outside of the default range.
|
|
var kDefaultTimezone = calendarDefaultTimezone();
|
|
var startTime = this.mStartDate.getInTimezone(kDefaultTimezone);
|
|
var endTime = this.mEndDate.getInTimezone(kDefaultTimezone);
|
|
if((startTime.hour < this.mStartHour) ||
|
|
(endTime.hour > this.mEndHour) ||
|
|
(startTime.isDate)) {
|
|
this.force24Hours = true;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- assumes this.mStartDate and this.mEndDate are up to date. -->
|
|
<method name="updateDateTime">
|
|
<body>
|
|
<![CDATA[
|
|
// convert to default timezone if the timezone option
|
|
// is *not* checked, otherwise keep the specific timezone
|
|
// and display the labels in order to modify the timezone.
|
|
if(this.mDisplayTimezone) {
|
|
|
|
var startTime = this.mStartDate.getInTimezone(this.mStartTimezone);
|
|
var endTime = this.mEndDate.getInTimezone(this.mEndTimezone);
|
|
|
|
if (startTime.isDate) {
|
|
document.getAnonymousElementByAttribute(this, "anonid", "all-day").setAttribute("checked","true");
|
|
}
|
|
|
|
// in the case where the timezones are different but
|
|
// the timezone of the endtime is "UTC", we convert
|
|
// the endtime into the timezone of the starttime.
|
|
if(startTime && endTime) {
|
|
if(startTime.timezone != endTime.timezone) {
|
|
if(endTime.timezone == "UTC") {
|
|
endTime = endTime.getInTimezone(startTime.timezone);
|
|
}
|
|
}
|
|
}
|
|
|
|
// before feeding the date/time value into the control we need
|
|
// to set the timezone to 'floating' in order to avoid the
|
|
// automatic conversion back into the OS timezone.
|
|
startTime.timezone = "floating";
|
|
endTime.timezone = "floating";
|
|
|
|
document.getAnonymousElementByAttribute(this, "anonid", "event-starttime").value = startTime.jsDate;
|
|
document.getAnonymousElementByAttribute(this, "anonid", "event-endtime").value = endTime.jsDate;
|
|
|
|
} else {
|
|
|
|
var kDefaultTimezone = calendarDefaultTimezone();
|
|
|
|
var startTime = this.mStartDate.getInTimezone(kDefaultTimezone);
|
|
var endTime = this.mEndDate.getInTimezone(kDefaultTimezone);
|
|
|
|
if (startTime.isDate) {
|
|
document.getAnonymousElementByAttribute(this, "anonid", "all-day").setAttribute("checked","true");
|
|
}
|
|
|
|
// before feeding the date/time value into the control we need
|
|
// to set the timezone to 'floating' in order to avoid the
|
|
// automatic conversion back into the OS timezone.
|
|
startTime.timezone = "floating";
|
|
endTime.timezone = "floating";
|
|
|
|
document.getAnonymousElementByAttribute(this, "anonid", "event-starttime").value = startTime.jsDate;
|
|
document.getAnonymousElementByAttribute(this, "anonid", "event-endtime").value = endTime.jsDate;
|
|
}
|
|
|
|
this.updateTimezone();
|
|
this.updateAllDay();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="timezoneString">
|
|
<parameter name="date"/>
|
|
<body>
|
|
<![CDATA[
|
|
var fragments = date.split('/');
|
|
var num = fragments.length;
|
|
if(num <= 1)
|
|
return fragments[0];
|
|
return fragments[num-2]+'/'+fragments[num-1];
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- assumes this.mStartDate and this.mEndDate are up to date. -->
|
|
<method name="updateTimezone">
|
|
<body>
|
|
<![CDATA[
|
|
this.mIgnoreUpdate = true;
|
|
|
|
if(this.mDisplayTimezone) {
|
|
|
|
var startTimezone = this.mStartTimezone;
|
|
var endTimezone = this.mEndTimezone;
|
|
|
|
var equalTimezones = false;
|
|
if(startTimezone && endTimezone) {
|
|
if(startTimezone == endTimezone || endTimezone == "UTC") {
|
|
equalTimezones = true;
|
|
}
|
|
}
|
|
|
|
var tzStart = document.getAnonymousElementByAttribute(this, "anonid", "timezone-starttime");
|
|
var tzEnd = document.getAnonymousElementByAttribute(this, "anonid", "timezone-endtime");
|
|
|
|
if(startTimezone != null) {
|
|
tzStart.removeAttribute('collapsed');
|
|
tzStart.value = this.timezoneString(startTimezone);
|
|
} else {
|
|
tzStart.setAttribute('collapsed','true');
|
|
}
|
|
|
|
// we never display the second timezone if both are equal
|
|
if(endTimezone != null && !equalTimezones) {
|
|
tzEnd.removeAttribute('collapsed');
|
|
tzEnd.value = this.timezoneString(endTimezone);
|
|
} else {
|
|
tzEnd.setAttribute('collapsed','true');
|
|
}
|
|
|
|
} else {
|
|
|
|
document.getAnonymousElementByAttribute(this, "anonid", "timezone-starttime").setAttribute('collapsed','true');
|
|
document.getAnonymousElementByAttribute(this, "anonid", "timezone-endtime").setAttribute('collapsed','true');
|
|
}
|
|
|
|
this.mIgnoreUpdate = false;
|
|
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="updateStartTime">
|
|
<body>
|
|
<![CDATA[
|
|
if(this.mIgnoreUpdate)
|
|
return;
|
|
|
|
var startWidgetId = "event-starttime";
|
|
var endWidgetId = "event-endtime";
|
|
|
|
var startWidget = document.getAnonymousElementByAttribute(this, "anonid", startWidgetId);
|
|
var endWidget = document.getAnonymousElementByAttribute(this, "anonid", endWidgetId);
|
|
|
|
// jsDate is always in OS timezone, thus we create a calIDateTime
|
|
// object from the jsDate representation and simply set the new
|
|
// timezone instead of converting.
|
|
var kDefaultTimezone = calendarDefaultTimezone();
|
|
var start = jsDateToDateTime(startWidget.value);
|
|
start = start.getInTimezone(kDefaultTimezone);
|
|
if(this.mDisplayTimezone) {
|
|
start.timezone = this.mStartTimezone;
|
|
}
|
|
this.mStartDate = start.clone();
|
|
start.addDuration(this.mDuration);
|
|
start = start.getInTimezone(this.mEndTimezone);
|
|
if(this.mDisplayTimezone) {
|
|
start.timezone = this.mEndTimezone;
|
|
}
|
|
this.mEndDate = start;
|
|
|
|
var allDayElement = document.getAnonymousElementByAttribute(this, "anonid", "all-day");
|
|
var allDay = allDayElement.getAttribute("checked") == "true";
|
|
if(allDay) {
|
|
this.mStartDate.isDate = true;
|
|
this.mEndDate.isDate = true;
|
|
}
|
|
|
|
this.propagateDateTime();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="updateEndTime">
|
|
<body>
|
|
<![CDATA[
|
|
if(this.mIgnoreUpdate)
|
|
return;
|
|
|
|
var startWidgetId = "event-starttime";
|
|
var endWidgetId = "event-endtime";
|
|
|
|
var startWidget = document.getAnonymousElementByAttribute(this, "anonid", startWidgetId);
|
|
var endWidget = document.getAnonymousElementByAttribute(this, "anonid", endWidgetId);
|
|
|
|
var saveStartTime = this.mStartDate;
|
|
var saveEndTime = this.mEndDate;
|
|
var kDefaultTimezone = calendarDefaultTimezone();
|
|
|
|
var start = jsDateToDateTime(startWidget.value);
|
|
start = start.getInTimezone(kDefaultTimezone);
|
|
if(this.mDisplayTimezone) {
|
|
start.timezone = this.mStartTimezone;
|
|
}
|
|
this.mStartDate = start;
|
|
|
|
var end = jsDateToDateTime(endWidget.value);
|
|
end = end.getInTimezone(kDefaultTimezone);
|
|
var timezone = this.mEndTimezone;
|
|
if(timezone == "UTC") {
|
|
if(this.mStartDate && this.mStartTimezone != this.mEndTimezone) {
|
|
timezone = this.mStartTimezone;
|
|
}
|
|
}
|
|
if(this.mDisplayTimezone) {
|
|
end.timezone = timezone;
|
|
}
|
|
this.mEndDate = end;
|
|
|
|
var allDayElement = document.getAnonymousElementByAttribute(this, "anonid", "all-day");
|
|
var allDay = allDayElement.getAttribute("checked") == "true";
|
|
if(allDay) {
|
|
this.mStartDate.isDate = true;
|
|
this.mEndDate.isDate = true;
|
|
}
|
|
|
|
// calculate the new duration of start/end-time.
|
|
// don't allow for negative durations.
|
|
var warning = false;
|
|
if(this.mEndDate.compare(this.mStartDate) >= 0) {
|
|
this.mDuration = end.subtractDate(start);
|
|
} else {
|
|
this.mStartDate = saveStartTime;
|
|
this.mEndDate = saveEndTime;
|
|
warning = true;
|
|
}
|
|
|
|
this.propagateDateTime();
|
|
|
|
if(warning) {
|
|
var callback = function func() {
|
|
var promptService =
|
|
Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
|
.getService(Components.interfaces.nsIPromptService);
|
|
promptService.alert(null,document.title,"The end date you entered occurs before the start date.");
|
|
}
|
|
setTimeout(callback, 1);
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="editStartTimezone">
|
|
<body>
|
|
<![CDATA[
|
|
var tzStart = document.getAnonymousElementByAttribute(this, "anonid", "timezone-starttime");
|
|
if(tzStart.hasAttribute("disabled"))
|
|
return;
|
|
|
|
var self = this;
|
|
var args = new Object();
|
|
args.time = this.mStartDate.getInTimezone(this.mStartTimezone);
|
|
args.onOk = function(datetime) {
|
|
|
|
var equalTimezones = false;
|
|
if(self.mStartTimezone && self.mEndTimezone) {
|
|
if(self.mStartTimezone == self.mEndTimezone) {
|
|
equalTimezones = true;
|
|
}
|
|
}
|
|
|
|
self.mStartTimezone = datetime.timezone;
|
|
if(equalTimezones)
|
|
self.mEndTimezone = datetime.timezone;
|
|
|
|
self.propagateDateTime();
|
|
};
|
|
|
|
// open the dialog modally
|
|
openDialog("chrome://calendar/content/sun-calendar-event-dialog-timezone.xul", "_blank", "chrome,titlebar,modal,resizable", args);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="editEndTimezone">
|
|
<body>
|
|
<![CDATA[
|
|
var tzStart = document.getAnonymousElementByAttribute(this, "anonid", "timezone-endtime");
|
|
if(tzStart.hasAttribute("disabled"))
|
|
return;
|
|
|
|
var self = this;
|
|
var args = new Object();
|
|
args.time = gEndTime.getInTimezone(gEndTimezone);
|
|
args.onOk = function(datetime) {
|
|
|
|
var equalTimezones = false;
|
|
if(self.mStartTimezone && self.mEndTimezone) {
|
|
if(self.mStartTimezone == self.mEndTimezone) {
|
|
equalTimezones = true;
|
|
}
|
|
}
|
|
|
|
if(equalTimezones)
|
|
self.mStartTimezone = datetime.timezone;
|
|
self.mEndTimezone = datetime.timezone;
|
|
|
|
self.propagateDateTime();
|
|
};
|
|
|
|
// open the dialog modally
|
|
openDialog("chrome://calendar/content/sun-calendar-event-dialog-timezone.xul", "_blank", "chrome,titlebar,modal,resizable", args);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="updateAllDay">
|
|
<body>
|
|
<![CDATA[
|
|
if(this.mIgnoreUpdate)
|
|
return;
|
|
|
|
var allDayElement = document.getAnonymousElementByAttribute(this, "anonid", "all-day");
|
|
var allDay = allDayElement.getAttribute("checked") == "true";
|
|
var startpicker = document.getAnonymousElementByAttribute(this, "anonid", "event-starttime");
|
|
var endpicker = document.getAnonymousElementByAttribute(this, "anonid", "event-endtime");
|
|
if(allDay) {
|
|
startpicker.setAttribute("timepickerdisabled","true");
|
|
endpicker.setAttribute("timepickerdisabled","true");
|
|
} else {
|
|
startpicker.removeAttribute("timepickerdisabled");
|
|
endpicker.removeAttribute("timepickerdisabled");
|
|
}
|
|
|
|
var tzStart = document.getAnonymousElementByAttribute(this, "anonid", "timezone-starttime");
|
|
var tzEnd = document.getAnonymousElementByAttribute(this, "anonid", "timezone-endtime");
|
|
|
|
// disable the timezone links if 'allday' is checked OR the
|
|
// calendar of this item is read-only. in any other case we
|
|
// enable the links.
|
|
if(allDay) {
|
|
tzStart.setAttribute("disabled","true");
|
|
tzEnd.setAttribute("disabled","true");
|
|
tzStart.removeAttribute("class");
|
|
tzEnd.removeAttribute("class");
|
|
} else {
|
|
tzStart.removeAttribute("disabled");
|
|
tzEnd.removeAttribute("disabled");
|
|
tzStart.setAttribute("class","text-link");
|
|
tzEnd.setAttribute("class","text-link");
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="changeAllDay">
|
|
<body>
|
|
<![CDATA[
|
|
var allDayElement = document.getAnonymousElementByAttribute(this, "anonid", "all-day");
|
|
var allDay = allDayElement.getAttribute("checked") == "true";
|
|
|
|
this.mStartDate = this.mStartDate.clone();
|
|
this.mEndDate = this.mEndDate.clone();
|
|
if(allDay) {
|
|
this.mStartDate.isDate = true;
|
|
this.mEndDate.isDate = true;
|
|
} else {
|
|
this.mStartDate.isDate = false;
|
|
this.mEndDate.isDate = false;
|
|
}
|
|
|
|
this.mStartDate.normalize();
|
|
this.mEndDate.normalize();
|
|
|
|
this.propagateDateTime();
|
|
|
|
// after propagating the modified times we enforce some constraints
|
|
// on the zoom-factor. in case this events is now said to be all-day,
|
|
// we automatically enforce a 25% zoom-factor and disable the control.
|
|
var zoom = document.getAnonymousElementByAttribute(this, "anonid", "zoom-menulist");
|
|
if(allDay) {
|
|
zoom.value = "400";
|
|
zoom.setAttribute("disabled","true");
|
|
this.zoomFactor = zoom.value;
|
|
this.force24Hours = true;
|
|
} else {
|
|
zoom.removeAttribute("disabled");
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<property name="startDate">
|
|
<getter>
|
|
<![CDATA[
|
|
return this.mStartDate;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="endDate">
|
|
<getter>
|
|
<![CDATA[
|
|
return this.mEndDate;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="attendees">
|
|
<getter>
|
|
<![CDATA[
|
|
var attendeelist = document.getAnonymousElementByAttribute(this, "anonid", "attendees-list");
|
|
return attendeelist.attendees;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="organizer">
|
|
<getter>
|
|
<![CDATA[
|
|
var attendeelist = document.getAnonymousElementByAttribute(this, "anonid", "attendees-list");
|
|
return attendeelist.organizer;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<method name="onResize">
|
|
<body>
|
|
<![CDATA[
|
|
// don't do anything if we haven't been initialized.
|
|
if(!this.mStartDate || !this.mEndDate)
|
|
return;
|
|
|
|
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
|
|
var gridScrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
|
|
grid.fitDummyRows();
|
|
var ratio = grid.boxObject.width / grid.documentSize;
|
|
var maxpos = gridScrollbar.getAttribute("maxpos");
|
|
var inc = maxpos*ratio/(1-ratio);
|
|
gridScrollbar.setAttribute("pageincrement",inc);
|
|
|
|
var attendees = document.getAnonymousElementByAttribute(this, "anonid", "attendees-list");
|
|
var attendeesScrollbar = document.getAnonymousElementByAttribute(this, "anonid", "vertical-scrollbar");
|
|
var box = document.getAnonymousElementByAttribute(this, "anonid", "vertical-scrollbar-box");
|
|
attendees.fitDummyRows();
|
|
var ratio = attendees.boxObject.height / attendees.documentSize;
|
|
if(ratio < 1) {
|
|
box.removeAttribute("collapsed");
|
|
var maxpos = attendeesScrollbar.getAttribute("maxpos");
|
|
var inc = maxpos*ratio/(1-ratio);
|
|
attendeesScrollbar.setAttribute("pageincrement",inc);
|
|
} else {
|
|
box.setAttribute("collapsed","true");
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onLoad">
|
|
<body>
|
|
<![CDATA[
|
|
var args = window.arguments[0];
|
|
var startTime = args.startTime;
|
|
var endTime = args.endTime;
|
|
var calendar = args.calendar;
|
|
|
|
this.mDisplayTimezone = args.displayTimezone;
|
|
|
|
this.onChangeCalendar(calendar);
|
|
|
|
// we need to enfore several layout constraints which can't be modelled
|
|
// with plain xul and css, at least as far as i know.
|
|
const kStylesheet = "chrome://calendar/content/sun-calendar-event-dialog.css";
|
|
for each(var stylesheet in document.styleSheets) {
|
|
if (stylesheet.href == kStylesheet) {
|
|
|
|
// make the dummy-spacer #1 [top] the same height as the timebar
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
stylesheet.insertRule(".attendee-spacer-top { height: "+timebar.boxObject.height+"px; }", 0);
|
|
|
|
// make the dummy-spacer #2 [bottom] the same height as the scrollbar
|
|
var scrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
|
|
stylesheet.insertRule(".attendee-spacer-bottom { height: "+scrollbar.boxObject.height+"px; }", 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
var zoom = document.getAnonymousElementByAttribute(this, "anonid", "zoom-menulist");
|
|
zoom.value = "100";
|
|
|
|
// Check if an all-day event has been passed in (to adapt endDate).
|
|
if (startTime.isDate) {
|
|
|
|
startTime = startTime.clone();
|
|
endTime = endTime.clone();
|
|
|
|
endTime.day -= 1;
|
|
|
|
// The date/timepicker uses jsDate internally. Because jsDate does
|
|
// not know the concept of dates we end up displaying times unequal
|
|
// to 00:00 for all-day events depending on local timezone setting.
|
|
// Calling normalize() recalculates that times to represent 00:00
|
|
// in local timezone.
|
|
endTime.normalize();
|
|
startTime.normalize();
|
|
|
|
// for all-day events we expand to 24hrs, set zoom-factor to 25%
|
|
// and disable the zoom-control.
|
|
this.force24Hours = true;
|
|
zoom.value = "400";
|
|
zoom.setAttribute("disabled","true");
|
|
this.zoomFactor = zoom.value;
|
|
}
|
|
|
|
this.loadDateTime(startTime,endTime);
|
|
this.propagateDateTime();
|
|
|
|
this.updateButtons();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onChangeCalendar">
|
|
<parameter name="calendar"/>
|
|
<body>
|
|
<![CDATA[
|
|
var args = window.arguments[0];
|
|
var organizer = args.organizer;
|
|
|
|
// set 'mIsReadOnly' if the calendar is read-only
|
|
if (calendar && calendar.readOnly)
|
|
this.mIsReadOnly = true;
|
|
|
|
// assume we're the organizer [in case that the calendar
|
|
// does not support the concept of identities].
|
|
this.mIsOrganizer = true;
|
|
|
|
//var organizerRow = document.getAnonymousElementByAttribute(this, "anonid", "event-organizer-row");
|
|
//organizerRow.setAttribute("collapsed","true");
|
|
|
|
try {
|
|
this.mUserID = "";
|
|
this.mOwnerID = "";
|
|
this.mOrganizerID = "";
|
|
|
|
var provider = calendar.QueryInterface(Components.interfaces.calIWcapCalendar);
|
|
this.mOwnerID = provider.ownerId;
|
|
this.mUserID = provider.session.userId;
|
|
this.mOrganizerID = ((organizer == null || organizer.id == null)
|
|
? this.mOwnerID // sensible default
|
|
: organizer.id);
|
|
|
|
// set 'mIsOrganizer' if the current calid originally scheduled this event.
|
|
this.mIsOrganizer = false;
|
|
if(this.mOwnerID == this.mOrganizerID)
|
|
this.mIsOrganizer = true;
|
|
|
|
// display the organizer [try to get the common name from the calid]
|
|
//TODO: either remove this row or enable this again -> organizerRow.removeAttribute("collapsed");
|
|
var organizer = document.getAnonymousElementByAttribute(this, "anonid", "event-organizer");
|
|
organizer.value = this.mOrganizerID;
|
|
var organizerCalendar = provider.session.getCalendarByCalId(this.mOrganizerID);
|
|
var props = organizerCalendar.getCalendarProperties("X-S1CS-CALPROPS-COMMON-NAME",{});
|
|
if(props.length > 0)
|
|
organizer.value = props[0];
|
|
}
|
|
catch(e) {}
|
|
|
|
if(this.mIsReadOnly || !this.mIsOrganizer) {
|
|
document.getAnonymousElementByAttribute(this, "anonid", "next-slot").setAttribute('disabled', 'true');
|
|
document.getAnonymousElementByAttribute(this, "anonid", "previous-slot").setAttribute('disabled', 'true');
|
|
}
|
|
|
|
var freebusy = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
|
|
freebusy.onChangeCalendar(calendar);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="updateButtons">
|
|
<body>
|
|
<![CDATA[
|
|
var previousButton = document.getAnonymousElementByAttribute(this, "anonid", "previous-slot");
|
|
if(this.mUndoStack.length > 0) {
|
|
previousButton.removeAttribute('disabled');
|
|
} else {
|
|
previousButton.setAttribute('disabled', 'true');
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onNextSlot">
|
|
<body>
|
|
<![CDATA[
|
|
// store the current setting in the undo-stack.
|
|
var currentSlot = {};
|
|
currentSlot.startTime = this.mStartDate;
|
|
currentSlot.endTime = this.mEndDate;
|
|
this.mUndoStack.push(currentSlot);
|
|
|
|
// ask the grid for the next possible timeslot.
|
|
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
|
|
var duration = this.mEndDate.subtractDate(this.mStartDate);
|
|
var start = grid.nextSlot();
|
|
var end = start.clone();
|
|
end.addDuration(duration);
|
|
if(start.isDate) {
|
|
end.day++;
|
|
end.normalize();
|
|
}
|
|
this.mStartDate = start.clone();
|
|
this.mEndDate = end.clone();
|
|
var endDate = this.mEndDate.clone();
|
|
|
|
// Check if an all-day event has been passed in (to adapt endDate).
|
|
if (this.mStartDate.isDate) {
|
|
this.mEndDate.day -= 1;
|
|
|
|
// The date/timepicker uses jsDate internally. Because jsDate does
|
|
// not know the concept of dates we end up displaying times unequal
|
|
// to 00:00 for all-day events depending on local timezone setting.
|
|
// Calling normalize() recalculates that times to represent 00:00
|
|
// in local timezone.
|
|
this.mEndDate.normalize();
|
|
this.mStartDate.normalize();
|
|
endDate.normalize();
|
|
}
|
|
this.mStartDate.makeImmutable();
|
|
this.mEndDate.makeImmutable();
|
|
endDate.makeImmutable();
|
|
|
|
this.propagateDateTime();
|
|
|
|
// scroll the grid/timebar such that the current time is visible
|
|
this.scrollToCurrentTime();
|
|
|
|
this.updateButtons();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onPreviousSlot">
|
|
<body>
|
|
<![CDATA[
|
|
var previousSlot = this.mUndoStack.pop();
|
|
if(!previousSlot)
|
|
return;
|
|
|
|
// in case the new starttime happens to be scheduled
|
|
// on a different day, we also need to update the
|
|
// complete freebusy informations and appropriate
|
|
// underlying arrays holding the information.
|
|
var refresh = previousSlot.startTime.day != this.mStartDate.day;
|
|
|
|
this.mStartDate = previousSlot.startTime.clone();
|
|
this.mEndDate = previousSlot.endTime.clone();
|
|
var endDate = this.mEndDate.clone();
|
|
|
|
this.propagateDateTime();
|
|
|
|
// scroll the grid/timebar such that the current time is visible
|
|
this.scrollToCurrentTime();
|
|
|
|
this.updateButtons();
|
|
|
|
if(refresh) {
|
|
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
|
|
grid.forceRefresh();
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onMinus">
|
|
<body>
|
|
<![CDATA[
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
var ratio = timebar.scroll;
|
|
ratio -= timebar.step;
|
|
if(ratio <= 0.0)
|
|
ratio = 0.0;
|
|
var scrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
|
|
var maxpos = scrollbar.getAttribute("maxpos");
|
|
scrollbar.setAttribute("curpos",ratio*maxpos);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onPlus">
|
|
<body>
|
|
<![CDATA[
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
var ratio = timebar.scroll;
|
|
ratio += timebar.step;
|
|
if(ratio >= 1.0)
|
|
ratio = 1.0;
|
|
var scrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
|
|
var maxpos = scrollbar.getAttribute("maxpos");
|
|
scrollbar.setAttribute("curpos",ratio*maxpos);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="scrollToCurrentTime">
|
|
<body>
|
|
<![CDATA[
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
var ratio = (this.mStartDate.hour-this.mStartHour) * timebar.step;
|
|
if(ratio <= 0.0)
|
|
ratio = 0.0;
|
|
if(ratio >= 1.0)
|
|
ratio = 1.0;
|
|
var scrollbar = document.getAnonymousElementByAttribute(this, "anonid", "horizontal-scrollbar");
|
|
var maxpos = scrollbar.getAttribute("maxpos");
|
|
scrollbar.setAttribute("curpos",ratio*maxpos);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
</implementation>
|
|
|
|
<handlers>
|
|
|
|
<handler event="DOMMouseScroll" phase="capturing">
|
|
<![CDATA[
|
|
// ignore mouse scrolling for now...
|
|
event.stopPropagation();
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="DOMAttrModified">
|
|
<![CDATA[
|
|
if(event.attrName == "width") {
|
|
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
|
|
selectionbar.setWidth(selectionbar.boxObject.width);
|
|
return;
|
|
}
|
|
|
|
// synchronize grid and attendee list
|
|
var target = event.originalTarget;
|
|
if(target.hasAttribute("anonid")) {
|
|
if(target.getAttribute("anonid") == "input") {
|
|
if(event.attrName == "focused") {
|
|
if(event.newValue == "true") {
|
|
var attendees = document.getAnonymousElementByAttribute(this, "anonid", "attendees-list");
|
|
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
|
|
if(grid.firstVisibleRow != attendees.firstVisibleRow)
|
|
grid.firstVisibleRow = attendees.firstVisibleRow;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var scrollbar = event.originalTarget.parentNode;
|
|
if (!scrollbar.hasAttribute("maxpos"))
|
|
return;
|
|
if(scrollbar.getAttribute("anonid") == "vertical-scrollbar") {
|
|
var attendees = document.getAnonymousElementByAttribute(this, "anonid", "attendees-list");
|
|
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
|
|
if(event.attrName == "curpos") {
|
|
var maxpos = scrollbar.getAttribute("maxpos");
|
|
attendees.ratio = event.newValue/maxpos;
|
|
}
|
|
grid.firstVisibleRow = attendees.firstVisibleRow;
|
|
}
|
|
else if(scrollbar.getAttribute("anonid") == "horizontal-scrollbar") {
|
|
if(event.attrName == "curpos") {
|
|
var maxpos = scrollbar.getAttribute("maxpos");
|
|
var ratio = event.newValue/maxpos;
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
|
|
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
|
|
timebar.scroll = ratio;
|
|
grid.scroll = ratio;
|
|
selectionbar.ratio = ratio;
|
|
}
|
|
}
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="timebar">
|
|
<![CDATA[
|
|
var selectionbar = document.getAnonymousElementByAttribute(this, "anonid", "selection-bar");
|
|
selectionbar.init(event.details,event.height);
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="timechange">
|
|
<![CDATA[
|
|
var start = event.startDate.getInTimezone(this.mStartTimezone);
|
|
var end = event.endDate.getInTimezone(this.mEndTimezone);
|
|
|
|
start.normalize();
|
|
end.normalize();
|
|
|
|
this.loadDateTime(start,end);
|
|
|
|
// fill the controls
|
|
this.updateDateTime();
|
|
|
|
// tell the timebar about the new start/enddate
|
|
var timebar = document.getAnonymousElementByAttribute(this, "anonid", "timebar");
|
|
timebar.startDate = this.mStartDate;
|
|
timebar.endDate = this.mEndDate;
|
|
timebar.refresh();
|
|
|
|
// tell the freebusy grid about the new start/enddate
|
|
var grid = document.getAnonymousElementByAttribute(this, "anonid", "freebusy-grid");
|
|
|
|
var refresh = (grid.startDate == null) ||
|
|
(grid.startDate.compare(this.mStartDate) != 0) ||
|
|
(grid.endDate == null) ||
|
|
(grid.endDate.compare(this.mEndDate) != 0);
|
|
grid.startDate = this.mStartDate;
|
|
grid.endDate = this.mEndDate;
|
|
if(refresh)
|
|
grid.forceRefresh();
|
|
]]>
|
|
</handler>
|
|
|
|
</handlers>
|
|
|
|
</binding>
|
|
|
|
</bindings>
|
|
|
|
|