[gen] Added a new calendar field, allowing to add a single (typed) event by day.

This commit is contained in:
Gaetan Delannay 2012-10-03 14:44:34 +02:00
parent 3bd66e3264
commit 93bde7a0f5
12 changed files with 523 additions and 19 deletions

178
gen/calendar.py Normal file
View file

@ -0,0 +1,178 @@
# ------------------------------------------------------------------------------
from appy import Object
from appy.gen import Type
from DateTime import DateTime
from BTrees.IOBTree import IOBTree
from persistent.list import PersistentList
# ------------------------------------------------------------------------------
class Calendar(Type):
'''This field allows to produce an agenda and view/edit events on it.'''
jsFiles = {'view': ('widgets/calendar.js',)}
def __init__(self, eventTypes, validator=None, default=None, show='view',
page='main', group=None, layouts=None, move=0,
specificReadPermission=False, specificWritePermission=False,
width=None, height=300, colspan=1, master=None,
masterValue=None, focus=False, mapping=None, label=None,
maxEventLength=50):
Type.__init__(self, validator, (0,1), None, default, False, False,
show, page, group, layouts, move, False, False,
specificReadPermission, specificWritePermission,
width, height, None, colspan, master, masterValue, focus,
False, True, mapping, label)
# eventTypes is a list of strings that identify the types of events
# that are supported by this calendar.
self.eventTypes = eventTypes
# It is not possible to create events that span more days than
# maxEventLength.
self.maxEventLength = maxEventLength
def getSiblingMonth(self, month, prevNext):
'''Gets the next or previous month (depending of p_prevNext) relative
to p_month.'''
dayOne = DateTime('%s/01' % month)
if prevNext == 'previous':
refDate = dayOne - 1
elif prevNext == 'next':
refDate = dayOne + 33
return refDate.strftime('%Y/%m')
weekDays = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')
def getNamesOfDays(self, obj, short=True):
res = []
for day in self.weekDays:
if short:
suffix = '_short'
else:
suffix = ''
res.append(obj.translate('day_%s%s' % (day, suffix)))
return res
def getMonthGrid(self, month):
'''Creates a list of lists of DateTime objects representing the calendar
grid to render for a given p_month.'''
# Month is a string "YYYY/mm".
currentDay = DateTime('%s/01 12:00' % month)
currentMonth = currentDay.month()
res = [[]]
dayOneNb = currentDay.dow() or 7 # This way, Sunday is 7 and not 0.
if dayOneNb != 1:
previousDate = DateTime(currentDay)
# If the 1st day of the month is not a Monday, start the row with
# the last days of the previous month.
for i in range(1, dayOneNb):
previousDate = previousDate - 1
res[0].insert(0, previousDate)
finished = False
while not finished:
# Insert currentDay in the grid
if len(res[-1]) == 7:
# Create a new row
res.append([currentDay])
else:
res[-1].append(currentDay)
currentDay = currentDay + 1
if currentDay.month() != currentMonth:
finished = True
# Complete, if needed, the last row with the first days of the next
# month.
if len(res[-1]) != 7:
while len(res[-1]) != 7:
res[-1].append(currentDay)
currentDay = currentDay + 1
return res
def getEventsAt(self, obj, date, asDict=True):
'''Returns the list of events that exist at some p_date (=day).'''
if not hasattr(obj, self.name): return
years = getattr(obj, self.name)
year = date.year()
if year not in years: return
months = years[year]
month = date.month()
if month not in months: return
days = months[month]
day = date.day()
if day not in days: return
if asDict:
res = [e.__dict__ for e in days[day]]
else:
res = days[day]
return res
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
events = self.getEventsAt(obj, date, asDict=False)
if not events: return False
return events[0].eventType == otherEvents[0]['eventType']
def createEvent(self, obj, date, handleEventSpan=True):
'''Create a new event in the calendar, at some p_date (day). If
p_handleEventSpan is True, we will use rq["eventSpan"] and also
create the same event for successive days.'''
rq = obj.REQUEST
year, month, day = date.year(), date.month(), date.day()
# Check that the "preferences" dict exists or not.
if not hasattr(obj.aq_base, self.name):
# 1st level: create a IOBTree whose keys are years.
setattr(obj, self.name, IOBTree())
yearsDict = getattr(obj, self.name)
# Get the sub-dict storing months for a given year
if year in yearsDict:
monthsDict = yearsDict[year]
else:
yearsDict[year] = monthsDict = IOBTree()
# Get the sub-dict storing days of a given month
if month in monthsDict:
daysDict = monthsDict[month]
else:
monthsDict[month] = daysDict = IOBTree()
# Get the list of events for a given day
if day in daysDict:
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=rq['eventType'])
events.append(event)
# Span the event on the successive days if required
if handleEventSpan and rq['eventSpan']:
nbOfDays = int(rq['eventSpan'])
for i in range(nbOfDays):
date = date + 1
self.createEvent(obj, date, handleEventSpan=False)
def deleteEvent(self, obj, date, handleEventSpan=True):
'''Deletes an event. It actually deletes all events at rq['day'].
If p_handleEventSpan is True, we will use rq["deleteNext"] to
delete successive events, too.'''
rq = obj.REQUEST
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)
del daysDict[date.day()]
if handleEventSpan and rq.has_key('deleteNext') and \
(rq['deleteNext'] == 'True'):
while True:
date = date + 1
if self.hasEventsAt(obj, date, events):
self.deleteEvent(obj, date, handleEventSpan=False)
else:
break
def process(self, obj):
'''Processes an action coming from the calendar widget, ie, the creation
or deletion of a calendar event.'''
rq = obj.REQUEST
action = rq['actionType']
# Get the date for this action
if action == 'createEvent':
return self.createEvent(obj, DateTime(rq['day']))
elif action == 'deleteEvent':
return self.deleteEvent(obj, DateTime(rq['day']))
# ------------------------------------------------------------------------------

