Some code cleanup

This commit is contained in:
Nathan Gray 2016-01-13 22:07:09 +00:00
parent 1e2b03abb0
commit 70686f0847
6 changed files with 330 additions and 205 deletions

View File

@ -3040,7 +3040,7 @@ app.classes.calendar = AppJS.extend(
{ {
var tempDate = new Date(); var tempDate = new Date();
var today = new Date(tempDate.getFullYear(), tempDate.getUTCMonth(), tempDate.getUTCDate()); var today = new Date(tempDate.getFullYear(), tempDate.getUTCMonth(), tempDate.getUTCDate());
var holidays = et2_calendar_daycol.get_holidays({day_class_holiday: function() {}}, date.getFullYear()); var holidays = et2_calendar_view.get_holidays({day_class_holiday: function() {}}, date.getFullYear());
var day_holidays = holidays[''+date.getFullYear() + var day_holidays = holidays[''+date.getFullYear() +
sprintf("%02d",date.getMonth()+1) + sprintf("%02d",date.getMonth()+1) +
sprintf("%02d",date.getDate())]; sprintf("%02d",date.getDate())];

View File

@ -17,11 +17,11 @@
*/ */
/** /**
* Class which implements the "calendar-timegrid" XET-Tag for displaying a span of days * Class which implements the "calendar-timegrid" XET-Tag for displaying a single days
* *
* This widget is responsible for the times on the side * This widget is responsible mostly for positioning its events
* *
* @augments et2_DOMWidget * @augments et2_valueWidget
*/ */
var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizeable], var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizeable],
{ {
@ -35,7 +35,7 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
}, },
owner: { owner: {
name: "Owner", name: "Owner",
type: "any", // Integer, or array of integers type: "any", // Integer, string, or array of either
default: 0, default: 0,
description: "Account ID number of the calendar owner, if not the current user" description: "Account ID number of the calendar owner, if not the current user"
}, },
@ -83,7 +83,7 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
this.date_helper = et2_createWidget('date-time',{},null); this.date_helper = et2_createWidget('date-time',{},null);
this.date_helper.loadingFinished(); this.date_helper.loadingFinished();
// Init to defaults, just in case // Init to defaults, just in case - they will be updated from parent
this.display_settings = { this.display_settings = {
wd_start: 60*9, wd_start: 60*9,
wd_end: 60*17, wd_end: 60*17,
@ -128,7 +128,7 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
}, },
/** /**
* Draw the individual divs for clicking * Draw the individual divs for clicking to add an event
*/ */
_draw: function() { _draw: function() {
// Remove any existing // Remove any existing
@ -197,7 +197,7 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
* Set the date * Set the date
* *
* @param {string|Date} _date New date * @param {string|Date} _date New date
* @param {Object[]} events=false List of events to be displayed, or false to * @param {Object[]} events=false List of event data to be displayed, or false to
* automatically fetch data from content array * automatically fetch data from content array
* @param {boolean} force_redraw=false Redraw even if the date is the same. * @param {boolean} force_redraw=false Redraw even if the date is the same.
* Used for when new data is available. * Used for when new data is available.
@ -295,7 +295,10 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
/** /**
* Set the owner of this day * Set the owner of this day
* *
* @param {number|number[]} _owner Account ID * @param {number|number[]|string|string[]} _owner - Owner ID, which can
* be an account ID, a resource ID (as defined in calendar_bo, not
* necessarily an entry from the resource app), or a list containing a
* combination of both.
*/ */
set_owner: function(_owner) { set_owner: function(_owner) {
@ -331,6 +334,9 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
/** /**
* Callback used when the daywise data changes * Callback used when the daywise data changes
* *
* Events should update themselves when their data changes, here we are
* dealing with a change in which events are displayed on this day.
*
* @param {String[]} event_ids * @param {String[]} event_ids
* @returns {undefined} * @returns {undefined}
*/ */
@ -406,7 +412,7 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
); );
// Holidays and birthdays // Holidays and birthdays
var holidays = et2_calendar_daycol.get_holidays(this,this.options.date.substring(0,4)); var holidays = et2_calendar_view.get_holidays(this,this.options.date.substring(0,4));
var holiday_list = []; var holiday_list = [];
if(holidays && holidays[this.options.date]) if(holidays && holidays[this.options.date])
{ {
@ -457,8 +463,8 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
{ {
var node = this._children[this._children.length-1]; var node = this._children[this._children.length-1];
this.removeChild(node); this.removeChild(node);
node.free(); node.free();
} }
// Make sure children are in cronological order, or columns are backwards // Make sure children are in cronological order, or columns are backwards
events.sort(function(a,b) { events.sort(function(a,b) {
@ -547,7 +553,7 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
event.title.css('top',timegrid.scrolling.scrollTop() - event.div.position().top); event.title.css('top',timegrid.scrolling.scrollTop() - event.div.position().top);
event.body.css('padding-top',timegrid.scrolling.scrollTop() - event.div.position().top); event.body.css('padding-top',timegrid.scrolling.scrollTop() - event.div.position().top);
} }
// Too many in list view, show indicator // Too many in gridlist view, show indicator
else if (this.display_settings.granularity === 0 && hidden.completely) else if (this.display_settings.granularity === 0 && hidden.completely)
{ {
var day = this; var day = this;
@ -728,10 +734,11 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
}, },
/** /**
* Position the event according to it's time and how this widget is laid * Position the event according to its time and how this widget is laid
* out. * out.
* *
* @param {undefined|Object|et2_calendar_event} event * @param {et2_calendar_event} [event] - Event to be updated
* If a single event is not provided, all events are repositioned.
*/ */
position_event: function(event) position_event: function(event)
{ {
@ -750,8 +757,6 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
} }
if (left + width > 100.0) width = 98.0 - left; if (left + width > 100.0) width = 98.0 - left;
var whole_day_counter = 0;
for(var i = 0; (columns[c].indexOf(event) >= 0 || !event) && i < columns[c].length; i++) for(var i = 0; (columns[c].indexOf(event) >= 0 || !event) && i < columns[c].length; i++)
{ {
// Calculate vertical positioning // Calculate vertical positioning
@ -794,8 +799,8 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
columns[c][i].div.appendTo(this.div); columns[c][i].div.appendTo(this.div);
this._parent.resizeTimes(); this._parent.resizeTimes();
} }
top = this._time_to_position(columns[c][i].options.value.start_m,whole_day_counter); top = this._time_to_position(columns[c][i].options.value.start_m);
height = this._time_to_position(columns[c][i].options.value.end_m,whole_day_counter)-top; height = this._time_to_position(columns[c][i].options.value.end_m)-top;
} }
// Position the event // Position the event
@ -831,21 +836,16 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
* This calculation is a percentage from 00:00 to 23:59 * This calculation is a percentage from 00:00 to 23:59
* *
* @param {int} time in minutes from midnight * @param {int} time in minutes from midnight
* @param {int} [row_offset=0] Add extra spacing for additional rows
* @return {float} position in percent * @return {float} position in percent
*/ */
_time_to_position: function(time,row_offset) _time_to_position: function(time)
{ {
var pos = 0.0; var pos = 0.0;
if(typeof row_offset === 'undefined')
{
row_offset = 0;
}
// 24h // 24h
pos = ((time / 60) / 24) * 100 pos = ((time / 60) / 24) * 100;
pos = pos.toFixed(1) pos = pos.toFixed(1);
return pos; return pos;
}, },
@ -957,54 +957,3 @@ var et2_calendar_daycol = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResizea
}); });
et2_register_widget(et2_calendar_daycol, ["calendar-daycol"]); et2_register_widget(et2_calendar_daycol, ["calendar-daycol"]);
// Static class stuff
jQuery.extend(et2_calendar_daycol,
{
holiday_cache: {},
/**
* Fetch and cache a list of the year's holidays
*
* @param {et2_calendar_timegrid} widget
* @param {string|numeric} year
* @returns {Array}
*/
get_holidays: function(widget,year)
{
// Loaded in an iframe or something
if(!egw.window.et2_calendar_daycol) return {};
var cache = egw.window.et2_calendar_daycol.holiday_cache[year];
if (typeof cache == 'undefined')
{
// Fetch with json instead of jsonq because there may be more than
// one widget listening for the response by the time it gets back,
// and we can't do that when it's queued.
egw.window.et2_calendar_daycol.holiday_cache[year] = egw.json(
'calendar_timegrid_etemplate_widget::ajax_get_holidays',
[year]
).sendRequest(true);
}
cache = egw.window.et2_calendar_daycol.holiday_cache[year];
if(typeof cache.done == 'function')
{
// pending, wait for it
cache.done(jQuery.proxy(function(response) {
egw.window.et2_calendar_daycol.holiday_cache[this.year] = response.response[0].data||undefined;
egw.window.setTimeout(jQuery.proxy(function() {
// Make sure widget hasn't been destroyed while we wait
if(typeof this.widget.free == 'undefined')
{
this.widget.day_class_holiday();
}
},this),1);
},{widget:widget,year:year}));
return {};
}
else
{
return cache;
}
}
});

View File

@ -16,7 +16,28 @@
*/ */
/** /**
* Class for a single event, displayed in a timegrid * Class for a single event, displayed in either the timegrid or planner view
*
* It is possible to directly provide all information directly, but calendar
* uses egw.data for caching, so ID is all that is needed.
*
* Note that there are several pieces of information that have 'ID' in them:
* - row_id - used by both et2_calendar_event and the nextmatch to uniquely
* identify a particular entry or entry ocurrence
* - id - Recurring events may have their recurrence as a timestamp after their ID,
* such as '194:1453318200', or not. It's usually (always?) the same as row ID.
* - app_id - the ID according to the source application. For calendar, this
* is the same as ID (but always with the recurrence), for other apps this is
* usually just an integer. With app_id and app, you should be able to call
* egw.open() and get the specific entry.
* - Events from other apps will have their app name prepended to their ID, such
* as 'infolog123', so app_id and id will be different for these events
* - Cache ID is the same as other apps, and looks like 'calendar::<row_id>'
* - The DOM ID for the containing div is event_<row_id>
*
* Events are expected to be added to either et2_calendar_daycol or
* et2_calendar_planner_row rather than either et2_calendar_timegrid or
* et2_calendar_planner directly.
* *
* *
* @augments et2_valueWidget * @augments et2_valueWidget
@ -88,20 +109,12 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
doLoadingFinished: function() { doLoadingFinished: function() {
this._super.apply(this, arguments); this._super.apply(this, arguments);
// Parent will have everything we need, just load it from there // Already know what is needed to hook to cache
if(this.title.text() == '' && this.options.date &&
this._parent && this._parent.instanceOf(et2_calendar_timegrid))
{
// Forces an update
var date = this.options.date;
this.options.date = '';
this.set_date(date);
}
if(this.options.value && this.options.value.row_id) if(this.options.value && this.options.value.row_id)
{ {
egw.dataRegisterUID( egw.dataRegisterUID(
'calendar::'+this.options.value.row_id, 'calendar::'+this.options.value.row_id,
this._UID_callback , this._UID_callback,
this, this,
this.getInstanceManager().execId, this.getInstanceManager().execId,
this.id this.id
@ -132,7 +145,7 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
// Unregister, or we'll continue to be notified... // Unregister, or we'll continue to be notified...
if(this.options.value) if(this.options.value)
{ {
var old_app_id = this.options.value.app_id; var old_app_id = this.options.value.row_id;
egw.dataUnregisterUID('calendar::'+old_app_id,false,this); egw.dataUnregisterUID('calendar::'+old_app_id,false,this);
} }
}, },
@ -161,8 +174,11 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
} }
}, },
/**
* Callback for changes in cached data
*/
_UID_callback: function _UID_callback(event) { _UID_callback: function _UID_callback(event) {
// Make sure id is a string // Make sure id is a string, check values
if(event) if(event)
{ {
this._values_check(event); this._values_check(event);
@ -201,6 +217,9 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
} }
}, },
/**
* Draw the event
*/
_update: function(event) { _update: function(event) {
// Copy new information // Copy new information
@ -329,6 +348,7 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
Math.max(0.8, parseFloat(jQuery.Color(this.title.css('background-color')).lightness())) Math.max(0.8, parseFloat(jQuery.Color(this.title.css('background-color')).lightness()))
)); ));
// Tooltip
this.set_statustext(this._tooltip()); this.set_statustext(this._tooltip());
}, },
@ -361,6 +381,11 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
return status_class; return status_class;
}, },
/**
* Create tooltip shown on hover
*
* @return {String}
*/
_tooltip: function() { _tooltip: function() {
if(!this.div) return ''; if(!this.div) return '';
@ -588,6 +613,22 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
event.whole_day_on_top = (event.non_blocking && event.non_blocking != '0'); event.whole_day_on_top = (event.non_blocking && event.non_blocking != '0');
} }
}, },
/**
* Check to see if the provided event information is for the same date as
* what we're currently expecting, and that it has not been changed.
*
* If the date has changed, we adjust the associated daywise caches to move
* the event's ID to where it should be.
*
* @param {Object} event Map of event data from cache
* @param {string} event.date For non-recurring, single day events, this is
* the date the event is on.
* @param {string} event.start Start of the event (used for multi-day events)
* @param {string} event.end End of the event (used for multi-day events)
*
* @return {Boolean} Provided event data is for the same date
*/
_sameday_check: function(event) _sameday_check: function(event)
{ {
// Event somehow got orphaned, or deleted // Event somehow got orphaned, or deleted
@ -672,7 +713,9 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
/** /**
* Show the recur prompt for this event * Show the recur prompt for this event
* *
* @param {function} callback * Calls et2_calendar_event.recur_prompt with this event's value.
*
* @param {et2_calendar_event~prompt_callback} callback
* @param {Object} [extra_data] * @param {Object} [extra_data]
*/ */
recur_prompt: function(callback, extra_data) recur_prompt: function(callback, extra_data)
@ -683,7 +726,9 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
/** /**
* Show the series split prompt for this event * Show the series split prompt for this event
* *
* @param {function} callback * Calls et2_calendar_event.series_split_prompt with this event's value.
*
* @param {et2_calendar_event~prompt_callback} callback
*/ */
series_split_prompt: function(callback) series_split_prompt: function(callback)
{ {
@ -750,17 +795,33 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
et2_register_widget(et2_calendar_event, ["calendar-event"]); et2_register_widget(et2_calendar_event, ["calendar-event"]);
// Static class stuff // Static class stuff
/**
* @callback et2_calendar_event~prompt_callback
* @param {string} button_id - One of ok, exception, series, single or cancel
* depending on which buttons are on the prompt
* @param {Object} event_data - Event information - whatever you passed in to
* the prompt.
*/
/** /**
* Recur prompt * Recur prompt
* If the event is recurring, asks the user if they want to edit the event as * If the event is recurring, asks the user if they want to edit the event as
* an exception, or change the whole series. Then the callback is called. * an exception, or change the whole series. Then the callback is called.
* *
* If callback is not provided, egw.open() will be used to open an edit dialog.
*
* If you call this on a single (non-recurring) event, the callback will be
* executed immediately, with the passed button_id as 'single'.
*
* @param {Object} event_data - Event information * @param {Object} event_data - Event information
* @param {string} event_data.id - Unique ID for the event, possibly with a timestamp * @param {string} event_data.id - Unique ID for the event, possibly with a
* timestamp
* @param {string|Date} event_data.start - Start date/time for the event * @param {string|Date} event_data.start - Start date/time for the event
* @param {number} event_data.recur_type - Recur type, or 0 for a non-recurring event * @param {number} event_data.recur_type - Recur type, or 0 for a non-recurring event
* @param {Function} [callback] - Callback is called with the button (exception, series, single or cancel) and the event data. * @param {et2_calendar_event~prompt_callback} [callback] - Callback is
* @param {Object} [extra_data] - Additional data passed to the callback, used as extra parameters for default callback * called with the button (exception, series, single or cancel) and the event
* data.
* @param {Object} [extra_data] - Additional data passed to the callback, used
* as extra parameters for default callback
* *
* @augments {et2_calendar_event} * @augments {et2_calendar_event}
*/ */
@ -820,11 +881,14 @@ et2_calendar_event.recur_prompt = function(event_data, callback, extra_data)
* to split the series, ending the current one and creating a new one with the changes. * to split the series, ending the current one and creating a new one with the changes.
* This prompts the user if they really want to do that. * This prompts the user if they really want to do that.
* *
* There is no default callback, and nothing happens if you call this on a
* single (non-recurring) event
*
* @param {Object} event_data - Event information * @param {Object} event_data - Event information
* @param {string} event_data.id - Unique ID for the event, possibly with a timestamp * @param {string} event_data.id - Unique ID for the event, possibly with a timestamp
* @param {string|Date} instance_date - The date of the edited instance of the event * @param {string|Date} instance_date - The date of the edited instance of the event
* @param {Function} [callback] - Callback is called with the button (ok or cancel) and the event data. * @param {et2_calendar_event~prompt_callback} callback - Callback is
* * called with the button (ok or cancel) and the event data.
* @augments {et2_calendar_event} * @augments {et2_calendar_event}
*/ */
et2_calendar_event.series_split_prompt = function(event_data, instance_date, callback) et2_calendar_event.series_split_prompt = function(event_data, instance_date, callback)
@ -889,7 +953,7 @@ et2_calendar_event.split_status = function(status,quantity,role)
/** /**
* The egw_action system requires an egwActionObjectInterface Interface implementation * The egw_action system requires an egwActionObjectInterface Interface implementation
* to tie actions to DOM nodes. This one can be used by any widget. * to tie actions to DOM nodes. I'm not sure if we need this.
* *
* The class extension is different than the widgets * The class extension is different than the widgets
* *

View File

@ -19,10 +19,11 @@
/** /**
* Class which implements the "calendar-planner" XET-Tag for displaying a longer * Class which implements the "calendar-planner" XET-Tag for displaying a longer
* ( > 10 days) span of time * ( > 10 days) span of time. Events can be grouped into rows by either user,
* category, or month. Their horizontal position and size in the row is determined
* by their start date and duration relative to the displayed date range.
* *
* @augments et2_valueWidget * @augments et2_calendar_view
* @class
*/ */
var et2_calendar_planner = et2_calendar_view.extend([et2_IDetachedDOM, et2_IResizeable], var et2_calendar_planner = et2_calendar_view.extend([et2_IDetachedDOM, et2_IResizeable],
{ {
@ -109,12 +110,6 @@ var et2_calendar_planner = et2_calendar_view.extend([et2_IDetachedDOM, et2_IResi
{ {
egw.dataUnregisterUID(this.registeredCallbacks[i],false,this); egw.dataUnregisterUID(this.registeredCallbacks[i],false,this);
} }
// Stop the invalidate timer
if(this.update_timer)
{
window.clearTimeout(this.update_timer);
}
}, },
doLoadingFinished: function() { doLoadingFinished: function() {
@ -1114,7 +1109,7 @@ var et2_calendar_planner = et2_calendar_view.extend([et2_IDetachedDOM, et2_IResi
var day_class = ''; var day_class = '';
// Holidays and birthdays // Holidays and birthdays
var holidays = et2_calendar_daycol.get_holidays(this,date.getUTCFullYear()); var holidays = et2_calendar_view.get_holidays(this,date.getUTCFullYear());
// Pass a string rather than the date object, to make sure it doesn't get changed // Pass a string rather than the date object, to make sure it doesn't get changed
this.date_helper.set_value(date.toJSON()); this.date_helper.set_value(date.toJSON());
@ -1434,7 +1429,7 @@ var et2_calendar_planner = et2_calendar_view.extend([et2_IDetachedDOM, et2_IResi
var drop_date = this.dropEnd.toJSON() ||false; var drop_date = this.dropEnd.toJSON() ||false;
var event_data = planner._get_event_info(ui.draggable); var event_data = planner._get_event_info(ui.draggable);
var event_widget = planner.getWidgetById('event_'+event_data.id); var event_widget = planner.getWidgetById(event_data.widget_id);
if(event_widget) if(event_widget)
{ {
event_widget._parent.date_helper.set_value(drop_date); event_widget._parent.date_helper.set_value(drop_date);
@ -1582,21 +1577,7 @@ var et2_calendar_planner = et2_calendar_view.extend([et2_IDetachedDOM, et2_IResi
{ {
if(typeof events !== 'object') return false; if(typeof events !== 'object') return false;
if(events.owner) this._super.apply(this, arguments);
{
this.set_owner(events.owner);
delete events.owner;
}
if(events.start_date)
{
this.set_start_date(events.start_date);
delete events.start_date;
}
if(events.end_date)
{
this.set_end_date(events.end_date);
delete events.end_date;
}
if(typeof events.length === "undefined" && events) if(typeof events.length === "undefined" && events)
{ {
@ -1719,7 +1700,7 @@ var et2_calendar_planner = et2_calendar_view.extend([et2_IDetachedDOM, et2_IResi
* onclick function. * onclick function.
* *
* @param {Event} _ev * @param {Event} _ev
* @returns {boolean} * @returns {boolean} Continue processing event (true) or stop (false)
*/ */
click: function(_ev) click: function(_ev)
{ {
@ -1794,28 +1775,13 @@ var et2_calendar_planner = et2_calendar_view.extend([et2_IDetachedDOM, et2_IResi
} }
}, },
_get_event_info: function(dom_node)
{
// Determine as much relevant info as can be found
var event_node = $j(dom_node).closest('[data-id]',this.div)[0];
var day_node = $j(event_node).closest('[data-date]',this.div)[0];
return jQuery.extend({
event_node: event_node,
day_node: day_node,
},
event_node ? event_node.dataset : {},
day_node ? day_node.dataset : {}
);
},
/** /**
* Get time from position * Get time from position
* *
* @param {number} x * @param {number} x
* @param {number} y * @param {number} y
* @returns {DOMNode[]} time node(s) for the given position * @returns {Date|Boolean} A time for the given position, or false if one
* could not be determined.
*/ */
_get_time_from_position: function(x,y) { _get_time_from_position: function(x,y) {

View File

@ -20,9 +20,16 @@
/** /**
* Class which implements the "calendar-timegrid" XET-Tag for displaying a span of days * Class which implements the "calendar-timegrid" XET-Tag for displaying a span of days
* *
* This widget is responsible for the times on the side * This widget is responsible for the times on the side, and it is also the
* controller for both positioning and setting the day columns. Day columns are
* recycled rather than removed and re-created to reduce reloading. Similarly,
* the horizontal time grid (when used - see granularity attribute) is only
* redrawn or resized when needed. Unfortunately resizing is needed every time
* the all day section has an event added or removed so the full work day from
* start time to end time is properly displayed.
* *
* @augments et2_DOMWidget *
* @augments et2_calendar_view
*/ */
var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IResizeable], var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IResizeable],
{ {
@ -126,12 +133,6 @@ var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IRes
}, },
destroy: function() { destroy: function() {
// Stop the invalidate timer
if(this.update_timer)
{
window.clearTimeout(this.update_timer);
}
// Stop listening to tab changes // Stop listening to tab changes
$j(framework.getApplicationByName('calendar').tab.contentDiv).off('show.' + this.id); $j(framework.getApplicationByName('calendar').tab.contentDiv).off('show.' + this.id);
@ -151,11 +152,7 @@ var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IRes
this.scrolling = null; this.scrolling = null;
this._labelContainer = null; this._labelContainer = null;
// Stop the invalidate timer // Stop the resize timer
if(this.update_timer)
{
window.clearTimeout(this.update_timer);
}
if(this.resize_timer) if(this.resize_timer)
{ {
window.clearTimeout(this.resize_timer); window.clearTimeout(this.resize_timer);
@ -386,11 +383,11 @@ var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IRes
var drop_date = dropEnd.date||false; var drop_date = dropEnd.date||false;
var event_data = timegrid._get_event_info(ui.draggable); var event_data = timegrid._get_event_info(ui.draggable);
var event_widget = timegrid.getWidgetById('event_'+event_data.id); var event_widget = timegrid.getWidgetById(event_data.widget_id);
if(!event_widget) if(!event_widget)
{ {
// Widget was moved across weeks / owners // Widget was moved across weeks / owners
event_widget = timegrid.getParent().getWidgetById('event_'+event_data.id); event_widget = timegrid.getParent().getWidgetById(event_data.widget_id);
} }
if(event_widget) if(event_widget)
{ {
@ -611,7 +608,7 @@ var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IRes
/** /**
* Creates the DOM nodes for the times in the left column, and the horizontal * Creates the DOM nodes for the times in the left column, and the horizontal
* lines (mostly via CSS) that span the whole time span. * lines (mostly via CSS) that span the whole date range.
*/ */
_drawTimes: function() { _drawTimes: function() {
$j('.calendar_calTimeRow',this.div).remove(); $j('.calendar_calTimeRow',this.div).remove();
@ -727,6 +724,7 @@ var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IRes
* As window size and number of all day non-blocking events change, we need * As window size and number of all day non-blocking events change, we need
* to re-scale the time grid to make sure the full working day is shown. * to re-scale the time grid to make sure the full working day is shown.
* *
* We use a timeout to avoid doing it multiple times if redrawing or resizing.
*/ */
resizeTimes: function() { resizeTimes: function() {
// Wait a bit to see if anything else changes, then re-draw the times // Wait a bit to see if anything else changes, then re-draw the times
@ -747,6 +745,10 @@ var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IRes
},this),1); },this),1);
}, },
/**
* Re-scale the time grid to make sure the full working day is shown.
* This is the timeout callback that does the actual re-size immediately.
*/
_resizeTimes: function() { _resizeTimes: function() {
if(!this.div.is(':visible')) if(!this.div.is(':visible'))
@ -876,6 +878,7 @@ var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IRes
day.set_left((day_width * i) + 'px'); day.set_left((day_width * i) + 'px');
if(daily_owner) if(daily_owner)
{ {
// Each 'day' is the same date, different user
day.set_id(this.day_list[0]+'-'+this.options.owner[i]); day.set_id(this.day_list[0]+'-'+this.options.owner[i]);
day.set_date(this.day_list[0], false); day.set_date(this.day_list[0], false);
day.set_owner(this.options.owner[i]); day.set_owner(this.options.owner[i]);
@ -883,7 +886,7 @@ var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IRes
} }
else else
{ {
// Go back to self-calculated date // Go back to self-calculated date by clearing the label
day.set_label(''); day.set_label('');
day.set_id(this.day_list[i]); day.set_id(this.day_list[i]);
day.set_date(this.day_list[i], this.value[this.day_list[i]] || false); day.set_date(this.day_list[i], this.value[this.day_list[i]] || false);
@ -1258,7 +1261,10 @@ var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IRes
/** /**
* Provide specific data to be displayed. * Provide specific data to be displayed.
* This is a way to set start and end dates, owner and event data in once call. * This is a way to set start and end dates, owner and event data in one call.
*
* Events will be retrieved automatically from the egw.data cache, so there
* is no great need to provide them.
* *
* @param {Object[]} events Array of events, indexed by date in Ymd format: * @param {Object[]} events Array of events, indexed by date in Ymd format:
* { * {
@ -1266,41 +1272,29 @@ var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IRes
* 20150502: [...] * 20150502: [...]
* } * }
* Days should be in order. * Days should be in order.
* * @param {string|number|Date} events.start_date - New start date
* @param {string|number|Date} events.end_date - New end date
* @param {number|number[]|string|string[]} event.owner - Owner ID, which can
* be an account ID, a resource ID (as defined in calendar_bo, not
* necessarily an entry from the resource app), or a list containing a
* combination of both.
*/ */
set_value: function(events) set_value: function(events)
{ {
if(typeof events !== 'object') return false; if(typeof events !== 'object') return false;
this.loader.show();
var use_days_sent = true; var use_days_sent = true;
if(events.id)
{
this.set_id(events.id);
delete events.id;
}
if(events.start_date) if(events.start_date)
{ {
this.set_start_date(events.start_date);
delete events.start_date;
use_days_sent = false; use_days_sent = false;
} }
if(events.end_date) if(events.end_date)
{ {
this.set_end_date(events.end_date);
delete events.end_date;
use_days_sent = false; use_days_sent = false;
} }
// set_owner() wants start_date set to get the correct week number
// for the corner label
if(events.owner)
{
this.set_owner(events.owner);
delete events.owner;
}
this.value = events || {}; this._super.apply(this,arguments);
if(use_days_sent) if(use_days_sent)
{ {
@ -1401,6 +1395,10 @@ var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IRes
/** /**
* Set how big the time divisions are * Set how big the time divisions are
* *
* Setting granularity to 0 will remove the time divisions and display
* each days events in a list style. This 'gridlist' is not to be confused
* with the list view, which uses a nextmatch.
*
* @param {number} minutes * @param {number} minutes
*/ */
set_granularity: function(minutes) set_granularity: function(minutes)
@ -1514,7 +1512,7 @@ var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IRes
* onclick function. * onclick function.
* *
* @param {Event} _ev * @param {Event} _ev
* @returns {boolean} * @returns {boolean} Continue processing event (true) or stop (false)
*/ */
click: function(_ev) click: function(_ev)
{ {
@ -1577,23 +1575,11 @@ var et2_calendar_timegrid = et2_calendar_view.extend([et2_IDetachedDOM, et2_IRes
} }
}, },
_get_event_info: function(dom_node)
{
// Determine as much relevant info as can be found
var event_node = $j(dom_node).closest('[data-id]',this.div)[0];
var day_node = $j(event_node).closest('[data-date]',this.div)[0];
return jQuery.extend({
event_node: event_node,
day_node: day_node,
},
event_node ? event_node.dataset : {},
day_node ? day_node.dataset : {}
);
},
/** /**
* Get time from position * Get time from position for drag and drop
*
* This does not return an actual time on a clock, but finds the closest
* time node (.calendar_calAddEvent or day column) to the given position.
* *
* @param {number} x * @param {number} x
* @param {number} y * @param {number} y

View File

@ -17,6 +17,10 @@
/** /**
* Parent class for the various calendar views to reduce copied code * Parent class for the various calendar views to reduce copied code
* *
*
* et2_calendar_view is responsible for its own loader div, which is displayed while
* the times & days are redrawn.
*
* @augments et2_valueWidget * @augments et2_valueWidget
*/ */
var et2_calendar_view = et2_valueWidget.extend( var et2_calendar_view = et2_valueWidget.extend(
@ -54,6 +58,7 @@ var et2_calendar_view = et2_valueWidget.extend(
this.date_helper.loadingFinished(); this.date_helper.loadingFinished();
this.loader = $j('<div class="egw-loading-prompt-container ui-front loading"></div>'); this.loader = $j('<div class="egw-loading-prompt-container ui-front loading"></div>');
this.update_timer = null;
}, },
destroy: function destroy() { destroy: function destroy() {
@ -62,6 +67,12 @@ var et2_calendar_view = et2_valueWidget.extend(
// date_helper has no parent, so we must explicitly remove it // date_helper has no parent, so we must explicitly remove it
this.date_helper.destroy(); this.date_helper.destroy();
this.date_helper = null; this.date_helper = null;
// Stop the invalidate timer
if(this.update_timer)
{
window.clearTimeout(this.update_timer);
}
}, },
doLoadingFinished: function() { doLoadingFinished: function() {
@ -80,7 +91,9 @@ var et2_calendar_view = et2_valueWidget.extend(
* *
* @memberOf et2_calendar_view * @memberOf et2_calendar_view
*/ */
invalidate: function invalidate(trigger_event) {}, invalidate: function invalidate(trigger_event) {
// If this wasn't a stub, we'd set this.update_timer
},
/** /**
* Returns the current start date * Returns the current start date
@ -107,7 +120,11 @@ var et2_calendar_view = et2_valueWidget.extend(
/** /**
* Change the start date * Change the start date
* *
* @param {string|number|Date} new_date New starting date * Changing the start date will invalidate the display, and it will be redrawn
* after a timeout.
*
* @param {string|number|Date} new_date New starting date. Strings can be in
* any format understood by et2_widget_date, or Ymd (eg: 20160101).
* @returns {undefined} * @returns {undefined}
* *
* @memberOf et2_calendar_view * @memberOf et2_calendar_view
@ -143,7 +160,11 @@ var et2_calendar_view = et2_valueWidget.extend(
/** /**
* Change the end date * Change the end date
* *
* @param {string|number|Date} new_date New end date * Changing the end date will invalidate the display, and it will be redrawn
* after a timeout.
*
* @param {string|number|Date} new_date - New end date. Strings can be in
* any format understood by et2_widget_date, or Ymd (eg: 20160101).
* @returns {undefined} * @returns {undefined}
* *
* @memberOf et2_calendar_view * @memberOf et2_calendar_view
@ -178,7 +199,13 @@ var et2_calendar_view = et2_valueWidget.extend(
/** /**
* Set which users to display * Set which users to display
* *
* @param {number|number[]|string|string[]} _owner Account ID * Changing the owner will invalidate the display, and it will be redrawn
* after a timeout.
*
* @param {number|number[]|string|string[]} _owner - Owner ID, which can
* be an account ID, a resource ID (as defined in calendar_bo, not
* necessarily an entry from the resource app), or a list containing a
* combination of both.
* *
* @memberOf et2_calendar_view * @memberOf et2_calendar_view
*/ */
@ -207,6 +234,60 @@ var et2_calendar_view = et2_valueWidget.extend(
} }
}, },
/**
* Provide specific data to be displayed.
* This is a way to set start and end dates, owner and event data in one call.
*
* If events are not provided in the array,
* @param {Object[]} events Array of events, indexed by date in Ymd format:
* {
* 20150501: [...],
* 20150502: [...]
* }
* Days should be in order.
* @param {string|number|Date} events.start_date - New start date
* @param {string|number|Date} events.end_date - New end date
* @param {number|number[]|string|string[]} event.owner - Owner ID, which can
* be an account ID, a resource ID (as defined in calendar_bo, not
* necessarily an entry from the resource app), or a list containing a
* combination of both.
*/
set_value: function set_value(events)
{
if(typeof events !== 'object') return false;
if(events.id)
{
this.set_id(events.id);
delete events.id;
}
if(events.start_date)
{
this.set_start_date(events.start_date);
delete events.start_date;
}
if(events.end_date)
{
this.set_end_date(events.end_date);
delete events.end_date;
}
// set_owner() wants start_date set to get the correct week number
// for the corner label
if(events.owner)
{
this.set_owner(events.owner);
delete events.owner;
}
this.value = events || {};
// None of the above changed anything, hide the loader
if(!this.update_timer)
{
window.setTimeout(jQuery.proxy(function() {this.loader.hide();},this),100);
}
},
/** /**
* Calendar supports many different owner types, including users & resources. * Calendar supports many different owner types, including users & resources.
* This translates an ID to a user-friendly name. * This translates an ID to a user-friendly name.
@ -252,5 +333,84 @@ var et2_calendar_view = et2_valueWidget.extend(
} }
} }
return user; return user;
},
/**
* Find the event information linked to a given DOM node
*
* @param {HTMLElement} dom_node - It should have something to do with an event
* @returns {Object}
*/
_get_event_info: function _get_event_info(dom_node)
{
// Determine as much relevant info as can be found
var event_node = $j(dom_node).closest('[data-id]',this.div)[0];
var day_node = $j(event_node).closest('[data-date]',this.div)[0];
// Widget ID should be the DOM node ID without the event_ prefix
var widget_id = event_node.id || '';
widget_id = widget_id.split('event_');
widget_id.shift();
return jQuery.extend({
event_node: event_node,
day_node: day_node,
widget_id: 'event_' + widget_id.join('')
},
event_node ? event_node.dataset : {},
day_node ? day_node.dataset : {}
);
},
});
// Static class stuff
jQuery.extend(et2_calendar_view,
{
holiday_cache: {},
/**
* Fetch and cache a list of the year's holidays
*
* @param {et2_calendar_timegrid} widget
* @param {string|numeric} year
* @returns {Array}
*/
get_holidays: function(widget,year)
{
// Loaded in an iframe or something
if(!egw.window.et2_calendar_view) return {};
var cache = egw.window.et2_calendar_view.holiday_cache[year];
if (typeof cache == 'undefined')
{
// Fetch with json instead of jsonq because there may be more than
// one widget listening for the response by the time it gets back,
// and we can't do that when it's queued.
egw.window.et2_calendar_view.holiday_cache[year] = egw.json(
'calendar_timegrid_etemplate_widget::ajax_get_holidays',
[year]
).sendRequest(true);
}
cache = egw.window.et2_calendar_view.holiday_cache[year];
if(typeof cache.done == 'function')
{
// pending, wait for it
cache.done(jQuery.proxy(function(response) {
egw.window.et2_calendar_view.holiday_cache[this.year] = response.response[0].data||undefined;
egw.window.setTimeout(jQuery.proxy(function() {
// Make sure widget hasn't been destroyed while we wait
if(typeof this.widget.free == 'undefined')
{
this.widget.day_class_holiday();
}
},this),1);
},{widget:widget,year:year}));
return {};
}
else
{
return cache;
}
} }
}); });