[gen] Bugfix for IE in master/slave relationships. Calendar field: added 2 params: 'preCompute' allows to specify a method that is called once every time a month is shown and whose result can be accessed by other methods; 'applicableEvents' allows to specify, for every day, a list of applicable events which can be a sub-set of all aplicable events (or event nothing).
This commit is contained in:
parent
caca61516f
commit
1505264887
|
@ -8,7 +8,8 @@ from persistent.list import PersistentList
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
class Calendar(Type):
|
class Calendar(Type):
|
||||||
'''This field allows to produce an agenda and view/edit events on it.'''
|
'''This field allows to produce an agenda (monthly view) and view/edit
|
||||||
|
events on it.'''
|
||||||
jsFiles = {'view': ('widgets/calendar.js',)}
|
jsFiles = {'view': ('widgets/calendar.js',)}
|
||||||
|
|
||||||
def __init__(self, eventTypes, eventNameMethod=None, validator=None,
|
def __init__(self, eventTypes, eventNameMethod=None, validator=None,
|
||||||
|
@ -18,7 +19,8 @@ class Calendar(Type):
|
||||||
colspan=1, master=None, masterValue=None, focus=False,
|
colspan=1, master=None, masterValue=None, focus=False,
|
||||||
mapping=None, label=None, maxEventLength=50,
|
mapping=None, label=None, maxEventLength=50,
|
||||||
otherCalendars=None, additionalInfo=None, startDate=None,
|
otherCalendars=None, additionalInfo=None, startDate=None,
|
||||||
endDate=None, defaultDate=None):
|
endDate=None, defaultDate=None, preCompute=None,
|
||||||
|
applicableEvents=None):
|
||||||
Type.__init__(self, validator, (0,1), None, default, False, False,
|
Type.__init__(self, validator, (0,1), None, default, False, False,
|
||||||
show, page, group, layouts, move, False, False,
|
show, page, group, layouts, move, False, False,
|
||||||
specificReadPermission, specificWritePermission,
|
specificReadPermission, specificWritePermission,
|
||||||
|
@ -40,8 +42,23 @@ class Calendar(Type):
|
||||||
# It is not possible to create events that span more days than
|
# It is not possible to create events that span more days than
|
||||||
# maxEventLength.
|
# maxEventLength.
|
||||||
self.maxEventLength = maxEventLength
|
self.maxEventLength = maxEventLength
|
||||||
# If a method is specified in the following parameters, it must return
|
# When displaying a given month for this agenda, one may want to
|
||||||
# a list of calendars whose events must be shown within this agenda.
|
# pre-compute, once for the whole month, some information that will then
|
||||||
|
# be given as arg for other methods specified in subsequent parameters.
|
||||||
|
# This mechanism exists for performance reasons, to avoid recomputing
|
||||||
|
# this global information several times. If you specify a method in
|
||||||
|
# p_preCompute, it will be called every time a given month is shown, and
|
||||||
|
# will receive 2 args: the first day of the currently shown month (as a
|
||||||
|
# DateTime instance) and the grid of all shown dates (as a list of lists
|
||||||
|
# of DateTime instances, one sub-list by row in the month view). This
|
||||||
|
# grid may hold a little more than dates of the current month.
|
||||||
|
# Subsequently, the return of your method will be given as arg to other
|
||||||
|
# methods that you may specify as args of other parameters of this
|
||||||
|
# Calendar class (see comments below).
|
||||||
|
self.preCompute = preCompute
|
||||||
|
# If a method is specified in the following parameters, it must accept
|
||||||
|
# a single arg (the result of self.preCompute) and must return a list of
|
||||||
|
# calendars whose events must be shown within this agenda.
|
||||||
# Every element in this list must be a sub-list [object, name, color]
|
# Every element in this list must be a sub-list [object, name, color]
|
||||||
# (not a tuple):
|
# (not a tuple):
|
||||||
# - object must refer to the other object on which the other calendar
|
# - object must refer to the other object on which the other calendar
|
||||||
|
@ -52,11 +69,11 @@ class Calendar(Type):
|
||||||
# leading "#" when relevant) into which events of the calendar must
|
# leading "#" when relevant) into which events of the calendar must
|
||||||
# appear.
|
# appear.
|
||||||
self.otherCalendars = otherCalendars
|
self.otherCalendars = otherCalendars
|
||||||
# One may want to add custom information in the calendar. When a method
|
# One may want to add, day by day, custom information in the calendar.
|
||||||
# is given in p_additionalInfo, for every cell of the month view, this
|
# When a method is given in p_additionalInfo, for every cell of the
|
||||||
# method will be called with a single arg (the cell's date). The
|
# month view, this method will be called with 2 args: the cell's date
|
||||||
# method's result (a string that can hold text or a chunk of XHTML) will
|
# and the result of self.preCompute. The method's result (a string that
|
||||||
# be inserted in the cell.
|
# can hold text or a chunk of XHTML) will be inserted in the cell.
|
||||||
self.additionalInfo = additionalInfo
|
self.additionalInfo = additionalInfo
|
||||||
# One may limit event encoding and viewing to a limited period of time,
|
# One may limit event encoding and viewing to a limited period of time,
|
||||||
# via p_startDate and p_endDate. Those parameters, if given, must hold
|
# via p_startDate and p_endDate. Those parameters, if given, must hold
|
||||||
|
@ -71,6 +88,23 @@ class Calendar(Type):
|
||||||
# date is specified, it will be 'now' at the moment the calendar is
|
# date is specified, it will be 'now' at the moment the calendar is
|
||||||
# shown.
|
# shown.
|
||||||
self.defaultDate = defaultDate
|
self.defaultDate = defaultDate
|
||||||
|
# For a specific day, all event types may not be applicable. If this is
|
||||||
|
# the case, one may specify here a method that defines, for a given day,
|
||||||
|
# a sub-set of all event types. This method must accept 3 args: the day
|
||||||
|
# in question (as a DateTime instance), the list of all event types,
|
||||||
|
# which is a copy of the (possibly computed) self.eventTypes) and
|
||||||
|
# the result of calling self.preCompute. The method must modify
|
||||||
|
# the 2nd arg and remove from it potentially not applicable events.
|
||||||
|
# This method can also return a message, that will be shown to the user
|
||||||
|
# for explaining him why he can, for this day, only create events of a
|
||||||
|
# sub-set of the possible event types (or even no event at all).
|
||||||
|
self.applicableEvents = applicableEvents
|
||||||
|
|
||||||
|
def getPreComputedInfo(self, obj, monthDayOne, grid):
|
||||||
|
'''Returns the result of calling self.preComputed, or None if no such
|
||||||
|
method exists.'''
|
||||||
|
if self.preCompute:
|
||||||
|
return self.preCompute(obj.appy(), monthDayOne, grid)
|
||||||
|
|
||||||
def getSiblingMonth(self, month, prevNext):
|
def getSiblingMonth(self, month, prevNext):
|
||||||
'''Gets the next or previous month (depending of p_prevNext) relative
|
'''Gets the next or previous month (depending of p_prevNext) relative
|
||||||
|
@ -127,22 +161,22 @@ class Calendar(Type):
|
||||||
currentDay = currentDay + 1
|
currentDay = currentDay + 1
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getOtherCalendars(self, obj):
|
def getOtherCalendars(self, obj, preComputed):
|
||||||
'''Returns the list of other calendars whose events must also be shown
|
'''Returns the list of other calendars whose events must also be shown
|
||||||
on this calendar.'''
|
on this calendar.'''
|
||||||
if self.otherCalendars:
|
if self.otherCalendars:
|
||||||
res = self.callMethod(obj, self.otherCalendars)
|
res = self.callMethod(obj, self.otherCalendars, preComputed)
|
||||||
# Replace field names with field objects
|
# Replace field names with field objects
|
||||||
for i in range(len(res)):
|
for i in range(len(res)):
|
||||||
res[i][1] = res[i][0].getField(res[i][1])
|
res[i][1] = res[i][0].getField(res[i][1])
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def getAdditionalInfoAt(self, obj, date):
|
def getAdditionalInfoAt(self, obj, date, preComputed):
|
||||||
'''If the user has specified a method in self.additionalInfo, we call
|
'''If the user has specified a method in self.additionalInfo, we call
|
||||||
it for displaying this additional info in the calendar, at some
|
it for displaying this additional info in the calendar, at some
|
||||||
p_date.'''
|
p_date.'''
|
||||||
if not self.additionalInfo: return
|
if not self.additionalInfo: return
|
||||||
return self.additionalInfo(obj.appy(), date)
|
return self.additionalInfo(obj.appy(), date, preComputed)
|
||||||
|
|
||||||
def getEventTypes(self, obj):
|
def getEventTypes(self, obj):
|
||||||
'''Returns the (dynamic or static) event types as defined in
|
'''Returns the (dynamic or static) event types as defined in
|
||||||
|
@ -152,6 +186,32 @@ class Calendar(Type):
|
||||||
else:
|
else:
|
||||||
return self.eventTypes
|
return self.eventTypes
|
||||||
|
|
||||||
|
def getApplicableEventsTypesAt(self, obj, date, allEventTypes, preComputed,
|
||||||
|
forBrowser=False):
|
||||||
|
'''Returns the event types that are applicable at a given p_date. More
|
||||||
|
precisely, it returns an object with 2 attributes:
|
||||||
|
* "events" is the list of applicable event types;
|
||||||
|
* "message", not empty if some event types are not applicable,
|
||||||
|
contains a message explaining those event types are
|
||||||
|
not applicable.
|
||||||
|
'''
|
||||||
|
if not self.applicableEvents:
|
||||||
|
eventTypes = allEventTypes
|
||||||
|
message = None
|
||||||
|
else:
|
||||||
|
eventTypes = allEventTypes[:]
|
||||||
|
message = self.applicableEvents(obj.appy(), date, eventTypes,
|
||||||
|
preComputed)
|
||||||
|
res = Object(eventTypes=eventTypes, message=message)
|
||||||
|
if forBrowser:
|
||||||
|
res.eventTypes = ','.join(res.eventTypes)
|
||||||
|
if not res.message:
|
||||||
|
res.message = ''
|
||||||
|
else:
|
||||||
|
res.message = obj.formatText(res.message, format='js')
|
||||||
|
return res.__dict__
|
||||||
|
return res
|
||||||
|
|
||||||
def getEventsAt(self, obj, date, asDict=True):
|
def getEventsAt(self, obj, date, asDict=True):
|
||||||
'''Returns the list of events that exist at some p_date (=day).'''
|
'''Returns the list of events that exist at some p_date (=day).'''
|
||||||
obj = obj.o # Ensure p_obj is not a wrapper.
|
obj = obj.o # Ensure p_obj is not a wrapper.
|
||||||
|
|
|
@ -217,12 +217,14 @@ function getSlaveInfo(slave, infoType) {
|
||||||
|
|
||||||
function getMasterValues(master) {
|
function getMasterValues(master) {
|
||||||
// Returns the list of values that p_master currently has.
|
// Returns the list of values that p_master currently has.
|
||||||
|
var res = null;
|
||||||
if ((master.tagName == 'INPUT') && (master.type != 'checkbox')) {
|
if ((master.tagName == 'INPUT') && (master.type != 'checkbox')) {
|
||||||
res = master.value;
|
res = master.value;
|
||||||
if ((res[0] == '(') || (res[0] == '[')) {
|
if ((res.charAt(0) == '(') || (res.charAt(0) == '[')) {
|
||||||
// There are multiple values, split it
|
// There are multiple values, split it
|
||||||
values = res.substring(1, res.length-1).split(',');
|
values = res.substring(1, res.length-1).split(',');
|
||||||
res = [];
|
res = [];
|
||||||
|
var v = null;
|
||||||
for (var i=0; i < values.length; i++){
|
for (var i=0; i < values.length; i++){
|
||||||
v = values[i].replace(' ', '');
|
v = values[i].replace(' ', '');
|
||||||
res.push(v.substring(1, v.length-1));
|
res.push(v.substring(1, v.length-1));
|
||||||
|
|
|
@ -4,15 +4,20 @@ function askMonthView(hookId, objectUrl, fieldName, month) {
|
||||||
askAjaxChunk(hookId,'GET',objectUrl,'widgets/calendar','viewMonth', params);
|
askAjaxChunk(hookId,'GET',objectUrl,'widgets/calendar','viewMonth', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEventPopup(action, fieldName, day, spansDays) {
|
function openEventPopup(action, fieldName, day, spansDays,
|
||||||
|
applicableEventTypes, message) {
|
||||||
/* Opens the popup for creating (or deleting, depending on p_action) a
|
/* 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
|
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
|
(from p_spansDays) if the event spans more days, in order to propose a
|
||||||
checkbox allowing to delete events for those successive days. */
|
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 prefix = fieldName + '_' + action + 'Event';
|
||||||
var f = document.getElementById(prefix + 'Form');
|
var f = document.getElementById(prefix + 'Form');
|
||||||
f.day.value = day;
|
f.day.value = day;
|
||||||
if (action == 'del') {
|
if (action == 'del') {
|
||||||
|
// Show or hide the checkbox for deleting the event for successive days.
|
||||||
var elem = document.getElementById(prefix + 'DelNextEvent');
|
var elem = document.getElementById(prefix + 'DelNextEvent');
|
||||||
var cb = elem.getElementsByTagName('input');
|
var cb = elem.getElementsByTagName('input');
|
||||||
cb[0].checked = false;
|
cb[0].checked = false;
|
||||||
|
@ -20,6 +25,32 @@ function openEventPopup(action, fieldName, day, spansDays) {
|
||||||
if (spansDays == 'True') { elem.style.display = 'block' }
|
if (spansDays == 'True') { elem.style.display = 'block' }
|
||||||
else { elem.style.display = 'none' }
|
else { elem.style.display = 'none' }
|
||||||
}
|
}
|
||||||
|
else if (action == 'new') {
|
||||||
|
// First: reinitialise input fields
|
||||||
|
f.eventType.style.background = '';
|
||||||
|
var allOptions = f.eventType.options;
|
||||||
|
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.
|
||||||
|
var applicable = applicableEventTypes.split(',');
|
||||||
|
var applicableDict = {};
|
||||||
|
for (var i=0; i < applicable.length; i++) {
|
||||||
|
applicableDict[applicable[i]] = true;
|
||||||
|
}
|
||||||
|
for (var i=0; i < allOptions.length; i++) {
|
||||||
|
if (!allOptions[i].value) continue;
|
||||||
|
if (allOptions[i].value in applicableDict) {
|
||||||
|
allOptions[i].disabled = false;
|
||||||
|
allOptions[i].title = '';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
allOptions[i].disabled = true;
|
||||||
|
allOptions[i].title = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
openPopup(prefix + 'Popup');
|
openPopup(prefix + 'Popup');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +60,11 @@ function triggerCalendarEvent(action, hookId, fieldName, objectUrl, maxEventLeng
|
||||||
var prefix = fieldName + '_' + action + 'Event';
|
var prefix = fieldName + '_' + action + 'Event';
|
||||||
var f = document.getElementById(prefix + 'Form');
|
var f = document.getElementById(prefix + 'Form');
|
||||||
if (action == 'new') {
|
if (action == 'new') {
|
||||||
|
// Check that an event span has been specified
|
||||||
|
if (f.eventType.selectedIndex == 0) {
|
||||||
|
f.eventType.style.background = wrongTextInput;
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Check that eventSpan is empty or contains a valid number
|
// Check that eventSpan is empty or contains a valid number
|
||||||
var spanNumber = f.eventSpan.value.replace(' ', '');
|
var spanNumber = f.eventSpan.value.replace(' ', '');
|
||||||
if (spanNumber) {
|
if (spanNumber) {
|
||||||
|
|
|
@ -5,9 +5,11 @@
|
||||||
month request/month;
|
month request/month;
|
||||||
monthDayOne python: DateTime('%s/01' % month);
|
monthDayOne python: DateTime('%s/01' % month);
|
||||||
today python: DateTime('00:00');
|
today python: DateTime('00:00');
|
||||||
|
grid python: contextObj.callField(fieldName, 'getMonthGrid', month);
|
||||||
|
allEventTypes python: contextObj.callField(fieldName, 'getEventTypes', contextObj);
|
||||||
|
preComputed python: contextObj.callField(fieldName, 'getPreComputedInfo', contextObj, monthDayOne, grid);
|
||||||
defaultDate python: contextObj.callField(fieldName, 'getDefaultDate', contextObj);
|
defaultDate python: contextObj.callField(fieldName, 'getDefaultDate', contextObj);
|
||||||
defaultDateMonth python: defaultDate.strftime('%Y/%m');
|
defaultDateMonth python: defaultDate.strftime('%Y/%m');
|
||||||
grid python: contextObj.callField(fieldName, 'getMonthGrid', month);
|
|
||||||
previousMonth python: contextObj.callField(fieldName, 'getSiblingMonth', month, 'previous');
|
previousMonth python: contextObj.callField(fieldName, 'getSiblingMonth', month, 'previous');
|
||||||
nextMonth python: contextObj.callField(fieldName, 'getSiblingMonth', month, 'next');
|
nextMonth python: contextObj.callField(fieldName, 'getSiblingMonth', month, 'next');
|
||||||
widget python: contextObj.getAppyType(fieldName, asDict=True);
|
widget python: contextObj.getAppyType(fieldName, asDict=True);
|
||||||
|
@ -15,7 +17,7 @@
|
||||||
objUrl contextObj/absolute_url;
|
objUrl contextObj/absolute_url;
|
||||||
startDate python: contextObj.callField(fieldName, 'getStartDate', contextObj);
|
startDate python: contextObj.callField(fieldName, 'getStartDate', contextObj);
|
||||||
endDate python: contextObj.callField(fieldName, 'getEndDate', contextObj);
|
endDate python: contextObj.callField(fieldName, 'getEndDate', contextObj);
|
||||||
otherCalendars python: contextObj.callField(fieldName, 'getOtherCalendars', contextObj);"
|
otherCalendars python: contextObj.callField(fieldName, 'getOtherCalendars', contextObj, preComputed);"
|
||||||
tal:attributes="id ajaxHookId">
|
tal:attributes="id ajaxHookId">
|
||||||
|
|
||||||
<script type="text/javascript"
|
<script type="text/javascript"
|
||||||
|
@ -82,13 +84,17 @@
|
||||||
<span tal:condition="python: day == 1"
|
<span tal:condition="python: day == 1"
|
||||||
tal:content="python: _('month_%s_short' % date.aMonth())"></span>
|
tal:content="python: _('month_%s_short' % date.aMonth())"></span>
|
||||||
<tal:comment replace="nothing">Icon for adding an event</tal:comment>
|
<tal:comment replace="nothing">Icon for adding an event</tal:comment>
|
||||||
<img tal:condition="mayCreate" style="visibility:hidden; cursor:pointer"
|
<tal:create condition="mayCreate">
|
||||||
|
<img style="visibility:hidden; cursor:pointer"
|
||||||
|
tal:define="info python: contextObj.callField(fieldName, 'getApplicableEventsTypesAt', contextObj, date, allEventTypes, preComputed, True)"
|
||||||
|
tal:condition="info/eventTypes"
|
||||||
tal:attributes="src string: $appUrl/ui/plus.png;
|
tal:attributes="src string: $appUrl/ui/plus.png;
|
||||||
onclick python: 'openEventPopup(\'new\',\'%s\',\'%s\')' % (fieldName, dayString)"/>
|
onclick python: 'openEventPopup(\'new\',\'%s\',\'%s\',null,\'%s\',\'%s\')' % (fieldName, dayString, info['eventTypes'], info['message'])"/>
|
||||||
|
</tal:create>
|
||||||
<tal:comment replace="nothing">Icon for deleting an event</tal:comment>
|
<tal:comment replace="nothing">Icon for deleting an event</tal:comment>
|
||||||
<img tal:condition="mayDelete" style="visibility:hidden; cursor:pointer"
|
<img tal:condition="mayDelete" style="visibility:hidden; cursor:pointer"
|
||||||
tal:attributes="src string: $appUrl/ui/delete.png;
|
tal:attributes="src string: $appUrl/ui/delete.png;
|
||||||
onclick python: 'openEventPopup(\'del\',\'%s\',\'%s\',\'%s\')' % (fieldName, dayString, str(spansDays))"/>
|
onclick python: 'openEventPopup(\'del\',\'%s\',\'%s\',\'%s\',null,null)' % (fieldName, dayString, str(spansDays))"/>
|
||||||
<tal:events condition="events">
|
<tal:events condition="events">
|
||||||
<tal:comment replace="nothing">A single event is allowed for the moment</tal:comment>
|
<tal:comment replace="nothing">A single event is allowed for the moment</tal:comment>
|
||||||
<div tal:define="eventType python: events[0]['eventType']">
|
<div tal:define="eventType python: events[0]['eventType']">
|
||||||
|
@ -105,7 +111,7 @@
|
||||||
</tal:e>
|
</tal:e>
|
||||||
</tal:others>
|
</tal:others>
|
||||||
<tal:comment replace="nothing">Additional info</tal:comment>
|
<tal:comment replace="nothing">Additional info</tal:comment>
|
||||||
<tal:info define="info python: contextObj.callField(fieldName,'getAdditionalInfoAt', contextObj, date)"
|
<tal:info define="info python: contextObj.callField(fieldName,'getAdditionalInfoAt', contextObj, date, preComputed)"
|
||||||
condition="info" replace="structure info"/>
|
condition="info" replace="structure info"/>
|
||||||
</tal:day>
|
</tal:day>
|
||||||
</td>
|
</td>
|
||||||
|
@ -127,9 +133,11 @@
|
||||||
<input type="hidden" name="actionType" value="createEvent"/>
|
<input type="hidden" name="actionType" value="createEvent"/>
|
||||||
<input type="hidden" name="day"/>
|
<input type="hidden" name="day"/>
|
||||||
|
|
||||||
|
<tal:comment replace="nothing">Choose an event type</tal:comment>
|
||||||
<div align="center" style="margin-bottom: 3px" tal:content="python: _('which_event')"></div>
|
<div align="center" style="margin-bottom: 3px" tal:content="python: _('which_event')"></div>
|
||||||
<select name="eventType">
|
<select name="eventType">
|
||||||
<option tal:repeat="eventType python: contextObj.callField(fieldName, 'getEventTypes', contextObj)"
|
<option value="" tal:content="python: _('choose_a_value')"></option>
|
||||||
|
<option tal:repeat="eventType allEventTypes"
|
||||||
tal:content="python: contextObj.callField(fieldName, 'getEventName', contextObj, eventType)"
|
tal:content="python: contextObj.callField(fieldName, 'getEventName', contextObj, eventType)"
|
||||||
tal:attributes="value eventType">
|
tal:attributes="value eventType">
|
||||||
</option>
|
</option>
|
||||||
|
|
Loading…
Reference in a new issue