View file

@ -378,6 +378,14 @@ class FieldDescriptor:
msg.produceNiceDefault()
self.generator.labels.append(msg)
def walkCalendar(self):
# Add i18n-specific messages
for et in self.appyType.eventTypes:
label = '%s_%s_event_%s' % (self.classDescr.name,self.fieldName,et)
msg = PoMessage(label, '', et)
msg.produceNiceDefault()
self.generator.labels.append(msg)
def walkAppyType(self):
'''Walks into the Appy type definition and gathers data about the
i18n labels.'''
@ -435,6 +443,8 @@ class FieldDescriptor:
elif self.appyType.type == 'Pod': self.walkPod()
# Manage things which are specific to List types
elif self.appyType.type == 'List': self.walkList()
# Manage things which are specific to Calendar types
elif self.appyType.type == 'Calendar': self.walkCalendar()
def generate(self):
'''Generates the i18n labels for this type.'''

View file

@ -505,6 +505,50 @@ class ZopeGenerator(Generator):
msg('object_author', '', msg.OBJECT_AUTHOR),
msg('action_date', '', msg.ACTION_DATE),
msg('action_comment', '', msg.ACTION_COMMENT),
msg('day_Mon_short', '', msg.DAY_MON_SHORT),
msg('day_Tue_short', '', msg.DAY_TUE_SHORT),
msg('day_Wed_short', '', msg.DAY_WED_SHORT),
msg('day_Thu_short', '', msg.DAY_THU_SHORT),
msg('day_Fri_short', '', msg.DAY_FRI_SHORT),
msg('day_Sat_short', '', msg.DAY_SAT_SHORT),
msg('day_Sun_short', '', msg.DAY_SUN_SHORT),
msg('day_Mon', '', msg.DAY_MON),
msg('day_Tue', '', msg.DAY_TUE),
msg('day_Wed', '', msg.DAY_WED),
msg('day_Thu', '', msg.DAY_THU),
msg('day_Fri', '', msg.DAY_FRI),
msg('day_Sat', '', msg.DAY_SAT),
msg('day_Sun', '', msg.DAY_SUN),
msg('ampm_am', '', msg.AMPM_AM),
msg('ampm_pm', '', msg.AMPM_PM),
msg('month_Jan_short', '', msg.MONTH_JAN_SHORT),
msg('month_Feb_short', '', msg.MONTH_FEB_SHORT),
msg('month_Mar_short', '', msg.MONTH_MAR_SHORT),
msg('month_Apr_short', '', msg.MONTH_APR_SHORT),
msg('month_May_short', '', msg.MONTH_MAY_SHORT),
msg('month_Jun_short', '', msg.MONTH_JUN_SHORT),
msg('month_Jul_short', '', msg.MONTH_JUL_SHORT),
msg('month_Aug_short', '', msg.MONTH_AUG_SHORT),
msg('month_Sep_short', '', msg.MONTH_SEP_SHORT),
msg('month_Oct_short', '', msg.MONTH_OCT_SHORT),
msg('month_Nov_short', '', msg.MONTH_NOV_SHORT),
msg('month_Dec_short', '', msg.MONTH_DEC_SHORT),
msg('month_Jan', '', msg.MONTH_JAN),
msg('month_Feb', '', msg.MONTH_FEB),
msg('month_Mar', '', msg.MONTH_MAR),
msg('month_Apr', '', msg.MONTH_APR),
msg('month_May', '', msg.MONTH_MAY),
msg('month_Jun', '', msg.MONTH_JUN),
msg('month_Jul', '', msg.MONTH_JUL),
msg('month_Aug', '', msg.MONTH_AUG),
msg('month_Sep', '', msg.MONTH_SEP),
msg('month_Oct', '', msg.MONTH_OCT),
msg('month_Nov', '', msg.MONTH_NOV),
msg('month_Dec', '', msg.MONTH_DEC),
msg('today', '', msg.TODAY),
msg('which_event', '', msg.WHICH_EVENT),
msg('event_span', '', msg.EVENT_SPAN),
msg('del_next_events', '', msg.DEL_NEXT_EVENTS),
]
# Create a label for every role added by this application
for role in self.getAllUsedRoles():

