[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
					
				
					 4 changed files with 130 additions and 24 deletions
				
			
		|  | @ -8,7 +8,8 @@ from persistent.list import PersistentList | |||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| 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',)} | ||||
| 
 | ||||
|     def __init__(self, eventTypes, eventNameMethod=None, validator=None, | ||||
|  | @ -18,7 +19,8 @@ class Calendar(Type): | |||
|                  colspan=1, master=None, masterValue=None, focus=False, | ||||
|                  mapping=None, label=None, maxEventLength=50, | ||||
|                  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, | ||||
|                       show, page, group, layouts, move, False, False, | ||||
|                       specificReadPermission, specificWritePermission, | ||||
|  | @ -40,8 +42,23 @@ class Calendar(Type): | |||
|         # It is not possible to create events that span more days than | ||||
|         # maxEventLength. | ||||
|         self.maxEventLength = maxEventLength | ||||
|         # If a method is specified in the following parameters, it must return | ||||
|         # a list of calendars whose events must be shown within this agenda. | ||||
|         # When displaying a given month for this agenda, one may want to | ||||
|         # 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] | ||||
|         # (not a tuple): | ||||
|         # - 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 | ||||
|         #   appear. | ||||
|         self.otherCalendars = otherCalendars | ||||
|         # One may want to add custom information in the calendar. When a method | ||||
|         # is given in p_additionalInfo, for every cell of the month view, this | ||||
|         # method will be called with a single arg (the cell's date). The | ||||
|         # method's result (a string that can hold text or a chunk of XHTML) will | ||||
|         # be inserted in the cell. | ||||
|         # One may want to add, day by day, custom information in the calendar. | ||||
|         # When a method is given in p_additionalInfo, for every cell of the | ||||
|         # month view, this method will be called with 2 args: the cell's date | ||||
|         # and the result of self.preCompute. The method's result (a string that | ||||
|         # can hold text or a chunk of XHTML) will be inserted in the cell. | ||||
|         self.additionalInfo = additionalInfo | ||||
|         # 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 | ||||
|  | @ -71,6 +88,23 @@ class Calendar(Type): | |||
|         # date is specified, it will be 'now' at the moment the calendar is | ||||
|         # shown. | ||||
|         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): | ||||
|         '''Gets the next or previous month (depending of p_prevNext) relative | ||||
|  | @ -127,22 +161,22 @@ class Calendar(Type): | |||
|                 currentDay = currentDay + 1 | ||||
|         return res | ||||
| 
 | ||||
|     def getOtherCalendars(self, obj): | ||||
|     def getOtherCalendars(self, obj, preComputed): | ||||
|         '''Returns the list of other calendars whose events must also be shown | ||||
|            on this calendar.''' | ||||
|         if self.otherCalendars: | ||||
|             res = self.callMethod(obj, self.otherCalendars) | ||||
|             res = self.callMethod(obj, self.otherCalendars, preComputed) | ||||
|             # Replace field names with field objects | ||||
|             for i in range(len(res)): | ||||
|                 res[i][1] = res[i][0].getField(res[i][1]) | ||||
|             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 | ||||
|            it for displaying this additional info in the calendar, at some | ||||
|            p_date.''' | ||||
|         if not self.additionalInfo: return | ||||
|         return self.additionalInfo(obj.appy(), date) | ||||
|         return self.additionalInfo(obj.appy(), date, preComputed) | ||||
| 
 | ||||
|     def getEventTypes(self, obj): | ||||
|         '''Returns the (dynamic or static) event types as defined in | ||||
|  | @ -152,6 +186,32 @@ class Calendar(Type): | |||
|         else: | ||||
|             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): | ||||
|         '''Returns the list of events that exist at some p_date (=day).''' | ||||
|         obj = obj.o # Ensure p_obj is not a wrapper. | ||||
|  |  | |||
|  | @ -217,12 +217,14 @@ function getSlaveInfo(slave, infoType) { | |||
| 
 | ||||
| function getMasterValues(master) { | ||||
|   // Returns the list of values that p_master currently has.
 | ||||
|   var res = null; | ||||
|   if ((master.tagName == 'INPUT') && (master.type != 'checkbox')) { | ||||
|     res = master.value; | ||||
|     if ((res[0] == '(') || (res[0] == '[')) { | ||||
|     if ((res.charAt(0) == '(') || (res.charAt(0) == '[')) { | ||||
|       // There are multiple values, split it
 | ||||
|       values = res.substring(1, res.length-1).split(','); | ||||
|       res = []; | ||||
|       var v = null; | ||||
|       for (var i=0; i < values.length; i++){ | ||||
|         v = values[i].replace(' ', ''); | ||||
|         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); | ||||
| } | ||||
| 
 | ||||
| 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 | ||||
|      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. */ | ||||
|      checkbox allowing to delete events for those successive days. When action | ||||
|      is "new", a possibly restricted list of applicable event types for this | ||||
|      day is given in p_applicableEventTypes; p_message contains an optional | ||||
|      message explaining why not applicable types are not applicable. */ | ||||
|   var prefix = fieldName + '_' + action + 'Event'; | ||||
|   var f = document.getElementById(prefix + 'Form'); | ||||
|   f.day.value = day; | ||||
|   if (action == 'del') { | ||||
|     // Show or hide the checkbox for deleting the event for successive days.
 | ||||
|     var elem = document.getElementById(prefix + 'DelNextEvent'); | ||||
|     var cb = elem.getElementsByTagName('input'); | ||||
|     cb[0].checked = false; | ||||
|  | @ -20,6 +25,32 @@ function openEventPopup(action, fieldName, day, spansDays) { | |||
|     if (spansDays == 'True') { elem.style.display = 'block' } | ||||
|     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'); | ||||
| } | ||||
| 
 | ||||
|  | @ -29,6 +60,11 @@ function triggerCalendarEvent(action, hookId, fieldName, objectUrl, maxEventLeng | |||
|   var prefix = fieldName + '_' + action + 'Event'; | ||||
|   var f = document.getElementById(prefix + 'Form'); | ||||
|   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
 | ||||
|     var spanNumber = f.eventSpan.value.replace(' ', ''); | ||||
|     if (spanNumber) { | ||||
|  |  | |||
|  | @ -5,9 +5,11 @@ | |||
|                  month request/month; | ||||
|                  monthDayOne python: DateTime('%s/01' % month); | ||||
|                  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); | ||||
|                  defaultDateMonth python: defaultDate.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); | ||||
|  | @ -15,7 +17,7 @@ | |||
|                  objUrl contextObj/absolute_url; | ||||
|                  startDate python: contextObj.callField(fieldName, 'getStartDate', 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"> | ||||
| 
 | ||||
| <script type="text/javascript" | ||||
|  | @ -82,13 +84,17 @@ | |||
|        <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: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; | ||||
|                           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> | ||||
|        <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))"/> | ||||
|                           onclick python: 'openEventPopup(\'del\',\'%s\',\'%s\',\'%s\',null,null)' % (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']"> | ||||
|  | @ -105,7 +111,7 @@ | |||
|         </tal:e> | ||||
|        </tal:others> | ||||
|        <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"/> | ||||
|       </tal:day> | ||||
|      </td> | ||||
|  | @ -127,9 +133,11 @@ | |||
|   <input type="hidden" name="actionType" value="createEvent"/> | ||||
|   <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> | ||||
|   <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:attributes="value eventType"> | ||||
|    </option> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Gaetan Delannay
						Gaetan Delannay