[gen] Added a new calendar field, allowing to add a single (typed) event by day.
This commit is contained in:
parent
3bd66e3264
commit
93bde7a0f5
178
gen/calendar.py
Normal file
178
gen/calendar.py
Normal 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']))
|
||||||
|
# ------------------------------------------------------------------------------
|
|
@ -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.'''
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
48
gen/po.py
48
gen/po.py
|
@ -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):
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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"/>
|
||||||
|
|
|
@ -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"/>
|
||||||
|
|
49
gen/ui/widgets/calendar.js
Normal file
49
gen/ui/widgets/calendar.js
Normal 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
163
gen/ui/widgets/calendar.pt
Normal 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 < 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>
|
Loading…
Reference in a new issue