View file

@ -612,7 +612,8 @@ class BaseMixin:
def getGroupedAppyTypes(self, layoutType, pageName, cssJs=None):
'''Returns the fields sorted by group. For every field, the appyType
(dict version) is given.'''
(dict version) is given. If a dict is given in p_cssJs, we will add
it in the css and js files required by the fields.'''
res = []
groups = {} # The already encountered groups
# If a dict is given in p_cssJs, we must fill it with the CSS and JS
@ -1603,4 +1604,11 @@ class BaseMixin:
'''This method is a general hook for transfering processing of a request
to a given field, whose name must be in the request.'''
return self.getAppyType(self.REQUEST['name']).process(self)
def callField(self, name, method, *args, **kwargs):
'''This method i a general hook for calling a p_method defined on a
field named p_name.'''
field = self.getAppyType(name)
exec 'res = field.%s(*args, **kwargs)' % method
return res
# ------------------------------------------------------------------------------

View file

@ -19,8 +19,7 @@ msgstr ""
'''
fallbacks = {'en': 'en-us en-ca',
'fr': 'fr-be fr-ca fr-lu fr-mc fr-ch fr-fr'
}
'fr': 'fr-be fr-ca fr-lu fr-mc fr-ch fr-fr'}
# ------------------------------------------------------------------------------
class PoMessage:
@ -169,6 +168,51 @@ class PoMessage:
OBJECT_AUTHOR = 'Author'
ACTION_DATE = 'Date'
ACTION_COMMENT = 'Comment'
DAY_MON_SHORT = 'Mon'
DAY_TUE_SHORT = 'Tue'
DAY_WED_SHORT = 'Wed'
DAY_THU_SHORT = 'Thu'
DAY_FRI_SHORT = 'Fri'
DAY_SAT_SHORT = 'Sat'
DAY_SUN_SHORT = 'Sun'
DAY_MON = 'Monday'
DAY_TUE = 'Tuesday'
DAY_WED = 'Wednesday'
DAY_THU = 'Thursday'
DAY_FRI = 'Friday'
DAY_SAT = 'Saturday'
DAY_SUN = 'Sunday'
AMPM_AM = 'AM'
AMPM_PM = 'PM'
MONTH_JAN_SHORT = 'Jan'
MONTH_FEB_SHORT = 'Feb'
MONTH_MAR_SHORT = 'Mar'
MONTH_APR_SHORT = 'Apr'
MONTH_MAY_SHORT = 'May'
MONTH_JUN_SHORT = 'Jun'
MONTH_JUL_SHORT = 'Jul'
MONTH_AUG_SHORT = 'Aug'
MONTH_SEP_SHORT = 'Sep'
MONTH_OCT_SHORT = 'Oct'
MONTH_NOV_SHORT = 'Nov'
MONTH_DEC_SHORT = 'Dec'
MONTH_JAN = 'January'
MONTH_FEB = 'February'
MONTH_MAR = 'March'
MONTH_APR = 'April'
MONTH_MAY = 'May'
MONTH_JUN = 'June'
MONTH_JUL = 'July'
MONTH_AUG = 'Augustus'
MONTH_SEP = 'September'
MONTH_OCT = 'October'
MONTH_NOV = 'November'
MONTH_DEC = 'December'
TODAY = 'Today'
WHICH_EVENT = 'Which event type would you like to create?'
EVENT_SPAN = 'Extend the event on the following number of days (leave ' \
'blank to create an event on the current day only):'
DEL_NEXT_EVENTS = 'Also delete successive events of the same type.'
def __init__(self, id, msg, default, fuzzy=False, comments=[],
niceDefault=False):

