[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() msg.produceNiceDefault()
self.generator.labels.append(msg) 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): def walkAppyType(self):
'''Walks into the Appy type definition and gathers data about the '''Walks into the Appy type definition and gathers data about the
i18n labels.''' i18n labels.'''
@ -435,6 +443,8 @@ class FieldDescriptor:
elif self.appyType.type == 'Pod': self.walkPod() elif self.appyType.type == 'Pod': self.walkPod()
# Manage things which are specific to List types # Manage things which are specific to List types
elif self.appyType.type == 'List': self.walkList() 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): def generate(self):
'''Generates the i18n labels for this type.''' '''Generates the i18n labels for this type.'''

View file

@ -505,6 +505,50 @@ class ZopeGenerator(Generator):
msg('object_author', '', msg.OBJECT_AUTHOR), msg('object_author', '', msg.OBJECT_AUTHOR),
msg('action_date', '', msg.ACTION_DATE), msg('action_date', '', msg.ACTION_DATE),
msg('action_comment', '', msg.ACTION_COMMENT), 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 # Create a label for every role added by this application
for role in self.getAllUsedRoles(): for role in self.getAllUsedRoles():

View file

@ -612,7 +612,8 @@ class BaseMixin:
def getGroupedAppyTypes(self, layoutType, pageName, cssJs=None): def getGroupedAppyTypes(self, layoutType, pageName, cssJs=None):
'''Returns the fields sorted by group. For every field, the appyType '''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 = [] res = []
groups = {} # The already encountered groups groups = {} # The already encountered groups
# If a dict is given in p_cssJs, we must fill it with the CSS and JS # 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 '''This method is a general hook for transfering processing of a request
to a given field, whose name must be in the request.''' to a given field, whose name must be in the request.'''
return self.getAppyType(self.REQUEST['name']).process(self) 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', 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: class PoMessage:
@ -169,6 +168,51 @@ class PoMessage:
OBJECT_AUTHOR = 'Author' OBJECT_AUTHOR = 'Author'
ACTION_DATE = 'Date' ACTION_DATE = 'Date'
ACTION_COMMENT = 'Comment' 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=[], def __init__(self, id, msg, default, fuzzy=False, comments=[],
niceDefault=False): niceDefault=False):

View file

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

View file

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

View file

@ -14,11 +14,6 @@
groupedWidgets python: contextObj.getGroupedAppyTypes(layoutType, page, cssJs=cssJs);" groupedWidgets python: contextObj.getGroupedAppyTypes(layoutType, page, cssJs=cssJs);"
tal:on-error="structure python: tool.manageError(error)"> 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"/> <metal:prologue use-macro="context/ui/page/macros/prologue"/>
<form id="appyEditForm" name="appyEditForm" method="post" enctype="multipart/form-data" <form id="appyEditForm" name="appyEditForm" method="post" enctype="multipart/form-data"
tal:attributes="action python: contextObj.absolute_url()+'/do'; tal:attributes="action python: contextObj.absolute_url()+'/do';

View file

@ -2,6 +2,14 @@
This macro contains global page-related Javascripts. This macro contains global page-related Javascripts.
</tal:comment> </tal:comment>
<div metal:define-macro="prologue"> <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> <tal:comment replace="nothing">Javascript messages</tal:comment>
<script language="javascript" tal:content="tool/getJavascriptMessages"></script> <script language="javascript" tal:content="tool/getJavascriptMessages"></script>
@ -10,7 +18,7 @@
<input type="hidden" name="action" value="Delete"/> <input type="hidden" name="action" value="Delete"/>
<input type="hidden" name="objectUid"/> <input type="hidden" name="objectUid"/>
</form> </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" <form name="podTemplateForm" method="post"
tal:attributes="action python: tool.absolute_url() + '/generateDocument'"> tal:attributes="action python: tool.absolute_url() + '/generateDocument'">
<input type="hidden" name="objectUid"/> <input type="hidden" name="objectUid"/>

View file

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