[gen] Calendar field: allow to have several events at the same day via the concept of timeslots (ongoing work).

This commit is contained in:
Gaetan Delannay 2015-02-27 15:57:42 +01:00
parent 0c706c695e
commit da8f7a5bcd
10 changed files with 350 additions and 144 deletions

View file

@ -8,6 +8,24 @@ from appy.px import Px
from DateTime import DateTime
from BTrees.IOBTree import IOBTree
from persistent.list import PersistentList
from persistent import Persistent
# ------------------------------------------------------------------------------
class Timeslot:
'''A timeslot defines a time range within a single day'''
def __init__(self, id, start=None, end=None, name=None, eventTypes=None):
# A short, human-readable string identifier, unique among all timeslots
# for a given Calendar. Id "main" is reserved for the main timeslot that
# represents the whole day.
self.id = id
# The time range can be defined by p_start ~(i_hour, i_minute)~ and
# p_end (idem), or by a simple name, like "AM" or "PM".
self.start = start
self.end = end
self.name = name or id
# The event types (among all event types defined at the Calendar level)
# that can be assigned to this slot.
self.eventTypes = eventTypes # "None" means "all"
# ------------------------------------------------------------------------------
class Other:
@ -49,16 +67,33 @@ class Other:
info.name = eventNames[eventType]
res.append(info)
# ------------------------------------------------------------------------------
class Event(Persistent):
'''An event as will be stored in the database'''
def __init__(self, eventType, timeslot='main'):
self.eventType = eventType
self.timeslot = timeslot
def getName(self, allEventNames):
'''Gets the name for this event, that depends on it type and may include
the timeslot if not "main".'''
res = allEventNames[self.eventType]
if self.timeslot != 'main': res += ' ' + self.timeslot
return res
# ------------------------------------------------------------------------------
class Calendar(Field):
'''This field allows to produce an agenda (monthly view) and view/edit
events on it.'''
jsFiles = {'view': ('calendar.js',)}
DateTime = DateTime
Other = Other # Access to the Other class via the Calendar class
# Access to Calendar utility classes via the Calendar class
Timeslot = Timeslot
Other = Other
Event = Event
IterSub = IterSub
timelineBgColors = {'Fri': '#a6a6a6', 'Sat': '#c0c0c0', 'Sun': '#c0c0c0'}
timelineBgColors = {'Fri': '#dedede', 'Sat': '#c0c0c0', 'Sun': '#c0c0c0'}
# For timeline rendering, the row displaying month names
pxTimeLineMonths = Px('''
@ -120,7 +155,8 @@ class Calendar(Field):
<td></td>
</tr>
<!-- Other calendars -->
<tr for="other in field.IterSub(others)"
<x for="otherGroup in others">
<tr for="other in otherGroup"
var2="tlName=field.getTimelineName(other)">
<td class="tlLeft">::tlName</td>
<!-- A cell in this other calendar -->
@ -135,82 +171,21 @@ class Calendar(Field):
</x>
<td class="tlRight">::tlName</td>
</tr>
<!-- A separator between groups of other calendars -->
<tr if="not loop.otherGroup.last" height="5px">
<th colspan=":len(grid)+2"></th></tr>
</x>
<!-- Footer (repetition of months and days) -->
<x>:field.pxTimelineDayNumbers</x><x>:field.pxTimelineDayLetters</x>
<x>:field.pxTimeLineMonths</x>
</table>
<x>:field.pxTimelineLegend</x>''')
# Month view for a calendar
pxViewMonth = Px('''
<table cellpadding="0" cellspacing="0" width="100%" class="list"
style="font-size: 95%"
var="rowHeight=int(field.height/float(len(grid)))">
<!-- 1st row: names of days -->
<tr height="22px">
<th for="dayId in field.weekDays"
width="14%">:namesOfDays[dayId].short</th>
</tr>
<!-- The calendar in itself -->
<tr for="row in grid" valign="top" height=":rowHeight">
<x for="date in row"
var2="inRange=field.dateInRange(date, startDate, endDate);
cssClasses=field.getCellClass(zobj, date, render, today)">
<!-- Dump an empty cell if we are out of the supported date range -->
<td if="not inRange" class=":cssClasses"></td>
<!-- Dump a normal cell if we are in range -->
<td if="inRange"
var2="events=field.getEventsAt(zobj, date);
spansDays=field.hasEventsAt(zobj, date+1, events);
mayCreate=mayEdit and not events;
mayDelete=mayEdit and events and field.mayDelete(obj,events);
day=date.day();
dayString=date.strftime('%Y/%m/%d');
js=mayEdit and 'toggleVisibility(this, %s)' % q('img') \
or ''"
style=":date.isCurrentDay() and 'font-weight:bold' or \
'font-weight:normal'"
class=":cssClasses" onmouseover=":js" onmouseout=":js">
<span>:day</span>
<span if="day == 1">:_('month_%s_short' % date.aMonth())</span>
<!-- Icon for adding an event -->
<x if="mayCreate">
<img class="clickable" style="visibility:hidden"
var="info=field.getApplicableEventsTypesAt(zobj, date, \
eventTypes, preComputed, True)"
if="info and info.eventTypes" src=":url('plus')"
onclick=":'openEventPopup(%s, %s, %s, null, %s, %s)' % \
(q('new'), q(field.name), q(dayString), q(info.eventTypes),\
q(info.message))"/>
</x>
<!-- Icon for deleting an event -->
<img if="mayDelete" class="clickable" style="visibility:hidden"
src=":url('delete')"
onclick=":'openEventPopup(%s, %s, %s, %s, null, null)' % \
(q('del'), q(field.name), q(dayString), q(spansDays))"/>
<!-- A single event is allowed for the moment -->
<div if="events" var2="eventType=events[0].eventType">
<span style="color: grey">:allEventNames[eventType]</span>
</div>
<!-- Events from other calendars -->
<x if="others"
var2="otherEvents=field.getOtherEventsAt(zobj, date, \
others, allEventNames, render, colors)">
<div style=":'color: %s; font-style: italic' % event.color"
for="event in otherEvents">:event.name</div>
</x>
<!-- Additional info -->
<x var="info=field.getAdditionalInfoAt(zobj, date, preComputed)"
if="info">::info</x>
</td>
</x>
</tr>
</table>
<!-- Popup for creating a calendar event -->
<div if="eventTypes"
var="prefix='%s_newEvent' % field.name;
popupId=prefix + 'Popup'"
# Popup for adding an event in the month view
pxAddEvent = Px('''
<div var="prefix='%s_newEvent' % field.name;
popupId=prefix + 'Popup';
showTimeslots=len(field.timeslots) &gt; 2"
id=":popupId" class="popup" align="center">
<form id=":prefix + 'Form'" method="post">
<input type="hidden" name="fieldName" value=":field.name"/>
@ -222,16 +197,27 @@ class Calendar(Field):
<!-- Choose an event type -->
<div align="center" style="margin-bottom: 3px">:_('which_event')</div>
<select name="eventType">
<select name="eventType" style="margin-bottom: 10px">
<option value="">:_('choose_a_value')</option>
<option for="eventType in eventTypes"
value=":eventType">:allEventNames[eventType]</option>
</select><br/><br/>
<!--Span the event on several days -->
</select>
<!-- Choose a timeslot -->
<div if="showTimeslots" style="margin-bottom: 10px">
<span class="discreet">:_('timeslot')</span>
<select if="showTimeslots" name="timeslot">
<option value="main">:_('timeslot_main')</option>
<option for="timeslot in field.timeslots"
if="timeslot.id != 'main'">:timeslot.name</option>
</select>
</div>
<!-- Span the event on several days -->
<x if="not showTimeslots">
<div align="center" class="discreet" style="margin-bottom: 3px">
<span>:_('event_span')</span>
<input type="text" size="3" name="eventSpan"/>
</div>
</x>
<input type="button"
value=":_('object_save')"
onclick=":'triggerCalendarEvent(%s, %s, %s, %s, \
@ -241,9 +227,10 @@ class Calendar(Field):
value=":_('object_cancel')"
onclick=":'closePopup(%s)' % q(popupId)"/>
</form>
</div>
</div>''')
<!-- Popup for deleting a calendar event -->
# Popup for removing events in the month view
pxDelEvent = Px('''
<div var="prefix='%s_delEvent' % field.name;
popupId=prefix + 'Popup'"
id=":popupId" class="popup" align="center">
@ -253,6 +240,7 @@ class Calendar(Field):
<input type="hidden" name="name" value=":field.name"/>
<input type="hidden" name="action" value="process"/>
<input type="hidden" name="actionType" value="deleteEvent"/>
<input type="hidden" name="timeslot" value="main"/>
<input type="hidden" name="day"/>
<div align="center"
style="margin-bottom: 5px">:_('action_confirm')</div>
@ -275,6 +263,84 @@ class Calendar(Field):
</form>
</div>''')
# Month view for a calendar
pxViewMonth = Px('''
<table cellpadding="0" cellspacing="0" width="100%" class="list"
style="font-size: 95%"
var="rowHeight=int(field.height/float(len(grid)))">
<!-- 1st row: names of days -->
<tr height="22px">
<th for="dayId in field.weekDays"
width="14%">:namesOfDays[dayId].short</th>
</tr>
<!-- The calendar in itself -->
<tr for="row in grid" valign="top" height=":rowHeight">
<x for="date in row"
var2="inRange=field.dateInRange(date, startDate, endDate);
cssClasses=field.getCellClass(zobj, date, render, today)">
<!-- Dump an empty cell if we are out of the supported date range -->
<td if="not inRange" class=":cssClasses"></td>
<!-- Dump a normal cell if we are in range -->
<td if="inRange"
var2="events=field.getEventsAt(zobj, date);
single=events and (len(events) == 1);
spansDays=field.hasEventsAt(zobj, date+1, events);
mayCreate=mayEdit and not field.dayIsFull(date, events);
mayDelete=mayEdit and events and field.mayDelete(obj,events);
day=date.day();
dayString=date.strftime('%Y/%m/%d');
js=mayEdit and 'toggleVisibility(this, %s)' % q('img') \
or ''"
style=":date.isCurrentDay() and 'font-weight:bold' or \
'font-weight:normal'"
class=":cssClasses" onmouseover=":js" onmouseout=":js">
<span>:day</span>
<span if="day == 1">:_('month_%s_short' % date.aMonth())</span>
<!-- Icon for adding an event -->
<x if="mayCreate">
<img class="clickable" style="visibility:hidden"
var="info=field.getApplicableEventsTypesAt(zobj, date, \
eventTypes, preComputed, True)"
if="info and info.eventTypes" src=":url('plus')"
onclick=":'openEventPopup(%s, %s, %s, null, null, %s, %s)' % \
(q('new'), q(field.name), q(dayString), q(info.eventTypes),\
q(info.message))"/>
</x>
<!-- Icon for deleting event(s) -->
<img if="mayDelete" class="clickable" style="visibility:hidden"
src=":url(single and 'delete' or 'deleteMany')"
onclick=":'openEventPopup(%s, %s, %s, %s, %s, null, null)' % \
(q('del'), q(field.name), q(dayString), q('main'), q(spansDays))"/>
<!-- Events -->
<x if="events">
<div for="event in events" style="color: grey">
<x>:event.getName(allEventNames)</x>
<!-- Icon for delete this particular event -->
<img if="mayDelete and not single" class="clickable"
src=":url('delete')" style="visibility:hidden"
onclick=":'openEventPopup(%s, %s, %s, %s, null, null, null)'% \
(q('del'), q(field.name), q(dayString), q(event.timeslot))"/>
</div>
</x>
<!-- Events from other calendars -->
<x if="others"
var2="otherEvents=field.getOtherEventsAt(zobj, date, \
others, allEventNames, render, colors)">
<div style=":'color: %s; font-style: italic' % event.color"
for="event in otherEvents">:event.name</div>
</x>
<!-- Additional info -->
<x var="info=field.getAdditionalInfoAt(zobj, date, preComputed)"
if="info">::info</x>
</td>
</x>
</tr>
</table>
<!-- Popups for creating and deleting a calendar event -->
<x if="mayEdit and eventTypes">
<x>:field.pxAddEvent</x><x>:field.pxDelEvent</x></x>''')
pxView = pxCell = Px('''
<div var="defaultDate=field.getDefaultDate(zobj);
defaultDateMonth=defaultDate.strftime('%Y/%m');
@ -341,9 +407,9 @@ class Calendar(Field):
colspan=1, master=None, masterValue=None, focus=False,
mapping=None, label=None, maxEventLength=50, render='month',
others=None, timelineName=None, additionalInfo=None,
startDate=None, endDate=None, defaultDate=None, colors=None,
showUncolored=False, preCompute=None, applicableEvents=None,
view=None, xml=None, delete=True):
startDate=None, endDate=None, defaultDate=None, timeslots=None,
colors=None, showUncolored=False, preCompute=None,
applicableEvents=None, view=None, xml=None, delete=True):
Field.__init__(self, validator, (0,1), default, show, page, group,
layouts, move, False, True, False, specificReadPermission,
specificWritePermission, width, height, None, colspan,
@ -418,6 +484,14 @@ class Calendar(Field):
# date is specified, it will be 'now' at the moment the calendar is
# shown.
self.defaultDate = defaultDate
# "timeslots" are a way to define, within a single day, time ranges. It
# must be a list of Timeslot instances (see above). If you define
# timeslots, the first one must be the one representing the whole day
# and must have id "main".
if not timeslots: self.timeslots = [Timeslot('main')]
else:
self.timeslots = timeslots
self.checkTimeslots()
# "colors" must be or return a dict ~{s_eventType: s_color}~ giving a
# color to every event type defined in this calendar or in any calendar
# from "others". In a timeline, cells are too small to display
@ -442,6 +516,13 @@ class Calendar(Field):
# it must accept an event type as single arg.
self.delete = delete
def checkTimeslots(self):
'''Checks whether self.timeslots defines corect timeslots.'''
# The first timeslot must be the global one, named 'main'
if self.timeslots[0].id != 'main':
raise Exception('The first timeslot must have id "main" and is ' \
'the one representing the whole day.')
def getPreComputedInfo(self, obj, monthDayOne, grid):
'''Returns the result of calling self.preComputed, or None if no such
method exists.'''
@ -561,6 +642,15 @@ class Calendar(Field):
if callable(self.colors): return self.colors(obj)
return self.colors
def dayIsFull(self, date, events):
'''In the calendar full at p_date? Defined events at this p_date are in
p_events. We check here if the main timeslot is used or if all
others are used.'''
if not events: return
for e in events:
if e.timeslot == 'main': return True
return len(events) == len(self.timeslots)-1
def dateInRange(self, date, startDate, endDate):
'''Is p_date within the range (possibly) defined for this calendar by
p_startDate and p_endDate ?'''
@ -613,6 +703,28 @@ class Calendar(Field):
if not events: return
return events[0].eventType
def walkEvents(self, obj, callback):
'''Walks on p_obj, the calendar value for this field and calls
p_callback for every day containing events. The callback must accept
3 args: p_obj, the current day (as a DateTime instance) and the list
of events at that day (the database-stored PersistentList
instance). If the callback returns True we stop the walk.'''
obj = obj.o
if not hasattr(obj, self.name): return
# Browse years
years = getattr(obj, self.name)
if not years: return
for year in years.keys():
# Browse this year's months
months = years[year]
for month in months.keys():
# Browse this month's days
days = months[month]
for day in days.keys():
date = DateTime('%d/%d/%d UTC' % (year, month, day))
stop = callback(obj, date, days[day])
if stop: return
def getEventsByType(self, obj, eventType, minDate=None, maxDate=None,
sorted=True, groupSpanned=False):
'''Returns all the events of a given p_eventType. If p_eventType is
@ -669,7 +781,7 @@ class Calendar(Field):
# Filter unwanted events
if eventType and (event.eventType != eventType):
continue
# We have found a event.
# We have found a event
date = DateTime('%d/%d/%d UTC' % (year, month, day))
if groupSpanned:
singleRes = [date, None, event]
@ -699,9 +811,9 @@ class Calendar(Field):
def hasEventsAt(self, obj, date, otherEvents):
'''Returns True if, at p_date, an event is found of the same type as
p_otherEvents.'''
if not otherEvents: return False
if not otherEvents: return
events = self.getEventsAt(obj, date)
if not events: return False
if not events: return
return events[0].eventType == otherEvents[0].eventType
def getOtherEventsAt(self, obj, date, others, eventNames, render, colors):
@ -756,7 +868,7 @@ class Calendar(Field):
'''Get the end date for this calendar if defined'''
if self.endDate:
d = self.endDate(obj.appy())
# Return the end date without hour, in UTC.
# Return the end date without hour, in UTC
return DateTime('%d/%d/%d UTC' % (d.year(), d.month(), d.day()))
def getDefaultDate(self, obj):
@ -767,14 +879,14 @@ class Calendar(Field):
else:
return DateTime() # Now
def createEvent(self, obj, date, eventType=None, eventSpan=None,
def createEvent(self, obj, date, timeslot, eventType=None, eventSpan=None,
handleEventSpan=True):
'''Create a new event in the calendar, at some p_date (day).
If p_eventType is given, it is used; else, rq['eventType'] is used.
If p_handleEventSpan is True, we will use p_eventSpan (or
rq["eventSpan"] if p_eventSpan is not given) and also
create the same event for successive days.'''
obj = obj.o # Ensure p_obj is not a wrapper.
obj = obj.o # Ensure p_obj is not a wrapper
rq = obj.REQUEST
# Get values from parameters
if not eventType: eventType = rq['eventType']
@ -802,16 +914,27 @@ class Calendar(Field):
events = daysDict[day]
else:
daysDict[day] = events = PersistentList()
# Create and store the event, excepted if an event already exists
if not events:
event = Object(eventType=eventType)
events.append(event)
# Return an error if the creation cannot occur
for e in events:
if e.timeslot == timeslot:
return 'An event for this timeslot already exist'
elif e.timeslot == 'main':
return 'No more place for adding this event'
if events and (timeslot == 'main'):
return 'No more place (2) for adding this event'
# Create and store the event
events.append(Event(eventType, timeslot))
# Sort events in the order of timeslots
timeslots = [timeslot.id for timeslot in self.timeslots]
if len(events) > 1:
events.data.sort(key=lambda e: timeslots.index(e.timeslot))
events._p_changed = 1
# Span the event on the successive days if required
if handleEventSpan and eventSpan:
if handleEventSpan and eventSpan and (timeslot != 'main'):
nbOfDays = min(int(eventSpan), self.maxEventLength)
for i in range(nbOfDays):
date = date + 1
self.createEvent(obj, date, handleEventSpan=False)
self.createEvent(obj, date, timeslot, handleEventSpan=False)
def mayDelete(self, obj, events):
'''May the user delete p_events?'''
@ -819,15 +942,19 @@ class Calendar(Field):
if callable(self.delete): return self.delete(obj, events[0].eventType)
return True
def deleteEvent(self, obj, date, handleEventSpan=True):
'''Deletes an event. It actually deletes all events at p_date.
If p_handleEventSpan is True, we will use rq["deleteNext"] to
delete successive events, too.'''
obj = obj.o # Ensure p_obj is not a wrapper.
def deleteEvent(self, obj, date, timeslot, handleEventSpan=True):
'''Deletes an event. If t_timeslot is "main", it deletes all events at
p_date, be there a single event on the main timeslot or several
events on other timeslots. Else, it only deletes the event at
p_timeslot. If p_handleEventSpan is True, we will use
rq["deleteNext"] to delete successive events, too.'''
obj = obj.o # Ensure p_obj is not a wrapper
if not self.getEventsAt(obj, date): return
daysDict = getattr(obj, self.name)[date.year()][date.month()]
# Remember events, in case we must delete similar ones for next days.
events = self.getEventsAt(obj, date)
if timeslot == 'main':
# Delete all events; delete them also in the following days when
# relevant.
del daysDict[date.day()]
rq = obj.REQUEST
if handleEventSpan and rq.has_key('deleteNext') and \
@ -835,9 +962,18 @@ class Calendar(Field):
while True:
date = date + 1
if self.hasEventsAt(obj, date, events):
self.deleteEvent(obj, date, handleEventSpan=False)
self.deleteEvent(obj, date, timeslot,
handleEventSpan=False)
else:
break
else:
# Delete the event at p_timeslot
i = len(events) - 1
while i >= 0:
if events[i].timeslot == timeslot:
del events[i]
break
i -= 1
def process(self, obj):
'''Processes an action coming from the calendar widget, ie, the creation
@ -846,11 +982,13 @@ class Calendar(Field):
action = rq['actionType']
# Security check
obj.mayEdit(self.writePermission, raiseError=True)
# Get the date for this action
# Get the date and timeslot for this action
date = DateTime(rq['day'])
timeslot = rq.get('timeslot', 'main')
if action == 'createEvent':
return self.createEvent(obj, DateTime(rq['day']))
return self.createEvent(obj, date, timeslot)
elif action == 'deleteEvent':
return self.deleteEvent(obj, DateTime(rq['day']))
return self.deleteEvent(obj, date, timeslot)
def getColumnStyle(self, obj, date, render, today):
'''What style(s) must apply to the table column representing p_date

View file

@ -715,6 +715,14 @@ msgstr ""
msgid "del_next_events"
msgstr ""
#. Default: "Timeslot"
msgid "timeslot"
msgstr ""
#. Default: "All day"
msgid "timeslot_main"
msgstr ""
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr ""

View file

@ -715,6 +715,14 @@ msgstr ""
msgid "del_next_events"
msgstr ""
#. Default: "Timeslot"
msgid "timeslot"
msgstr ""
#. Default: "All day"
msgid "timeslot_main"
msgstr ""
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr ""

View file

@ -715,6 +715,14 @@ msgstr ""
msgid "del_next_events"
msgstr ""
#. Default: "Timeslot"
msgid "timeslot"
msgstr ""
#. Default: "All day"
msgid "timeslot_main"
msgstr ""
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr ""

View file

@ -716,6 +716,14 @@ msgstr "Extend the event on the following number of days (leave blank to create
msgid "del_next_events"
msgstr "Also delete successive events of the same type."
#. Default: "Timeslot"
msgid "timeslot"
msgstr "Timeslot"
#. Default: "All day"
msgid "timeslot_main"
msgstr "All day"
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr "Inserted by ${userName}"

View file

@ -715,6 +715,14 @@ msgstr ""
msgid "del_next_events"
msgstr ""
#. Default: "Timeslot"
msgid "timeslot"
msgstr ""
#. Default: "All day"
msgid "timeslot_main"
msgstr ""
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr ""

View file

@ -716,6 +716,14 @@ msgstr "Étendre l'événement sur le nombre de jours suivants (laissez vide pou
msgid "del_next_events"
msgstr "Supprimer aussi les événements successifs de même type"
#. Default: "Timeslot"
msgid "timeslot"
msgstr "Plage horaire"
#. Default: "All day"
msgid "timeslot_main"
msgstr "Toute la journée"
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr "Inséré par ${userName}"

View file

@ -715,6 +715,14 @@ msgstr ""
msgid "del_next_events"
msgstr ""
#. Default: "Timeslot"
msgid "timeslot"
msgstr ""
#. Default: "All day"
msgid "timeslot_main"
msgstr ""
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr ""

View file

@ -715,6 +715,14 @@ msgstr "Het event uitbreiden naar de volgende dagen (leeg laten om een event aan
msgid "del_next_events"
msgstr "Verwijder ook alle opeenvolgende events van hetzelfde type"
#. Default: "Timeslot"
msgid "timeslot"
msgstr ""
#. Default: "All day"
msgid "timeslot_main"
msgstr ""
#. Default: "Inserted by ${userName}"
msgid "history_insert"
msgstr "Ingevuld door ${userName}"

View file

@ -14,26 +14,28 @@ function askCalendar(hookId, objectUrl, render, fieldName, month) {
askAjaxChunk(hookId, 'GET', objectUrl, fieldName+':pxView', params);
}
function openEventPopup(action, fieldName, day, spansDays,
function openEventPopup(action, fieldName, day, timeslot, spansDays,
applicableEventTypes, message) {
/* Opens the popup for creating (or deleting, depending on p_action) a
calendar event at some p_day. When action is "del", we need to know
(from p_spansDays) if the event spans more days, in order to propose a
checkbox allowing to delete events for those successive days. When action
is "new", a possibly restricted list of applicable event types for this
day is given in p_applicableEventTypes; p_message contains an optional
message explaining why not applicable types are not applicable. */
calendar event at some p_day. When action is "del", we need to know the
p_timeslot where the event is assigned and if the event spans more days
(from p_spansDays), in order to propose a checkbox allowing to delete
events for those successive days. When action is "new", a possibly
restricted list of applicable event types for this day is given in
p_applicableEventTypes; p_message contains an optional message explaining
why not applicable types are not applicable. */
var prefix = fieldName + '_' + action + 'Event';
var f = document.getElementById(prefix + 'Form');
f.day.value = day;
if (action == 'del') {
// Show or hide the checkbox for deleting the event for successive days.
f.timeslot.value = timeslot;
// Show or hide the checkbox for deleting the event for successive days
var elem = document.getElementById(prefix + 'DelNextEvent');
var cb = elem.getElementsByTagName('input');
cb[0].checked = false;
cb[1].value = 'False';
if (spansDays == 'True') { elem.style.display = 'block' }
else { elem.style.display = 'none' }
if (spansDays == 'True') elem.style.display = 'block';
else elem.style.display = 'none';
}
else if (action == 'new') {
// First: reinitialise input fields
@ -42,8 +44,8 @@ function openEventPopup(action, fieldName, day, spansDays,
for (var i=0; i < allOptions.length; i++) {
allOptions[i].selected = false;
}
f.eventSpan.style.background = '';
// Among all event types, show applicable ones and hide the others.
if (f.eventSpan) f.eventSpan.style.background = '';
// Among all event types, show applicable ones and hide the others
var applicable = applicableEventTypes.split(',');
var applicableDict = {};
for (var i=0; i < applicable.length; i++) {
@ -76,6 +78,7 @@ function triggerCalendarEvent(action, hookId, fieldName, objectUrl,
f.eventType.style.background = wrongTextInput;
return;
}
if (f.eventSpan) {
// Check that eventSpan is empty or contains a valid number
var spanNumber = f.eventSpan.value.replace(' ', '');
if (spanNumber) {
@ -86,6 +89,7 @@ function triggerCalendarEvent(action, hookId, fieldName, objectUrl,
}
}
}
}
var elems = f.elements;
var params = {};
// Put form elements into "params"