View file

@ -6,6 +6,7 @@
</tal:comment>
<tal:ajax define="contextObj context/getParentNode;
tool contextObj/getTool;
_ python: contextObj.translate;
req python: request;
resp req/RESPONSE;
page req/page;

View file

@ -183,7 +183,7 @@ function askRefField(hookId, objectUrl, fieldName, innerRef, startNumber,
if (actionParams) {
for (key in actionParams) { params[key] = actionParams[key]; };
}
askAjaxChunk(hookId, 'GET', objectUrl, 'widgets/ref', 'viewContent',params);
askAjaxChunk(hookId, 'GET', objectUrl, 'widgets/ref', 'viewContent', params);
}
function askComputedField(hookId, objectUrl, fieldName) {
@ -384,8 +384,10 @@ function generatePodDocument(contextUid, fieldName, podFormat, queryData) {
// Functions for opening and closing a popup
function openPopup(popupId, msg) {
// Put the message into the popup
if (msg) {
var confirmElem = document.getElementById('appyConfirmText');
confirmElem.innerHTML = msg;
}
// Open the popup
var popup = document.getElementById(popupId);
// Put it at the right place on the screen
@ -451,8 +453,8 @@ function doConfirm() {
}
}
var wrongTextInput = '#F0C36D none';
// Function triggered when the user ask password reinitialisation
var wrongTextInput = '#F9EDBE none';
// Function triggered when the user asks password reinitialisation
function doAskPasswordReinit() {
// Check that the user has typed a login
var theForm = document.getElementById('askPasswordReinitForm');

View file

@ -14,11 +14,6 @@
groupedWidgets python: contextObj.getGroupedAppyTypes(layoutType, page, cssJs=cssJs);"
tal:on-error="structure python: tool.manageError(error)">
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
<link tal:repeat="cssFile cssJs/css" rel="stylesheet" type="text/css"
tal:attributes="href string:$appUrl/ui/$cssFile"/>
<script tal:repeat="jsFile cssJs/js" type="text/javascript"
tal:attributes="src string:$appUrl/ui/$jsFile"></script>
<metal:prologue use-macro="context/ui/page/macros/prologue"/>
<form id="appyEditForm" name="appyEditForm" method="post" enctype="multipart/form-data"
tal:attributes="action python: contextObj.absolute_url()+'/do';

View file

@ -2,6 +2,14 @@
This macro contains global page-related Javascripts.
</tal:comment>
<div metal:define-macro="prologue">
<tal:comment replace="nothing">Include type-specific CSS and JS.</tal:comment>
<tal:include condition="cssJs|nothing">
<link tal:repeat="cssFile cssJs/css" rel="stylesheet" type="text/css"
tal:attributes="href string:$appUrl/ui/$cssFile"/>
<script tal:repeat="jsFile cssJs/js" type="text/javascript"
tal:attributes="src string:$appUrl/ui/$jsFile"></script>
</tal:include>
<tal:comment replace="nothing">Javascript messages</tal:comment>
<script language="javascript" tal:content="tool/getJavascriptMessages"></script>
@ -10,7 +18,7 @@
<input type="hidden" name="action" value="Delete"/>
<input type="hidden" name="objectUid"/>
</form>
<tal:comment replace="nothing">Global form for generating a document from a pod template.</tal:comment>
<tal:comment replace="nothing">Global form for generating a document from a pod template</tal:comment>
<form name="podTemplateForm" method="post"
tal:attributes="action python: tool.absolute_url() + '/generateDocument'">
<input type="hidden" name="objectUid"/>

View file

@ -8,9 +8,11 @@
layout python: contextObj.getPageLayout(layoutType);
phaseInfo python: contextObj.getAppyPhases(currentOnly=True, layoutType='view');
phase phaseInfo/name;
cssJs python: {};
page req/page|python:contextObj.getDefaultViewPage();
groupedWidgets python: contextObj.getGroupedAppyTypes(layoutType, page);"
groupedWidgets python: contextObj.getGroupedAppyTypes(layoutType, page, cssJs=cssJs);"
tal:on-error="structure python: tool.manageError(error)">
<metal:prologue use-macro="context/ui/page/macros/prologue"/>
<metal:show use-macro="context/ui/page/macros/show"/>
<metal:footer use-macro="context/ui/page/macros/footer"/>

View file

@ -0,0 +1,49 @@
function askMonthView(hookId, objectUrl, fieldName, month) {
// Sends an Ajax request for getting the view month of a calendar field
var params = {'fieldName': fieldName, 'month': month};
askAjaxChunk(hookId,'GET',objectUrl,'widgets/calendar','viewMonth', params);
}
function openEventPopup(action, fieldName, day, spansDays) {
/* 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. */
var prefix = fieldName + '_' + action + 'Event';
var f = document.getElementById(prefix + 'Form');
f.day.value = day;
if (action == 'del') {
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' }
}
openPopup(prefix + 'Popup');
}
function triggerCalendarEvent(action, hookId, fieldName, objectUrl) {
/* Sends an Ajax request for triggering a calendar event (create or delete an
event) and refreshing the view month. */
var prefix = fieldName + '_' + action + 'Event';
var f = document.getElementById(prefix + 'Form');
if (action == 'new') {
// Check that eventSpan is empty or contains a valid number
var spanNumber = f.eventSpan.value.replace(' ', '');
if (spanNumber) {
if (isNaN(parseInt(spanNumber))) {
f.eventSpan.style.background = wrongTextInput;
return;
}
}
}
var elems = f.elements;
var params = {};
// Put form elements into "params".
for (var i=0; i < elems.length; i++) {
params[elems[i].name] = elems[i].value;
}
closePopup(prefix + 'Popup');
askAjaxChunk(hookId,'POST',objectUrl,'widgets/calendar','viewMonth',params);
}

163
gen/ui/widgets/calendar.pt Normal file
View file

@ -0,0 +1,163 @@
<tal:comment replace="nothing">View macro</tal:comment>
<div metal:define-macro="viewMonth"
tal:define="fieldName request/fieldName;
ajaxHookId python: contextObj.UID() + fieldName;
month request/month;
monthDayOne python: DateTime('%s/01' % month);
today python: DateTime('00:00');
todayMonth python: today.strftime('%Y/%m');
grid python: contextObj.callField(fieldName, 'getMonthGrid', month);
previousMonth python: contextObj.callField(fieldName, 'getSiblingMonth', month, 'previous');
nextMonth python: contextObj.callField(fieldName, 'getSiblingMonth', month, 'next');
widget python: contextObj.getAppyType(fieldName, asDict=True);
mayEdit python: contextObj.allows(widget['writePermission']);
objUrl contextObj/absolute_url"
tal:attributes="id ajaxHookId">
<tal:comment replace="nothing">Month chooser</tal:comment>
<div style="margin-bottom: 5px">
<img style="cursor:pointer"
tal:attributes="src string: $appUrl/ui/arrowLeftSimple.png;
onclick python: 'askMonthView(\'%s\',\'%s\',\'%s\',\'%s\')' % (ajaxHookId,objUrl,fieldName,previousMonth)"/>
<input type="button"
tal:attributes="value python: _('today');
onclick python: 'askMonthView(\'%s\',\'%s\',\'%s\',\'%s\')' % (ajaxHookId,objUrl,fieldName,todayMonth);
disabled monthDayOne/isCurrentMonth"/>
<img style="cursor:pointer"
tal:attributes="src string: $appUrl/ui/arrowRightSimple.png;
onclick python: 'askMonthView(\'%s\',\'%s\',\'%s\',\'%s\')' % (ajaxHookId,objUrl,fieldName,nextMonth)"/>
<span tal:content="python: _('month_%s' % monthDayOne.aMonth())"></span>
<span tal:content="python: month.split('/')[0]"></span>
</div>
<tal:comment replace="nothing">Calendar month view</tal:comment>
<table cellpadding="0" cellspacing="0" width="100%" class="list" style="font-size: 95%"
tal:define="rowHeight python: int(widget['height']/float(len(grid)))">
<tal:comment replace="nothing">1st row: names of days</tal:comment>
<tr height="22px">
<th tal:repeat="dayName python: contextObj.callField(fieldName, 'getNamesOfDays', contextObj)"
tal:content="dayName" width="14%">
</th>
</tr>
<tal:comment replace="nothing">The calendar in itself</tal:comment>
<tr tal:repeat="row grid" valign="top" tal:attributes="height rowHeight">
<tal:cell repeat="date row">
<td tal:define="events python: contextObj.callField(fieldName, 'getEventsAt', contextObj, date);
spansDays python: contextObj.callField(fieldName, 'hasEventsAt', contextObj, date+1, events);
mayCreate python: mayEdit and not events;
mayDelete python: mayEdit and events;"
tal:attributes="style python: test(date.isCurrentDay(), 'font-weight:bold', 'font-weight:normal');
class python: test(date &lt; today, 'even', 'odd');
onmouseover python: test(mayEdit, 'this.getElementsByTagName(\'img\')[0].style.visibility=\'visible\'', '');
onmouseout python: test(mayEdit, 'this.getElementsByTagName(\'img\')[0].style.visibility=\'hidden\'', '')">
<tal:day define="day date/day;
dayString python: date.strftime('%Y/%m/%d')">
<span tal:content="day"></span>
<span tal:condition="python: day == 1"
tal:content="python: _('month_%s_short' % date.aMonth())"></span>
<tal:comment replace="nothing">Icon for adding an event</tal:comment>
<img tal:condition="mayCreate" style="visibility:hidden; cursor:pointer"
tal:attributes="src string: $appUrl/ui/plus.png;
onclick python: 'openEventPopup(\'new\',\'%s\',\'%s\')' % (fieldName, dayString)"/>
<tal:comment replace="nothing">Icon for deleting an event</tal:comment>
<img tal:condition="mayDelete" style="visibility:hidden; cursor:pointer"
tal:attributes="src string: $appUrl/ui/delete.png;
onclick python: 'openEventPopup(\'del\',\'%s\',\'%s\',\'%s\')' % (fieldName, dayString, str(spansDays))"/>
<tal:events condition="events">
<tal:comment replace="nothing">A single event is allowed for the moment</tal:comment>
<div tal:define="eventType python: events[0]['eventType']">
<span style="color: grey"
tal:content="python: _('%s_event_%s' % (widget['labelId'], eventType))"></span>
</div>
</tal:events>
</tal:day>
</td>
</tal:cell>
</tr>
</table>
<tal:comment replace="nothing">Popup for creating a calendar event</tal:comment>
<div tal:define="prefix python: '%s_newEvent' % fieldName;
popupId python: prefix + 'Popup'"
tal:attributes="id popupId" class="popup" align="center">
<form tal:attributes="id python: prefix + 'Form'" method="post">
<input type="hidden" name="fieldName" tal:attributes="value fieldName"/>
<input type="hidden" name="month" tal:attributes="value month"/>
<input type="hidden" name="name" tal:attributes="value fieldName"/>
<input type="hidden" name="action" value="Process"/>
<input type="hidden" name="actionType" value="createEvent"/>
<input type="hidden" name="day"/>
<div align="center" style="margin-bottom: 3px" tal:content="python: _('which_event')"></div>
<select name="eventType">
<option tal:repeat="eventType widget/eventTypes"
tal:content="python: _('%s_event_%s' % (widget['labelId'], eventType))"
tal:attributes="value eventType">
</option>
</select><br/><br/>
<tal:comment replace="nothing">Span the event on several days</tal:comment>
<div align="center" class="discreet" style="margin-bottom: 3px">
<span tal:content="python: _('event_span')"></span>
<input type="text" size="3" name="eventSpan"/>
</div>
<input type="button"
tal:attributes="value python:_('object_save');
onClick python: 'triggerCalendarEvent(\'new\',\'%s\',\'%s\',\'%s\')' % (ajaxHookId,fieldName,objUrl)"/>
<input type="button"
tal:attributes="value python:_('object_cancel');
onclick python: 'closePopup(\'%s\')' % popupId"/>
</form>
</div>
<tal:comment replace="nothing">Popup for deleting a calendar event</tal:comment>
<div tal:define="prefix python: '%s_delEvent' % fieldName;
popupId python: prefix + 'Popup'"
tal:attributes="id popupId" class="popup" align="center">
<form tal:attributes="id python: prefix + 'Form'" method="post">
<input type="hidden" name="fieldName" tal:attributes="value fieldName"/>
<input type="hidden" name="month" tal:attributes="value month"/>
<input type="hidden" name="name" tal:attributes="value fieldName"/>
<input type="hidden" name="action" value="Process"/>
<input type="hidden" name="actionType" value="deleteEvent"/>
<input type="hidden" name="day"/>
<div align="center" style="margin-bottom: 5px"
tal:content="python: _('delete_confirm')"></div>
<tal:comment replace="nothing">Delete successive events?</tal:comment>
<div class="discreet" style="margin-bottom: 10px"
tal:attributes="id python: prefix + 'DelNextEvent'">
<input type="checkbox" name="deleteNext_cb"
tal:attributes="id python: prefix + '_cb';
onClick python:'toggleCheckbox(\'%s_cb\', \'%s_hd\')' % (prefix, prefix);"/>
<input type="hidden" tal:attributes="id python: prefix + '_hd'" name="deleteNext"/>
<span tal:content="python: _('del_next_events')"/>
</div>
<input type="button"
tal:attributes="value python:_('yes');
onClick python: 'triggerCalendarEvent(\'del\',\'%s\',\'%s\',\'%s\')' % (ajaxHookId,fieldName,objUrl)"/>
<input type="button"
tal:attributes="value python:_('no');
onclick python: 'closePopup(\'%s\')' % popupId"/>
</form>
</div>
</div>
<tal:comment replace="nothing">View macro</tal:comment>
<metal:view define-macro="view"
tal:define="now python: DateTime();
dummy python: request.set('fieldName', widget['name']);
dummy python: request.set('month', now.strftime('%Y/%m'))">
<metal:call use-macro="app/ui/widgets/calendar/macros/viewMonth"/>
</metal:view>
<tal:comment replace="nothing">Edit macro</tal:comment>
<metal:edit define-macro="edit"></metal:edit>
<tal:comment replace="nothing">Cell macro</tal:comment>
<metal:cell define-macro="cell">
<metal:call use-macro="app/ui/widgets/calendar/macros/view"/>
</metal:cell>
<tal:comment replace="nothing">Search macro</tal:comment>
<metal:search define-macro="search"></metal:search>