mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-22 06:30:59 +01:00
WIP Calendar to typescript
This commit is contained in:
parent
b03e8d167e
commit
e68071a073
432
calendar/js/View.ts
Normal file
432
calendar/js/View.ts
Normal file
@ -0,0 +1,432 @@
|
|||||||
|
/**
|
||||||
|
* Super class for the different views.
|
||||||
|
*
|
||||||
|
* Each separate view overrides what it needs
|
||||||
|
*/
|
||||||
|
import {etemplate2} from "../../api/js/etemplate/etemplate2";
|
||||||
|
|
||||||
|
export abstract class View
|
||||||
|
{
|
||||||
|
// List of etemplates to show for this view
|
||||||
|
public static etemplates : (string | etemplate2)[] = ['calendar.view'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translated label for header
|
||||||
|
* @param {Object} state
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static header(state)
|
||||||
|
{
|
||||||
|
let formatDate = new Date(state.date);
|
||||||
|
formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000);
|
||||||
|
return View._owner(state) + date(egw.preference('dateformat'), formatDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If one owner, get the owner text
|
||||||
|
*
|
||||||
|
* @param {object} state
|
||||||
|
*/
|
||||||
|
static _owner(state)
|
||||||
|
{
|
||||||
|
let owner = '';
|
||||||
|
if(state.owner.length && state.owner.length == 1 && app.calendar.sidebox_et2)
|
||||||
|
{
|
||||||
|
var own = app.calendar.sidebox_et2.getWidgetById('owner').getDOMNode();
|
||||||
|
if(own.selectedIndex >= 0)
|
||||||
|
{
|
||||||
|
owner = own.options[own.selectedIndex].innerHTML + ": ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the start date for this view
|
||||||
|
* @param {Object} state
|
||||||
|
* @returns {Date}
|
||||||
|
*/
|
||||||
|
static start_date(state)
|
||||||
|
{
|
||||||
|
const d = state.date ? new Date(state.date) : new Date();
|
||||||
|
d.setUTCHours(0);
|
||||||
|
d.setUTCMinutes(0);
|
||||||
|
d.setUTCSeconds(0);
|
||||||
|
d.setUTCMilliseconds(0);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the end date for this view
|
||||||
|
* @param {Object} state
|
||||||
|
* @returns {Date}
|
||||||
|
*/
|
||||||
|
static end_date(state)
|
||||||
|
{
|
||||||
|
const d = state.date ? new Date(state.date) : new Date();
|
||||||
|
d.setUTCHours(23);
|
||||||
|
d.setUTCMinutes(59);
|
||||||
|
d.setUTCSeconds(59);
|
||||||
|
d.setUTCMilliseconds(0);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the owner for this view
|
||||||
|
*
|
||||||
|
* This is always the owner from the given state, we use a function
|
||||||
|
* to trigger setting the widget value.
|
||||||
|
*
|
||||||
|
* @param {number[]|String} state state.owner List of owner IDs, or a comma seperated list
|
||||||
|
* @returns {number[]|String}
|
||||||
|
*/
|
||||||
|
static owner(state)
|
||||||
|
{
|
||||||
|
return state.owner || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should the view show the weekends
|
||||||
|
*
|
||||||
|
* @param {object} state
|
||||||
|
* @returns {boolean} Current preference to show 5 or 7 days in weekview
|
||||||
|
*/
|
||||||
|
static show_weekend(state)
|
||||||
|
{
|
||||||
|
return state.weekend;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How big or small are the displayed time chunks?
|
||||||
|
*
|
||||||
|
* @param {object} state
|
||||||
|
*/
|
||||||
|
static granularity(state)
|
||||||
|
{
|
||||||
|
var list = egw.preference('use_time_grid', 'calendar');
|
||||||
|
if(list == '0' || typeof list === 'undefined')
|
||||||
|
{
|
||||||
|
return parseInt('' + egw.preference('interval', 'calendar')) || 30;
|
||||||
|
}
|
||||||
|
if(typeof list == 'string') list = list.split(',');
|
||||||
|
if(!(<string><unknown>list).indexOf && jQuery.isPlainObject(list))
|
||||||
|
{
|
||||||
|
list = jQuery.map(list, function (el)
|
||||||
|
{
|
||||||
|
return el;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return list.indexOf(state.view) >= 0 ?
|
||||||
|
0 :
|
||||||
|
parseInt(egw.preference('interval', 'calendar')) || 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
static extend(sub)
|
||||||
|
{
|
||||||
|
return jQuery.extend({}, this, {_super: this}, sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the new date after scrolling. The default is 1 week.
|
||||||
|
*
|
||||||
|
* @param {number} delta Integer for how many 'ticks' to move, positive for
|
||||||
|
* forward, negative for backward
|
||||||
|
* @returns {Date}
|
||||||
|
*/
|
||||||
|
static scroll(delta)
|
||||||
|
{
|
||||||
|
var d = new Date(app.calendar.state.date);
|
||||||
|
d.setUTCDate(d.getUTCDate() + (7 * delta));
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Etemplates and settings for the different views. Some (day view)
|
||||||
|
* use more than one template, some use the same template as others,
|
||||||
|
* most need different handling for their various attributes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class day extends View
|
||||||
|
{
|
||||||
|
public static etemplates : (string | etemplate2)[] = ['calendar.view', 'calendar.todo'];
|
||||||
|
|
||||||
|
static header(state)
|
||||||
|
{
|
||||||
|
var formatDate = new Date(state.date);
|
||||||
|
formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000);
|
||||||
|
return date('l, ', formatDate) + super.header(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static start_date(state)
|
||||||
|
{
|
||||||
|
var d = super.start_date(state);
|
||||||
|
state.date = app.calendar.date.toString(d);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static show_weekend(state)
|
||||||
|
{
|
||||||
|
state.days = '1';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static scroll(delta)
|
||||||
|
{
|
||||||
|
var d = new Date(app.calendar.state.date);
|
||||||
|
d.setUTCDate(d.getUTCDate() + (delta));
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class day4 extends View
|
||||||
|
{
|
||||||
|
static end_date(state)
|
||||||
|
{
|
||||||
|
var d = super.end_date(state);
|
||||||
|
state.days = '4';
|
||||||
|
d.setUTCHours(24 * 4 - 1);
|
||||||
|
d.setUTCMinutes(59);
|
||||||
|
d.setUTCSeconds(59);
|
||||||
|
d.setUTCMilliseconds(0);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static show_weekend(state)
|
||||||
|
{
|
||||||
|
state.weekend = 'true';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static scroll(delta)
|
||||||
|
{
|
||||||
|
var d = new Date(app.calendar.state.date);
|
||||||
|
d.setUTCDate(d.getUTCDate() + (4 * delta));
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class week extends View
|
||||||
|
{
|
||||||
|
static header(state)
|
||||||
|
{
|
||||||
|
var end_date = state.last;
|
||||||
|
if(!week.show_weekend(state))
|
||||||
|
{
|
||||||
|
end_date = new Date(state.last);
|
||||||
|
end_date.setUTCDate(end_date.getUTCDate() - 2);
|
||||||
|
}
|
||||||
|
return super._owner(state) + app.calendar.egw.lang('Week') + ' ' +
|
||||||
|
app.calendar.date.week_number(state.first) + ': ' +
|
||||||
|
app.calendar.date.long_date(state.first, end_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
static start_date(state)
|
||||||
|
{
|
||||||
|
return app.calendar.date.start_of_week(super.start_date(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
static end_date(state)
|
||||||
|
{
|
||||||
|
var d = app.calendar.date.start_of_week(state.date || new Date());
|
||||||
|
// Always 7 days, we just turn weekends on or off
|
||||||
|
d.setUTCHours(24 * 7 - 1);
|
||||||
|
d.setUTCMinutes(59);
|
||||||
|
d.setUTCSeconds(59);
|
||||||
|
d.setUTCMilliseconds(0);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class weekN extends View
|
||||||
|
{
|
||||||
|
static header(state)
|
||||||
|
{
|
||||||
|
return super._owner(state) + app.calendar.egw.lang('Week') + ' ' +
|
||||||
|
app.calendar.date.week_number(state.first) + ' - ' +
|
||||||
|
app.calendar.date.week_number(state.last) + ': ' +
|
||||||
|
app.calendar.date.long_date(state.first, state.last);
|
||||||
|
}
|
||||||
|
|
||||||
|
static start_date(state)
|
||||||
|
{
|
||||||
|
return app.calendar.date.start_of_week(super.start_date(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
static end_date(state)
|
||||||
|
{
|
||||||
|
state.days = '' + (state.days >= 5 ? state.days : egw.preference('days_in_weekview', 'calendar') || 7);
|
||||||
|
|
||||||
|
var d = app.calendar.date.start_of_week(app.calendar.View.start_date.call(this, state));
|
||||||
|
// Always 7 days, we just turn weekends on or off
|
||||||
|
d.setUTCHours(24 * 7 * (parseInt(this.egw.preference('multiple_weeks', 'calendar')) || 3) - 1);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class month extends View
|
||||||
|
{
|
||||||
|
static header(state)
|
||||||
|
{
|
||||||
|
var formatDate = new Date(state.date);
|
||||||
|
formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000);
|
||||||
|
return super._owner(state) + app.calendar.egw.lang(date('F', formatDate)) + ' ' + date('Y', formatDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static start_date(state)
|
||||||
|
{
|
||||||
|
var d = super.start_date(state);
|
||||||
|
d.setUTCDate(1);
|
||||||
|
return app.calendar.date.start_of_week(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
static end_date(state)
|
||||||
|
{
|
||||||
|
var d = super.end_date(state);
|
||||||
|
d = new Date(d.getFullYear(), d.getUTCMonth() + 1, 1, 0, -d.getTimezoneOffset(), 0);
|
||||||
|
d.setUTCSeconds(d.getUTCSeconds() - 1);
|
||||||
|
return app.calendar.date.end_of_week(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
static scroll(delta)
|
||||||
|
{
|
||||||
|
var d = new Date(app.calendar.state.date);
|
||||||
|
// Set day to 15 so we don't get overflow on short months
|
||||||
|
// eg. Aug 31 + 1 month = Sept 31 -> Oct 1
|
||||||
|
d.setUTCDate(15);
|
||||||
|
d.setUTCMonth(d.getUTCMonth() + delta);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class planner extends View
|
||||||
|
{
|
||||||
|
public static etemplates : (string | etemplate2)[] = ['calendar.planner'];
|
||||||
|
|
||||||
|
static header(state)
|
||||||
|
{
|
||||||
|
var startDate = new Date(state.first);
|
||||||
|
startDate = new Date(startDate.valueOf() + startDate.getTimezoneOffset() * 60 * 1000);
|
||||||
|
|
||||||
|
var endDate = new Date(state.last);
|
||||||
|
endDate = new Date(endDate.valueOf() + endDate.getTimezoneOffset() * 60 * 1000);
|
||||||
|
return super._owner(state) + date(egw.preference('dateformat'), startDate) +
|
||||||
|
(startDate == endDate ? '' : ' - ' + date(egw.preference('dateformat'), endDate));
|
||||||
|
}
|
||||||
|
|
||||||
|
static group_by(state)
|
||||||
|
{
|
||||||
|
return state.sortby ? state.sortby : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Planner uses the additional value of planner_view to determine
|
||||||
|
// the start & end dates using other view's functions
|
||||||
|
static start_date(state)
|
||||||
|
{
|
||||||
|
// Start here, in case we can't find anything better
|
||||||
|
var d = super.start_date( state);
|
||||||
|
|
||||||
|
if(state.sortby && state.sortby === 'month')
|
||||||
|
{
|
||||||
|
d.setUTCDate(1);
|
||||||
|
}
|
||||||
|
else if(state.planner_view && app.classes.calendar.views[state.planner_view])
|
||||||
|
{
|
||||||
|
d = app.classes.calendar.views[state.planner_view].start_date.call(this, state);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d = app.calendar.date.start_of_week(d);
|
||||||
|
d.setUTCHours(0);
|
||||||
|
d.setUTCMinutes(0);
|
||||||
|
d.setUTCSeconds(0);
|
||||||
|
d.setUTCMilliseconds(0);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static end_date(state)
|
||||||
|
{
|
||||||
|
|
||||||
|
var d = super.end_date( state);
|
||||||
|
if(state.sortby && state.sortby === 'month')
|
||||||
|
{
|
||||||
|
d.setUTCDate(0);
|
||||||
|
d.setUTCFullYear(d.getUTCFullYear() + 1);
|
||||||
|
}
|
||||||
|
else if(state.planner_view && app.classes.calendar.views[state.planner_view])
|
||||||
|
{
|
||||||
|
d = app.classes.calendar.views[state.planner_view].end_date(state);
|
||||||
|
}
|
||||||
|
else if(state.days)
|
||||||
|
{
|
||||||
|
// This one comes from a grid view, but we'll use it
|
||||||
|
d.setUTCDate(d.getUTCDate() + parseInt(state.days) - 1);
|
||||||
|
delete state.days;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d = app.calendar.date.end_of_week(d);
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static hide_empty(state)
|
||||||
|
{
|
||||||
|
var check = state.sortby == 'user' ? ['user', 'both'] : ['cat', 'both'];
|
||||||
|
return (check.indexOf(egw.preference('planner_show_empty_rows', 'calendar') + '') === -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static scroll(delta)
|
||||||
|
{
|
||||||
|
if(app.calendar.state.planner_view)
|
||||||
|
{
|
||||||
|
return app.classes.calendar.views[app.calendar.state.planner_view].scroll(delta);
|
||||||
|
}
|
||||||
|
var d = new Date(app.calendar.state.date);
|
||||||
|
var days = 1;
|
||||||
|
|
||||||
|
// Yearly view, grouped by month - scroll 1 month
|
||||||
|
if(app.calendar.state.sortby === 'month')
|
||||||
|
{
|
||||||
|
d.setUTCMonth(d.getUTCMonth() + delta);
|
||||||
|
d.setUTCDate(1);
|
||||||
|
d.setUTCHours(0);
|
||||||
|
d.setUTCMinutes(0);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
// Need to set the day count, or auto date ranging takes over and
|
||||||
|
// makes things buggy
|
||||||
|
if(app.calendar.state.first && app.calendar.state.last)
|
||||||
|
{
|
||||||
|
var diff = new Date(app.calendar.state.last) - new Date(app.calendar.state.first);
|
||||||
|
days = Math.round(diff / (1000 * 3600 * 24));
|
||||||
|
}
|
||||||
|
d.setUTCDate(d.getUTCDate() + (days * delta));
|
||||||
|
if(days > 8)
|
||||||
|
{
|
||||||
|
d = app.calendar.date.start_of_week(d);
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class listview extends View
|
||||||
|
{
|
||||||
|
public static etemplates : (string | etemplate2)[] = ['calendar.list'];
|
||||||
|
|
||||||
|
static header(state)
|
||||||
|
{
|
||||||
|
var startDate = new Date(state.first || state.date);
|
||||||
|
startDate = new Date(startDate.valueOf() + startDate.getTimezoneOffset() * 60 * 1000);
|
||||||
|
var start_check = '' + startDate.getFullYear() + startDate.getMonth() + startDate.getDate();
|
||||||
|
|
||||||
|
var endDate = new Date(state.last || state.date);
|
||||||
|
endDate = new Date(endDate.valueOf() + endDate.getTimezoneOffset() * 60 * 1000);
|
||||||
|
var end_check = '' + endDate.getFullYear() + endDate.getMonth() + endDate.getDate();
|
||||||
|
return super._owner(state) +
|
||||||
|
date(egw.preference('dateformat'), startDate) +
|
||||||
|
(start_check == end_check ? '' : ' - ' + date(egw.preference('dateformat'), endDate));
|
||||||
|
}
|
||||||
|
}
|
1221
calendar/js/et2_widget_daycol.ts
Normal file
1221
calendar/js/et2_widget_daycol.ts
Normal file
File diff suppressed because it is too large
Load Diff
1317
calendar/js/et2_widget_event.ts
Normal file
1317
calendar/js/et2_widget_event.ts
Normal file
File diff suppressed because it is too large
Load Diff
142
calendar/js/et2_widget_owner.ts
Normal file
142
calendar/js/et2_widget_owner.ts
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
* Egroupware
|
||||||
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||||
|
* @package
|
||||||
|
* @subpackage
|
||||||
|
* @link http://www.egroupware.org
|
||||||
|
* @author Nathan Gray
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*egw:uses
|
||||||
|
et2_widget_taglist;
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {et2_register_widget} from "../../api/js/etemplate/et2_core_widget";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag list widget customised for calendar owner, which can be a user
|
||||||
|
* account or group, or an entry from almost any app, or an email address
|
||||||
|
*
|
||||||
|
* A cross between auto complete, selectbox and chosen multiselect
|
||||||
|
*
|
||||||
|
* Uses MagicSuggest library
|
||||||
|
* @see http://nicolasbize.github.io/magicsuggest/
|
||||||
|
* @augments et2_selectbox
|
||||||
|
*/
|
||||||
|
export class et2_calendar_owner extends et2_taglist_email
|
||||||
|
{
|
||||||
|
static readonly _attributes = {
|
||||||
|
"autocomplete_url": {
|
||||||
|
"default": "calendar_owner_etemplate_widget::ajax_owner"
|
||||||
|
},
|
||||||
|
"autocomplete_params": {
|
||||||
|
"name": "Autocomplete parameters",
|
||||||
|
"type": "any",
|
||||||
|
"default": {},
|
||||||
|
"description": "Extra parameters passed to autocomplete URL. It should be a stringified JSON object."
|
||||||
|
},
|
||||||
|
allowFreeEntries: {
|
||||||
|
"default": false,
|
||||||
|
ignore: true
|
||||||
|
},
|
||||||
|
select_options: {
|
||||||
|
"type": "any",
|
||||||
|
"name": "Select options",
|
||||||
|
// Set to empty object to use selectbox's option finding
|
||||||
|
"default": {},
|
||||||
|
"description": "Internally used to hold the select options."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allows sub-widgets to override options to the library
|
||||||
|
lib_options = {
|
||||||
|
autoSelect: false,
|
||||||
|
groupBy: 'app',
|
||||||
|
minChars: 2,
|
||||||
|
selectFirst: true,
|
||||||
|
// This option will also expand when the selection is changed
|
||||||
|
// via code, which we do not want
|
||||||
|
//expandOnFocus: true
|
||||||
|
toggleOnClick: true
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
doLoadingFinished()
|
||||||
|
{
|
||||||
|
super.doLoadingFinished();
|
||||||
|
|
||||||
|
var widget = this;
|
||||||
|
// onChange fired when losing focus, which is different from normal
|
||||||
|
this._oldValue = this.taglist.getValue();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectionRenderer(item)
|
||||||
|
{
|
||||||
|
if(this && this.options && this.options.allowFreeEntries)
|
||||||
|
{
|
||||||
|
return super.selectionRenderer(item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var label = jQuery('<span>').text(item.label);
|
||||||
|
if (item.class) label.addClass(item.class);
|
||||||
|
if (typeof item.title != 'undefined') label.attr('title', item.title);
|
||||||
|
if (typeof item.data != 'undefined') label.attr('data', item.data);
|
||||||
|
if (typeof item.icon != 'undefined')
|
||||||
|
{
|
||||||
|
var wrapper = jQuery('<div>').addClass('et2_taglist_tags_icon_wrapper');
|
||||||
|
jQuery('<span/>')
|
||||||
|
.addClass('et2_taglist_tags_icon')
|
||||||
|
.css({"background-image": "url("+(item.icon.match(/^(http|https|\/)/) ? item.icon : egw.image(item.icon, item.app))+")"})
|
||||||
|
.appendTo(wrapper);
|
||||||
|
label.appendTo(wrapper);
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue()
|
||||||
|
{
|
||||||
|
if(this.taglist == null) return null;
|
||||||
|
return this.taglist.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override parent to handle our special additional data types (c#,r#,etc.) when they
|
||||||
|
* are not available client side.
|
||||||
|
*
|
||||||
|
* @param {string|string[]} _value array of selected owners, which can be a number,
|
||||||
|
* or a number prefixed with one character indicating the resource type.
|
||||||
|
*/
|
||||||
|
set_value(_value)
|
||||||
|
{
|
||||||
|
super.set_value(_value);
|
||||||
|
|
||||||
|
// If parent didn't find a label, label will be the same as ID so we
|
||||||
|
// can find them that way
|
||||||
|
for(var i = 0; i < this.options.value.length; i++)
|
||||||
|
{
|
||||||
|
var value = this.options.value[i];
|
||||||
|
if(value.id == value.label)
|
||||||
|
{
|
||||||
|
// Proper label was not fount by parent - ask directly
|
||||||
|
egw.json('calendar_owner_etemplate_widget::ajax_owner',value.id,function(data) {
|
||||||
|
this.widget.options.value[this.i].label = data;
|
||||||
|
this.widget.set_value(this.widget.options.value);
|
||||||
|
}, this,true,{widget: this, i: i}).sendRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.taglist)
|
||||||
|
{
|
||||||
|
this.taglist.clear(true);
|
||||||
|
this.taglist.addToSelection(this.options.value,true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
et2_register_widget(et2_calendar_owner, ["calendar-owner"]);
|
2500
calendar/js/et2_widget_planner.ts
Normal file
2500
calendar/js/et2_widget_planner.ts
Normal file
File diff suppressed because it is too large
Load Diff
839
calendar/js/et2_widget_planner_row.ts
Normal file
839
calendar/js/et2_widget_planner_row.ts
Normal file
@ -0,0 +1,839 @@
|
|||||||
|
/*
|
||||||
|
* Egroupware
|
||||||
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||||
|
* @package
|
||||||
|
* @subpackage
|
||||||
|
* @link http://www.egroupware.org
|
||||||
|
* @author Nathan Gray
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*egw:uses
|
||||||
|
/calendar/js/et2_widget_view.js;
|
||||||
|
/calendar/js/et2_widget_daycol.js;
|
||||||
|
/calendar/js/et2_widget_event.js;
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import {et2_register_widget, WidgetConfig} from "../../api/js/etemplate/et2_core_widget";
|
||||||
|
import {et2_valueWidget} from "../../api/js/etemplate/et2_core_valueWidget";
|
||||||
|
import {ClassWithAttributes} from "../../api/js/etemplate/et2_core_inheritance";
|
||||||
|
import {et2_date} from "../../api/js/etemplate/et2_widget_date";
|
||||||
|
import {et2_action_object_impl} from "../../api/js/etemplate/et2_core_DOMWidget";
|
||||||
|
import {et2_calendar_planner} from "./et2_widget_planner";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for one row of a planner
|
||||||
|
*
|
||||||
|
* This widget is responsible for the label on the side
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export class et2_calendar_planner_row extends et2_valueWidget implements et2_IResizeable
|
||||||
|
{
|
||||||
|
static readonly _attributes: any = {
|
||||||
|
start_date: {
|
||||||
|
name: "Start date",
|
||||||
|
type: "any"
|
||||||
|
},
|
||||||
|
end_date: {
|
||||||
|
name: "End date",
|
||||||
|
type: "any"
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: "any"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private div: JQuery;
|
||||||
|
private title: JQuery;
|
||||||
|
private rows: JQuery;
|
||||||
|
private _date_helper: et2_date;
|
||||||
|
private _cached_rows: any[];
|
||||||
|
private _row_height = 20;
|
||||||
|
private _actionObject: egwActionObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
constructor(_parent, _attrs? : WidgetConfig, _child? : object)
|
||||||
|
{
|
||||||
|
// Call the inherited constructor
|
||||||
|
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_calendar_planner_row._attributes, _child || {}));
|
||||||
|
|
||||||
|
// Main container
|
||||||
|
this.div = jQuery(document.createElement("div"))
|
||||||
|
.addClass("calendar_plannerRowWidget")
|
||||||
|
.css('width',this.options.width);
|
||||||
|
this.title = jQuery(document.createElement('div'))
|
||||||
|
.addClass("calendar_plannerRowHeader")
|
||||||
|
.appendTo(this.div);
|
||||||
|
this.rows = jQuery(document.createElement('div'))
|
||||||
|
.addClass("calendar_eventRows")
|
||||||
|
.appendTo(this.div);
|
||||||
|
|
||||||
|
this.setDOMNode(this.div[0]);
|
||||||
|
|
||||||
|
// Used for its date calculations
|
||||||
|
this._date_helper = et2_createWidget('date-time', {}, null);
|
||||||
|
this._date_helper.loadingFinished();
|
||||||
|
|
||||||
|
this.set_start_date(this.options.start_date);
|
||||||
|
this.set_end_date(this.options.end_date);
|
||||||
|
|
||||||
|
this._cached_rows = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
doLoadingFinished( )
|
||||||
|
{
|
||||||
|
super.doLoadingFinished();
|
||||||
|
|
||||||
|
this.set_label(this.options.label);
|
||||||
|
this._draw();
|
||||||
|
|
||||||
|
// Actions are set on the parent, so we need to explicitly get in here
|
||||||
|
// and get ours
|
||||||
|
this._link_actions(this.getParent().options.actions || []);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy( )
|
||||||
|
{
|
||||||
|
super.destroy();
|
||||||
|
|
||||||
|
// date_helper has no parent, so we must explicitly remove it
|
||||||
|
this._date_helper.destroy();
|
||||||
|
this._date_helper = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDOMNode(_sender)
|
||||||
|
{
|
||||||
|
if(_sender === this || !_sender)
|
||||||
|
{
|
||||||
|
return this.div[0];
|
||||||
|
}
|
||||||
|
if(_sender._parent === this)
|
||||||
|
{
|
||||||
|
return this.rows[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link the actions to the DOM nodes / widget bits.
|
||||||
|
*
|
||||||
|
* @param {object} actions {ID: {attributes..}+} map of egw action information
|
||||||
|
*/
|
||||||
|
_link_actions(actions)
|
||||||
|
{
|
||||||
|
// Get the parent? Might be a grid row, might not. Either way, it is
|
||||||
|
// just a container with no valid actions
|
||||||
|
let objectManager = egw_getObjectManager(this.getInstanceManager().app, true, 1);
|
||||||
|
objectManager = objectManager.getObjectById(this.getInstanceManager().uniqueId,2) || objectManager;
|
||||||
|
let parent = objectManager.getObjectById(this.id, 1) || objectManager.getObjectById(this.getParent().id, 1) || objectManager;
|
||||||
|
if(!parent)
|
||||||
|
{
|
||||||
|
egw.debug('error','No parent objectManager found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This binds into the egw action system. Most user interactions (drag to move, resize)
|
||||||
|
// are handled internally using jQuery directly.
|
||||||
|
let widget_object = this._actionObject || parent.getObjectById(this.id);
|
||||||
|
const aoi = new et2_action_object_impl(this, this.getDOMNode(this)).getAOI();
|
||||||
|
const planner = this.getParent();
|
||||||
|
|
||||||
|
for(let i = 0; i < parent.children.length; i++)
|
||||||
|
{
|
||||||
|
const parent_finder = jQuery(parent.children[i].iface.doGetDOMNode()).find(this.div);
|
||||||
|
if(parent_finder.length > 0)
|
||||||
|
{
|
||||||
|
parent = parent.children[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if we allow a dropped event to use the invite/change actions
|
||||||
|
const _invite_enabled = function (action, event, target)
|
||||||
|
{
|
||||||
|
var event = event.iface.getWidget();
|
||||||
|
const row = target.iface.getWidget() || false;
|
||||||
|
if(event === row || !event || !row ||
|
||||||
|
!event.options || !event.options.value.participants
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let owner_match = false;
|
||||||
|
const own_row = event.getParent() === row;
|
||||||
|
|
||||||
|
for (let id in event.options.value.participants)
|
||||||
|
{
|
||||||
|
owner_match = owner_match || row.node.dataset.participants === '' + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const enabled = !owner_match &&
|
||||||
|
// Not inside its own timegrid
|
||||||
|
!own_row;
|
||||||
|
|
||||||
|
widget_object.getActionLink('invite').enabled = enabled;
|
||||||
|
widget_object.getActionLink('change_participant').enabled = enabled;
|
||||||
|
|
||||||
|
// If invite or change participant are enabled, drag is not
|
||||||
|
widget_object.getActionLink('egw_link_drop').enabled = !enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
aoi.doTriggerEvent = function(_event, _data) {
|
||||||
|
|
||||||
|
// Determine target node
|
||||||
|
var event = _data.event || false;
|
||||||
|
if(!event) return;
|
||||||
|
if(_data.ui.draggable.hasClass('rowNoEdit')) return;
|
||||||
|
/*
|
||||||
|
We have to handle the drop in the normal event stream instead of waiting
|
||||||
|
for the egwAction system so we can get the helper, and destination
|
||||||
|
*/
|
||||||
|
if(event.type === 'drop' && widget_object.getActionLink('egw_link_drop').enabled)
|
||||||
|
{
|
||||||
|
this.getWidget().getParent()._event_drop.call(
|
||||||
|
jQuery('.calendar_d-n-d_timeCounter',_data.ui.helper)[0],
|
||||||
|
this.getWidget().getParent(), event, _data.ui,
|
||||||
|
this.getWidget()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const drag_listener = function (_event, ui)
|
||||||
|
{
|
||||||
|
if(planner.options.group_by === 'month')
|
||||||
|
{
|
||||||
|
var position = {left: _event.clientX, top: _event.clientY};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var position = {top: ui.position.top, left: ui.position.left - jQuery(this).parent().offset().left};
|
||||||
|
}
|
||||||
|
aoi.getWidget().getParent()._drag_helper(
|
||||||
|
jQuery('.calendar_d-n-d_timeCounter', ui.helper)[0],
|
||||||
|
position, 0
|
||||||
|
);
|
||||||
|
|
||||||
|
let event = _data.ui.draggable.data('selected')[0];
|
||||||
|
if(!event || event.id && event.id.indexOf('calendar') !== 0)
|
||||||
|
{
|
||||||
|
event = false;
|
||||||
|
}
|
||||||
|
if(event)
|
||||||
|
{
|
||||||
|
_invite_enabled(
|
||||||
|
widget_object.getActionLink('invite').actionObj,
|
||||||
|
event,
|
||||||
|
widget_object
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const time = jQuery('.calendar_d-n-d_timeCounter', _data.ui.helper);
|
||||||
|
switch(_event)
|
||||||
|
{
|
||||||
|
// Triggered once, when something is dragged into the timegrid's div
|
||||||
|
case EGW_AI_DRAG_OVER:
|
||||||
|
// Listen to the drag and update the helper with the time
|
||||||
|
// This part lets us drag between different timegrids
|
||||||
|
_data.ui.draggable.on('drag.et2_timegrid_row'+widget_object.id, drag_listener);
|
||||||
|
_data.ui.draggable.on('dragend.et2_timegrid_row'+widget_object.id, function() {
|
||||||
|
_data.ui.draggable.off('drag.et2_timegrid_row' + widget_object.id);
|
||||||
|
});
|
||||||
|
widget_object.iface.getWidget().div.addClass('drop-hover');
|
||||||
|
|
||||||
|
// Disable invite / change actions for same calendar or already participant
|
||||||
|
var event = _data.ui.draggable.data('selected')[0];
|
||||||
|
if(!event || event.id && event.id.indexOf('calendar') !== 0)
|
||||||
|
{
|
||||||
|
event = false;
|
||||||
|
}
|
||||||
|
if(event)
|
||||||
|
{
|
||||||
|
_invite_enabled(
|
||||||
|
widget_object.getActionLink('invite').actionObj,
|
||||||
|
event,
|
||||||
|
widget_object
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if(time.length)
|
||||||
|
{
|
||||||
|
// The out will trigger after the over, so we count
|
||||||
|
time.data('count',time.data('count')+1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_data.ui.helper.prepend('<div class="calendar_d-n-d_timeCounter" data-count="1"><span></span></div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Triggered once, when something is dragged out of the timegrid
|
||||||
|
case EGW_AI_DRAG_OUT:
|
||||||
|
// Stop listening
|
||||||
|
_data.ui.draggable.off('drag.et2_timegrid_row'+widget_object.id);
|
||||||
|
// Remove highlight
|
||||||
|
widget_object.iface.getWidget().div.removeClass('drop-hover');
|
||||||
|
|
||||||
|
// Out triggers after the over, count to not accidentally remove
|
||||||
|
time.data('count',time.data('count')-1);
|
||||||
|
if(time.length && time.data('count') <= 0)
|
||||||
|
{
|
||||||
|
time.remove();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (widget_object == null) {
|
||||||
|
// Add a new container to the object manager which will hold the widget
|
||||||
|
// objects
|
||||||
|
widget_object = parent.insertObject(false, new egwActionObject(
|
||||||
|
this.id, parent, aoi,
|
||||||
|
this._actionManager|| parent.manager.getActionById(this.id) || parent.manager
|
||||||
|
));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
widget_object.setAOI(aoi);
|
||||||
|
}
|
||||||
|
this._actionObject = widget_object;
|
||||||
|
|
||||||
|
// Delete all old objects
|
||||||
|
widget_object.clear();
|
||||||
|
widget_object.unregisterActions();
|
||||||
|
|
||||||
|
// Go over the widget & add links - this is where we decide which actions are
|
||||||
|
// 'allowed' for this widget at this time
|
||||||
|
const action_links = this._get_action_links(actions);
|
||||||
|
|
||||||
|
this.getParent()._init_links_dnd(widget_object.manager, action_links);
|
||||||
|
|
||||||
|
widget_object.updateActionLinks(action_links);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all action-links / id's of 1.-level actions from a given action object
|
||||||
|
*
|
||||||
|
* Here we are only interested in drop events.
|
||||||
|
*
|
||||||
|
* @param actions
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
_get_action_links(actions)
|
||||||
|
{
|
||||||
|
const action_links = [];
|
||||||
|
|
||||||
|
// Only these actions are allowed without a selection (empty actions)
|
||||||
|
const empty_actions = ['add'];
|
||||||
|
|
||||||
|
for(let i in actions)
|
||||||
|
{
|
||||||
|
const action = actions[i];
|
||||||
|
if(empty_actions.indexOf(action.id) !== -1 || action.type == 'drop')
|
||||||
|
{
|
||||||
|
action_links.push(typeof action.id != 'undefined' ? action.id : i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return action_links;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the individual divs for weekends and events
|
||||||
|
*/
|
||||||
|
_draw( )
|
||||||
|
{
|
||||||
|
// Remove any existing
|
||||||
|
this.rows.remove('.calendar_eventRowsMarkedDay,.calendar_eventRowsFiller').nextAll().remove();
|
||||||
|
|
||||||
|
let days = 31;
|
||||||
|
let width = '100';
|
||||||
|
if (this.getParent().options.group_by === 'month')
|
||||||
|
{
|
||||||
|
days = this.options.end_date.getUTCDate();
|
||||||
|
|
||||||
|
if(days < 31)
|
||||||
|
{
|
||||||
|
const diff = 31 - days;
|
||||||
|
width = 'calc('+(diff * 3.23) + '% - ' + (diff * 7) + 'px)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark weekends and other special days in yearly planner
|
||||||
|
if (this.getParent().options.group_by == 'month')
|
||||||
|
{
|
||||||
|
this.rows.append(this._yearlyPlannerMarkDays(this.options.start_date, days));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.getParent().options.group_by === 'month' && days < 31)
|
||||||
|
{
|
||||||
|
// add a filler for non existing days in that month
|
||||||
|
this.rows.after('<div class="calendar_eventRowsFiller"'+
|
||||||
|
' style="width:'+width+';" ></div>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_label(label)
|
||||||
|
{
|
||||||
|
this.options.label = label;
|
||||||
|
this.title.text(label);
|
||||||
|
if(this.getParent().options.group_by === 'month')
|
||||||
|
{
|
||||||
|
this.title.attr('data-date', this.options.start_date.toJSON());
|
||||||
|
this.title.attr('data-sortby', 'user');
|
||||||
|
this.title.addClass('et2_clickable et2_link');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.title.attr('data-date','');
|
||||||
|
this.title.removeClass('et2_clickable');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the start date
|
||||||
|
*
|
||||||
|
* @param {Date} new_date New end date
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
set_start_date(new_date)
|
||||||
|
{
|
||||||
|
if(!new_date || new_date === null)
|
||||||
|
{
|
||||||
|
throw new TypeError('Invalid end date. ' + new_date.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.options.start_date = new Date(typeof new_date == 'string' ? new_date : new_date.toJSON());
|
||||||
|
this.options.start_date.setUTCHours(0);
|
||||||
|
this.options.start_date.setUTCMinutes(0);
|
||||||
|
this.options.start_date.setUTCSeconds(0);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Change the end date
|
||||||
|
*
|
||||||
|
* @param {string|number|Date} new_date New end date
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
set_end_date(new_date)
|
||||||
|
{
|
||||||
|
if(!new_date || new_date === null)
|
||||||
|
{
|
||||||
|
throw new TypeError('Invalid end date. ' + new_date.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.options.end_date = new Date(typeof new_date == 'string' ? new_date : new_date.toJSON());
|
||||||
|
this.options.end_date.setUTCHours(23);
|
||||||
|
this.options.end_date.setUTCMinutes(59);
|
||||||
|
this.options.end_date.setUTCSeconds(59);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark special days (birthdays, holidays) on the planner
|
||||||
|
*
|
||||||
|
* @param {Date} start Start of the month
|
||||||
|
* @param {number} days How many days in the month
|
||||||
|
*/
|
||||||
|
_yearlyPlannerMarkDays(start,days)
|
||||||
|
{
|
||||||
|
const day_width = 3.23;
|
||||||
|
const t = new Date(start);
|
||||||
|
let content = '';
|
||||||
|
for(let i = 0; i < days; i++)
|
||||||
|
{
|
||||||
|
const holidays = [];
|
||||||
|
// TODO: implement this, pull / copy data from et2_widget_timegrid
|
||||||
|
const day_class = (<et2_calendar_planner>this.getParent()).day_class_holiday(t, holidays);
|
||||||
|
|
||||||
|
if (day_class) // no regular weekday
|
||||||
|
{
|
||||||
|
content += '<div class="calendar_eventRowsMarkedDay '+day_class+
|
||||||
|
'" style="left: '+(i*day_width)+'%; width:'+day_width+'%;"'+
|
||||||
|
(holidays ? ' title="'+holidays.join(',')+'"' : '')+
|
||||||
|
' ></div>';
|
||||||
|
}
|
||||||
|
t.setUTCDate(t.getUTCDate()+1);
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 row.
|
||||||
|
*
|
||||||
|
* @param {String[]} event_ids
|
||||||
|
* @returns {undefined}
|
||||||
|
*/
|
||||||
|
_data_callback( event_ids)
|
||||||
|
{
|
||||||
|
const events = [];
|
||||||
|
if(event_ids == null || typeof event_ids.length == 'undefined') event_ids = [];
|
||||||
|
for(let i = 0; i < event_ids.length; i++)
|
||||||
|
{
|
||||||
|
let event = <any>egw.dataGetUIDdata('calendar::' + event_ids[i]);
|
||||||
|
event = event && event.data || false;
|
||||||
|
if(event && event.date)
|
||||||
|
{
|
||||||
|
events.push(event);
|
||||||
|
}
|
||||||
|
else if (event)
|
||||||
|
{
|
||||||
|
// Got an ID that doesn't belong
|
||||||
|
event_ids.splice(i--,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!this.getParent().disabled && event_ids.length > 0)
|
||||||
|
{
|
||||||
|
this.resize();
|
||||||
|
this._update_events(events);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get date_helper(): et2_date
|
||||||
|
{
|
||||||
|
return this._date_helper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the event data for this day and create event widgets for each.
|
||||||
|
*
|
||||||
|
* If event information is not provided, it will be pulled from the content array.
|
||||||
|
*
|
||||||
|
* @param {Object[]} [events] Array of event information, one per event.
|
||||||
|
*/
|
||||||
|
_update_events(events)
|
||||||
|
{
|
||||||
|
// Remove all events
|
||||||
|
while(this._children.length > 0)
|
||||||
|
{
|
||||||
|
const node = this._children[this._children.length - 1];
|
||||||
|
this.removeChild(node);
|
||||||
|
node.destroy();
|
||||||
|
}
|
||||||
|
this._cached_rows = [];
|
||||||
|
|
||||||
|
for(var c = 0; c < events.length; c++)
|
||||||
|
{
|
||||||
|
// Create event
|
||||||
|
var event = et2_createWidget('calendar-event',{
|
||||||
|
id:'event_'+events[c].row_id,
|
||||||
|
value: events[c]
|
||||||
|
},this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seperate loop so column sorting finds all children in the right place
|
||||||
|
for(var c = 0; c < events.length; c++)
|
||||||
|
{
|
||||||
|
let event = this.getWidgetById('event_'+events[c].row_id);
|
||||||
|
if(!event) continue;
|
||||||
|
if(this.isInTree())
|
||||||
|
{
|
||||||
|
event.doLoadingFinished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Position the event according to it's time and how this widget is laid
|
||||||
|
* out.
|
||||||
|
*
|
||||||
|
* @param {undefined|Object|et2_calendar_event} event
|
||||||
|
*/
|
||||||
|
position_event(event?)
|
||||||
|
{
|
||||||
|
const rows = this._spread_events();
|
||||||
|
const height = rows.length * this._row_height;
|
||||||
|
let row_width = this.rows.width();
|
||||||
|
if(row_width == 0)
|
||||||
|
{
|
||||||
|
// Not rendered yet or something
|
||||||
|
row_width = this.getParent().gridHeader.width() - this.title.width()
|
||||||
|
}
|
||||||
|
row_width -= 15;
|
||||||
|
|
||||||
|
for(let c = 0; c < rows.length; c++)
|
||||||
|
{
|
||||||
|
// Calculate vertical positioning
|
||||||
|
const top = c * (100.0 / rows.length);
|
||||||
|
|
||||||
|
for(let i = 0; (rows[c].indexOf(event) >=0 || !event) && i < rows[c].length; i++)
|
||||||
|
{
|
||||||
|
// Calculate horizontal positioning
|
||||||
|
const left = this._time_to_position(rows[c][i].options.value.start);
|
||||||
|
const width = this._time_to_position(rows[c][i].options.value.end) - left;
|
||||||
|
|
||||||
|
// Position the event
|
||||||
|
rows[c][i].div.css('top', top+'%');
|
||||||
|
rows[c][i].div.css('height', (100/rows.length)+'%');
|
||||||
|
rows[c][i].div.css('left', left.toFixed(1)+'%');
|
||||||
|
rows[c][i].div.outerWidth((width/100 * row_width) +'px');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(height)
|
||||||
|
{
|
||||||
|
this.div.height(height+'px');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort a day's events into non-overlapping rows
|
||||||
|
*
|
||||||
|
* @returns {Array[]} Events sorted into rows
|
||||||
|
*/
|
||||||
|
_spread_events()
|
||||||
|
{
|
||||||
|
// Keep it so we don't have to re-do it when the next event asks
|
||||||
|
let cached_length = 0;
|
||||||
|
this._cached_rows.map(function(row) {cached_length+=row.length;});
|
||||||
|
if(cached_length === this._children.length)
|
||||||
|
{
|
||||||
|
return this._cached_rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sorting the events in non-overlapping rows
|
||||||
|
const rows = [];
|
||||||
|
const row_end = [0];
|
||||||
|
|
||||||
|
// Sort in chronological order, so earliest ones are at the top
|
||||||
|
this._children.sort(function(a,b) {
|
||||||
|
const start = new Date(a.options.value.start) - new Date(b.options.value.start);
|
||||||
|
const end = new Date(a.options.value.end) - new Date(b.options.value.end);
|
||||||
|
// Whole day events sorted by ID, normal events by start / end time
|
||||||
|
if(a.options.value.whole_day && b.options.value.whole_day)
|
||||||
|
{
|
||||||
|
// Longer duration comes first so we have nicer bars across the top
|
||||||
|
const duration =
|
||||||
|
(new Date(b.options.value.end) - new Date(b.options.value.start)) -
|
||||||
|
(new Date(a.options.value.end) - new Date(a.options.value.start));
|
||||||
|
|
||||||
|
return duration ? duration : (a.options.value.app_id - b.options.value.app_id);
|
||||||
|
}
|
||||||
|
else if (a.options.value.whole_day || b.options.value.whole_day)
|
||||||
|
{
|
||||||
|
return a.options.value.whole_day ? -1 : 1;
|
||||||
|
}
|
||||||
|
return start ? start : end;
|
||||||
|
});
|
||||||
|
|
||||||
|
for(let n = 0; n < this._children.length; n++)
|
||||||
|
{
|
||||||
|
const event = this._children[n].options.value || false;
|
||||||
|
if(typeof event.start !== 'object')
|
||||||
|
{
|
||||||
|
this._date_helper.set_value(event.start);
|
||||||
|
event.start = new Date(this._date_helper.getValue());
|
||||||
|
}
|
||||||
|
if(typeof event.end !== 'object')
|
||||||
|
{
|
||||||
|
this._date_helper.set_value(event.end);
|
||||||
|
event.end = new Date(this._date_helper.getValue());
|
||||||
|
}
|
||||||
|
if(typeof event['start_m'] === 'undefined')
|
||||||
|
{
|
||||||
|
let day_start = event.start.valueOf() / 1000;
|
||||||
|
const dst_check = new Date(event.start);
|
||||||
|
dst_check.setUTCHours(12);
|
||||||
|
|
||||||
|
// if daylight saving is switched on or off, correct $day_start
|
||||||
|
// gives correct times after 2am, times between 0am and 2am are wrong
|
||||||
|
const daylight_diff = day_start + 12 * 60 * 60 - (dst_check.valueOf() / 1000);
|
||||||
|
if(daylight_diff)
|
||||||
|
{
|
||||||
|
day_start -= daylight_diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
event['start_m'] = event.start.getUTCHours() * 60 + event.start.getUTCMinutes();
|
||||||
|
if (event['start_m'] < 0)
|
||||||
|
{
|
||||||
|
event['start_m'] = 0;
|
||||||
|
event['multiday'] = true;
|
||||||
|
}
|
||||||
|
event['end_m'] = event.end.getUTCHours() * 60 + event.end.getUTCMinutes();
|
||||||
|
if (event['end_m'] >= 24*60)
|
||||||
|
{
|
||||||
|
event['end_m'] = 24*60-1;
|
||||||
|
event['multiday'] = true;
|
||||||
|
}
|
||||||
|
if(!event.start.getUTCHours() && !event.start.getUTCMinutes() && event.end.getUTCHours() == 23 && event.end.getUTCMinutes() == 59)
|
||||||
|
{
|
||||||
|
event.whole_day_on_top = (event.non_blocking && event.non_blocking != '0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip events entirely on hidden weekends
|
||||||
|
if(this._hidden_weekend_event(event))
|
||||||
|
{
|
||||||
|
const node = this._children[n];
|
||||||
|
this.removeChild(n--);
|
||||||
|
node.destroy();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const event_start = new Date(event.start).valueOf();
|
||||||
|
for(var row = 0; row_end[row] > event_start; ++row); // find a "free" row (no other event)
|
||||||
|
if(typeof rows[row] === 'undefined') rows[row] = [];
|
||||||
|
rows[row].push(this._children[n]);
|
||||||
|
row_end[row] = new Date(event['end']).valueOf();
|
||||||
|
}
|
||||||
|
this._cached_rows = rows;
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check to see if the event is entirely on a hidden weekend
|
||||||
|
*
|
||||||
|
* @param values Array of event values, not an et2_widget_event
|
||||||
|
*/
|
||||||
|
_hidden_weekend_event(values)
|
||||||
|
{
|
||||||
|
if(!this.getParent() || this.getParent().options.group_by == 'month' || this.getParent().options.show_weekend)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Starts on Saturday or Sunday, ends Sat or Sun, less than 2 days long
|
||||||
|
else if([0,6].indexOf(values.start.getUTCDay()) !== -1 && [0,6].indexOf(values.end.getUTCDay()) !== -1
|
||||||
|
&& values.end - values.start < 2 * 24 * 3600 * 1000)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the horizontal position based on the time given, as a percentage
|
||||||
|
* between the start and end times
|
||||||
|
*
|
||||||
|
* @param {int|Date|string} time in minutes from midnight, or a Date in string or object form
|
||||||
|
* @param {int|Date|string} start Earliest possible time (0%)
|
||||||
|
* @param {int|Date|string} end Latest possible time (100%)
|
||||||
|
* @return {float} position in percent
|
||||||
|
*/
|
||||||
|
_time_to_position(time, start?, end?)
|
||||||
|
{
|
||||||
|
let pos = 0.0;
|
||||||
|
|
||||||
|
// Handle the different value types
|
||||||
|
start = this.options.start_date;
|
||||||
|
end = this.options.end_date;
|
||||||
|
|
||||||
|
if(typeof start === 'string')
|
||||||
|
{
|
||||||
|
start = new Date(start);
|
||||||
|
end = new Date(end);
|
||||||
|
}
|
||||||
|
const wd_start = 60 * (parseInt(''+egw.preference('workdaystarts', 'calendar')) || 9);
|
||||||
|
const wd_end = 60 * (parseInt(''+egw.preference('workdayends', 'calendar')) || 17);
|
||||||
|
|
||||||
|
let t = time;
|
||||||
|
if(typeof time === 'number' && time < 3600)
|
||||||
|
{
|
||||||
|
t = new Date(start.valueOf() + wd_start * 3600*1000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
t = new Date(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limits
|
||||||
|
if(t <= start) return 0; // We are left of our scale
|
||||||
|
if(t >= end) return 100; // We are right of our scale
|
||||||
|
|
||||||
|
// Remove space for weekends, if hidden
|
||||||
|
let weekend_count = 0;
|
||||||
|
let weekend_before = 0;
|
||||||
|
let partial_weekend = 0;
|
||||||
|
if(this.getParent().options.group_by !== 'month' && this.getParent() && !this.getParent().options.show_weekend)
|
||||||
|
{
|
||||||
|
|
||||||
|
const counter_date = new Date(start);
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if([0,6].indexOf(counter_date.getUTCDay()) !== -1)
|
||||||
|
{
|
||||||
|
if(counter_date.getUTCDate() === t.getUTCDate() && counter_date.getUTCMonth() === t.getUTCMonth())
|
||||||
|
{
|
||||||
|
// Event is partially on a weekend
|
||||||
|
partial_weekend += (t.getUTCHours() *60 + t.getUTCMinutes())*60*1000;
|
||||||
|
}
|
||||||
|
else if(counter_date < t)
|
||||||
|
{
|
||||||
|
weekend_before++;
|
||||||
|
}
|
||||||
|
weekend_count++;
|
||||||
|
}
|
||||||
|
counter_date.setUTCDate(counter_date.getUTCDate() + 1);
|
||||||
|
} while(counter_date < end);
|
||||||
|
// Put it in ms
|
||||||
|
weekend_before *= 24 * 3600 * 1000;
|
||||||
|
weekend_count *= 24 * 3600 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic scaling, doesn't consider working times
|
||||||
|
pos = (t - start - weekend_before-partial_weekend) / (end - start - weekend_count);
|
||||||
|
|
||||||
|
// Month view
|
||||||
|
if(this.getParent().options.group_by !== 'month')
|
||||||
|
{
|
||||||
|
// Daywise scaling
|
||||||
|
/* Needs hourly scales that consider working hours
|
||||||
|
var start_date = new Date(start.getUTCFullYear(), start.getUTCMonth(),start.getUTCDate());
|
||||||
|
var end_date = new Date(end.getUTCFullYear(), end.getUTCMonth(),end.getUTCDate());
|
||||||
|
var t_date = new Date(t.getUTCFullYear(), t.getUTCMonth(),t.getUTCDate());
|
||||||
|
|
||||||
|
var days = Math.round((end_date - start_date) / (24 * 3600 * 1000))+1;
|
||||||
|
pos = 1 / days * Math.round((t_date - start_date) / (24*3600 * 1000));
|
||||||
|
|
||||||
|
var time_of_day = typeof t === 'object' ? 60 * t.getUTCHours() + t.getUTCMinutes() : t;
|
||||||
|
|
||||||
|
if (time_of_day >= wd_start)
|
||||||
|
{
|
||||||
|
var day_percentage = 0.1;
|
||||||
|
if (time_of_day > wd_end)
|
||||||
|
{
|
||||||
|
day_percentage = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var wd_length = wd_end - wd_start;
|
||||||
|
if (wd_length <= 0) wd_length = 24*60;
|
||||||
|
day_percentage = (time_of_day-wd_start) / wd_length; // between 0 and 1
|
||||||
|
}
|
||||||
|
pos += day_percentage / days;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 2678400 is the number of seconds in 31 days
|
||||||
|
pos = (t - start) / 2678400000;
|
||||||
|
}
|
||||||
|
pos = 100 * pos;
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resizable interface
|
||||||
|
/**
|
||||||
|
* Resize
|
||||||
|
*
|
||||||
|
* Parent takes care of setting proper width & height for the containing div
|
||||||
|
* here we just need to adjust the events to fit the new size.
|
||||||
|
*/
|
||||||
|
resize ()
|
||||||
|
{
|
||||||
|
if(this.disabled || !this.div.is(':visible') || this.getParent().disabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = jQuery('<div class="calendar_plannerEventRowWidget"></div>').appendTo(this.rows);
|
||||||
|
this._row_height = (parseInt(window.getComputedStyle(row[0]).getPropertyValue("height")) || 20);
|
||||||
|
row.remove();
|
||||||
|
|
||||||
|
// Resize & position all events
|
||||||
|
this.position_event();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
et2_register_widget(et2_calendar_planner_row, ["calendar-planner_row"]);
|
2389
calendar/js/et2_widget_timegrid.ts
Normal file
2389
calendar/js/et2_widget_timegrid.ts
Normal file
File diff suppressed because it is too large
Load Diff
700
calendar/js/et2_widget_view.ts
Normal file
700
calendar/js/et2_widget_view.ts
Normal file
@ -0,0 +1,700 @@
|
|||||||
|
/*
|
||||||
|
* Egroupware
|
||||||
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||||
|
* @package
|
||||||
|
* @subpackage
|
||||||
|
* @link http://www.egroupware.org
|
||||||
|
* @author Nathan Gray
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*egw:uses
|
||||||
|
/etemplate/js/et2_core_valueWidget;
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {et2_widget, WidgetConfig} from "../../api/js/etemplate/et2_core_widget";
|
||||||
|
import {et2_valueWidget} from "../../api/js/etemplate/et2_core_valueWidget";
|
||||||
|
import {ClassWithAttributes} from "../../api/js/etemplate/et2_core_inheritance";
|
||||||
|
import {et2_date} from "../../api/js/etemplate/et2_widget_date";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
export class et2_calendar_view extends et2_valueWidget
|
||||||
|
{
|
||||||
|
static readonly _attributes : any = {
|
||||||
|
owner: {
|
||||||
|
name: "Owner",
|
||||||
|
type: "any", // Integer, or array of integers, or string like r13 (resources, addressbook)
|
||||||
|
default: [egw.user('account_id')],
|
||||||
|
description: "Account ID number of the calendar owner, if not the current user"
|
||||||
|
},
|
||||||
|
start_date: {
|
||||||
|
name: "Start date",
|
||||||
|
type: "any"
|
||||||
|
},
|
||||||
|
end_date: {
|
||||||
|
name: "End date",
|
||||||
|
type: "any"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
protected dataStorePrefix: string = 'calendar';
|
||||||
|
protected _date_helper: et2_date;
|
||||||
|
protected loader: JQuery;
|
||||||
|
protected div: JQuery;
|
||||||
|
|
||||||
|
protected update_timer: number = null;
|
||||||
|
protected drag_create: { parent: et2_widget; start: any; end: any; event: et2_calendar_event };
|
||||||
|
protected value: any;
|
||||||
|
|
||||||
|
protected _actionObject: egwActionObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
constructor(_parent, _attrs? : WidgetConfig, _child? : object)
|
||||||
|
{
|
||||||
|
// Call the inherited constructor
|
||||||
|
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_calendar_view._attributes, _child || {}));
|
||||||
|
|
||||||
|
|
||||||
|
// Used for its date calculations
|
||||||
|
this._date_helper = et2_createWidget('date-time', {}, null);
|
||||||
|
this._date_helper.loadingFinished();
|
||||||
|
|
||||||
|
this.loader = jQuery('<div class="egw-loading-prompt-container ui-front loading"></div>');
|
||||||
|
this.update_timer = null;
|
||||||
|
|
||||||
|
// Used to support dragging on empty space to create an event
|
||||||
|
this.drag_create = {
|
||||||
|
start: null,
|
||||||
|
end: null,
|
||||||
|
parent: null,
|
||||||
|
event: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
super.destroy();
|
||||||
|
|
||||||
|
// date_helper has no parent, so we must explicitly remove it
|
||||||
|
this._date_helper.destroy();
|
||||||
|
this._date_helper = null;
|
||||||
|
|
||||||
|
// Stop the invalidate timer
|
||||||
|
if(this.update_timer)
|
||||||
|
{
|
||||||
|
window.clearTimeout(this.update_timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doLoadingFinished( )
|
||||||
|
{
|
||||||
|
super.doLoadingFinished();
|
||||||
|
this.loader.hide(0).prependTo(this.div);
|
||||||
|
if(this.options.owner) this.set_owner(this.options.owner);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Something changed, and the view need to be re-drawn. We wait a bit to
|
||||||
|
* avoid re-drawing twice if start and end date both changed, then recreate
|
||||||
|
* as needed.
|
||||||
|
*
|
||||||
|
* @param {boolean} [trigger_event=false] Trigger an event once things are done.
|
||||||
|
* Waiting until invalidate completes prevents 2 updates when changing the date range.
|
||||||
|
* @returns {undefined}
|
||||||
|
*
|
||||||
|
* @memberOf et2_calendar_view
|
||||||
|
*/
|
||||||
|
invalidate(trigger_event) {
|
||||||
|
// If this wasn't a stub, we'd set this.update_timer
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current start date
|
||||||
|
*
|
||||||
|
* @returns {Date}
|
||||||
|
*
|
||||||
|
* @memberOf et2_calendar_view
|
||||||
|
*/
|
||||||
|
get_start_date() {
|
||||||
|
return new Date(this.options.start_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current start date
|
||||||
|
*
|
||||||
|
* @returns {Date}
|
||||||
|
*
|
||||||
|
* @memberOf et2_calendar_view
|
||||||
|
*/
|
||||||
|
get_end_date() {
|
||||||
|
return new Date(this.options.end_date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the start 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}
|
||||||
|
*
|
||||||
|
* @memberOf et2_calendar_view
|
||||||
|
*/
|
||||||
|
set_start_date(new_date)
|
||||||
|
{
|
||||||
|
if(!new_date || new_date === null)
|
||||||
|
{
|
||||||
|
new_date = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use date widget's existing functions to deal
|
||||||
|
if(typeof new_date === "object" || typeof new_date === "string" && new_date.length > 8)
|
||||||
|
{
|
||||||
|
this._date_helper.set_value(new_date);
|
||||||
|
}
|
||||||
|
else if(typeof new_date === "string")
|
||||||
|
{
|
||||||
|
this._date_helper.set_year(new_date.substring(0, 4));
|
||||||
|
// Avoid overflow into next month, since we re-use date_helper
|
||||||
|
this._date_helper.set_date(1);
|
||||||
|
this._date_helper.set_month(new_date.substring(4, 6));
|
||||||
|
this._date_helper.set_date(new_date.substring(6, 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
var old_date = this.options.start_date;
|
||||||
|
this.options.start_date = new Date(this._date_helper.getValue());
|
||||||
|
|
||||||
|
if(old_date !== this.options.start_date && this.isAttached())
|
||||||
|
{
|
||||||
|
this.invalidate(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the 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}
|
||||||
|
*
|
||||||
|
* @memberOf et2_calendar_view
|
||||||
|
*/
|
||||||
|
set_end_date(new_date)
|
||||||
|
{
|
||||||
|
if(!new_date || new_date === null)
|
||||||
|
{
|
||||||
|
new_date = new Date();
|
||||||
|
}
|
||||||
|
// Use date widget's existing functions to deal
|
||||||
|
if(typeof new_date === "object" || typeof new_date === "string" && new_date.length > 8)
|
||||||
|
{
|
||||||
|
this._date_helper.set_value(new_date);
|
||||||
|
}
|
||||||
|
else if(typeof new_date === "string")
|
||||||
|
{
|
||||||
|
this._date_helper.set_year(new_date.substring(0, 4));
|
||||||
|
// Avoid overflow into next month, since we re-use date_helper
|
||||||
|
this._date_helper.set_date(1);
|
||||||
|
this._date_helper.set_month(new_date.substring(4, 6));
|
||||||
|
this._date_helper.set_date(new_date.substring(6, 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
var old_date = this.options.end_date;
|
||||||
|
this.options.end_date = new Date(this._date_helper.getValue());
|
||||||
|
|
||||||
|
if(old_date !== this.options.end_date && this.isAttached())
|
||||||
|
{
|
||||||
|
this.invalidate(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set which users to display
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
set_owner(_owner)
|
||||||
|
{
|
||||||
|
var old = this.options.owner;
|
||||||
|
|
||||||
|
// 0 means current user, but that causes problems for comparison,
|
||||||
|
// so we'll just switch to the actual ID
|
||||||
|
if(_owner == '0')
|
||||||
|
{
|
||||||
|
_owner = [egw.user('account_id')];
|
||||||
|
}
|
||||||
|
if(!jQuery.isArray(_owner))
|
||||||
|
{
|
||||||
|
if(typeof _owner === "string")
|
||||||
|
{
|
||||||
|
_owner = _owner.split(',');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_owner = [_owner];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_owner = jQuery.extend([],_owner);
|
||||||
|
}
|
||||||
|
this.options.owner = _owner;
|
||||||
|
if(this.isAttached() && (
|
||||||
|
typeof old === "number" && typeof _owner === "number" && old !== this.options.owner ||
|
||||||
|
// Array of ids will not compare as equal
|
||||||
|
((typeof old === 'object' || typeof _owner === 'object') && old.toString() !== _owner.toString()) ||
|
||||||
|
// Strings
|
||||||
|
typeof old === 'string' && ''+old !== ''+this.options.owner
|
||||||
|
))
|
||||||
|
{
|
||||||
|
this.invalidate(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* {string|number|Date} events.start_date - New start date
|
||||||
|
* {string|number|Date} events.end_date - New end date
|
||||||
|
* {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(events)
|
||||||
|
{
|
||||||
|
if(typeof events !== 'object') return false;
|
||||||
|
|
||||||
|
if(events.length && events.length > 0 || !jQuery.isEmptyObject(events))
|
||||||
|
{
|
||||||
|
this.set_disabled(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),200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get date_helper(): et2_date
|
||||||
|
{
|
||||||
|
return this._date_helper;
|
||||||
|
}
|
||||||
|
_createNamespace() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calendar supports many different owner types, including users & resources.
|
||||||
|
* This translates an ID to a user-friendly name.
|
||||||
|
*
|
||||||
|
* @param {string} user
|
||||||
|
* @returns {string}
|
||||||
|
*
|
||||||
|
* @memberOf et2_calendar_view
|
||||||
|
*/
|
||||||
|
_get_owner_name(user) {
|
||||||
|
var label = undefined;
|
||||||
|
if(parseInt(user) === 0)
|
||||||
|
{
|
||||||
|
// 0 means current user
|
||||||
|
user = egw.user('account_id');
|
||||||
|
}
|
||||||
|
if(et2_calendar_view.owner_name_cache[user])
|
||||||
|
{
|
||||||
|
return et2_calendar_view.owner_name_cache[user];
|
||||||
|
}
|
||||||
|
if (!isNaN(user))
|
||||||
|
{
|
||||||
|
user = parseInt(user);
|
||||||
|
var accounts = <any[]>egw.accounts('both');
|
||||||
|
for(var j = 0; j < accounts.length; j++)
|
||||||
|
{
|
||||||
|
if(accounts[j].value === user)
|
||||||
|
{
|
||||||
|
label = accounts[j].label;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(typeof label === 'undefined')
|
||||||
|
{
|
||||||
|
// Not found? Ask the sidebox owner widget (it gets updated) or the original arrayMgr
|
||||||
|
let options = false;
|
||||||
|
if(app.calendar && app.calendar.sidebox_et2 && app.calendar.sidebox_et2.getWidgetById('owner'))
|
||||||
|
{
|
||||||
|
options = app.calendar.sidebox_et2.getWidgetById('owner').taglist.getSelection();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
options = this.getArrayMgr("sel_options").getRoot().getEntry('owner');
|
||||||
|
}
|
||||||
|
if(options && options.find)
|
||||||
|
{
|
||||||
|
var found = options.find(function(element) {return element.id == user;}) || {};
|
||||||
|
if(found && found.label && found.label !== user)
|
||||||
|
{
|
||||||
|
label = found.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!label)
|
||||||
|
{
|
||||||
|
// No sidebox? Must be in home or sitemgr (no caching) - ask directly
|
||||||
|
label = '?';
|
||||||
|
egw.jsonq('calendar_owner_etemplate_widget::ajax_owner',user,function(data) {
|
||||||
|
et2_calendar_view.owner_name_cache[user] = data;
|
||||||
|
this.invalidate(true);
|
||||||
|
|
||||||
|
// Set owner to make sure labels get set
|
||||||
|
if(this.owner && typeof this.owner.set_value === 'function')
|
||||||
|
{
|
||||||
|
this.owner.set_value(data);
|
||||||
|
}
|
||||||
|
}.bind(this), this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(label)
|
||||||
|
{
|
||||||
|
et2_calendar_view.owner_name_cache[user] = label;
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(dom_node)
|
||||||
|
{
|
||||||
|
// Determine as much relevant info as can be found
|
||||||
|
var event_node = jQuery(dom_node).closest('[data-id]',this.div)[0];
|
||||||
|
var day_node = jQuery(event_node).closest('[data-date]',this.div)[0];
|
||||||
|
|
||||||
|
var result = jQuery.extend({
|
||||||
|
event_node: event_node,
|
||||||
|
day_node: day_node
|
||||||
|
},
|
||||||
|
event_node ? event_node.dataset : {},
|
||||||
|
day_node ? day_node.dataset : {}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Widget ID should be the DOM node ID without the event_ prefix
|
||||||
|
if(event_node && event_node.id)
|
||||||
|
{
|
||||||
|
var widget_id = event_node.id || '';
|
||||||
|
widget_id = widget_id.split('event_');
|
||||||
|
widget_id.shift();
|
||||||
|
result.widget_id = 'event_' + widget_id.join('');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starting (mousedown) handler to support drag to create
|
||||||
|
*
|
||||||
|
* Extending classes need to set this.drag_create.parent, which is the
|
||||||
|
* parent container (child of extending class) that will directly hold the
|
||||||
|
* event.
|
||||||
|
*
|
||||||
|
* @param {String} start Date string (JSON format)
|
||||||
|
*/
|
||||||
|
_drag_create_start(start)
|
||||||
|
{
|
||||||
|
this.drag_create.start = jQuery.extend({},start);
|
||||||
|
if(!this.drag_create.start.date)
|
||||||
|
{
|
||||||
|
this.drag_create.start = null;
|
||||||
|
}
|
||||||
|
this.drag_create.end = start;
|
||||||
|
|
||||||
|
// Clear some stuff, if last time did not complete
|
||||||
|
if(this.drag_create.event)
|
||||||
|
{
|
||||||
|
if(this.drag_create.event.destroy)
|
||||||
|
{
|
||||||
|
this.drag_create.event.destroy();
|
||||||
|
}
|
||||||
|
this.drag_create.event = null;
|
||||||
|
}
|
||||||
|
// Wait a bit before adding an "event", it may be just a click
|
||||||
|
window.setTimeout(jQuery.proxy(function() {
|
||||||
|
// Create event
|
||||||
|
this._drag_create_event();
|
||||||
|
}, this), 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create or update an event used for feedback while dragging on empty space,
|
||||||
|
* so user can see something is happening
|
||||||
|
*/
|
||||||
|
_drag_create_event()
|
||||||
|
{
|
||||||
|
if(!this.drag_create.parent || !this.drag_create.start)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!this.drag_create.event)
|
||||||
|
{
|
||||||
|
this._date_helper.set_value(this.drag_create.start.date);
|
||||||
|
var value = jQuery.extend({},
|
||||||
|
this.drag_create.start,
|
||||||
|
this.drag_create.end,
|
||||||
|
{
|
||||||
|
start: this.drag_create.start.date,
|
||||||
|
end: this.drag_create.end && this.drag_create.end.date || this.drag_create.start.date,
|
||||||
|
date: ""+this._date_helper.get_year()+
|
||||||
|
sprintf("%02d",this._date_helper.get_month())+
|
||||||
|
sprintf("%02d",this._date_helper.get_date()),
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
owner: this.options.owner,
|
||||||
|
participants: this.options.owner,
|
||||||
|
app: 'calendar',
|
||||||
|
whole_day_on_top: this.drag_create.start.whole_day
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.drag_create.event = et2_createWidget('calendar-event',{
|
||||||
|
id:'event_drag',
|
||||||
|
value: value
|
||||||
|
},this.drag_create.parent);
|
||||||
|
this.drag_create.event._values_check(value);
|
||||||
|
this.drag_create.event.doLoadingFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_drag_update_event()
|
||||||
|
{
|
||||||
|
if(!this.drag_create.event || !this.drag_create.start || !this.drag_create.end
|
||||||
|
|| !this.drag_create.parent || !this.drag_create.event._type)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (this.drag_create.end)
|
||||||
|
{
|
||||||
|
this.drag_create.event.options.value.end = this.drag_create.end.date;
|
||||||
|
this.drag_create.event._values_check(this.drag_create.event.options.value);
|
||||||
|
}
|
||||||
|
this.drag_create.event._update();
|
||||||
|
this.drag_create.parent.position_event(this.drag_create.event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ending (mouseup) handler to support drag to create
|
||||||
|
*
|
||||||
|
* @param {String} end Date string (JSON format)
|
||||||
|
*/
|
||||||
|
_drag_create_end(end?)
|
||||||
|
{
|
||||||
|
this.div.css('cursor','');
|
||||||
|
if(typeof end === 'undefined')
|
||||||
|
{
|
||||||
|
end = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.drag_create.start && end.date &&
|
||||||
|
JSON.stringify(this.drag_create.start.date) !== JSON.stringify(end.date))
|
||||||
|
{
|
||||||
|
// Drag from start to end, open dialog
|
||||||
|
var options = {
|
||||||
|
start: this.drag_create.start.date < end.date ? this.drag_create.start.date : end.date,
|
||||||
|
end: this.drag_create.start.date < end.date ? end.date : this.drag_create.start.date
|
||||||
|
};
|
||||||
|
|
||||||
|
// Whole day needs to go from 00:00 to 23:59
|
||||||
|
if(end.whole_day || this.drag_create.start.whole_day)
|
||||||
|
{
|
||||||
|
var start = new Date(options.start);
|
||||||
|
start.setUTCHours(0);
|
||||||
|
start.setUTCMinutes(0);
|
||||||
|
options.start = start.toJSON();
|
||||||
|
|
||||||
|
var end = new Date(options.end);
|
||||||
|
end.setUTCHours(23);
|
||||||
|
end.setUTCMinutes(59);
|
||||||
|
options.end = end.toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add anything else that was set, but not date
|
||||||
|
jQuery.extend(options,this.drag_create.start, end);
|
||||||
|
delete(options.date);
|
||||||
|
|
||||||
|
// Make sure parent is set, if needed
|
||||||
|
if (this.drag_create.parent && this.drag_create.parent.options.owner !== app.calendar.state.owner && !options.owner)
|
||||||
|
{
|
||||||
|
options.owner = this.drag_create.parent.options.owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove empties
|
||||||
|
for(var key in options)
|
||||||
|
{
|
||||||
|
if(!options[key]) delete options[key];
|
||||||
|
}
|
||||||
|
app.calendar.add(options, this.drag_create.event);
|
||||||
|
|
||||||
|
// Wait a bit, having these stops the click
|
||||||
|
window.setTimeout(jQuery.proxy(function() {
|
||||||
|
this.drag_create.start = null;
|
||||||
|
this.drag_create.end = null;
|
||||||
|
this.drag_create.parent = null;
|
||||||
|
if(this.drag_create.event)
|
||||||
|
{
|
||||||
|
this.drag_create.event = null;
|
||||||
|
}
|
||||||
|
},this),100);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.drag_create.start = null;
|
||||||
|
this.drag_create.end = null;
|
||||||
|
this.drag_create.parent = null;
|
||||||
|
if(this.drag_create.event)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(this.drag_create.event.destroy)
|
||||||
|
{
|
||||||
|
this.drag_create.event.destroy();
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
this.drag_create.event = null;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the view should be consolidated into one, or listed seperately
|
||||||
|
* based on the user's preferences
|
||||||
|
*
|
||||||
|
* @param {string[]} owners List of owners
|
||||||
|
* @param {string} view Name of current view (day, week)
|
||||||
|
* @returns {boolean} True of only one is needed, false if each owner needs
|
||||||
|
* to be listed seperately.
|
||||||
|
*/
|
||||||
|
static is_consolidated(owners, view)
|
||||||
|
{
|
||||||
|
// Seperate owners, or consolidated?
|
||||||
|
return !(
|
||||||
|
owners.length > 1 &&
|
||||||
|
(view === 'day' && owners.length < parseInt(''+egw.preference('day_consolidate','calendar')) ||
|
||||||
|
view === 'week' && owners.length < parseInt(''+egw.preference('week_consolidate','calendar')))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache to map owner & resource IDs to names, helps cut down on server requests
|
||||||
|
*/
|
||||||
|
static owner_name_cache = {};
|
||||||
|
|
||||||
|
public static holiday_cache = {};
|
||||||
|
/**
|
||||||
|
* Fetch and cache a list of the year's holidays
|
||||||
|
*
|
||||||
|
* @param {et2_calendar_timegrid} widget
|
||||||
|
* @param {string|numeric} year
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
static get_holidays(widget,year)
|
||||||
|
{
|
||||||
|
// Loaded in an iframe or something
|
||||||
|
var view = egw.window.et2_calendar_view ? egw.window.et2_calendar_view : this;
|
||||||
|
|
||||||
|
// No country selected causes error, so skip if it's missing
|
||||||
|
if(!view || !egw.preference('country','common')) return {};
|
||||||
|
|
||||||
|
var cache = 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.
|
||||||
|
view.holiday_cache[year] = jQuery.getJSON(
|
||||||
|
egw.link('/calendar/holidays.php', {year: year})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
cache = view.holiday_cache[year];
|
||||||
|
if(typeof cache.done == 'function')
|
||||||
|
{
|
||||||
|
// pending, wait for it
|
||||||
|
cache.done(jQuery.proxy(function(response) {
|
||||||
|
view.holiday_cache[this.year] = response||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}))
|
||||||
|
.fail(jQuery.proxy(function() {
|
||||||
|
view.holiday_cache[this.year] = undefined;
|
||||||
|
}, {widget: widget, year: year}));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user