').addClass('et2_taglist_tags_icon_wrapper');
+ jQuery('
')
+ .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;
+ }
+ };
+ et2_calendar_owner.prototype.getValue = function () {
+ 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.
+ */
+ et2_calendar_owner.prototype.set_value = function (_value) {
+ _super.prototype.set_value.call(this, _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_calendar_owner._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."
+ }
+ };
+ return et2_calendar_owner;
+}(et2_taglist_email));
+exports.et2_calendar_owner = et2_calendar_owner;
+et2_core_widget_1.et2_register_widget(et2_calendar_owner, ["calendar-owner"]);
+//# sourceMappingURL=et2_widget_owner.js.map
\ No newline at end of file
diff --git a/calendar/js/et2_widget_planner.js b/calendar/js/et2_widget_planner.js
index 7b716af221..5473623a2d 100644
--- a/calendar/js/et2_widget_planner.js
+++ b/calendar/js/et2_widget_planner.js
@@ -1,3 +1,4 @@
+"use strict";
/*
* Egroupware Calendar timegrid
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
@@ -7,14 +8,31 @@
* @author Nathan Gray
* @version $Id$
*/
-
-
+var __extends = (this && this.__extends) || (function () {
+ var extendStatics = function (d, b) {
+ extendStatics = Object.setPrototypeOf ||
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+ return extendStatics(d, b);
+ };
+ return function (d, b) {
+ extendStatics(d, b);
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+ };
+})();
+Object.defineProperty(exports, "__esModule", { value: true });
/*egw:uses
- /calendar/js/et2_widget_view.js;
- /calendar/js/et2_widget_planner_row.js;
- /calendar/js/et2_widget_event.js;
+ /calendar/js/et2_widget_view.js;
+ /calendar/js/et2_widget_planner_row.js;
+ /calendar/js/et2_widget_event.js;
*/
-
+var et2_core_widget_1 = require("../../api/js/etemplate/et2_core_widget");
+var et2_core_inheritance_1 = require("../../api/js/etemplate/et2_core_inheritance");
+var et2_widget_view_1 = require("./et2_widget_view");
+var et2_core_DOMWidget_1 = require("../../api/js/etemplate/et2_core_DOMWidget");
+var et2_widget_event_1 = require("./et2_widget_event");
+var et2_widget_planner_row_1 = require("./et2_widget_planner_row");
/**
* Class which implements the "calendar-planner" XET-Tag for displaying a longer
* ( > 10 days) span of time. Events can be grouped into rows by either user,
@@ -23,2430 +41,1956 @@
*
* @augments et2_calendar_view
*/
-var et2_calendar_planner = (function(){ "use strict"; return et2_calendar_view.extend([et2_IDetachedDOM, et2_IResizeable, et2_IPrint],
-{
- createNamespace: true,
-
- attributes: {
- group_by: {
- name: "Group by",
- type: "string", // or category ID
- default: "0",
- description: "Display planner by 'user', 'month', or the given category"
- },
- filter: {
- name: "Filter",
- type: "string",
- default: '',
- description: 'A filter that is used to select events. It is passed along when events are queried.'
- },
- show_weekend: {
- name: "Weekends",
- type: "boolean",
- default: egw.preference('days_in_weekview','calendar') != 5,
- description: "Display weekends. The date range should still include them for proper scrolling, but they just won't be shown."
- },
- hide_empty: {
- name: "Hide empty rows",
- type: "boolean",
- default: false,
- description: "Hide rows with no events."
- },
- value: {
- type: "any",
- description: "A list of events, optionally you can set start_date, end_date and group_by as keys and events will be fetched"
- },
- "onchange": {
- "name": "onchange",
- "type": "js",
- "default": et2_no_init,
- "description": "JS code which is executed when the date range changes."
- },
- "onevent_change": {
- "name": "onevent_change",
- "type": "js",
- "default": et2_no_init,
- "description": "JS code which is executed when an event changes."
- }
- },
-
- DEFERRED_ROW_TIME: 100,
-
- /**
- * Constructor
- *
- * @memberOf et2_calendar_planner
- * @constructor
- */
- init: function() {
- this._super.apply(this, arguments);
-
- // Main container
- this.div = jQuery(document.createElement("div"))
- .addClass("calendar_plannerWidget");
-
- // Header
- this.gridHeader = jQuery(document.createElement("div"))
- .addClass("calendar_plannerHeader")
- .appendTo(this.div);
- this.headerTitle = jQuery(document.createElement("div"))
- .addClass("calendar_plannerHeaderTitle")
- .appendTo(this.gridHeader);
- this.headers = jQuery(document.createElement("div"))
- .addClass("calendar_plannerHeaderRows")
- .appendTo(this.gridHeader);
-
- this.rows = jQuery(document.createElement("div"))
- .addClass("calendar_plannerRows")
- .appendTo(this.div);
- this.grid = jQuery(document.createElement("div"))
- .addClass("calendar_plannerGrid")
- .appendTo(this.div);
-
- this.vertical_bar = jQuery(document.createElement("div"))
- .addClass('verticalBar')
- .appendTo(this.div);
-
- this.value = [];
-
- // Update timer, to avoid redrawing twice when changing start & end date
- this.update_timer = null;
- this.doInvalidate = true;
-
- this.setDOMNode(this.div[0]);
-
- this.registeredCallbacks = [];
- this.cache = {};
- this._deferred_row_updates = {};
- },
-
- destroy: function() {
- this._super.apply(this, arguments);
- this.div.off();
-
- for(var i = 0; i < this.registeredCallbacks.length; i++)
- {
- egw.dataUnregisterUID(this.registeredCallbacks[i],false,this);
- }
- },
-
- doLoadingFinished: function() {
- this._super.apply(this, arguments);
-
- // Don't bother to draw anything if there's no date yet
- if(this.options.start_date)
- {
- this._drawGrid();
- }
-
- // Automatically bind drag and resize for every event using jQuery directly
- // - no action system -
- var planner = this;
-
- this.cache = {};
- this._deferred_row_updates = {};
-
- /**
- * If user puts the mouse over an event, then we'll set up resizing so
- * they can adjust the length. Should be a little better on resources
- * than binding it for every calendar event.
- */
- this.div.on('mouseover', '.calendar_calEvent:not(.ui-resizable):not(.rowNoEdit)', function() {
- // Load the event
- planner._get_event_info(this);
- var that = this;
-
- //Resizable event handler
- jQuery(this).resizable
- ({
- distance: 10,
- grid: [5, 10000],
- autoHide: false,
- handles: 'e',
- containment:'parent',
-
- /**
- * Triggered when the resizable is created.
- *
- * @param {event} event
- * @param {Object} ui
- */
- create:function(event, ui)
- {
- var resizeHelper = event.target.getAttribute('data-resize');
- if (resizeHelper == 'WD' || resizeHelper == 'WDS')
- {
- jQuery(this).resizable('destroy');
- }
- },
-
- /**
- * If dragging to resize an event, abort drag to create
- *
- * @param {jQuery.Event} event
- * @param {Object} ui
- */
- start: function(event, ui)
- {
- if(planner.drag_create.start)
- {
- // Abort drag to create, we're dragging to resize
- planner._drag_create_end({});
- }
- },
-
- /**
- * Triggered at the end of resizing the calEvent.
- *
- * @param {event} event
- * @param {Object} ui
- */
- stop:function(event, ui)
- {
- var e = new jQuery.Event('change');
- e.originalEvent = event;
- e.data = {duration: 0};
- var event_data = planner._get_event_info(this);
- var event_widget = planner.getWidgetById(event_data.widget_id);
- var sT = event_widget.options.value.start_m;
- if (typeof this.dropEnd != 'undefined')
- {
- var eT = parseInt(this.dropEnd.getUTCHours() * 60) + parseInt(this.dropEnd.getUTCMinutes());
- e.data.duration = ((eT - sT)/60) * 3600;
-
- if(event_widget)
- {
- event_widget.options.value.end_m = eT;
- event_widget.options.value.duration = e.data.duration;
- }
-
- // Leave the helper there until the update is done
- var loading = ui.helper.clone().appendTo(ui.helper.parent());
-
- // and add a loading icon so user knows something is happening
- jQuery('.calendar_timeDemo',loading).after('
');
-
- jQuery(this).trigger(e);
-
- // That cleared the resize handles, so remove for re-creation...
- jQuery(this).resizable('destroy');
-
- // Remove loading, done or not
- loading.remove();
- }
- // Clear the helper, re-draw
- if(event_widget)
- {
- event_widget._parent.position_event(event_widget);
- }
- },
-
- /**
- * Triggered during the resize, on the drag of the resize handler
- *
- * @param {event} event
- * @param {Object} ui
- */
- resize: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 + ui.helper.width()};
- }
- planner._drag_helper(this,position,ui.helper.outerHeight());
- }
- });
- })
- .on('mousemove', function(event) {
- // Ignore headers
- if(planner.headers.has(event.target).length !== 0)
- {
- planner.vertical_bar.hide();
- return;
- }
- // Position bar by mouse
- planner.vertical_bar.position({
- my: 'right-1',
- of: event,
- collision: 'fit'
- });
- planner.vertical_bar.css('top','0px');
-
- // Get time at mouse
- if(jQuery(event.target).closest('.calendar_eventRows').length == 0)
- {
- // "Invalid" times, from space after the last planner row, or header
- var time = planner._get_time_from_position(event.pageX - planner.grid.offset().left, 10);
- }
- else if(planner.options.group_by == 'month')
- {
- var time = planner._get_time_from_position(event.clientX, event.clientY);
- }
- else
- {
- var time = planner._get_time_from_position(event.offsetX, event.offsetY);
- }
- // Passing to formatter, cancel out timezone
- if(time)
- {
- var formatDate = new Date(time.valueOf() + time.getTimezoneOffset() * 60 * 1000);
- planner.vertical_bar
- .html('
'+date(egw.preference('timeformat','calendar') == 12 ? 'h:ia' : 'H:i',formatDate)+'')
- .show();
-
- if(planner.drag_create.event && planner.drag_create.parent && planner.drag_create.end)
- {
-
- planner.drag_create.end.date = time.toJSON()
- planner._drag_update_event();
- }
- }
- else
- {
- // No (valid) time, just hide
- planner.vertical_bar.hide();
- }
- })
- .on('mousedown', jQuery.proxy(this._mouse_down, this))
- .on('mouseup', jQuery.proxy(this._mouse_up, this));
-
- // Actions may be set on a parent, so we need to explicitly get in here
- // and get ours
- this._link_actions(this.options.actions || this._parent.options.actions || []);
-
- // Customize and override some draggable settings
- this.div.on('dragcreate','.calendar_calEvent', function(event, ui) {
- jQuery(this).draggable('option','cancel','.rowNoEdit');
- // Act like you clicked the header, makes it easier to position
- jQuery(this).draggable('option','cursorAt', {top: 5, left: 5});
- })
- .on('dragstart', '.calendar_calEvent', function(event,ui) {
- jQuery('.calendar_calEvent',ui.helper).width(jQuery(this).width())
- .height(jQuery(this).outerHeight())
- .css('top', '').css('left','')
- .appendTo(ui.helper);
- ui.helper.width(jQuery(this).width());
-
- // Cancel drag to create, we're dragging an existing event
- planner._drag_create_end();
- });
- return true;
- },
-
- /**
- * These handle the differences between the different group types.
- * They provide the different titles, labels and grouping
- */
- groupers: {
- // Group by user has one row for each user
- user:
- {
- // Title in top left corner
- title: function() { return this.egw().lang('User');},
- // Column headers
- headers: function() {
- var start = new Date(this.options.start_date);
- var end = new Date(this.options.end_date);
- var start_date = new Date(start.getUTCFullYear(), start.getUTCMonth(),start.getUTCDate());
- var end_date = new Date(end.getUTCFullYear(), end.getUTCMonth(),end.getUTCDate());
- var day_count = Math.round((end_date - start_date) /(1000*3600*24))+1;
- if(day_count >= 6)
- {
- this.headers.append(this._header_months(start, day_count));
- }
- if(day_count < 120)
- {
- var weeks = this._header_weeks(start, day_count);
- this.headers.append(weeks);
- this.grid.append(weeks);
- }
- if(day_count < 60)
- {
- var days = this._header_days(start, day_count);
- this.headers.append(days);
- this.grid.append(days);
- }
- if(day_count <= 7)
- {
- var hours = this._header_hours(start, day_count);
- this.headers.append(hours);
- this.grid.append(hours);
- }
- },
- // Labels for the rows
- row_labels: function() {
- var labels = [];
- var already_added = [];
- var options = false;
- var resource = null;
- 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');
- }
- for(var i = 0; i < this.options.owner.length; i++)
- {
- var user = this.options.owner[i];
- // Handle grouped resources like mailing lists - pull it from sidebox owner
- // and expand to their contents
- if(options && options.find &&
- ((resource = options.find(function(element) {return element.id == user;}) || {}) || isNaN(user)))
- {
- if(resource && resource.resources)
- {
- for(var j = 0; j < resource.resources.length; j++)
- {
- var id = resource.resources[j];
- if(already_added.indexOf(''+id) < 0)
- {
- labels.push({
- id: id,
- label: this._get_owner_name(id)||'',
- data: {participants:id,owner:id}
- });
- already_added.push(''+id);
- }
- }
- }
- else if(already_added.indexOf(''+user) < 0)
- {
- labels.push({
- id: user,
- label: this._get_owner_name(user),
- data: {participants:user,owner:user}
- });
- already_added.push(''+user);
- }
- }
- else if (user < 0) // groups
- {
- egw.accountData(parseInt(user),'account_fullname',true,function(result) {
- for(var id in result)
- {
- if(already_added.indexOf(''+id) < 0)
- {
- this.push({id: id, label: result[id]||'', data: {participants:id,owner:id}});
- already_added.push(''+id);
- }
- }
- },labels);
- }
- else // users
- {
- if(already_added.indexOf(user) < 0)
- {
- var label = this._get_owner_name(user)||'';
- labels.push({id: user, label: label, data: {participants:user,owner:''}});
- already_added.push(''+user);
- }
- }
- }
-
- return labels.sort(function(a,b) {
- return a.label.localeCompare(b.label);
- });
- },
- // Group the events into the rows
- group: function(labels, rows, event) {
- // convert filter to allowed status
- var status_to_show = ['U','A','T','D','G'];
- switch(this.options.filter)
- {
- case 'unknown':
- status_to_show = ['U','G']; break;
- case 'accepted':
- status_to_show = ['A']; break;
- case 'tentative':
- status_to_show = ['T']; break;
- case 'rejected':
- status_to_show = ['R']; break;
- case 'delegated':
- status_to_show = ['D']; break;
- case 'all':
- status_to_show = ['U','A','T','D','G','R']; break;
- default:
- status_to_show = ['U','A','T','D','G']; break;
- }
- var participants = event.participants;
- var add_row = function(user, participant) {
- var label_index = false;
- for(var i = 0; i < labels.length; i++)
- {
- if(labels[i].id == user)
- {
- label_index = i;
- break;
- }
- }
- if(participant && label_index !== false && status_to_show.indexOf(participant.substr(0,1)) >= 0 ||
- !participant && label_index !== false ||
- this.options.filter === 'owner' && event.owner === user)
- {
- if(typeof rows[label_index] === 'undefined')
- {
- rows[label_index] = [];
- }
- rows[label_index].push(event);
- }
- };
- for(var user in participants)
- {
- var participant = participants[user];
- if (parseInt(user) < 0) // groups
- {
- var planner = this;
- egw.accountData(user,'account_fullname',true,function(result) {
- for(var id in result)
- {
- if(!participants[id]) add_row.call(planner,id,participant);
- }
- },labels);
- continue;
- }
- add_row.call(this, user, participant);
- }
- },
- // Draw a single row
- draw_row: function(sort_key, label, events) {
- var row = this._drawRow(sort_key, label,events,this.options.start_date, this.options.end_date);
- if(this.options.hide_empty && !events.length)
- {
- row.set_disabled(true);
- }
- // Highlight current user, sort_key is account_id
- if(sort_key === egw.user('account_id'))
- {
- row.set_class('current_user')
- }
- // Since the daywise cache is by user, we can tap in here
- var t = new Date(this.options.start_date);
- var end = new Date(this.options.end_date);
- do
- {
- var cache_id = app.classes.calendar._daywise_cache_id(t, sort_key);
- egw.dataRegisterUID(cache_id, row._data_callback, row);
-
- t.setUTCDate(t.getUTCDate() + 1);
- }
- while(t < end);
- return row;
- }
- },
-
- // Group by month has one row for each month
- month:
- {
- title: function() { return this.egw().lang('Month');},
- headers: function() {
- this.headers.append(this._header_day_of_month());
- },
- row_labels: function() {
- var labels = [];
- var d = new Date(this.options.start_date);
- d = new Date(d.valueOf() + d.getTimezoneOffset() * 60 * 1000);
- for(var i = 0; i < 12; i++)
- {
- // Not using UTC because we corrected for timezone offset
- labels.push({id:sprintf('%04d-%02d', d.getFullYear(), d.getMonth()), label:this.egw().lang(date('F',d))+' '+d.getFullYear()});
- d.setMonth(d.getMonth()+1);
- }
- return labels;
- },
- group: function(labels, rows,event) {
- // Yearly planner does not show infologs
- if(event && event.app && event.app == 'infolog') return;
-
- var start = new Date(event.start);
- start = new Date(start.valueOf() + start.getTimezoneOffset() * 60 * 1000);
- var key = sprintf('%04d-%02d', start.getFullYear(), start.getMonth());
- var label_index = false;
- for(var i = 0; i < labels.length; i++)
- {
- if(labels[i].id == key)
- {
- label_index = i;
- break;
- }
- }
- if(typeof rows[label_index] === 'undefined')
- {
- rows[label_index] = [];
- }
- rows[label_index].push(event);
-
- // end in a different month?
- var end = new Date(event.end);
- end = new Date(end.valueOf() + end.getTimezoneOffset() * 60 * 1000);
- var end_key = sprintf('%04d-%02d',end.getFullYear(),end.getMonth());
- var year = start.getFullYear();
- var month = start.getMonth();
- key = sprintf('%04d-%02d',year,month);
-
- do
- {
- var end_label_index = label_index;
-
- for(var i = end_label_index; i < labels.length; i++)
- {
- if(labels[i].id == key)
- {
- end_label_index = i;
- if(typeof rows[end_label_index] === 'undefined')
- {
- rows[end_label_index] = [];
- }
- break;
- }
- }
- if(end_label_index != label_index)
- {
- rows[label_index].push(event);
- }
- if (++month > 11)
- {
- ++year;
- month = 0;
- }
- key = sprintf('%04d-%02d',year,month);
- } while(key <= end_key)
- },
- // Draw a single row, but split up the dates
- draw_row: function(sort_key, label, events)
- {
- var key = sort_key.split('-');
- var start = new Date(key[0]+"-"+sprintf("%02d",parseInt(key[1])+1)+"-01T00:00:00Z");
- // Use some care to avoid issues with timezones and daylight savings
- var end = new Date(start);
- end.setUTCMonth(start.getUTCMonth() + 1);
- end.setUTCDate(1);
- end.setUTCHours(0);
- end.setUTCMinutes(0);
- end = new Date(end.valueOf() - 1000);
- end.setUTCMonth(start.getUTCMonth())
- this._drawRow(sort_key, label, events, start, end);
- }
- },
- // Group by category has one row for each [sub]category
- category:
- {
- title: function() { return this.egw().lang('Category');},
- headers: function() {
- var start = new Date(this.options.start_date);
- var end = new Date(this.options.end_date);
- var start_date = new Date(start.getUTCFullYear(), start.getUTCMonth(),start.getUTCDate());
- var end_date = new Date(end.getUTCFullYear(), end.getUTCMonth(),end.getUTCDate());
- var day_count = Math.round((end_date - start_date) /(1000*3600*24))+1;
-
- if(day_count >= 6)
- {
- this.headers.append(this._header_months(start, day_count));
- }
- if(day_count < 120)
- {
- var weeks = this._header_weeks(start, day_count);
- this.headers.append(weeks);
- this.grid.append(weeks);
- }
- if(day_count < 60)
- {
- var days = this._header_days(start, day_count);
- this.headers.append(days);
- this.grid.append(days);
- }
- if(day_count <= 7)
- {
- var hours = this._header_hours(start, day_count);
- this.headers.append(hours);
- this.grid.append(hours);
- }
- },
- row_labels: function() {
- var im = this.getInstanceManager();
- var categories = et2_selectbox.cat_options({
- _type:'select-cat',
- getInstanceManager: function() {return im;}
- },{application: 'calendar'});
-
- var labels = [];
- if(!app.calendar.state.cat_id ||
- app.calendar.state.cat_id.toString() === '' ||
- app.calendar.state.cat_id.toString() == '0'
- )
- {
- app.calendar.state.cat_id = '';
- labels.push({id:'',value:'',label: egw.lang('none'), main: '', data: {}});
- labels = labels.concat(categories);
- }
- else
- {
- var cat_id = app.calendar.state.cat_id;
- if(typeof cat_id == 'string')
- {
- cat_id = cat_id.split(',');
- }
- for(var i = 0; i < cat_id.length; i++)
- {
- // Find label for that category
- for(var j = 0; j < categories.length; j++)
- {
- if(categories[j].value == cat_id[i])
- {
- categories[j].id = categories[j].value;
- labels.push(categories[j]);
- break;
- }
- }
-
- // Get its children immediately
- egw.json(
- 'EGroupware\\Api\\Etemplate\\Widget\\Select::ajax_get_options',
- ['select-cat',',,,calendar,'+cat_id[i]],
- function(data) {
- labels = labels.concat(data);
- }
- ).sendRequest(false);
- }
- }
-
- for(var i = labels.length -1; i >= 0; i--)
- {
- labels[i].id = labels[i].value;
- labels[i].data = {
- cat_id: labels[i].id,
- main: labels[i].value==labels[i].main
- };
- if(labels[i].children && labels[i].children.length)
- {
- labels[i].data.has_children = true;
- }
- }
- return labels;
- },
- group: function(labels, rows, event) {
- var cats = event.category;
- if(typeof event.category === 'string')
- {
- cats = cats.split(',');
- }
- for(var cat = 0; cat < cats.length; cat++)
- {
- var label_index = false;
- var category = cats[cat] ? parseInt(cats[cat],10) : false;
- if(category == 0 || !category) category = '';
- for(var i = 0; i < labels.length; i++)
- {
- if(labels[i].id == category)
- {
- // If there's no cat filter, only show the top level
- if(!app.calendar.state.cat_id)
- {
- for(var j = 0; j < labels.length; j++)
- {
- if(labels[j].id == labels[i].main)
- {
- label_index = j;
- break;
- }
- }
- break;
- }
- label_index = i;
- break;
- }
- }
- if(label_index !== false && typeof rows[label_index] === 'undefined')
- {
- rows[label_index] = [];
- }
- if(label_index !== false && rows[label_index].indexOf(event) === -1)
- {
- rows[label_index].push(event);
- }
- }
- },
- draw_row: function(sort_key, label, events) {
- var row = this._drawRow(sort_key, label,events,this.options.start_date, this.options.end_date);
- if(this.options.hide_empty && !events.length)
- {
- row.set_disabled(true);
- }
- return row;
- }
- }
- },
-
- /**
- * Something changed, and the planner needs to be re-drawn. We wait a bit to
- * avoid re-drawing twice if start and end date both changed, then recreate.
- *
- * @param {boolean} trigger =false Trigger an event once things are done.
- * Waiting until invalidate completes prevents 2 updates when changing the date range.
- * @returns {undefined}
- */
- invalidate: function(trigger) {
-
- // Busy
- if(!this.doInvalidate) return;
-
- // Not yet ready
- if(!this.options.start_date || !this.options.end_date) return;
-
- // Wait a bit to see if anything else changes, then re-draw the days
- if(this.update_timer !== null)
- {
- window.clearTimeout(this.update_timer);
- }
- this.update_timer = window.setTimeout(jQuery.proxy(function() {
- this.widget.doInvalidate = false;
-
- // Show AJAX loader
- this.widget.loader.show();
-
- this.widget.cache = {};
- this._deferred_row_updates = {};
-
- this.widget._fetch_data();
-
- this.widget._drawGrid();
-
- if(this.trigger)
- {
- this.widget.change();
- }
- this.widget.update_timer = null;
- this.widget.doInvalidate = true;
-
- window.setTimeout(jQuery.proxy(function() {if(this.loader) this.loader.hide();},this.widget),500);
- },{widget:this,"trigger":trigger}),ET2_GRID_INVALIDATE_TIMEOUT);
- },
-
- detachFromDOM: function() {
- // Remove the binding to the change handler
- jQuery(this.div).off("change.et2_calendar_timegrid");
-
- this._super.apply(this, arguments);
- },
-
- attachToDOM: function() {
- this._super.apply(this, arguments);
-
- // Add the binding for the event change handler
- jQuery(this.div).on("change.et2_calendar_timegrid", '.calendar_calEvent', this, function(e) {
- // Make sure function gets a reference to the widget
- var args = Array.prototype.slice.call(arguments);
- if(args.indexOf(this) == -1) args.push(this);
-
- return e.data.event_change.apply(e.data, args);
- });
-
- // Add the binding for the change handler
- jQuery(this.div).on("change.et2_calendar_timegrid", '*:not(.calendar_calEvent)', this, function(e) {
- return e.data.change.call(e.data, e, this);
- });
-
- },
-
- getDOMNode: function(_sender)
- {
- if(_sender === this || !_sender)
- {
- return this.div[0];
- }
- if(_sender._parent === this)
- {
- return this.rows[0];
- }
- },
-
- /**
- * Creates all the DOM nodes for the planner grid
- *
- * Any existing nodes (& children) are removed, the headers & labels are
- * determined according to the current group_by value, and then the rows
- * are created.
- *
- * @method
- * @private
- *
- */
- _drawGrid: function()
- {
-
- this.div.css('height', this.options.height);
-
- // Clear old events
- var delete_index = this._children.length - 1;
- while(this._children.length > 0 && delete_index >= 0)
- {
- this._children[delete_index].free();
- this.removeChild(this._children[delete_index--]);
- }
-
- // Clear old rows
- this.rows.empty()
- .append(this.grid);
- this.grid.empty();
-
- var grouper = this.grouper;
- if(!grouper) return;
-
- // Headers
- this.headers.empty();
- this.headerTitle.text(grouper.title.apply(this));
- grouper.headers.apply(this);
- this.grid.find('*').contents().filter(function(){
- return this.nodeType === 3;
- }).remove();
-
- // Get the rows / labels
- var labels = grouper.row_labels.call(this);
-
- // Group the events
- var events = {};
- for(var i = 0; i < this.value.length; i++)
- {
- grouper.group.call(this, labels, events, this.value[i]);
- }
-
- // Set height for rows
- this.rows.height(this.div.height() - this.headers.outerHeight());
-
- // Draw the rows
- for(var key in labels)
- {
- if (!labels.hasOwnProperty(key)) continue;
-
- // Skip sub-categories (events are merged into top level)
- if(this.options.group_by == 'category' &&
- (!app.calendar.state.cat_id || app.calendar.state.cat_id == '') &&
- labels[key].id != labels[key].main
- )
- {
- continue;
- }
- var row = grouper.draw_row.call(this,labels[key].id, labels[key].label, events[key] || []);
-
- // Add extra data for clicking on row
- if(row)
- {
- for(var extra in labels[key].data)
- {
- row.getDOMNode().dataset[extra] = labels[key].data[extra];
- }
- }
- }
-
- // Adjust header if there's a scrollbar
- if(this.rows.children().last().length)
- {
- this.gridHeader.css('margin-right', (this.rows.width() - this.rows.children().last().width()) + 'px');
- }
- // Add actual events
- for(var key in this._deferred_row_updates)
- {
- window.clearTimeout(key);
- }
- window.setTimeout(jQuery.proxy(function() {
- this._deferred_row_update();
- }, this ),this.DEFERRED_ROW_TIME)
- this.value = [];
- },
-
- /**
- * Draw a single row of the planner
- *
- * @param {string} key Index into the grouped labels & events
- * @param {string} label
- * @param {Array} events
- * @param {Date} start
- * @param {Date} end
- */
- _drawRow: function(key, label, events, start, end)
- {
- var row = et2_createWidget('calendar-planner_row',{
- id: 'planner_row_'+key,
- label: label,
- start_date: start,
- end_date: end,
- value: events,
- readonly: this.options.readonly
- },this);
-
-
- if(this.isInTree())
- {
- row.doLoadingFinished();
- }
-
- return row;
- },
-
-
- _header_day_of_month: function()
- {
- var day_width = 3.23; // 100.0 / 31;
-
- // month scale with navigation
- var content = '
';
- var start = new Date(this.options.start_date);
- start = new Date(start.valueOf() + start.getTimezoneOffset() * 60 * 1000);
- var end = new Date(this.options.end_date);
- end = new Date(end.valueOf() + end.getTimezoneOffset() * 60 * 1000);
-
- var title = this.egw().lang(date('F',start))+' '+date('Y',start)+' - '+
- this.egw().lang(date('F',end))+' '+date('Y',end);
-
- content += '
'+
- title+"
";
- content += "
"; // end of plannerScale
-
- // day of month scale
- content +='
';
-
- for(var left = 0, i = 0; i < 31; left += day_width,++i)
- {
- content += '
'+
- (1+i)+"
\n";
- }
- content += "
\n";
-
- return content;
- },
-
- /**
- * Make a header showing the months
- * @param {Date} start
- * @param {number} days
- * @returns {string} HTML snippet
- */
- _header_months: function(start, days)
- {
- var content = '
';
- var days_in_month = 0;
- var day_width = 100 / days;
- var end = new Date(start);
- end.setUTCDate(end.getUTCDate()+days);
- var t = new Date(start.valueOf());
- for(var left = 0,i = 0; i < days;t.setUTCDate(1),t.setUTCMonth(t.getUTCMonth()+1),left += days_in_month*day_width,i += days_in_month)
- {
- var u = new Date(t.getUTCFullYear(),t.getUTCMonth()+1,0,-t.getTimezoneOffset()/60);
- days_in_month = 1+ ((u-t) / (24*3600*1000));
-
- var first = new Date(t.getUTCFullYear(),t.getUTCMonth(),1,-t.getTimezoneOffset()/60);
- if(days_in_month <= 0) break;
-
- if (i + days_in_month > days)
- {
- days_in_month = days - i;
- }
- var title = this.egw().lang(date('F',new Date(t.valueOf() + t.getTimezoneOffset() * 60 * 1000)));
- if (days_in_month > 10)
- {
- title += '
'+t.getUTCFullYear();
- }
- else if (days_in_month < 5)
- {
- title = ' ';
- }
- content += ''+
- title+"
";
- }
- content += ""; // end of plannerScale
-
- return content;
- },
-
- /**
- * Make a header showing the week numbers
- *
- * @param {Date} start
- * @param {number} days
- * @returns {string} HTML snippet
- */
- _header_weeks: function(start, days)
- {
-
- var content = '
';
- var state = '';
-
- // we're not using UTC so date() formatting function works
- var t = new Date(start.valueOf());
-
- // Make sure we're lining up on the week
- var week_end = app.calendar.date.end_of_week(start);
- var days_in_week = Math.floor(((week_end-start ) / (24*3600*1000))+1);
- var week_width = 100 / days * (days <= 7 ? days : days_in_week);
- for(var left = 0,i = 0; i < days; t.setUTCDate(t.getUTCDate() + 7),left += week_width)
- {
- // Avoid overflow at the end
- if(days - i < 7)
- {
- days_in_week = days-i;
- }
- var usertime = new Date(t.valueOf());
- if(start.getTimezoneOffset() < 0)
- {
- // Gets the right week # east of GMT. West does not need it(?)
- usertime.setUTCMinutes(usertime.getUTCMinutes() - start.getTimezoneOffset());
- }
-
- week_width = 100 / days * Math.min(days, days_in_week);
-
- var title = this.egw().lang('Week')+' '+app.calendar.date.week_number(usertime);
-
- if(start.getTimezoneOffset() > 0)
- {
- // Gets the right week start west of GMT
- usertime.setUTCMinutes(usertime.getUTCMinutes() +start.getTimezoneOffset());
- }
- state = app.calendar.date.start_of_week(usertime);
- state.setUTCHours(0);
- state.setUTCMinutes(0);
- state = state.toJSON();
-
- if(days_in_week > 1 || days == 1)
- {
- content += '
'+title+"
";
- }
- i+= days_in_week;
- if(days_in_week != 7)
- {
- t.setUTCDate(t.getUTCDate() - (7 - days_in_week));
- days_in_week = 7;
- }
- }
- content += "
"; // end of plannerScale
-
- return content;
- },
-
- /**
- * Make a header for some days
- *
- * @param {Date} start
- * @param {number} days
- * @returns {string} HTML snippet
- */
- _header_days: function(start, days)
- {
- var day_width = 100 / days;
- var content = '
';
-
- // we're not using UTC so date() formatting function works
- var t = new Date(start.valueOf() + start.getTimezoneOffset() * 60 * 1000);
- for(var left = 0,i = 0; i < days; t.setDate(t.getDate()+1),left += day_width,++i)
- {
- if(!this.options.show_weekend && [0,6].indexOf(t.getDay()) !== -1 ) continue;
- var holidays = [];
- var tempDate = new Date(t);
- tempDate.setMinutes(tempDate.getMinutes()-tempDate.getTimezoneOffset());
- var title = '';
- var state = '';
- state = new Date(t.valueOf() - t.getTimezoneOffset() * 60 * 1000);
- var day_class = this.day_class_holiday(state,holidays, days);
-
- if (days <= 3)
- {
- title = this.egw().lang(date('l',t))+', '+date('j',t)+'. '+this.egw().lang(date('F',t));
- }
- else if (days <= 7)
- {
- title = this.egw().lang(date('l',t))+' '+date('j',t);
- }
- else
- {
- title = this.egw().lang(date('D',t)).substr(0,2)+'
'+date('j',t);
- }
-
- content += '
'+title+"
\n";
- }
- content += "
"; // end of plannerScale
-
- return content;
- },
-
- /**
- * Create a header with hours
- *
- * @param {Date} start
- * @param {number} days
- * @returns {string} HTML snippet for the header
- */
- _header_hours: function(start,days)
- {
- var divisors = [1,2,3,4,6,8,12];
- var decr = 1;
- for(var i = 0; i < divisors.length; i++) // numbers dividing 24 without rest
- {
- if (divisors[i] > days) break;
- decr = divisors[i];
- }
- var hours = days * 24;
- if (days === 1) // for a single day we calculate the hours of a days, to take into account daylight saving changes (23 or 25 hours)
- {
- var t = new Date(start.getUTCFullYear(),start.getUTCMonth(),start.getUTCDate(),-start.getTimezoneOffset()/60);
- var s = new Date(start);
- s.setUTCHours(23);
- s.setUTCMinutes(59);
- s.setUTCSeconds(59);
- hours = Math.ceil((s.getTime() - t.getTime()) / 3600000);
- }
- var cell_width = 100 / hours * decr;
-
- var content = '
';
-
- // we're not using UTC so date() formatting function works
- var t = new Date(start.valueOf() + start.getTimezoneOffset() * 60 * 1000);
- for(var left = 0,i = 0; i < hours; left += cell_width,i += decr)
- {
- if(!this.options.show_weekend && [0,6].indexOf(t.getDay()) !== -1 ) continue;
- var title = date(egw.preference('timeformat','calendar') == 12 ? 'ha' : 'H',t);
-
- content += '
'+title+"
";
- t.setHours(t.getHours()+decr);
- }
- content += "
"; // end of plannerScale
-
- return content;
- },
-
- /**
- * Applies class for today, and any holidays for current day
- *
- * @param {Date} date
- * @param {string[]} holiday_list Filled with a list of holidays for that day
- * @param {integer} days Number of days shown in the day header
- *
- * @return {string} CSS Classes for the day. calendar_calBirthday, calendar_calHoliday, calendar_calToday and calendar_weekend as appropriate
- */
- day_class_holiday: function(date,holiday_list, days) {
-
- if(!date) return '';
-
- var day_class = '';
-
- // Holidays and birthdays
- 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
- this.date_helper.set_value(date.toJSON());
- var date_key = ''+this.date_helper.get_year() + sprintf('%02d',this.date_helper.get_month()) + sprintf('%02d',this.date_helper.get_date());
- if(holidays && holidays[date_key])
- {
- holidays = holidays[date_key];
- for(var i = 0; i < holidays.length; i++)
- {
- if (typeof holidays[i]['birthyear'] !== 'undefined')
- {
- day_class += ' calendar_calBirthday ';
- if(typeof days == 'undefined' || days <= 21)
- {
- day_class += ' calendar_calBirthdayIcon ';
- }
-
- holiday_list.push(holidays[i]['name']);
- }
- else
- {
- day_class += 'calendar_calHoliday ';
-
- holiday_list.push(holidays[i]['name']);
- }
- }
- }
- holidays = holiday_list.join(',');
- var today = new Date();
- if(date_key === ''+today.getFullYear()+
- sprintf("%02d",today.getMonth()+1)+
- sprintf("%02d",today.getDate())
- )
- {
- day_class += "calendar_calToday ";
- }
- if(date.getUTCDay() == 0 || date.getUTCDay() == 6)
- {
- day_class += "calendar_weekend ";
- }
- return day_class;
- },
-
- /**
- * Link the actions to the DOM nodes / widget bits.
- *
- * @todo This currently does nothing
- * @param {object} actions {ID: {attributes..}+} map of egw action information
- */
- _link_actions: function(actions)
- {
- if(!this._actionObject)
- {
- // Get the parent? Might be a grid row, might not. Either way, it is
- // just a container with no valid actions
- var objectManager = egw_getObjectManager(this.getInstanceManager().app,true,1);
- objectManager = objectManager.getObjectById(this.getInstanceManager().uniqueId,2) || objectManager;
- var parent = objectManager.getObjectById(this.id,3) || objectManager.getObjectById(this._parent.id,3) || objectManager;
- if(!parent)
- {
- debugger;
- egw.debug('error','No parent objectManager found');
- return;
- }
-
- for(var i = 0; i < parent.children.length; i++)
- {
- var parent_finder = jQuery('#'+this.div.id, parent.children[i].iface.doGetDOMNode());
- if(parent_finder.length > 0)
- {
- parent = parent.children[i];
- break;
- }
- }
- }
-
- // This binds into the egw action system. Most user interactions (drag to move, resize)
- // are handled internally using jQuery directly.
- var widget_object = this._actionObject || parent.getObjectById(this.id);
-
- var aoi = new et2_action_object_impl(this,this.getDOMNode());
-
- /**
- * Determine if we allow a dropped event to use the invite/change actions,
- * and enable or disable them appropriately
- *
- * @param {egwAction} action
- * @param {et2_calendar_event} event The event widget being dragged
- * @param {egwActionObject} target Planner action object
- */
- var _invite_enabled = function(action, event, target)
- {
- var event = event.iface.getWidget();
- var planner = target.iface.getWidget() || false;
- //debugger;
- if(event === planner || !event || !planner ||
- !event.options || !event.options.value.participants || !planner.options.owner
- )
- {
- return false;
- }
- var owner_match = false;
- var own_row = false;
-
- for(var id in event.options.value.participants)
- {
- planner.iterateOver(function(row) {
- // Check scroll section or header section
- if(row.div.hasClass('drop-hover') || row.div.has(':hover'))
- {
- owner_match = owner_match || row.node.dataset[planner.options.group_by] === ''+id;
- own_row = (row === event.getParent());
- }
- }, this, et2_calendar_planner_row);
-
- }
- var enabled = !owner_match &&
- // Not inside its own row
- !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')
- {
- this.getWidget()._event_drop.call(jQuery('.calendar_d-n-d_timeCounter',_data.ui.helper)[0],this.getWidget(),event, _data.ui);
- }
- var drag_listener = function(event, ui) {
- aoi.getWidget()._drag_helper(jQuery('.calendar_d-n-d_timeCounter',ui.helper)[0],{
- top:ui.position.top,
- left: ui.position.left - jQuery(this).parent().offset().left
- },0);
- };
- var 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'+widget_object.id, drag_listener);
- _data.ui.draggable.on('dragend.et2_timegrid'+widget_object.id, function() {
- _data.ui.draggable.off('drag.et2_timegrid' + widget_object.id);
- });
- 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('
');
- }
-
- 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'+widget_object.id);
- // Remove any highlighted time squares
- jQuery('[data-date]',this.doGetDOMNode()).removeClass("ui-state-active");
-
- // 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
- ),EGW_AO_FLAG_IS_CONTAINER);
- }
- else
- {
- widget_object.setAOI(aoi);
- }
- // Go over the widget & add links - this is where we decide which actions are
- // 'allowed' for this widget at this time
- var action_links = this._get_action_links(actions);
-
- this._init_links_dnd(widget_object.manager, action_links);
-
- widget_object.updateActionLinks(action_links);
- this._actionObject = widget_object;
- },
-
- /**
- * Automatically add dnd support for linking
- *
- * @param {type} mgr
- * @param {type} actionLinks
- */
- _init_links_dnd: function(mgr,actionLinks) {
-
- if (this.options.readonly) return;
-
- var self = this;
-
- var drop_action = mgr.getActionById('egw_link_drop');
- var drop_change_participant = mgr.getActionById('change_participant');
- var drop_invite = mgr.getActionById('invite');
- var drag_action = mgr.getActionById('egw_link_drag');
- var paste_action = mgr.getActionById('egw_paste');
-
- // Disable paste action
- if(paste_action == null)
- {
- paste_action = mgr.addAction('popup', 'egw_paste', egw.lang('Paste'), egw.image('editpaste'), function(){},true);
- }
- paste_action.set_enabled(false);
-
- // Check if this app supports linking
- if(!egw.link_get_registry(this.dataStorePrefix || 'calendar', 'query') ||
- egw.link_get_registry(this.dataStorePrefix || 'calendar', 'title'))
- {
- if(drop_action)
- {
- drop_action.remove();
- if(actionLinks.indexOf(drop_action.id) >= 0)
- {
- actionLinks.splice(actionLinks.indexOf(drop_action.id),1);
- }
- }
- if(drag_action)
- {
- drag_action.remove();
- if(actionLinks.indexOf(drag_action.id) >= 0)
- {
- actionLinks.splice(actionLinks.indexOf(drag_action.id),1);
- }
- }
- return;
- }
-
- // Don't re-add
- if(drop_action == null)
- {
- // Create the drop action that links entries
- drop_action = mgr.addAction('drop', 'egw_link_drop', egw.lang('Create link'), egw.image('link'), function(action, source, dropped) {
- // Extract link IDs
- var links = [];
- var id = '';
- for(var i = 0; i < source.length; i++)
- {
- if(!source[i].id) continue;
- id = source[i].id.split('::');
- links.push({app: id[0] == 'filemanager' ? 'link' : id[0], id: id[1]});
- }
- if(!links.length)
- {
- return;
- }
- if(links.length && dropped && dropped.iface.getWidget() && dropped.iface.getWidget().instanceOf(et2_calendar_event))
- {
- // Link the entries
- egw.json(self.egw().getAppName()+".etemplate_widget_link.ajax_link.etemplate",
- dropped.id.split('::').concat([links]),
- function(result) {
- if(result)
- {
- this.egw().message('Linked');
- }
- },
- self,
- true,
- self
- ).sendRequest();
- }
- },true);
-
- drop_action.acceptedTypes = ['default','link'];
- drop_action.hideOnDisabled = true;
-
- // Create the drop action for moving events between planner rows
- var invite_action = function(action, source, target) {
-
- // Extract link IDs
- var links = [];
- var id = '';
- for(var i = 0; i < source.length; i++)
- {
- // Check for no ID (invalid) or same manager (dragging an event)
- if(!source[i].id) continue;
- if(source[i].manager === target.manager)
- {
-
- // Find the row, could have dropped on an event
- var row = target.iface.getWidget();
- while(target.parent && row.instanceOf && !row.instanceOf(et2_calendar_planner_row))
- {
- target = target.parent;
- row = target.iface.getWidget();
- }
-
- // Leave the helper there until the update is done
- var loading = action.ui.helper.clone(true).appendTo(jQuery('body'));
-
- // and add a loading icon so user knows something is happening
- if(jQuery('.calendar_timeDemo',loading).length == 0)
- {
- jQuery('.calendar_calEventHeader',loading).addClass('loading');
- }
- else
- {
- jQuery('.calendar_timeDemo',loading).after('
');
- }
-
- var event_data = egw.dataGetUIDdata(source[i].id).data;
- et2_calendar_event.recur_prompt(event_data, function(button_id) {
- if(button_id === 'cancel' || !button_id)
- {
- return;
- }
- var add_owner = [row.node.dataset.participants];
-
- egw().json('calendar.calendar_uiforms.ajax_invite', [
- button_id==='series' ? event_data.id : event_data.app_id,
- add_owner,
- action.id === 'change_participant' ?
- [source[i].iface.getWidget().getParent().node.dataset.participants] :
- []
- ],
- function() { loading.remove();}
- ).sendRequest(true);
- });
- // Ok, stop.
- return false;
- }
- }
- };
-
- drop_change_participant = mgr.addAction('drop', 'change_participant', egw.lang('Move to'), egw.image('participant'), invite_action,true);
- drop_change_participant.acceptedTypes = ['calendar'];
- drop_change_participant.hideOnDisabled = true;
-
- drop_invite = mgr.addAction('drop', 'invite', egw.lang('Invite'), egw.image('participant'), invite_action,true);
- drop_invite.acceptedTypes = ['calendar'];
- drop_invite.hideOnDisabled = true;
- }
- if(actionLinks.indexOf(drop_action.id) < 0)
- {
- actionLinks.push(drop_action.id);
- }
- actionLinks.push(drop_invite.id);
- actionLinks.push(drop_change_participant.id);
-
- // Accept other links, and files dragged from the filemanager
- // This does not handle files dragged from the desktop. They are
- // handled by et2_nextmatch, since it needs DOM stuff
- if(drop_action.acceptedTypes.indexOf('link') == -1)
- {
- drop_action.acceptedTypes.push('link');
- }
-
- // Don't re-add
- if(drag_action == null)
- {
- // Create drag action that allows linking
- drag_action = mgr.addAction('drag', 'egw_link_drag', egw.lang('link'), 'link', function(action, selected) {
- // As we wanted to have a general defaul helper interface, we return null here and not using customize helper for links
- // TODO: Need to decide if we need to create a customized helper interface for links anyway
- //return helper;
- return null;
- },true);
- }
- // The planner itself is not draggable, the action is there for the children
- if(false && actionLinks.indexOf(drag_action.id) < 0)
- {
- actionLinks.push(drag_action.id);
- }
- drag_action.set_dragType(['link','calendar']);
- },
-
- /**
- * 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: function(actions)
- {
- var action_links = [];
-
- // Only these actions are allowed without a selection (empty actions)
- var empty_actions = ['add'];
-
- for(var i in actions)
- {
- var action = actions[i];
- if(empty_actions.indexOf(action.id) !== -1 || action.type === 'drop')
- {
- action_links.push(typeof action.id !== 'undefined' ? action.id : i);
- }
- }
- // Disable automatic paste action, it doesn't have what is needed to work
- action_links.push({
- "actionObj": 'egw_paste',
- "enabled": false,
- "visible": false
- });
- return action_links;
- },
-
- /**
- * Show the current time while dragging
- * Used for resizing as well as drag & drop
- *
- * @param {type} element
- * @param {type} position
- * @param {type} height
- */
- _drag_helper: function(element, position ,height)
- {
- var time = this._get_time_from_position(position.left, position.top);
- element.dropEnd = time;
- var formatted_time = jQuery.datepicker.formatTime(
- egw.preference("timeformat") === "12" ? "h:mmtt" : "HH:mm",
- {
- hour: time.getUTCHours(),
- minute: time.getUTCMinutes(),
- seconds: 0,
- timezone: 0
- },
- {"ampm": (egw.preference("timeformat") === "12")}
- );
-
- element.innerHTML = '
'+formatted_time+'
';
-
- //jQuery(element).width(jQuery(helper).width());
- },
-
- /**
- * Handler for dropping an event on the timegrid
- *
- * @param {type} planner
- * @param {type} event
- * @param {type} ui
- */
- _event_drop: function(planner, event,ui) {
- var e = new jQuery.Event('change');
- e.originalEvent = event;
- e.data = {start: 0};
- if (typeof this.dropEnd != 'undefined')
- {
- var drop_date = this.dropEnd.toJSON() ||false;
-
- var event_data = planner._get_event_info(ui.draggable);
- var event_widget = planner.getWidgetById(event_data.widget_id);
- if(event_widget)
- {
- event_widget._parent.date_helper.set_value(drop_date);
- event_widget.options.value.start = new Date(event_widget._parent.date_helper.getValue());
-
- // Leave the helper there until the update is done
- var loading = ui.helper.clone().appendTo(ui.helper.parent());
- // and add a loading icon so user knows something is happening
- jQuery('.calendar_timeDemo',loading).after('
');
-
- event_widget.recur_prompt(function(button_id) {
- if(button_id === 'cancel' || !button_id) return;
- //Get infologID if in case if it's an integrated infolog event
- if (event_data.app === 'infolog')
- {
- // If it is an integrated infolog event we need to edit infolog entry
- egw().json('stylite_infolog_calendar_integration::ajax_moveInfologEvent',
- [event_data.id, event_widget.options.value.start||false],
- function() {loading.remove();}
- ).sendRequest(true);
- }
- else
- {
- //Edit calendar event
- egw().json('calendar.calendar_uiforms.ajax_moveEvent', [
- button_id==='series' ? event_data.id : event_data.app_id,event_data.owner,
- event_widget.options.value.start,
- planner.options.owner||egw.user('account_id')
- ],
- function() { loading.remove();}
- ).sendRequest(true);
- }
- });
- }
- }
- },
-
- /**
- * Use the egw.data system to get data from the calendar list for the
- * selected time span.
- *
- */
- _fetch_data: function()
- {
- var value = [];
- var fetch = false;
- this.doInvalidate = false;
-
- for(var i = 0; i < this.registeredCallbacks.length; i++)
- {
- egw.dataUnregisterUID(this.registeredCallbacks[i],false,this);
- }
- this.registeredCallbacks.splice(0,this.registeredCallbacks.length);
-
- // Remember previous day to avoid multi-days duplicating
- var last_data = [];
-
- var t = new Date(this.options.start_date);
- var end = new Date(this.options.end_date);
- do
- {
- value = value.concat(this._cache_register(t, this.options.owner, last_data));
-
- t.setUTCDate(t.getUTCDate() + 1);
- }
- while(t < end);
-
- this.doInvalidate = true;
- return value;
- },
-
- /**
- * Deal with registering for data cache
- *
- * @param Date t
- * @param String owner Calendar owner
- */
- _cache_register: function _cache_register(t, owner, last_data)
- {
- // Cache is by date (and owner, if seperate)
- var date = t.getUTCFullYear() + sprintf('%02d',t.getUTCMonth()+1) + sprintf('%02d',t.getUTCDate());
- var cache_id = app.classes.calendar._daywise_cache_id(date, owner);
- var value = [];
-
- if(egw.dataHasUID(cache_id))
- {
- var c = egw.dataGetUIDdata(cache_id);
- if(c.data && c.data !== null)
- {
- // There is data, pass it along now
- for(var j = 0; j < c.data.length; j++)
- {
- if(last_data.indexOf(c.data[j]) === -1 && egw.dataHasUID('calendar::'+c.data[j]))
- {
- value.push(egw.dataGetUIDdata('calendar::'+c.data[j]).data);
- }
- }
- last_data = c.data;
- }
- }
- else
- {
- fetch = true;
- // Assume it's empty, if there is data it will be filled later
- egw.dataStoreUID(cache_id, []);
- }
- this.registeredCallbacks.push(cache_id);
-
- egw.dataRegisterUID(cache_id, function(data) {
-
- if(data && data.length)
- {
- var invalidate = true;
-
- // Try to determine rows interested
- var labels = [];
- var events = {};
- if(this.grouper)
- {
- labels = this.grouper.row_labels.call(this);
- invalidate = false;
- }
-
- var im = this.getInstanceManager();
- for(var i = 0; i < data.length; i++)
- {
- var event = egw.dataGetUIDdata('calendar::'+data[i]);
-
- if(!event) continue;
- events = {};
-
- // Try to determine rows interested
- if(event.data && this.grouper)
- {
- this.grouper.group.call(this, labels, events, event.data);
- }
- if(Object.keys(events).length > 0 )
- {
- for(var label_id in events)
- {
- var id = ""+labels[label_id].id;
- if(typeof this.cache[id] === 'undefined')
- {
- this.cache[id] = [];
- }
- if(this.cache[id].indexOf(event.data.row_id) === -1)
- {
- this.cache[id].push(event.data.row_id);
- }
- if (this._deferred_row_updates[id])
- {
- window.clearTimeout(this._deferred_row_updates[id]);
- }
- this._deferred_row_updates[id] = window.setTimeout(jQuery.proxy(this._deferred_row_update,this,id),this.DEFERRED_ROW_TIME);
- }
- }
- else
- {
- // Could be an event no row is interested in, could be a problem.
- // Just redraw everything
- invalidate = true;
- continue;
- }
-
- // If displaying by category, we need the infolog (or other app) categories too
- if(event && event.data && event.data.app && this.options.group_by == 'category')
- {
- // Fake it to use the cache / call
- et2_selectbox.cat_options({
- _type:'select-cat',
- getInstanceManager: function() {return im;}
- }, {application:event.data.app||'calendar'});
- }
- }
-
- if(invalidate)
- {
- this.invalidate(false);
- }
- }
- }, this, this.getInstanceManager().execId,this.id);
-
- return value;
- },
-
- /**
- * Because users may be participants in various events and the time it takes
- * to create many events, we don't want to update a row too soon - we may have
- * to re-draw it if we find the user / category in another event. Pagination
- * makes this worse. We wait a bit before updating the row to avoid
- * having to re-draw it multiple times.
- *
- * @param {type} id
- * @returns {undefined}
- */
- _deferred_row_update: function(id) {
- // Something's in progress, skip
- if(!this.doInvalidate) return;
-
- this.grid.height(0);
-
- var id_list = typeof id === 'undefined' ? Object.keys(this.cache) : [id];
- for(var i = 0; i < id_list.length; i++)
- {
- var cache_id = id_list[i];
- var row = this.getWidgetById('planner_row_'+cache_id);
-
- window.clearTimeout(this._deferred_row_updates[cache_id]);
- delete this._deferred_row_updates[cache_id];
-
- if(row)
- {
- row._data_callback(this.cache[cache_id]);
- row.set_disabled(this.options.hide_empty && this.cache[cache_id].length === 0);
- }
- else
- {
- break;
- }
- }
-
- // Updating the row may push things longer, update length
- // Add 1 to keep the scrollbar, otherwise we need to recalculate the
- // header widths too.
- this.grid.height(this.rows[0].scrollHeight+1);
-
- // Adjust header if there's a scrollbar - Firefox needs this re-calculated,
- // otherwise the header will be missing the margin space for the scrollbar
- // in some cases
- if(this.rows.children().last().length)
- {
- this.gridHeader.css('margin-right', (this.rows.width() - this.rows.children().last().width()) + 'px');
- }
- },
-
- /**
- * Provide specific data to be displayed.
- * This is a way to set start and end dates, owner and event data in once call.
- *
- * @param {Object[]} events Array of events, indexed by date in Ymd format:
- * {
- * 20150501: [...],
- * 20150502: [...]
- * }
- * Days should be in order.
- *
- */
- set_value: function set_value(events)
- {
- if(typeof events !== 'object') return false;
-
- this._super.apply(this, arguments);
-
- // Planner uses an array, not map
- var val = this.value;
- var array = [];
- Object.keys(this.value).forEach(function (key) {
- array.push(val[key]);
- });
- this.value = array;
- },
-
- /**
- * Change the start date
- * Planner view uses a date object internally
- *
- * @param {string|number|Date} new_date New starting date
- * @returns {undefined}
- */
- set_start_date: function set_start_date(new_date)
- {
- this._super.apply(this, arguments);
- this.options.start_date = new Date(this.options.start_date);
- },
-
- /**
- * Change the end date
- * Planner view uses a date object internally
- *
- * @param {string|number|Date} new_date New end date
- * @returns {undefined}
- */
- set_end_date: function set_end_date(new_date)
- {
- this._super.apply(this, arguments);
- this.options.end_date = new Date(this.options.end_date);
- },
-
- /**
- * Change how the planner is grouped
- *
- * @param {string|number} group_by 'user', 'month', or an integer category ID
- * @returns {undefined}
- */
- set_group_by: function set_group_by(group_by)
- {
- if(isNaN(group_by) && typeof this.groupers[group_by] === 'undefined')
- {
- throw new Error('Invalid group_by "'+group_by+'"');
- }
- var old = this.options.group_by;
- this.options.group_by = ''+group_by;
-
- this.grouper = this.groupers[isNaN(this.options.group_by) ? this.options.group_by : 'category'];
-
- if(old !== this.options.group_by && this.isAttached())
- {
- this.invalidate(true);
- }
- },
-
- /**
- * Turn on or off the visibility of weekends
- *
- * @param {boolean} weekends
- */
- set_show_weekend: function set_show_weekend(weekends)
- {
- weekends = weekends ? true : false;
- if(this.options.show_weekend !== weekends)
- {
- this.options.show_weekend = weekends;
- if(this.isAttached())
- {
- this.invalidate();
- }
- }
- },
-
- /**
- * Turn on or off the visibility of hidden (empty) rows
- *
- * @param {boolean} hidden
- */
- set_hide_empty: function set_hide_empty(hidden)
- {
- this.options.hide_empty = hidden;
- },
-
- /**
- * Call change handler, if set
- *
- * @param {type} event
- */
- change: function(event) {
- if (this.onchange)
- {
- if(typeof this.onchange == 'function')
- {
- // Make sure function gets a reference to the widget
- var args = Array.prototype.slice.call(arguments);
- if(args.indexOf(this) == -1) args.push(this);
-
- return this.onchange.apply(this, args);
- } else {
- return (et2_compileLegacyJS(this.options.onchange, this, _node))();
- }
- }
- },
-
- /**
- * Call event change handler, if set
- *
- * @param {type} event
- * @param {type} dom_node
- */
- event_change: function(event, dom_node) {
- if (this.onevent_change)
- {
- var event_data = this._get_event_info(dom_node);
- var event_widget = this.getWidgetById(event_data.widget_id);
- et2_calendar_event.recur_prompt(event_data, jQuery.proxy(function(button_id, event_data) {
- // No need to continue
- if(button_id === 'cancel') return false;
-
- if(typeof this.onevent_change == 'function')
- {
- // Make sure function gets a reference to the widget
- var args = Array.prototype.slice.call(arguments);
-
- if(args.indexOf(event_widget) == -1) args.push(event_widget);
-
- // Put button ID in event
- event.button_id = button_id;
-
- return this.onevent_change.apply(this, [event, event_widget, button_id]);
- } else {
- return (et2_compileLegacyJS(this.options.onevent_change, event_widget, dom_node))();
- }
- },this));
- }
- return false;
- },
-
- /**
- * Click handler calling custom handler set via onclick attribute to this.onclick
- *
- * This also handles all its own actions, including navigation. If there is
- * an event associated with the click, it will be found and passed to the
- * onclick function.
- *
- * @param {Event} _ev
- * @returns {boolean} Continue processing event (true) or stop (false)
- */
- click: function(_ev)
- {
- var result = true;
-
- // Drag to create in progress
- if(this.drag_create.start !== null) return;
-
- // Is this click in the event stuff, or in the header?
- if(!this.options.readonly && this.gridHeader.has(_ev.target).length === 0 && !jQuery(_ev.target).hasClass('calendar_plannerRowHeader'))
- {
- // Event came from inside, maybe a calendar event
- var event = this._get_event_info(_ev.originalEvent.target);
- if(typeof this.onclick == 'function')
- {
- // Make sure function gets a reference to the widget, splice it in as 2. argument if not
- var args = Array.prototype.slice.call(arguments);
- if(args.indexOf(this) == -1) args.splice(1, 0, this);
-
- result = this.onclick.apply(this, args);
- }
-
- if(event.id && result && !this.options.disabled && !this.options.readonly)
- {
- et2_calendar_event.recur_prompt(event);
-
- return false;
- }
- else if (!event.id)
- {
- // Clicked in row, but not on an event
- // Default handler to open a new event at the selected time
- if(jQuery(event.target).closest('.calendar_eventRows').length == 0)
- {
- // "Invalid" times, from space after the last planner row, or header
- var date = this._get_time_from_position(_ev.pageX - this.grid.offset().left, _ev.pageY - this.grid.offset().top);
- }
- else if(this.options.group_by == 'month')
- {
- var date = this._get_time_from_position(_ev.clientX, _ev.clientY);
- }
- else
- {
- var date = this._get_time_from_position(_ev.offsetX, _ev.offsetY);
- }
- var row = jQuery(_ev.target).closest('.calendar_plannerRowWidget');
- var data = row.length ? row[0].dataset : {};
- app.calendar.add(jQuery.extend({
- start: date.toJSON(),
- hour: date.getUTCHours(),
- minute: date.getUTCMinutes()
- },data));
- return false;
- }
- return result;
- }
- else if (this.gridHeader.has(_ev.target).length > 0 && !jQuery.isEmptyObject(_ev.target.dataset) ||
- jQuery(_ev.target).hasClass('calendar_plannerRowHeader') && !jQuery.isEmptyObject(_ev.target.dataset))
- {
- // Click on a header, we can go there
- _ev.data = jQuery.extend({},_ev.target.parentNode.dataset, _ev.target.dataset);
- for(var key in _ev.data)
- {
- if(!_ev.data[key])
- {
- delete _ev.data[key];
- }
- }
- app.calendar.update_state(_ev.data);
- }
- else if (!this.options.readonly)
- {
- // Default handler to open a new event at the selected time
- // TODO: Determine date / time more accurately from position
- app.calendar.add({
- date: _ev.target.dataset.date || this.options.start_date.toJSON(),
- hour: _ev.target.dataset.hour || this.options.day_start,
- minute: _ev.target.dataset.minute || 0
- });
- return false;
- }
- },
-
- /**
- * Get time from position
- *
- * @param {number} x
- * @param {number} y
- * @returns {Date|Boolean} A time for the given position, or false if one
- * could not be determined.
- */
- _get_time_from_position: function(x,y) {
-
- x = Math.round(x);
- y = Math.round(y);
-
- // Round to user's preferred event interval
- var interval = egw.preference('interval','calendar') || 30;
-
- // Relative horizontal position, as a percentage
- var width = 0;
- jQuery('.calendar_eventRows',this.div).each(function() {width = Math.max(width,jQuery(this).width());});
- var rel_x = Math.min(x / width,1);
-
- // Relative time, in minutes from start
- var rel_time = 0;
-
- var day_header = jQuery('.calendar_plannerScaleDay',this.headers);
-
- // Simple math, the x is offset from start date
- if(this.options.group_by !== 'month' && (
- // Either all days are visible, or only 1 day (no day header)
- this.options.show_weekend || day_header.length === 0
- ))
- {
- rel_time = (new Date(this.options.end_date) - new Date(this.options.start_date))*rel_x/1000;
- this.date_helper.set_value(this.options.start_date.toJSON());
- }
- // Not so simple math, need to account for missing days
- else if(this.options.group_by !== 'month' && !this.options.show_weekend)
- {
- // Find which day
- if(day_header.length === 0) return false;
- var day = document.elementFromPoint(
- day_header.offset().left + rel_x * this.headers.innerWidth(),
- day_header.offset().top
- );
-
- // Use day, and find time in that day
- if(day && day.dataset && day.dataset.date)
- {
- this.date_helper.set_value(day.dataset.date);
- rel_time = ((x - jQuery(day).position().left) / jQuery(day).outerWidth(true)) * 24*60;
- this.date_helper.set_minutes(Math.round(rel_time/interval) * interval);
- return new Date(this.date_helper.getValue());
- }
- return false;
- }
- else
- {
- // Find the correct row so we know which month, then get the offset
- var hidden_nodes = [];
- var row = null;
- // Hide any drag or tooltips that may interfere
- do
- {
- row = document.elementFromPoint(x, y);
- if(this.div.has(row).length == 0)
- {
- hidden_nodes.push(jQuery(row).hide());
- }
- else
- {
- break;
- }
- } while(row && row.nodeName !== 'BODY');
- if(!row) return false;
-
- // Restore hidden nodes
- for(var i = 0; i < hidden_nodes.length; i++)
- {
- hidden_nodes[i].show();
- }
- row = jQuery(row).closest('.calendar_plannerRowWidget');
-
-
- var row_widget = null;
- for(var i = 0; i < this._children.length && row.length > 0; i++)
- {
- if(this._children[i].div[0] == row[0])
- {
- row_widget = this._children[i];
- break;
- }
- }
- if(row_widget)
- {
- // Not sure where the extra -1 and +2 are coming from, but it makes it work out
- // in FF & Chrome
- rel_x = Math.min((x-row_widget.rows.offset().left-1)/(row_widget.rows.width()+2),1);
-
- // 2678400 is the number of seconds in 31 days
- rel_time = (2678400)*rel_x;
- this.date_helper.set_value(row_widget.options.start_date.toJSON());
- }
- else
- {
- return false;
- }
- }
- if(rel_time < 0) return false;
-
- this.date_helper.set_minutes(Math.round(rel_time / (60 * interval))*interval);
-
- return new Date(this.date_helper.getValue());
- },
-
- /**
- * Mousedown handler to support drag to create
- *
- * @param {jQuery.Event} event
- */
- _mouse_down: function(event)
- {
- // Only left mouse button
- if(event.which !== 1) return;
-
- // Ignore headers
- if(this.headers.has(event.target).length !== 0) return false;
-
- // Get time at mouse
- if(this.options.group_by === 'month')
- {
- var time = this._get_time_from_position(event.clientX, event.clientY);
- }
- else
- {
- var time = this._get_time_from_position(event.offsetX, event.offsetY);
- }
- if(!time) return false;
-
- // Find the correct row so we know the parent
- var row = event.target.closest('.calendar_plannerRowWidget');
- for(var i = 0; i < this._children.length && row; i++)
- {
- if(this._children[i].div[0] === row)
- {
- this.drag_create.parent = this._children[i];
- // Clear cached events for re-layout
- this._children[i]._cached_rows = [];
- break;
- }
- }
- if(!this.drag_create.parent) return false;
-
- this.div.css('cursor', 'ew-resize');
-
- return this._drag_create_start(jQuery.extend({},this.drag_create.parent.node.dataset,{date: time.toJSON()}));
- },
-
- /**
- * Mouseup handler to support drag to create
- *
- * @param {jQuery.Event} event
- */
- _mouse_up: function(event)
- {
- // Get time at mouse
- if(this.options.group_by === 'month')
- {
- var time = this._get_time_from_position(event.clientX, event.clientY);
- }
- else
- {
- var time = this._get_time_from_position(event.offsetX, event.offsetY);
- }
-
- return this._drag_create_end(time ? {date: time.toJSON()} : false);
- },
-
- /**
- * Code for implementing et2_IDetachedDOM
- *
- * @param {array} _attrs array to add further attributes to
- */
- getDetachedAttributes: function(_attrs) {
- _attrs.push('start_date','end_date');
- },
-
- getDetachedNodes: function() {
- return [this.getDOMNode()];
- },
-
- setDetachedAttributes: function(_nodes, _values) {
- this.div = jQuery(_nodes[0]);
-
- if(_values.start_date)
- {
- this.set_start_date(_values.start_date);
- }
- if(_values.end_date)
- {
- this.set_end_date(_values.end_date);
- }
- },
-
- // Resizable interface
- resize: function ()
- {
- // Take the whole tab height
- var height = Math.min(jQuery(this.getInstanceManager().DOMContainer).height(),jQuery(this.getInstanceManager().DOMContainer).parent().innerHeight());
-
- // Allow for toolbar
- height -= jQuery('#calendar-toolbar',this.div.parents('.egw_fw_ui_tab_content')).outerHeight(true);
-
- this.options.height = height;
- this.div.css('height', this.options.height);
- // Set height for rows
- this.rows.height(this.div.height() - this.headers.outerHeight());
-
- this.grid.height(this.rows[0].scrollHeight);
- },
-
- /**
- * Set up for printing
- *
- * @return {undefined|Deferred} Return a jQuery Deferred object if not done setting up
- * (waiting for data)
- */
- beforePrint: function() {
-
- if(this.disabled || !this.div.is(':visible'))
- {
- return;
- }
- this.rows.css('overflow-y', 'visible');
-
- var rows = jQuery('.calendar_eventRows');
- var width = rows.width();
- var events = jQuery('.calendar_calEvent', rows)
- .each(function() {
- var event = jQuery(this);
- event.width((event.width() / width) * 100 + '%')
- });
-
- },
-
- /**
- * Reset after printing
- */
- afterPrint: function() {
- this.rows.css('overflow-y', 'auto');
- }
-});}).call(this);
-et2_register_widget(et2_calendar_planner, ["calendar-planner"]);
+var et2_calendar_planner = /** @class */ (function (_super) {
+ __extends(et2_calendar_planner, _super);
+ /**
+ * Constructor
+ */
+ function et2_calendar_planner(_parent, _attrs, _child) {
+ var _this =
+ // Call the inherited constructor
+ _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_calendar_planner._attributes, _child || {})) || this;
+ /**
+ * These handle the differences between the different group types.
+ * They provide the different titles, labels and grouping
+ */
+ _this.groupers = {
+ // Group by user has one row for each user
+ user: {
+ // Title in top left corner
+ title: function () {
+ return this.egw().lang('User');
+ },
+ // Column headers
+ headers: function () {
+ var start = new Date(this.options.start_date);
+ var end = new Date(this.options.end_date);
+ var start_date = new Date(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate());
+ var end_date = new Date(end.getUTCFullYear(), end.getUTCMonth(), end.getUTCDate());
+ var day_count = Math.round((end_date - start_date) / (1000 * 3600 * 24)) + 1;
+ if (day_count >= 6) {
+ this.headers.append(this._header_months(start, day_count));
+ }
+ if (day_count < 120) {
+ var weeks = this._header_weeks(start, day_count);
+ this.headers.append(weeks);
+ this.grid.append(weeks);
+ }
+ if (day_count < 60) {
+ var days = this._header_days(start, day_count);
+ this.headers.append(days);
+ this.grid.append(days);
+ }
+ if (day_count <= 7) {
+ var hours = this._header_hours(start, day_count);
+ this.headers.append(hours);
+ this.grid.append(hours);
+ }
+ },
+ // Labels for the rows
+ row_labels: function () {
+ var labels = [];
+ var already_added = [];
+ var options = false;
+ var resource = null;
+ 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');
+ }
+ for (var i = 0; i < this.options.owner.length; i++) {
+ var user = this.options.owner[i];
+ // Handle grouped resources like mailing lists - pull it from sidebox owner
+ // and expand to their contents
+ if (options && options.find &&
+ ((resource = options.find(function (element) { return element.id == user; }) || {}) || isNaN(user))) {
+ if (resource && resource.resources) {
+ for (var j = 0; j < resource.resources.length; j++) {
+ var id = resource.resources[j];
+ if (already_added.indexOf('' + id) < 0) {
+ labels.push({
+ id: id,
+ label: this._get_owner_name(id) || '',
+ data: { participants: id, owner: id }
+ });
+ already_added.push('' + id);
+ }
+ }
+ }
+ else if (already_added.indexOf('' + user) < 0) {
+ labels.push({
+ id: user,
+ label: this._get_owner_name(user),
+ data: { participants: user, owner: user }
+ });
+ already_added.push('' + user);
+ }
+ }
+ else if (user < 0) // groups
+ {
+ egw.accountData(parseInt(user), 'account_fullname', true, function (result) {
+ for (var id in result) {
+ if (already_added.indexOf('' + id) < 0) {
+ this.push({ id: id, label: result[id] || '', data: { participants: id, owner: id } });
+ already_added.push('' + id);
+ }
+ }
+ }, labels);
+ }
+ else // users
+ {
+ if (already_added.indexOf(user) < 0) {
+ var label = this._get_owner_name(user) || '';
+ labels.push({ id: user, label: label, data: { participants: user, owner: '' } });
+ already_added.push('' + user);
+ }
+ }
+ }
+ return labels.sort(function (a, b) {
+ return a.label.localeCompare(b.label);
+ });
+ },
+ // Group the events into the rows
+ group: function (labels, rows, event) {
+ // convert filter to allowed status
+ var status_to_show = ['U', 'A', 'T', 'D', 'G'];
+ switch (this.options.filter) {
+ case 'unknown':
+ status_to_show = ['U', 'G'];
+ break;
+ case 'accepted':
+ status_to_show = ['A'];
+ break;
+ case 'tentative':
+ status_to_show = ['T'];
+ break;
+ case 'rejected':
+ status_to_show = ['R'];
+ break;
+ case 'delegated':
+ status_to_show = ['D'];
+ break;
+ case 'all':
+ status_to_show = ['U', 'A', 'T', 'D', 'G', 'R'];
+ break;
+ default:
+ status_to_show = ['U', 'A', 'T', 'D', 'G'];
+ break;
+ }
+ var participants = event.participants;
+ var add_row = function (user, participant) {
+ var label_index = false;
+ for (var i = 0; i < labels.length; i++) {
+ if (labels[i].id == user) {
+ label_index = i;
+ break;
+ }
+ }
+ if (participant && label_index !== false && status_to_show.indexOf(participant.substr(0, 1)) >= 0 ||
+ !participant && label_index !== false ||
+ this.options.filter === 'owner' && event.owner === user) {
+ if (typeof rows[label_index] === 'undefined') {
+ rows[label_index] = [];
+ }
+ rows[label_index].push(event);
+ }
+ };
+ for (var user in participants) {
+ var participant = participants[user];
+ if (parseInt(user) < 0) // groups
+ {
+ var planner = this;
+ egw.accountData(user, 'account_fullname', true, function (result) {
+ for (var id in result) {
+ if (!participants[id])
+ add_row.call(planner, id, participant);
+ }
+ }, labels);
+ continue;
+ }
+ add_row.call(this, user, participant);
+ }
+ },
+ // Draw a single row
+ draw_row: function (sort_key, label, events) {
+ var row = this._drawRow(sort_key, label, events, this.options.start_date, this.options.end_date);
+ if (this.options.hide_empty && !events.length) {
+ row.set_disabled(true);
+ }
+ // Highlight current user, sort_key is account_id
+ if (sort_key === egw.user('account_id')) {
+ row.set_class('current_user');
+ }
+ // Since the daywise cache is by user, we can tap in here
+ var t = new Date(this.options.start_date);
+ var end = new Date(this.options.end_date);
+ do {
+ var cache_id = CalendarApp._daywise_cache_id(t, sort_key);
+ egw.dataRegisterUID(cache_id, row._data_callback, row);
+ t.setUTCDate(t.getUTCDate() + 1);
+ } while (t < end);
+ return row;
+ }
+ },
+ // Group by month has one row for each month
+ month: {
+ title: function () { return this.egw().lang('Month'); },
+ headers: function () {
+ this.headers.append(this._header_day_of_month());
+ },
+ row_labels: function () {
+ var labels = [];
+ var d = new Date(this.options.start_date);
+ d = new Date(d.valueOf() + d.getTimezoneOffset() * 60 * 1000);
+ for (var i = 0; i < 12; i++) {
+ // Not using UTC because we corrected for timezone offset
+ labels.push({ id: sprintf('%04d-%02d', d.getFullYear(), d.getMonth()), label: this.egw().lang(date('F', d)) + ' ' + d.getFullYear() });
+ d.setMonth(d.getMonth() + 1);
+ }
+ return labels;
+ },
+ group: function (labels, rows, event) {
+ // Yearly planner does not show infologs
+ if (event && event.app && event.app == 'infolog')
+ return;
+ var start = new Date(event.start);
+ start = new Date(start.valueOf() + start.getTimezoneOffset() * 60 * 1000);
+ var key = sprintf('%04d-%02d', start.getFullYear(), start.getMonth());
+ var label_index = false;
+ for (var i = 0; i < labels.length; i++) {
+ if (labels[i].id == key) {
+ label_index = i;
+ break;
+ }
+ }
+ if (typeof rows[label_index] === 'undefined') {
+ rows[label_index] = [];
+ }
+ rows[label_index].push(event);
+ // end in a different month?
+ var end = new Date(event.end);
+ end = new Date(end.valueOf() + end.getTimezoneOffset() * 60 * 1000);
+ var end_key = sprintf('%04d-%02d', end.getFullYear(), end.getMonth());
+ var year = start.getFullYear();
+ var month = start.getMonth();
+ key = sprintf('%04d-%02d', year, month);
+ do {
+ var end_label_index = label_index;
+ for (var i = end_label_index; i < labels.length; i++) {
+ if (labels[i].id == key) {
+ end_label_index = i;
+ if (typeof rows[end_label_index] === 'undefined') {
+ rows[end_label_index] = [];
+ }
+ break;
+ }
+ }
+ if (end_label_index != label_index) {
+ rows[label_index].push(event);
+ }
+ if (++month > 11) {
+ ++year;
+ month = 0;
+ }
+ key = sprintf('%04d-%02d', year, month);
+ } while (key <= end_key);
+ },
+ // Draw a single row, but split up the dates
+ draw_row: function (sort_key, label, events) {
+ var key = sort_key.split('-');
+ var start = new Date(key[0] + "-" + sprintf("%02d", parseInt(key[1]) + 1) + "-01T00:00:00Z");
+ // Use some care to avoid issues with timezones and daylight savings
+ var end = new Date(start);
+ end.setUTCMonth(start.getUTCMonth() + 1);
+ end.setUTCDate(1);
+ end.setUTCHours(0);
+ end.setUTCMinutes(0);
+ end = new Date(end.valueOf() - 1000);
+ end.setUTCMonth(start.getUTCMonth());
+ this._drawRow(sort_key, label, events, start, end);
+ }
+ },
+ // Group by category has one row for each [sub]category
+ category: {
+ title: function () { return this.egw().lang('Category'); },
+ headers: function () {
+ var start = new Date(this.options.start_date);
+ var end = new Date(this.options.end_date);
+ var start_date = new Date(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate());
+ var end_date = new Date(end.getUTCFullYear(), end.getUTCMonth(), end.getUTCDate());
+ var day_count = Math.round((end_date - start_date) / (1000 * 3600 * 24)) + 1;
+ if (day_count >= 6) {
+ this.headers.append(this._header_months(start, day_count));
+ }
+ if (day_count < 120) {
+ var weeks = this._header_weeks(start, day_count);
+ this.headers.append(weeks);
+ this.grid.append(weeks);
+ }
+ if (day_count < 60) {
+ var days = this._header_days(start, day_count);
+ this.headers.append(days);
+ this.grid.append(days);
+ }
+ if (day_count <= 7) {
+ var hours = this._header_hours(start, day_count);
+ this.headers.append(hours);
+ this.grid.append(hours);
+ }
+ },
+ row_labels: function () {
+ var im = this.getInstanceManager();
+ var categories = et2_selectbox.cat_options({
+ _type: 'select-cat',
+ getInstanceManager: function () { return im; }
+ }, { application: 'calendar' });
+ var labels = [];
+ if (!app.calendar.state.cat_id ||
+ app.calendar.state.cat_id.toString() === '' ||
+ app.calendar.state.cat_id.toString() == '0') {
+ app.calendar.state.cat_id = '';
+ labels.push({ id: '', value: '', label: egw.lang('none'), main: '', data: {} });
+ labels = labels.concat(categories);
+ }
+ else {
+ var cat_id = app.calendar.state.cat_id;
+ if (typeof cat_id == 'string') {
+ cat_id = cat_id.split(',');
+ }
+ for (var i = 0; i < cat_id.length; i++) {
+ // Find label for that category
+ for (var j = 0; j < categories.length; j++) {
+ if (categories[j].value == cat_id[i]) {
+ categories[j].id = categories[j].value;
+ labels.push(categories[j]);
+ break;
+ }
+ }
+ // Get its children immediately
+ egw.json('EGroupware\\Api\\Etemplate\\Widget\\Select::ajax_get_options', ['select-cat', ',,,calendar,' + cat_id[i]], function (data) {
+ labels = labels.concat(data);
+ }).sendRequest(false);
+ }
+ }
+ for (var i = labels.length - 1; i >= 0; i--) {
+ labels[i].id = labels[i].value;
+ labels[i].data = {
+ cat_id: labels[i].id,
+ main: labels[i].value == labels[i].main
+ };
+ if (labels[i].children && labels[i].children.length) {
+ labels[i].data.has_children = true;
+ }
+ }
+ return labels;
+ },
+ group: function (labels, rows, event) {
+ var cats = event.category;
+ if (typeof event.category === 'string') {
+ cats = cats.split(',');
+ }
+ for (var cat = 0; cat < cats.length; cat++) {
+ var label_index = false;
+ var category = cats[cat] ? parseInt(cats[cat], 10) : false;
+ if (category == 0 || !category)
+ category = '';
+ for (var i = 0; i < labels.length; i++) {
+ if (labels[i].id == category) {
+ // If there's no cat filter, only show the top level
+ if (!app.calendar.state.cat_id) {
+ for (var j = 0; j < labels.length; j++) {
+ if (labels[j].id == labels[i].main) {
+ label_index = j;
+ break;
+ }
+ }
+ break;
+ }
+ label_index = i;
+ break;
+ }
+ }
+ if (label_index !== false && typeof rows[label_index] === 'undefined') {
+ rows[label_index] = [];
+ }
+ if (label_index !== false && rows[label_index].indexOf(event) === -1) {
+ rows[label_index].push(event);
+ }
+ }
+ },
+ draw_row: function (sort_key, label, events) {
+ var row = this._drawRow(sort_key, label, events, this.options.start_date, this.options.end_date);
+ if (this.options.hide_empty && !events.length) {
+ row.set_disabled(true);
+ }
+ return row;
+ }
+ }
+ };
+ // Main container
+ _this.div = jQuery(document.createElement("div"))
+ .addClass("calendar_plannerWidget");
+ // Header
+ _this.gridHeader = jQuery(document.createElement("div"))
+ .addClass("calendar_plannerHeader")
+ .appendTo(_this.div);
+ _this.headerTitle = jQuery(document.createElement("div"))
+ .addClass("calendar_plannerHeaderTitle")
+ .appendTo(_this.gridHeader);
+ _this.headers = jQuery(document.createElement("div"))
+ .addClass("calendar_plannerHeaderRows")
+ .appendTo(_this.gridHeader);
+ _this.rows = jQuery(document.createElement("div"))
+ .addClass("calendar_plannerRows")
+ .appendTo(_this.div);
+ _this.grid = jQuery(document.createElement("div"))
+ .addClass("calendar_plannerGrid")
+ .appendTo(_this.div);
+ _this.vertical_bar = jQuery(document.createElement("div"))
+ .addClass('verticalBar')
+ .appendTo(_this.div);
+ _this.value = [];
+ // Update timer, to avoid redrawing twice when changing start & end date
+ _this.update_timer = null;
+ _this.doInvalidate = true;
+ _this.setDOMNode(_this.div[0]);
+ _this.registeredCallbacks = [];
+ _this.cache = {};
+ _this._deferred_row_updates = {};
+ return _this;
+ }
+ et2_calendar_planner.prototype.destroy = function () {
+ _super.prototype.destroy.call(this);
+ this.div.off();
+ for (var i = 0; i < this.registeredCallbacks.length; i++) {
+ egw.dataUnregisterUID(this.registeredCallbacks[i], null, this);
+ }
+ };
+ et2_calendar_planner.prototype.doLoadingFinished = function () {
+ _super.prototype.doLoadingFinished.call(this);
+ // Don't bother to draw anything if there's no date yet
+ if (this.options.start_date) {
+ this._drawGrid();
+ }
+ // Automatically bind drag and resize for every event using jQuery directly
+ // - no action system -
+ var planner = this;
+ this.cache = {};
+ this._deferred_row_updates = {};
+ /**
+ * If user puts the mouse over an event, then we'll set up resizing so
+ * they can adjust the length. Should be a little better on resources
+ * than binding it for every calendar event.
+ */
+ this.div.on('mouseover', '.calendar_calEvent:not(.ui-resizable):not(.rowNoEdit)', function () {
+ // Load the event
+ planner._get_event_info(this);
+ var that = this;
+ //Resizable event handler
+ jQuery(this).resizable({
+ distance: 10,
+ grid: [5, 10000],
+ autoHide: false,
+ handles: 'e',
+ containment: 'parent',
+ /**
+ * Triggered when the resizable is created.
+ *
+ * @param {event} event
+ * @param {Object} ui
+ */
+ create: function (event, ui) {
+ var resizeHelper = event.target.getAttribute('data-resize');
+ if (resizeHelper == 'WD' || resizeHelper == 'WDS') {
+ jQuery(this).resizable('destroy');
+ }
+ },
+ /**
+ * If dragging to resize an event, abort drag to create
+ *
+ * @param {jQuery.Event} event
+ * @param {Object} ui
+ */
+ start: function (event, ui) {
+ if (planner.drag_create.start) {
+ // Abort drag to create, we're dragging to resize
+ planner._drag_create_end({});
+ }
+ },
+ /**
+ * Triggered at the end of resizing the calEvent.
+ *
+ * @param {event} event
+ * @param {Object} ui
+ */
+ stop: function (event, ui) {
+ var e = new jQuery.Event('change');
+ e.originalEvent = event;
+ e.data = { duration: 0 };
+ var event_data = planner._get_event_info(this);
+ var event_widget = planner.getWidgetById(event_data.widget_id);
+ var sT = event_widget.options.value.start_m;
+ if (typeof this.dropEnd != 'undefined') {
+ var eT = parseInt(this.dropEnd.getUTCHours() * 60) + parseInt(this.dropEnd.getUTCMinutes());
+ e.data.duration = ((eT - sT) / 60) * 3600;
+ if (event_widget) {
+ event_widget.options.value.end_m = eT;
+ event_widget.options.value.duration = e.data.duration;
+ }
+ // Leave the helper there until the update is done
+ var loading = ui.helper.clone().appendTo(ui.helper.parent());
+ // and add a loading icon so user knows something is happening
+ jQuery('.calendar_timeDemo', loading).after('
');
+ jQuery(this).trigger(e);
+ // That cleared the resize handles, so remove for re-creation...
+ jQuery(this).resizable('destroy');
+ // Remove loading, done or not
+ loading.remove();
+ }
+ // Clear the helper, re-draw
+ if (event_widget) {
+ event_widget.getParent().position_event(event_widget);
+ }
+ },
+ /**
+ * Triggered during the resize, on the drag of the resize handler
+ *
+ * @param {event} event
+ * @param {Object} ui
+ */
+ resize: function (event, ui) {
+ var position;
+ if (planner.options.group_by == 'month') {
+ position = { left: event.clientX, top: event.clientY };
+ }
+ else {
+ position = { top: ui.position.top, left: ui.position.left + ui.helper.width() };
+ }
+ planner._drag_helper(this, position, ui.helper.outerHeight());
+ }
+ });
+ })
+ .on('mousemove', function (event) {
+ // Ignore headers
+ if (planner.headers.has(event.target).length !== 0) {
+ planner.vertical_bar.hide();
+ return;
+ }
+ // Position bar by mouse
+ planner.vertical_bar.position({
+ my: 'right-1',
+ of: event,
+ collision: 'fit'
+ });
+ planner.vertical_bar.css('top', '0px');
+ // Get time at mouse
+ if (jQuery(event.target).closest('.calendar_eventRows').length == 0) {
+ // "Invalid" times, from space after the last planner row, or header
+ var time = planner._get_time_from_position(event.pageX - planner.grid.offset().left, 10);
+ }
+ else if (planner.options.group_by == 'month') {
+ var time = planner._get_time_from_position(event.clientX, event.clientY);
+ }
+ else {
+ var time = planner._get_time_from_position(event.offsetX, event.offsetY);
+ }
+ // Passing to formatter, cancel out timezone
+ if (time) {
+ var formatDate = new Date(time.valueOf() + time.getTimezoneOffset() * 60 * 1000);
+ planner.vertical_bar
+ .html('
' + date(egw.preference('timeformat', 'calendar') == 12 ? 'h:ia' : 'H:i', formatDate) + '')
+ .show();
+ if (planner.drag_create.event && planner.drag_create.parent && planner.drag_create.end) {
+ planner.drag_create.end.date = time.toJSON();
+ planner._drag_update_event();
+ }
+ }
+ else {
+ // No (valid) time, just hide
+ planner.vertical_bar.hide();
+ }
+ })
+ .on('mousedown', jQuery.proxy(this._mouse_down, this))
+ .on('mouseup', jQuery.proxy(this._mouse_up, this));
+ // Actions may be set on a parent, so we need to explicitly get in here
+ // and get ours
+ this._link_actions(this.options.actions || this.getParent().options.actions || []);
+ // Customize and override some draggable settings
+ this.div.on('dragcreate', '.calendar_calEvent', function (event, ui) {
+ jQuery(this).draggable('option', 'cancel', '.rowNoEdit');
+ // Act like you clicked the header, makes it easier to position
+ jQuery(this).draggable('option', 'cursorAt', { top: 5, left: 5 });
+ })
+ .on('dragstart', '.calendar_calEvent', function (event, ui) {
+ jQuery('.calendar_calEvent', ui.helper).width(jQuery(this).width())
+ .height(jQuery(this).outerHeight())
+ .css('top', '').css('left', '')
+ .appendTo(ui.helper);
+ ui.helper.width(jQuery(this).width());
+ // Cancel drag to create, we're dragging an existing event
+ planner._drag_create_end();
+ });
+ return true;
+ };
+ et2_calendar_planner.prototype._createNamespace = function () {
+ return true;
+ };
+ /**
+ * Something changed, and the planner needs to be re-drawn. We wait a bit to
+ * avoid re-drawing twice if start and end date both changed, then recreate.
+ *
+ * @param {boolean} trigger =false Trigger an event once things are done.
+ * Waiting until invalidate completes prevents 2 updates when changing the date range.
+ * @returns {undefined}
+ */
+ et2_calendar_planner.prototype.invalidate = function (trigger) {
+ // Busy
+ if (!this.doInvalidate)
+ return;
+ // Not yet ready
+ if (!this.options.start_date || !this.options.end_date)
+ return;
+ // Wait a bit to see if anything else changes, then re-draw the days
+ if (this.update_timer !== null) {
+ window.clearTimeout(this.update_timer);
+ }
+ this.update_timer = window.setTimeout(jQuery.proxy(function () {
+ this.widget.doInvalidate = false;
+ // Show AJAX loader
+ this.widget.loader.show();
+ this.widget.cache = {};
+ this._deferred_row_updates = {};
+ this.widget._fetch_data();
+ this.widget._drawGrid();
+ if (this.trigger) {
+ this.widget.change();
+ }
+ this.widget.update_timer = null;
+ this.widget.doInvalidate = true;
+ window.setTimeout(jQuery.proxy(function () { if (this.loader)
+ this.loader.hide(); }, this.widget), 500);
+ }, { widget: this, "trigger": trigger }), et2_dataview_grid.ET2_GRID_INVALIDATE_TIMEOUT);
+ };
+ et2_calendar_planner.prototype.detachFromDOM = function () {
+ // Remove the binding to the change handler
+ jQuery(this.div).off("change.et2_calendar_timegrid");
+ return _super.prototype.detachFromDOM.call(this);
+ };
+ et2_calendar_planner.prototype.attachToDOM = function () {
+ var result = _super.prototype.attachToDOM.call(this);
+ // Add the binding for the event change handler
+ jQuery(this.div).on("change.et2_calendar_timegrid", '.calendar_calEvent', this, function (e) {
+ // Make sure function gets a reference to the widget
+ var args = Array.prototype.slice.call(arguments);
+ if (args.indexOf(this) == -1)
+ args.push(this);
+ return e.data.event_change.apply(e.data, args);
+ });
+ // Add the binding for the change handler
+ jQuery(this.div).on("change.et2_calendar_timegrid", '*:not(.calendar_calEvent)', this, function (e) {
+ return e.data.change.call(e.data, e, this);
+ });
+ return result;
+ };
+ et2_calendar_planner.prototype.getDOMNode = function (_sender) {
+ if (_sender === this || !_sender) {
+ return this.div[0];
+ }
+ if (_sender._parent === this) {
+ return this.rows[0];
+ }
+ };
+ /**
+ * Creates all the DOM nodes for the planner grid
+ *
+ * Any existing nodes (& children) are removed, the headers & labels are
+ * determined according to the current group_by value, and then the rows
+ * are created.
+ *
+ * @method
+ * @private
+ *
+ */
+ et2_calendar_planner.prototype._drawGrid = function () {
+ this.div.css('height', this.options.height);
+ // Clear old events
+ var delete_index = this._children.length - 1;
+ while (this._children.length > 0 && delete_index >= 0) {
+ this._children[delete_index].destroy();
+ this.removeChild(this._children[delete_index--]);
+ }
+ // Clear old rows
+ this.rows.empty()
+ .append(this.grid);
+ this.grid.empty();
+ var grouper = this.grouper;
+ if (!grouper)
+ return;
+ // Headers
+ this.headers.empty();
+ this.headerTitle.text(grouper.title.apply(this));
+ grouper.headers.apply(this);
+ this.grid.find('*').contents().filter(function () {
+ return this.nodeType === 3;
+ }).remove();
+ // Get the rows / labels
+ var labels = grouper.row_labels.call(this);
+ // Group the events
+ var events = {};
+ for (var i = 0; i < this.value.length; i++) {
+ grouper.group.call(this, labels, events, this.value[i]);
+ }
+ // Set height for rows
+ this.rows.height(this.div.height() - this.headers.outerHeight());
+ // Draw the rows
+ for (var key in labels) {
+ if (!labels.hasOwnProperty(key))
+ continue;
+ // Skip sub-categories (events are merged into top level)
+ if (this.options.group_by == 'category' &&
+ (!app.calendar.state.cat_id || app.calendar.state.cat_id == '') &&
+ labels[key].id != labels[key].main) {
+ continue;
+ }
+ var row = grouper.draw_row.call(this, labels[key].id, labels[key].label, events[key] || []);
+ // Add extra data for clicking on row
+ if (row) {
+ for (var extra in labels[key].data) {
+ row.getDOMNode().dataset[extra] = labels[key].data[extra];
+ }
+ }
+ }
+ // Adjust header if there's a scrollbar
+ if (this.rows.children().last().length) {
+ this.gridHeader.css('margin-right', (this.rows.width() - this.rows.children().last().width()) + 'px');
+ }
+ // Add actual events
+ for (var key in this._deferred_row_updates) {
+ window.clearTimeout(key);
+ }
+ window.setTimeout(jQuery.proxy(function () {
+ this._deferred_row_update();
+ }, this), et2_calendar_planner.DEFERRED_ROW_TIME);
+ this.value = [];
+ };
+ /**
+ * Draw a single row of the planner
+ *
+ * @param {string} key Index into the grouped labels & events
+ * @param {string} label
+ * @param {Array} events
+ * @param {Date} start
+ * @param {Date} end
+ */
+ et2_calendar_planner.prototype._drawRow = function (key, label, events, start, end) {
+ var row = et2_createWidget('calendar-planner_row', {
+ id: 'planner_row_' + key,
+ label: label,
+ start_date: start,
+ end_date: end,
+ value: events,
+ readonly: this.options.readonly
+ }, this);
+ if (this.isInTree()) {
+ row.doLoadingFinished();
+ }
+ return row;
+ };
+ et2_calendar_planner.prototype._header_day_of_month = function () {
+ var day_width = 3.23; // 100.0 / 31;
+ // month scale with navigation
+ var content = '
';
+ var start = new Date(this.options.start_date);
+ start = new Date(start.valueOf() + start.getTimezoneOffset() * 60 * 1000);
+ var end = new Date(this.options.end_date);
+ end = new Date(end.valueOf() + end.getTimezoneOffset() * 60 * 1000);
+ var title = this.egw().lang(date('F', start)) + ' ' + date('Y', start) + ' - ' +
+ this.egw().lang(date('F', end)) + ' ' + date('Y', end);
+ content += '
' +
+ title + "
";
+ content += "
"; // end of plannerScale
+ // day of month scale
+ content += '
';
+ for (var left = 0, i = 0; i < 31; left += day_width, ++i) {
+ content += '
' +
+ (1 + i) + "
\n";
+ }
+ content += "
\n";
+ return content;
+ };
+ /**
+ * Make a header showing the months
+ * @param {Date} start
+ * @param {number} days
+ * @returns {string} HTML snippet
+ */
+ et2_calendar_planner.prototype._header_months = function (start, days) {
+ var content = '
';
+ var days_in_month = 0;
+ var day_width = 100 / days;
+ var end = new Date(start);
+ end.setUTCDate(end.getUTCDate() + days);
+ var t = new Date(start.valueOf());
+ for (var left = 0, i = 0; i < days; t.setUTCDate(1), t.setUTCMonth(t.getUTCMonth() + 1), left += days_in_month * day_width, i += days_in_month) {
+ var u = new Date(t.getUTCFullYear(), t.getUTCMonth() + 1, 0, -t.getTimezoneOffset() / 60);
+ days_in_month = 1 + ((u - t) / (24 * 3600 * 1000));
+ var first = new Date(t.getUTCFullYear(), t.getUTCMonth(), 1, -t.getTimezoneOffset() / 60);
+ if (days_in_month <= 0)
+ break;
+ if (i + days_in_month > days) {
+ days_in_month = days - i;
+ }
+ var title = this.egw().lang(date('F', new Date(t.valueOf() + t.getTimezoneOffset() * 60 * 1000)));
+ if (days_in_month > 10) {
+ title += '
' + t.getUTCFullYear();
+ }
+ else if (days_in_month < 5) {
+ title = ' ';
+ }
+ content += '' +
+ title + "
";
+ }
+ content += ""; // end of plannerScale
+ return content;
+ };
+ /**
+ * Make a header showing the week numbers
+ *
+ * @param {Date} start
+ * @param {number} days
+ * @returns {string} HTML snippet
+ */
+ et2_calendar_planner.prototype._header_weeks = function (start, days) {
+ var content = '
';
+ var state = '';
+ // we're not using UTC so date() formatting function works
+ var t = new Date(start.valueOf());
+ // Make sure we're lining up on the week
+ var week_end = app.calendar.date.end_of_week(start);
+ var days_in_week = Math.floor(((week_end - start) / (24 * 3600 * 1000)) + 1);
+ var week_width = 100 / days * (days <= 7 ? days : days_in_week);
+ for (var left = 0, i = 0; i < days; t.setUTCDate(t.getUTCDate() + 7), left += week_width) {
+ // Avoid overflow at the end
+ if (days - i < 7) {
+ days_in_week = days - i;
+ }
+ var usertime = new Date(t.valueOf());
+ if (start.getTimezoneOffset() < 0) {
+ // Gets the right week # east of GMT. West does not need it(?)
+ usertime.setUTCMinutes(usertime.getUTCMinutes() - start.getTimezoneOffset());
+ }
+ week_width = 100 / days * Math.min(days, days_in_week);
+ var title = this.egw().lang('Week') + ' ' + app.calendar.date.week_number(usertime);
+ if (start.getTimezoneOffset() > 0) {
+ // Gets the right week start west of GMT
+ usertime.setUTCMinutes(usertime.getUTCMinutes() + start.getTimezoneOffset());
+ }
+ state = app.calendar.date.start_of_week(usertime);
+ state.setUTCHours(0);
+ state.setUTCMinutes(0);
+ state = state.toJSON();
+ if (days_in_week > 1 || days == 1) {
+ content += '
' + title + "
";
+ }
+ i += days_in_week;
+ if (days_in_week != 7) {
+ t.setUTCDate(t.getUTCDate() - (7 - days_in_week));
+ days_in_week = 7;
+ }
+ }
+ content += "
"; // end of plannerScale
+ return content;
+ };
+ /**
+ * Make a header for some days
+ *
+ * @param {Date} start
+ * @param {number} days
+ * @returns {string} HTML snippet
+ */
+ et2_calendar_planner.prototype._header_days = function (start, days) {
+ var day_width = 100 / days;
+ var content = '
';
+ // we're not using UTC so date() formatting function works
+ var t = new Date(start.valueOf() + start.getTimezoneOffset() * 60 * 1000);
+ for (var left = 0, i = 0; i < days; t.setDate(t.getDate() + 1), left += day_width, ++i) {
+ if (!this.options.show_weekend && [0, 6].indexOf(t.getDay()) !== -1)
+ continue;
+ var holidays = [];
+ var tempDate = new Date(t);
+ tempDate.setMinutes(tempDate.getMinutes() - tempDate.getTimezoneOffset());
+ var title = '';
+ var state = new Date(t.valueOf() - t.getTimezoneOffset() * 60 * 1000);
+ var day_class = this.day_class_holiday(state, holidays, days);
+ if (days <= 3) {
+ title = this.egw().lang(date('l', t)) + ', ' + date('j', t) + '. ' + this.egw().lang(date('F', t));
+ }
+ else if (days <= 7) {
+ title = this.egw().lang(date('l', t)) + ' ' + date('j', t);
+ }
+ else {
+ title = this.egw().lang(date('D', t)).substr(0, 2) + '
' + date('j', t);
+ }
+ content += '
' + title + "
\n";
+ }
+ content += "
"; // end of plannerScale
+ return content;
+ };
+ /**
+ * Create a header with hours
+ *
+ * @param {Date} start
+ * @param {number} days
+ * @returns {string} HTML snippet for the header
+ */
+ et2_calendar_planner.prototype._header_hours = function (start, days) {
+ var divisors = [1, 2, 3, 4, 6, 8, 12];
+ var decr = 1;
+ for (var i = 0; i < divisors.length; i++) // numbers dividing 24 without rest
+ {
+ if (divisors[i] > days)
+ break;
+ decr = divisors[i];
+ }
+ var hours = days * 24;
+ if (days === 1) // for a single day we calculate the hours of a days, to take into account daylight saving changes (23 or 25 hours)
+ {
+ var t = new Date(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate(), -start.getTimezoneOffset() / 60);
+ var s = new Date(start);
+ s.setUTCHours(23);
+ s.setUTCMinutes(59);
+ s.setUTCSeconds(59);
+ hours = Math.ceil((s.getTime() - t.getTime()) / 3600000);
+ }
+ var cell_width = 100 / hours * decr;
+ var content = '
';
+ // we're not using UTC so date() formatting function works
+ var t = new Date(start.valueOf() + start.getTimezoneOffset() * 60 * 1000);
+ for (var left = 0, i = 0; i < hours; left += cell_width, i += decr) {
+ if (!this.options.show_weekend && [0, 6].indexOf(t.getDay()) !== -1)
+ continue;
+ var title = date(egw.preference('timeformat', 'calendar') == 12 ? 'ha' : 'H', t);
+ content += '
' + title + "
";
+ t.setHours(t.getHours() + decr);
+ }
+ content += "
"; // end of plannerScale
+ return content;
+ };
+ /**
+ * Applies class for today, and any holidays for current day
+ *
+ * @param {Date} date
+ * @param {string[]} holiday_list Filled with a list of holidays for that day
+ * @param {integer} days Number of days shown in the day header
+ *
+ * @return {string} CSS Classes for the day. calendar_calBirthday, calendar_calHoliday, calendar_calToday and calendar_weekend as appropriate
+ */
+ et2_calendar_planner.prototype.day_class_holiday = function (date, holiday_list, days) {
+ if (!date)
+ return '';
+ var day_class = '';
+ // Holidays and birthdays
+ var holidays = et2_widget_view_1.et2_calendar_view.get_holidays(this, date.getUTCFullYear());
+ // Pass a string rather than the date object, to make sure it doesn't get changed
+ this.date_helper.set_value(date.toJSON());
+ var date_key = '' + this.date_helper.get_year() + sprintf('%02d', this.date_helper.get_month()) + sprintf('%02d', this.date_helper.get_date());
+ if (holidays && holidays[date_key]) {
+ holidays = holidays[date_key];
+ for (var i = 0; i < holidays.length; i++) {
+ if (typeof holidays[i]['birthyear'] !== 'undefined') {
+ day_class += ' calendar_calBirthday ';
+ if (typeof days == 'undefined' || days <= 21) {
+ day_class += ' calendar_calBirthdayIcon ';
+ }
+ holiday_list.push(holidays[i]['name']);
+ }
+ else {
+ day_class += 'calendar_calHoliday ';
+ holiday_list.push(holidays[i]['name']);
+ }
+ }
+ }
+ holidays = holiday_list.join(',');
+ var today = new Date();
+ if (date_key === '' + today.getFullYear() +
+ sprintf("%02d", today.getMonth() + 1) +
+ sprintf("%02d", today.getDate())) {
+ day_class += "calendar_calToday ";
+ }
+ if (date.getUTCDay() == 0 || date.getUTCDay() == 6) {
+ day_class += "calendar_weekend ";
+ }
+ return day_class;
+ };
+ /**
+ * Link the actions to the DOM nodes / widget bits.
+ *
+ * @todo This currently does nothing
+ * @param {object} actions {ID: {attributes..}+} map of egw action information
+ */
+ et2_calendar_planner.prototype._link_actions = function (actions) {
+ if (!this._actionObject) {
+ // Get the parent? Might be a grid row, might not. Either way, it is
+ // just a container with no valid actions
+ var objectManager = egw_getObjectManager(this.getInstanceManager().app, true, 1);
+ objectManager = objectManager.getObjectById(this.getInstanceManager().uniqueId, 2) || objectManager;
+ var parent = objectManager.getObjectById(this.id, 3) || objectManager.getObjectById(this._parent.id, 3) || objectManager;
+ if (!parent) {
+ debugger;
+ egw.debug('error', 'No parent objectManager found');
+ return;
+ }
+ for (var i = 0; i < parent.children.length; i++) {
+ var parent_finder = jQuery('#' + this.div.id, parent.children[i].iface.doGetDOMNode());
+ if (parent_finder.length > 0) {
+ parent = parent.children[i];
+ break;
+ }
+ }
+ }
+ // This binds into the egw action system. Most user interactions (drag to move, resize)
+ // are handled internally using jQuery directly.
+ var widget_object = this._actionObject || parent.getObjectById(this.id);
+ var aoi = new et2_core_DOMWidget_1.et2_action_object_impl(this, this.getDOMNode(this)).getAOI();
+ /**
+ * Determine if we allow a dropped event to use the invite/change actions,
+ * and enable or disable them appropriately
+ *
+ * @param {egwAction} action
+ * @param {et2_calendar_event} event The event widget being dragged
+ * @param {egwActionObject} target Planner action object
+ */
+ var _invite_enabled = function (action, event, target) {
+ var event = event.iface.getWidget();
+ var planner = target.iface.getWidget() || false;
+ //debugger;
+ if (event === planner || !event || !planner ||
+ !event.options || !event.options.value.participants || !planner.options.owner) {
+ return false;
+ }
+ var owner_match = false;
+ var own_row = false;
+ for (var id in event.options.value.participants) {
+ planner.iterateOver(function (row) {
+ // Check scroll section or header section
+ if (row.div.hasClass('drop-hover') || row.div.has(':hover')) {
+ owner_match = owner_match || row.node.dataset[planner.options.group_by] === '' + id;
+ own_row = (row === event.getParent());
+ }
+ }, this, et2_widget_planner_row_1.et2_calendar_planner_row);
+ }
+ var enabled = !owner_match &&
+ // Not inside its own row
+ !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') {
+ this.getWidget()._event_drop.call(jQuery('.calendar_d-n-d_timeCounter', _data.ui.helper)[0], this.getWidget(), event, _data.ui);
+ }
+ var drag_listener = function (event, ui) {
+ aoi.getWidget()._drag_helper(jQuery('.calendar_d-n-d_timeCounter', ui.helper)[0], {
+ top: ui.position.top,
+ left: ui.position.left - jQuery(this).parent().offset().left
+ }, 0);
+ };
+ var 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' + widget_object.id, drag_listener);
+ _data.ui.draggable.on('dragend.et2_timegrid' + widget_object.id, function () {
+ _data.ui.draggable.off('drag.et2_timegrid' + widget_object.id);
+ });
+ 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('
');
+ }
+ 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' + widget_object.id);
+ // Remove any highlighted time squares
+ jQuery('[data-date]', this.doGetDOMNode()).removeClass("ui-state-active");
+ // 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), EGW_AO_FLAG_IS_CONTAINER);
+ }
+ else {
+ widget_object.setAOI(aoi);
+ }
+ // Go over the widget & add links - this is where we decide which actions are
+ // 'allowed' for this widget at this time
+ var action_links = this._get_action_links(actions);
+ this._init_links_dnd(widget_object.manager, action_links);
+ widget_object.updateActionLinks(action_links);
+ this._actionObject = widget_object;
+ };
+ /**
+ * Automatically add dnd support for linking
+ *
+ * @param {type} mgr
+ * @param {type} actionLinks
+ */
+ et2_calendar_planner.prototype._init_links_dnd = function (mgr, actionLinks) {
+ if (this.options.readonly)
+ return;
+ var self = this;
+ var drop_action = mgr.getActionById('egw_link_drop');
+ var drop_change_participant = mgr.getActionById('change_participant');
+ var drop_invite = mgr.getActionById('invite');
+ var drag_action = mgr.getActionById('egw_link_drag');
+ var paste_action = mgr.getActionById('egw_paste');
+ // Disable paste action
+ if (paste_action == null) {
+ paste_action = mgr.addAction('popup', 'egw_paste', egw.lang('Paste'), egw.image('editpaste'), function () { }, true);
+ }
+ paste_action.set_enabled(false);
+ // Check if this app supports linking
+ if (!egw.link_get_registry(this.dataStorePrefix || 'calendar', 'query') ||
+ egw.link_get_registry(this.dataStorePrefix || 'calendar', 'title')) {
+ if (drop_action) {
+ drop_action.remove();
+ if (actionLinks.indexOf(drop_action.id) >= 0) {
+ actionLinks.splice(actionLinks.indexOf(drop_action.id), 1);
+ }
+ }
+ if (drag_action) {
+ drag_action.remove();
+ if (actionLinks.indexOf(drag_action.id) >= 0) {
+ actionLinks.splice(actionLinks.indexOf(drag_action.id), 1);
+ }
+ }
+ return;
+ }
+ // Don't re-add
+ if (drop_action == null) {
+ // Create the drop action that links entries
+ drop_action = mgr.addAction('drop', 'egw_link_drop', egw.lang('Create link'), egw.image('link'), function (action, source, dropped) {
+ // Extract link IDs
+ var links = [];
+ var id = '';
+ for (var i = 0; i < source.length; i++) {
+ if (!source[i].id)
+ continue;
+ id = source[i].id.split('::');
+ links.push({ app: id[0] == 'filemanager' ? 'link' : id[0], id: id[1] });
+ }
+ if (!links.length) {
+ return;
+ }
+ if (links.length && dropped && dropped.iface.getWidget() && dropped.iface.getWidget().instanceOf(et2_widget_event_1.et2_calendar_event)) {
+ // Link the entries
+ egw.json(self.egw().getAppName() + ".etemplate_widget_link.ajax_link.etemplate", dropped.id.split('::').concat([links]), function (result) {
+ if (result) {
+ this.egw().message('Linked');
+ }
+ }, self, true, self).sendRequest();
+ }
+ }, true);
+ drop_action.acceptedTypes = ['default', 'link'];
+ drop_action.hideOnDisabled = true;
+ // Create the drop action for moving events between planner rows
+ var invite_action = function (action, source, target) {
+ // Extract link IDs
+ var links = [];
+ var id = '';
+ for (var i = 0; i < source.length; i++) {
+ // Check for no ID (invalid) or same manager (dragging an event)
+ if (!source[i].id)
+ continue;
+ if (source[i].manager === target.manager) {
+ // Find the row, could have dropped on an event
+ var row = target.iface.getWidget();
+ while (target.parent && row.instanceOf && !row.instanceOf(et2_widget_planner_row_1.et2_calendar_planner_row)) {
+ target = target.parent;
+ row = target.iface.getWidget();
+ }
+ // Leave the helper there until the update is done
+ var loading = action.ui.helper.clone(true).appendTo(jQuery('body'));
+ // and add a loading icon so user knows something is happening
+ if (jQuery('.calendar_timeDemo', loading).length == 0) {
+ jQuery('.calendar_calEventHeader', loading).addClass('loading');
+ }
+ else {
+ jQuery('.calendar_timeDemo', loading).after('
');
+ }
+ var event_data = egw.dataGetUIDdata(source[i].id).data;
+ et2_widget_event_1.et2_calendar_event.recur_prompt(event_data, function (button_id) {
+ if (button_id === 'cancel' || !button_id) {
+ return;
+ }
+ var add_owner = [row.node.dataset.participants];
+ egw().json('calendar.calendar_uiforms.ajax_invite', [
+ button_id === 'series' ? event_data.id : event_data.app_id,
+ add_owner,
+ action.id === 'change_participant' ?
+ [source[i].iface.getWidget().getParent().node.dataset.participants] :
+ []
+ ], function () { loading.remove(); }).sendRequest(true);
+ });
+ // Ok, stop.
+ return false;
+ }
+ }
+ };
+ drop_change_participant = mgr.addAction('drop', 'change_participant', egw.lang('Move to'), egw.image('participant'), invite_action, true);
+ drop_change_participant.acceptedTypes = ['calendar'];
+ drop_change_participant.hideOnDisabled = true;
+ drop_invite = mgr.addAction('drop', 'invite', egw.lang('Invite'), egw.image('participant'), invite_action, true);
+ drop_invite.acceptedTypes = ['calendar'];
+ drop_invite.hideOnDisabled = true;
+ }
+ if (actionLinks.indexOf(drop_action.id) < 0) {
+ actionLinks.push(drop_action.id);
+ }
+ actionLinks.push(drop_invite.id);
+ actionLinks.push(drop_change_participant.id);
+ // Accept other links, and files dragged from the filemanager
+ // This does not handle files dragged from the desktop. They are
+ // handled by et2_nextmatch, since it needs DOM stuff
+ if (drop_action.acceptedTypes.indexOf('link') == -1) {
+ drop_action.acceptedTypes.push('link');
+ }
+ // Don't re-add
+ if (drag_action == null) {
+ // Create drag action that allows linking
+ drag_action = mgr.addAction('drag', 'egw_link_drag', egw.lang('link'), 'link', function (action, selected) {
+ // As we wanted to have a general defaul helper interface, we return null here and not using customize helper for links
+ // TODO: Need to decide if we need to create a customized helper interface for links anyway
+ //return helper;
+ return null;
+ }, true);
+ }
+ // The planner itself is not draggable, the action is there for the children
+ if (false && actionLinks.indexOf(drag_action.id) < 0) {
+ actionLinks.push(drag_action.id);
+ }
+ drag_action.set_dragType(['link', 'calendar']);
+ };
+ /**
+ * 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}
+ */
+ et2_calendar_planner.prototype._get_action_links = function (actions) {
+ var action_links = [];
+ // Only these actions are allowed without a selection (empty actions)
+ var empty_actions = ['add'];
+ for (var i in actions) {
+ var action = actions[i];
+ if (empty_actions.indexOf(action.id) !== -1 || action.type === 'drop') {
+ action_links.push(typeof action.id !== 'undefined' ? action.id : i);
+ }
+ }
+ // Disable automatic paste action, it doesn't have what is needed to work
+ action_links.push({
+ "actionObj": 'egw_paste',
+ "enabled": false,
+ "visible": false
+ });
+ return action_links;
+ };
+ /**
+ * Show the current time while dragging
+ * Used for resizing as well as drag & drop
+ *
+ * @param {type} element
+ * @param {type} position
+ * @param {type} height
+ */
+ et2_calendar_planner.prototype._drag_helper = function (element, position, height) {
+ var time = this._get_time_from_position(position.left, position.top);
+ element.dropEnd = time;
+ var formatted_time = jQuery.datepicker.formatTime(egw.preference("timeformat") === "12" ? "h:mmtt" : "HH:mm", {
+ hour: time.getUTCHours(),
+ minute: time.getUTCMinutes(),
+ seconds: 0,
+ timezone: 0
+ }, { "ampm": (egw.preference("timeformat") === "12") });
+ element.innerHTML = '
' + formatted_time + '
';
+ //jQuery(element).width(jQuery(helper).width());
+ };
+ /**
+ * Handler for dropping an event on the timegrid
+ *
+ * @param {type} planner
+ * @param {type} event
+ * @param {type} ui
+ */
+ et2_calendar_planner.prototype._event_drop = function (planner, event, ui) {
+ var e = new jQuery.Event('change');
+ e.originalEvent = event;
+ e.data = { start: 0 };
+ if (typeof this.dropEnd != 'undefined') {
+ var drop_date = this.dropEnd.toJSON() || false;
+ var event_data = planner._get_event_info(ui.draggable);
+ var event_widget = planner.getWidgetById(event_data.widget_id);
+ if (event_widget) {
+ event_widget._parent.date_helper.set_value(drop_date);
+ event_widget.options.value.start = new Date(event_widget._parent.date_helper.getValue());
+ // Leave the helper there until the update is done
+ var loading = ui.helper.clone().appendTo(ui.helper.parent());
+ // and add a loading icon so user knows something is happening
+ jQuery('.calendar_timeDemo', loading).after('
');
+ event_widget.recur_prompt(function (button_id) {
+ if (button_id === 'cancel' || !button_id)
+ return;
+ //Get infologID if in case if it's an integrated infolog event
+ if (event_data.app === 'infolog') {
+ // If it is an integrated infolog event we need to edit infolog entry
+ egw().json('stylite_infolog_calendar_integration::ajax_moveInfologEvent', [event_data.id, event_widget.options.value.start || false], function () { loading.remove(); }).sendRequest(true);
+ }
+ else {
+ //Edit calendar event
+ egw().json('calendar.calendar_uiforms.ajax_moveEvent', [
+ button_id === 'series' ? event_data.id : event_data.app_id, event_data.owner,
+ event_widget.options.value.start,
+ planner.options.owner || egw.user('account_id')
+ ], function () { loading.remove(); }).sendRequest(true);
+ }
+ });
+ }
+ }
+ };
+ /**
+ * Use the egw.data system to get data from the calendar list for the
+ * selected time span.
+ *
+ */
+ et2_calendar_planner.prototype._fetch_data = function () {
+ var value = [];
+ var fetch = false;
+ this.doInvalidate = false;
+ for (var i = 0; i < this.registeredCallbacks.length; i++) {
+ egw.dataUnregisterUID(this.registeredCallbacks[i], false, this);
+ }
+ this.registeredCallbacks.splice(0, this.registeredCallbacks.length);
+ // Remember previous day to avoid multi-days duplicating
+ var last_data = [];
+ var t = new Date(this.options.start_date);
+ var end = new Date(this.options.end_date);
+ do {
+ value = value.concat(this._cache_register(t, this.options.owner, last_data));
+ t.setUTCDate(t.getUTCDate() + 1);
+ } while (t < end);
+ this.doInvalidate = true;
+ return value;
+ };
+ /**
+ * Deal with registering for data cache
+ *
+ * @param Date t
+ * @param String owner Calendar owner
+ */
+ et2_calendar_planner.prototype._cache_register = function (t, owner, last_data) {
+ // Cache is by date (and owner, if seperate)
+ var date = t.getUTCFullYear() + sprintf('%02d', t.getUTCMonth() + 1) + sprintf('%02d', t.getUTCDate());
+ var cache_id = CalendarApp._daywise_cache_id(date, owner);
+ var value = [];
+ if (egw.dataHasUID(cache_id)) {
+ var c = egw.dataGetUIDdata(cache_id);
+ if (c.data && c.data !== null) {
+ // There is data, pass it along now
+ for (var j = 0; j < c.data.length; j++) {
+ if (last_data.indexOf(c.data[j]) === -1 && egw.dataHasUID('calendar::' + c.data[j])) {
+ value.push(egw.dataGetUIDdata('calendar::' + c.data[j]).data);
+ }
+ }
+ last_data = c.data;
+ }
+ }
+ else {
+ fetch = true;
+ // Assume it's empty, if there is data it will be filled later
+ egw.dataStoreUID(cache_id, []);
+ }
+ this.registeredCallbacks.push(cache_id);
+ egw.dataRegisterUID(cache_id, function (data) {
+ if (data && data.length) {
+ var invalidate = true;
+ // Try to determine rows interested
+ var labels = [];
+ var events = {};
+ if (this.grouper) {
+ labels = this.grouper.row_labels.call(this);
+ invalidate = false;
+ }
+ var im = this.getInstanceManager();
+ for (var i = 0; i < data.length; i++) {
+ var event = egw.dataGetUIDdata('calendar::' + data[i]);
+ if (!event)
+ continue;
+ events = {};
+ // Try to determine rows interested
+ if (event.data && this.grouper) {
+ this.grouper.group.call(this, labels, events, event.data);
+ }
+ if (Object.keys(events).length > 0) {
+ for (var label_id in events) {
+ var id = "" + labels[label_id].id;
+ if (typeof this.cache[id] === 'undefined') {
+ this.cache[id] = [];
+ }
+ if (this.cache[id].indexOf(event.data.row_id) === -1) {
+ this.cache[id].push(event.data.row_id);
+ }
+ if (this._deferred_row_updates[id]) {
+ window.clearTimeout(this._deferred_row_updates[id]);
+ }
+ this._deferred_row_updates[id] = window.setTimeout(jQuery.proxy(this._deferred_row_update, this, id), this.DEFERRED_ROW_TIME);
+ }
+ }
+ else {
+ // Could be an event no row is interested in, could be a problem.
+ // Just redraw everything
+ invalidate = true;
+ continue;
+ }
+ // If displaying by category, we need the infolog (or other app) categories too
+ if (event && event.data && event.data.app && this.options.group_by == 'category') {
+ // Fake it to use the cache / call
+ et2_selectbox.cat_options({
+ _type: 'select-cat',
+ getInstanceManager: function () { return im; }
+ }, { application: event.data.app || 'calendar' });
+ }
+ }
+ if (invalidate) {
+ this.invalidate(false);
+ }
+ }
+ }, this, this.getInstanceManager().execId, this.id);
+ return value;
+ };
+ /**
+ * Because users may be participants in various events and the time it takes
+ * to create many events, we don't want to update a row too soon - we may have
+ * to re-draw it if we find the user / category in another event. Pagination
+ * makes this worse. We wait a bit before updating the row to avoid
+ * having to re-draw it multiple times.
+ *
+ * @param {type} id
+ * @returns {undefined}
+ */
+ et2_calendar_planner.prototype._deferred_row_update = function (id) {
+ // Something's in progress, skip
+ if (!this.doInvalidate)
+ return;
+ this.grid.height(0);
+ var id_list = typeof id === 'undefined' ? Object.keys(this.cache) : [id];
+ for (var i = 0; i < id_list.length; i++) {
+ var cache_id = id_list[i];
+ var row = this.getWidgetById('planner_row_' + cache_id);
+ window.clearTimeout(this._deferred_row_updates[cache_id]);
+ delete this._deferred_row_updates[cache_id];
+ if (row) {
+ row._data_callback(this.cache[cache_id]);
+ row.set_disabled(this.options.hide_empty && this.cache[cache_id].length === 0);
+ }
+ else {
+ break;
+ }
+ }
+ // Updating the row may push things longer, update length
+ // Add 1 to keep the scrollbar, otherwise we need to recalculate the
+ // header widths too.
+ this.grid.height(this.rows[0].scrollHeight + 1);
+ // Adjust header if there's a scrollbar - Firefox needs this re-calculated,
+ // otherwise the header will be missing the margin space for the scrollbar
+ // in some cases
+ if (this.rows.children().last().length) {
+ this.gridHeader.css('margin-right', (this.rows.width() - this.rows.children().last().width()) + 'px');
+ }
+ };
+ /**
+ * Provide specific data to be displayed.
+ * This is a way to set start and end dates, owner and event data in once call.
+ *
+ * @param {Object[]} events Array of events, indexed by date in Ymd format:
+ * {
+ * 20150501: [...],
+ * 20150502: [...]
+ * }
+ * Days should be in order.
+ *
+ */
+ et2_calendar_planner.prototype.set_value = function (events) {
+ if (typeof events !== 'object')
+ return false;
+ _super.prototype.set_value.call(this, events);
+ // Planner uses an array, not map
+ var val = this.value;
+ var array = [];
+ Object.keys(this.value).forEach(function (key) {
+ array.push(val[key]);
+ });
+ this.value = array;
+ };
+ /**
+ * Change the start date
+ * Planner view uses a date object internally
+ *
+ * @param {string|number|Date} new_date New starting date
+ * @returns {undefined}
+ */
+ et2_calendar_planner.prototype.set_start_date = function (new_date) {
+ _super.prototype.set_start_date.call(this, new_date);
+ this.options.start_date = new Date(this.options.start_date);
+ };
+ /**
+ * Change the end date
+ * Planner view uses a date object internally
+ *
+ * @param {string|number|Date} new_date New end date
+ * @returns {undefined}
+ */
+ et2_calendar_planner.prototype.set_end_date = function (new_date) {
+ _super.prototype.set_end_date.call(this, new_date);
+ this.options.end_date = new Date(this.options.end_date);
+ };
+ /**
+ * Change how the planner is grouped
+ *
+ * @param {string|number} group_by 'user', 'month', or an integer category ID
+ * @returns {undefined}
+ */
+ et2_calendar_planner.prototype.set_group_by = function (group_by) {
+ if (isNaN(group_by) && typeof this.groupers[group_by] === 'undefined') {
+ throw new Error('Invalid group_by "' + group_by + '"');
+ }
+ var old = this.options.group_by;
+ this.options.group_by = '' + group_by;
+ this.grouper = this.groupers[isNaN(this.options.group_by) ? this.options.group_by : 'category'];
+ if (old !== this.options.group_by && this.isAttached()) {
+ this.invalidate(true);
+ }
+ };
+ /**
+ * Turn on or off the visibility of weekends
+ *
+ * @param {boolean} weekends
+ */
+ et2_calendar_planner.prototype.set_show_weekend = function (weekends) {
+ weekends = weekends ? true : false;
+ if (this.options.show_weekend !== weekends) {
+ this.options.show_weekend = weekends;
+ if (this.isAttached()) {
+ this.invalidate();
+ }
+ }
+ };
+ /**
+ * Turn on or off the visibility of hidden (empty) rows
+ *
+ * @param {boolean} hidden
+ */
+ et2_calendar_planner.prototype.set_hide_empty = function (hidden) {
+ this.options.hide_empty = hidden;
+ };
+ /**
+ * Call change handler, if set
+ *
+ * @param {type} event
+ */
+ et2_calendar_planner.prototype.change = function (event) {
+ if (this.onchange) {
+ if (typeof this.onchange == 'function') {
+ // Make sure function gets a reference to the widget
+ var args = Array.prototype.slice.call(arguments);
+ if (args.indexOf(this) == -1)
+ args.push(this);
+ return this.onchange.apply(this, args);
+ }
+ else {
+ return (et2_compileLegacyJS(this.options.onchange, this, _node))();
+ }
+ }
+ };
+ /**
+ * Call event change handler, if set
+ *
+ * @param {type} event
+ * @param {type} dom_node
+ */
+ et2_calendar_planner.prototype.event_change = function (event, dom_node) {
+ if (this.onevent_change) {
+ var event_data = this._get_event_info(dom_node);
+ var event_widget = this.getWidgetById(event_data.widget_id);
+ et2_widget_event_1.et2_calendar_event.recur_prompt(event_data, jQuery.proxy(function (button_id, event_data) {
+ // No need to continue
+ if (button_id === 'cancel')
+ return false;
+ if (typeof this.onevent_change == 'function') {
+ // Make sure function gets a reference to the widget
+ var args = Array.prototype.slice.call(arguments);
+ if (args.indexOf(event_widget) == -1)
+ args.push(event_widget);
+ // Put button ID in event
+ event.button_id = button_id;
+ return this.onevent_change.apply(this, [event, event_widget, button_id]);
+ }
+ else {
+ return (et2_compileLegacyJS(this.options.onevent_change, event_widget, dom_node))();
+ }
+ }, this));
+ }
+ return false;
+ };
+ /**
+ * Click handler calling custom handler set via onclick attribute to this.onclick
+ *
+ * This also handles all its own actions, including navigation. If there is
+ * an event associated with the click, it will be found and passed to the
+ * onclick function.
+ *
+ * @param {Event} _ev
+ * @returns {boolean} Continue processing event (true) or stop (false)
+ */
+ et2_calendar_planner.prototype.click = function (_ev) {
+ var result = true;
+ // Drag to create in progress
+ if (this.drag_create.start !== null)
+ return;
+ // Is this click in the event stuff, or in the header?
+ if (!this.options.readonly && this.gridHeader.has(_ev.target).length === 0 && !jQuery(_ev.target).hasClass('calendar_plannerRowHeader')) {
+ // Event came from inside, maybe a calendar event
+ var event = this._get_event_info(_ev.originalEvent.target);
+ if (typeof this.onclick == 'function') {
+ // Make sure function gets a reference to the widget, splice it in as 2. argument if not
+ var args = Array.prototype.slice.call(arguments);
+ if (args.indexOf(this) == -1)
+ args.splice(1, 0, this);
+ result = this.onclick.apply(this, args);
+ }
+ if (event.id && result && !this.options.disabled && !this.options.readonly) {
+ et2_widget_event_1.et2_calendar_event.recur_prompt(event);
+ return false;
+ }
+ else if (!event.id) {
+ // Clicked in row, but not on an event
+ // Default handler to open a new event at the selected time
+ if (jQuery(event.target).closest('.calendar_eventRows').length == 0) {
+ // "Invalid" times, from space after the last planner row, or header
+ var date = this._get_time_from_position(_ev.pageX - this.grid.offset().left, _ev.pageY - this.grid.offset().top);
+ }
+ else if (this.options.group_by == 'month') {
+ var date = this._get_time_from_position(_ev.clientX, _ev.clientY);
+ }
+ else {
+ var date = this._get_time_from_position(_ev.offsetX, _ev.offsetY);
+ }
+ var row = jQuery(_ev.target).closest('.calendar_plannerRowWidget');
+ var data = row.length ? row[0].dataset : {};
+ if (date) {
+ app.calendar.add(jQuery.extend({
+ start: date.toJSON(),
+ hour: date.getUTCHours(),
+ minute: date.getUTCMinutes()
+ }, data));
+ return false;
+ }
+ }
+ return result;
+ }
+ else if (this.gridHeader.has(_ev.target).length > 0 && !jQuery.isEmptyObject(_ev.target.dataset) ||
+ jQuery(_ev.target).hasClass('calendar_plannerRowHeader') && !jQuery.isEmptyObject(_ev.target.dataset)) {
+ // Click on a header, we can go there
+ _ev.data = jQuery.extend({}, _ev.target.parentNode.dataset, _ev.target.dataset);
+ for (var key in _ev.data) {
+ if (!_ev.data[key]) {
+ delete _ev.data[key];
+ }
+ }
+ app.calendar.update_state(_ev.data);
+ }
+ else if (!this.options.readonly) {
+ // Default handler to open a new event at the selected time
+ // TODO: Determine date / time more accurately from position
+ app.calendar.add({
+ date: _ev.target.dataset.date || this.options.start_date.toJSON(),
+ hour: _ev.target.dataset.hour || this.options.day_start,
+ minute: _ev.target.dataset.minute || 0
+ });
+ return false;
+ }
+ };
+ /**
+ * Get time from position
+ *
+ * @param {number} x
+ * @param {number} y
+ * @returns {Date|Boolean} A time for the given position, or false if one
+ * could not be determined.
+ */
+ et2_calendar_planner.prototype._get_time_from_position = function (x, y) {
+ x = Math.round(x);
+ y = Math.round(y);
+ // Round to user's preferred event interval
+ var interval = egw.preference('interval', 'calendar') || 30;
+ // Relative horizontal position, as a percentage
+ var width = 0;
+ jQuery('.calendar_eventRows', this.div).each(function () { width = Math.max(width, jQuery(this).width()); });
+ var rel_x = Math.min(x / width, 1);
+ // Relative time, in minutes from start
+ var rel_time = 0;
+ var day_header = jQuery('.calendar_plannerScaleDay', this.headers);
+ // Simple math, the x is offset from start date
+ if (this.options.group_by !== 'month' && (
+ // Either all days are visible, or only 1 day (no day header)
+ this.options.show_weekend || day_header.length === 0)) {
+ rel_time = (new Date(this.options.end_date) - new Date(this.options.start_date)) * rel_x / 1000;
+ this.date_helper.set_value(this.options.start_date.toJSON());
+ }
+ // Not so simple math, need to account for missing days
+ else if (this.options.group_by !== 'month' && !this.options.show_weekend) {
+ // Find which day
+ if (day_header.length === 0)
+ return false;
+ var day = document.elementFromPoint(day_header.offset().left + rel_x * this.headers.innerWidth(), day_header.offset().top);
+ // Use day, and find time in that day
+ if (day && day.dataset && day.dataset.date) {
+ this.date_helper.set_value(day.dataset.date);
+ rel_time = ((x - jQuery(day).position().left) / jQuery(day).outerWidth(true)) * 24 * 60;
+ this.date_helper.set_minutes(Math.round(rel_time / interval) * interval);
+ return new Date(this.date_helper.getValue());
+ }
+ return false;
+ }
+ else {
+ // Find the correct row so we know which month, then get the offset
+ var hidden_nodes = [];
+ var row = null;
+ // Hide any drag or tooltips that may interfere
+ do {
+ row = document.elementFromPoint(x, y);
+ if (this.div.has(row).length == 0) {
+ hidden_nodes.push(jQuery(row).hide());
+ }
+ else {
+ break;
+ }
+ } while (row && row.nodeName !== 'BODY');
+ if (!row)
+ return false;
+ // Restore hidden nodes
+ for (var i = 0; i < hidden_nodes.length; i++) {
+ hidden_nodes[i].show();
+ }
+ row = jQuery(row).closest('.calendar_plannerRowWidget');
+ var row_widget = null;
+ for (var i = 0; i < this._children.length && row.length > 0; i++) {
+ if (this._children[i].div[0] == row[0]) {
+ row_widget = this._children[i];
+ break;
+ }
+ }
+ if (row_widget) {
+ // Not sure where the extra -1 and +2 are coming from, but it makes it work out
+ // in FF & Chrome
+ rel_x = Math.min((x - row_widget.rows.offset().left - 1) / (row_widget.rows.width() + 2), 1);
+ // 2678400 is the number of seconds in 31 days
+ rel_time = (2678400) * rel_x;
+ this.date_helper.set_value(row_widget.options.start_date.toJSON());
+ }
+ else {
+ return false;
+ }
+ }
+ if (rel_time < 0)
+ return false;
+ this.date_helper.set_minutes(Math.round(rel_time / (60 * interval)) * interval);
+ return new Date(this.date_helper.getValue());
+ };
+ /**
+ * Mousedown handler to support drag to create
+ *
+ * @param {jQuery.Event} event
+ */
+ et2_calendar_planner.prototype._mouse_down = function (event) {
+ // Only left mouse button
+ if (event.which !== 1)
+ return;
+ // Ignore headers
+ if (this.headers.has(event.target).length !== 0)
+ return false;
+ // Get time at mouse
+ if (this.options.group_by === 'month') {
+ var time = this._get_time_from_position(event.clientX, event.clientY);
+ }
+ else {
+ var time = this._get_time_from_position(event.offsetX, event.offsetY);
+ }
+ if (!time)
+ return false;
+ // Find the correct row so we know the parent
+ var row = event.target.closest('.calendar_plannerRowWidget');
+ for (var i = 0; i < this._children.length && row; i++) {
+ if (this._children[i].div[0] === row) {
+ this.drag_create.parent = this._children[i];
+ // Clear cached events for re-layout
+ this._children[i]._cached_rows = [];
+ break;
+ }
+ }
+ if (!this.drag_create.parent)
+ return false;
+ this.div.css('cursor', 'ew-resize');
+ return this._drag_create_start(jQuery.extend({}, this.drag_create.parent.node.dataset, { date: time.toJSON() }));
+ };
+ /**
+ * Mouseup handler to support drag to create
+ *
+ * @param {jQuery.Event} event
+ */
+ et2_calendar_planner.prototype._mouse_up = function (event) {
+ // Get time at mouse
+ if (this.options.group_by === 'month') {
+ var time = this._get_time_from_position(event.clientX, event.clientY);
+ }
+ else {
+ var time = this._get_time_from_position(event.offsetX, event.offsetY);
+ }
+ return this._drag_create_end(time ? { date: time.toJSON() } : false);
+ };
+ /**
+ * Code for implementing et2_IDetachedDOM
+ *
+ * @param {array} _attrs array to add further attributes to
+ */
+ et2_calendar_planner.prototype.getDetachedAttributes = function (_attrs) {
+ _attrs.push('start_date', 'end_date');
+ };
+ et2_calendar_planner.prototype.getDetachedNodes = function () {
+ return [this.getDOMNode()];
+ };
+ et2_calendar_planner.prototype.setDetachedAttributes = function (_nodes, _values) {
+ this.div = jQuery(_nodes[0]);
+ if (_values.start_date) {
+ this.set_start_date(_values.start_date);
+ }
+ if (_values.end_date) {
+ this.set_end_date(_values.end_date);
+ }
+ };
+ // Resizable interface
+ et2_calendar_planner.prototype.resize = function () {
+ // Take the whole tab height
+ var height = Math.min(jQuery(this.getInstanceManager().DOMContainer).height(), jQuery(this.getInstanceManager().DOMContainer).parent().innerHeight());
+ // Allow for toolbar
+ height -= jQuery('#calendar-toolbar', this.div.parents('.egw_fw_ui_tab_content')).outerHeight(true);
+ this.options.height = height;
+ this.div.css('height', this.options.height);
+ // Set height for rows
+ this.rows.height(this.div.height() - this.headers.outerHeight());
+ this.grid.height(this.rows[0].scrollHeight);
+ };
+ /**
+ * Set up for printing
+ *
+ * @return {undefined|Deferred} Return a jQuery Deferred object if not done setting up
+ * (waiting for data)
+ */
+ et2_calendar_planner.prototype.beforePrint = function () {
+ if (this.disabled || !this.div.is(':visible')) {
+ return;
+ }
+ this.rows.css('overflow-y', 'visible');
+ var rows = jQuery('.calendar_eventRows');
+ var width = rows.width();
+ var events = jQuery('.calendar_calEvent', rows)
+ .each(function () {
+ var event = jQuery(this);
+ event.width((event.width() / width) * 100 + '%');
+ });
+ };
+ /**
+ * Reset after printing
+ */
+ et2_calendar_planner.prototype.afterPrint = function () {
+ this.rows.css('overflow-y', 'auto');
+ };
+ et2_calendar_planner._attributes = {
+ group_by: {
+ name: "Group by",
+ type: "string",
+ default: "0",
+ description: "Display planner by 'user', 'month', or the given category"
+ },
+ filter: {
+ name: "Filter",
+ type: "string",
+ default: '',
+ description: 'A filter that is used to select events. It is passed along when events are queried.'
+ },
+ show_weekend: {
+ name: "Weekends",
+ type: "boolean",
+ default: egw.preference('days_in_weekview', 'calendar') != 5,
+ description: "Display weekends. The date range should still include them for proper scrolling, but they just won't be shown."
+ },
+ hide_empty: {
+ name: "Hide empty rows",
+ type: "boolean",
+ default: false,
+ description: "Hide rows with no events."
+ },
+ value: {
+ type: "any",
+ description: "A list of events, optionally you can set start_date, end_date and group_by as keys and events will be fetched"
+ },
+ "onchange": {
+ "name": "onchange",
+ "type": "js",
+ "default": et2_no_init,
+ "description": "JS code which is executed when the date range changes."
+ },
+ "onevent_change": {
+ "name": "onevent_change",
+ "type": "js",
+ "default": et2_no_init,
+ "description": "JS code which is executed when an event changes."
+ }
+ };
+ et2_calendar_planner.DEFERRED_ROW_TIME = 100;
+ return et2_calendar_planner;
+}(et2_widget_view_1.et2_calendar_view));
+exports.et2_calendar_planner = et2_calendar_planner;
+et2_core_widget_1.et2_register_widget(et2_calendar_planner, ["calendar-planner"]);
+//# sourceMappingURL=et2_widget_planner.js.map
\ No newline at end of file
diff --git a/calendar/js/et2_widget_planner_row.js b/calendar/js/et2_widget_planner_row.js
index 7e713bb8b1..fa978f58ec 100644
--- a/calendar/js/et2_widget_planner_row.js
+++ b/calendar/js/et2_widget_planner_row.js
@@ -1,3 +1,4 @@
+"use strict";
/*
* Egroupware
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
@@ -7,812 +8,663 @@
* @author Nathan Gray
* @version $Id$
*/
-
+var __extends = (this && this.__extends) || (function () {
+ var extendStatics = function (d, b) {
+ extendStatics = Object.setPrototypeOf ||
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+ return extendStatics(d, b);
+ };
+ return function (d, b) {
+ extendStatics(d, b);
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+ };
+})();
+Object.defineProperty(exports, "__esModule", { value: true });
/*egw:uses
- /calendar/js/et2_widget_view.js;
- /calendar/js/et2_widget_daycol.js;
- /calendar/js/et2_widget_event.js;
+ /calendar/js/et2_widget_view.js;
+ /calendar/js/et2_widget_daycol.js;
+ /calendar/js/et2_widget_event.js;
*/
-
-
+var et2_core_widget_1 = require("../../api/js/etemplate/et2_core_widget");
+var et2_core_valueWidget_1 = require("../../api/js/etemplate/et2_core_valueWidget");
+var et2_core_inheritance_1 = require("../../api/js/etemplate/et2_core_inheritance");
+var et2_core_DOMWidget_1 = require("../../api/js/etemplate/et2_core_DOMWidget");
/**
* Class for one row of a planner
*
* This widget is responsible for the label on the side
*
- * @augments et2_valueWidget
*/
-var et2_calendar_planner_row = (function(){ "use strict"; return et2_valueWidget.extend([et2_IResizeable],
-{
- attributes: {
- start_date: {
- name: "Start date",
- type: "any"
- },
- end_date: {
- name: "End date",
- type: "any"
- },
- value: {
- type: "any"
- }
- },
-
- /**
- * Constructor
- *
- * @memberOf et2_calendar_daycol
- */
- init: function() {
- this._super.apply(this, arguments);
-
- // 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 = [];
- this._row_height = 20;
- },
-
- doLoadingFinished: function() {
- this._super.apply(this, arguments);
-
- 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._parent.options.actions || []);
- return true;
- },
-
- destroy: function() {
- this._super.apply(this, arguments);
-
- // date_helper has no parent, so we must explicitly remove it
- this.date_helper.destroy();
- this.date_helper = null;
- },
-
- getDOMNode: function(_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: function(actions)
- {
- // Get the parent? Might be a grid row, might not. Either way, it is
- // just a container with no valid actions
- var objectManager = egw_getObjectManager(this.getInstanceManager().app,true,1);
- objectManager = objectManager.getObjectById(this.getInstanceManager().uniqueId,2) || objectManager;
- var parent = objectManager.getObjectById(this.id,1) || objectManager.getObjectById(this._parent.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.
- var widget_object = this._actionObject || parent.getObjectById(this.id);
- var aoi = new et2_action_object_impl(this,this.getDOMNode());
- var planner = this.getParent();
-
- for(var i = 0; i < parent.children.length; i++)
- {
- var 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
- var _invite_enabled = function(action, event, target)
- {
- var event = event.iface.getWidget();
- var row = target.iface.getWidget() || false;
- if(event === row || !event || !row ||
- !event.options || !event.options.value.participants
- )
- {
- return false;
- }
-
- var owner_match = false;
- var own_row = event.getParent() === row;
-
- for(var id in event.options.value.participants)
- {
- owner_match = owner_match || row.node.dataset.participants === ''+id;
- }
-
- var 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()
- );
- }
- var 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
- );
-
- 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
- );
- }
- };
- var 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('
');
- }
-
-
- 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
- var 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: function(actions)
- {
- var action_links = [];
-
- // Only these actions are allowed without a selection (empty actions)
- var empty_actions = ['add'];
-
- for(var i in actions)
- {
- var 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: function() {
- // Remove any existing
- this.rows.remove('.calendar_eventRowsMarkedDay,.calendar_eventRowsFiller').nextAll().remove();
-
- var days = 31;
- var width = 100;
- if (this._parent.options.group_by === 'month')
- {
- days = this.options.end_date.getUTCDate();
-
- if(days < 31)
- {
- var diff = 31 - days;
- width = 'calc('+(diff * 3.23) + '% - ' + (diff * 7) + 'px)';
- }
- }
-
- // mark weekends and other special days in yearly planner
- if (this._parent.options.group_by == 'month')
- {
- this.rows.append(this._yearlyPlannerMarkDays(this.options.start_date, days));
- }
-
- if (this._parent.options.group_by === 'month' && days < 31)
- {
- // add a filler for non existing days in that month
- this.rows.after('
');
- }
- },
-
- set_label: function(label)
- {
- this.options.label = label;
- this.title.text(label);
- if(this._parent.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: function(new_date)
- {
- if(!new_date || new_date === null)
- {
- throw exception('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: function(new_date)
- {
- if(!new_date || new_date === null)
- {
- throw exception('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: function(start,days)
- {
- var day_width = 3.23;
- var t = new Date(start);
- var content = '';
- for(var i = 0; i < days;i++)
- {
- var holidays = [];
- // TODO: implement this, pull / copy data from et2_widget_timegrid
- var day_class = this._parent.day_class_holiday(t,holidays);
-
- if (day_class) // no regular weekday
- {
- content += '
';
- }
- 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: function(event_ids) {
- var events = [];
- if(event_ids == null || typeof event_ids.length == 'undefined') event_ids = [];
- for(var i = 0; i < event_ids.length; i++)
- {
- var event = 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._parent.disabled && event_ids.length > 0)
- {
- this.resize();
- this._update_events(events);
- }
- },
-
- /**
- * 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: function(events)
- {
- // Remove all events
- while(this._children.length > 0)
- {
- var node = this._children[this._children.length-1];
- this.removeChild(node);
- node.free();
- }
- 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++)
- {
- var 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: function(event)
- {
- var rows = this._spread_events();
- var height = rows.length * this._row_height;
- var 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(var c = 0; c < rows.length; c++)
- {
- // Calculate vertical positioning
- var top = c * (100.0 / rows.length);
-
- for(var i = 0; (rows[c].indexOf(event) >=0 || !event) && i < rows[c].length; i++)
- {
- // Calculate horizontal positioning
- var left = this._time_to_position(rows[c][i].options.value.start);
- var 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: function()
- {
- // Keep it so we don't have to re-do it when the next event asks
- var 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
- var rows = [];
- var row_end = [0];
-
- // Sort in chronological order, so earliest ones are at the top
- this._children.sort(function(a,b) {
- var start = new Date(a.options.value.start) - new Date(b.options.value.start);
- var 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
- var 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(var n = 0; n < this._children.length; n++)
- {
- var 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')
- {
- var day_start = event.start.valueOf() / 1000;
- var 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
- var 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))
- {
- var node = this._children[n];
- this.removeChild(n--);
- node.free();
- continue;
- }
-
- var 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: function(values)
- {
- if(!this._parent || this._parent.options.group_by == 'month' || this._parent.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: function(time, start, end)
- {
- var 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);
- }
- var wd_start = 60 * (parseInt(egw.preference('workdaystarts','calendar')) || 9);
- var wd_end = 60 * (parseInt(egw.preference('workdayends','calendar')) || 17);
-
- var 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
- var weekend_count = 0;
- var weekend_before = 0;
- var partial_weekend = 0;
- if(this._parent.options.group_by !== 'month' && this._parent && !this._parent.options.show_weekend)
- {
-
- var 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._parent.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: function ()
- {
- if(this.disabled || !this.div.is(':visible') || this._parent.disabled)
- {
- return;
- }
-
- var row = jQuery('
').appendTo(this.rows);
- this._row_height = (parseInt(window.getComputedStyle(row[0]).getPropertyValue("height")) || 20);
- row.remove();
-
- // Resize & position all events
- this.position_event();
- }
-
-});}).call(this);
-
-et2_register_widget(et2_calendar_planner_row, ["calendar-planner_row"]);
\ No newline at end of file
+var et2_calendar_planner_row = /** @class */ (function (_super) {
+ __extends(et2_calendar_planner_row, _super);
+ /**
+ * Constructor
+ */
+ function et2_calendar_planner_row(_parent, _attrs, _child) {
+ var _this =
+ // Call the inherited constructor
+ _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_calendar_planner_row._attributes, _child || {})) || this;
+ _this._row_height = 20;
+ // 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 = [];
+ return _this;
+ }
+ et2_calendar_planner_row.prototype.doLoadingFinished = function () {
+ _super.prototype.doLoadingFinished.call(this);
+ 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;
+ };
+ et2_calendar_planner_row.prototype.destroy = function () {
+ _super.prototype.destroy.call(this);
+ // date_helper has no parent, so we must explicitly remove it
+ this._date_helper.destroy();
+ this._date_helper = null;
+ };
+ et2_calendar_planner_row.prototype.getDOMNode = function (_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
+ */
+ et2_calendar_planner_row.prototype._link_actions = function (actions) {
+ // Get the parent? Might be a grid row, might not. Either way, it is
+ // just a container with no valid actions
+ var objectManager = egw_getObjectManager(this.getInstanceManager().app, true, 1);
+ objectManager = objectManager.getObjectById(this.getInstanceManager().uniqueId, 2) || objectManager;
+ var 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.
+ var widget_object = this._actionObject || parent.getObjectById(this.id);
+ var aoi = new et2_core_DOMWidget_1.et2_action_object_impl(this, this.getDOMNode(this)).getAOI();
+ var planner = this.getParent();
+ for (var i = 0; i < parent.children.length; i++) {
+ var 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
+ var _invite_enabled = function (action, event, target) {
+ var event = event.iface.getWidget();
+ var row = target.iface.getWidget() || false;
+ if (event === row || !event || !row ||
+ !event.options || !event.options.value.participants) {
+ return false;
+ }
+ var owner_match = false;
+ var own_row = event.getParent() === row;
+ for (var id in event.options.value.participants) {
+ owner_match = owner_match || row.node.dataset.participants === '' + id;
+ }
+ var 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());
+ }
+ var 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);
+ 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);
+ }
+ };
+ var 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('
');
+ }
+ 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
+ var 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}
+ */
+ et2_calendar_planner_row.prototype._get_action_links = function (actions) {
+ var action_links = [];
+ // Only these actions are allowed without a selection (empty actions)
+ var empty_actions = ['add'];
+ for (var i in actions) {
+ var 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
+ */
+ et2_calendar_planner_row.prototype._draw = function () {
+ // Remove any existing
+ this.rows.remove('.calendar_eventRowsMarkedDay,.calendar_eventRowsFiller').nextAll().remove();
+ var days = 31;
+ var width = '100';
+ if (this.getParent().options.group_by === 'month') {
+ days = this.options.end_date.getUTCDate();
+ if (days < 31) {
+ var 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('
');
+ }
+ };
+ et2_calendar_planner_row.prototype.set_label = function (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}
+ */
+ et2_calendar_planner_row.prototype.set_start_date = function (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}
+ */
+ et2_calendar_planner_row.prototype.set_end_date = function (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
+ */
+ et2_calendar_planner_row.prototype._yearlyPlannerMarkDays = function (start, days) {
+ var day_width = 3.23;
+ var t = new Date(start);
+ var content = '';
+ for (var i = 0; i < days; i++) {
+ var holidays = [];
+ // TODO: implement this, pull / copy data from et2_widget_timegrid
+ var day_class = this.getParent().day_class_holiday(t, holidays);
+ if (day_class) // no regular weekday
+ {
+ content += '
';
+ }
+ 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}
+ */
+ et2_calendar_planner_row.prototype._data_callback = function (event_ids) {
+ var events = [];
+ if (event_ids == null || typeof event_ids.length == 'undefined')
+ event_ids = [];
+ for (var i = 0; i < event_ids.length; i++) {
+ var event_1 = egw.dataGetUIDdata('calendar::' + event_ids[i]);
+ event_1 = event_1 && event_1.data || false;
+ if (event_1 && event_1.date) {
+ events.push(event_1);
+ }
+ else if (event_1) {
+ // 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);
+ }
+ };
+ Object.defineProperty(et2_calendar_planner_row.prototype, "date_helper", {
+ get: function () {
+ return this._date_helper;
+ },
+ enumerable: true,
+ configurable: true
+ });
+ /**
+ * 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.
+ */
+ et2_calendar_planner_row.prototype._update_events = function (events) {
+ // Remove all events
+ while (this._children.length > 0) {
+ var 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++) {
+ var event_2 = this.getWidgetById('event_' + events[c].row_id);
+ if (!event_2)
+ continue;
+ if (this.isInTree()) {
+ event_2.doLoadingFinished();
+ }
+ }
+ };
+ /**
+ * Position the event according to it's time and how this widget is laid
+ * out.
+ *
+ * @param {undefined|Object|et2_calendar_event} event
+ */
+ et2_calendar_planner_row.prototype.position_event = function (event) {
+ var rows = this._spread_events();
+ var height = rows.length * this._row_height;
+ var 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 (var c = 0; c < rows.length; c++) {
+ // Calculate vertical positioning
+ var top_1 = c * (100.0 / rows.length);
+ for (var i = 0; (rows[c].indexOf(event) >= 0 || !event) && i < rows[c].length; i++) {
+ // Calculate horizontal positioning
+ var left = this._time_to_position(rows[c][i].options.value.start);
+ var width = this._time_to_position(rows[c][i].options.value.end) - left;
+ // Position the event
+ rows[c][i].div.css('top', top_1 + '%');
+ 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
+ */
+ et2_calendar_planner_row.prototype._spread_events = function () {
+ // Keep it so we don't have to re-do it when the next event asks
+ var 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
+ var rows = [];
+ var row_end = [0];
+ // Sort in chronological order, so earliest ones are at the top
+ this._children.sort(function (a, b) {
+ var start = new Date(a.options.value.start) - new Date(b.options.value.start);
+ var 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
+ var 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 (var n = 0; n < this._children.length; n++) {
+ var event_3 = this._children[n].options.value || false;
+ if (typeof event_3.start !== 'object') {
+ this._date_helper.set_value(event_3.start);
+ event_3.start = new Date(this._date_helper.getValue());
+ }
+ if (typeof event_3.end !== 'object') {
+ this._date_helper.set_value(event_3.end);
+ event_3.end = new Date(this._date_helper.getValue());
+ }
+ if (typeof event_3['start_m'] === 'undefined') {
+ var day_start = event_3.start.valueOf() / 1000;
+ var dst_check = new Date(event_3.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
+ var daylight_diff = day_start + 12 * 60 * 60 - (dst_check.valueOf() / 1000);
+ if (daylight_diff) {
+ day_start -= daylight_diff;
+ }
+ event_3['start_m'] = event_3.start.getUTCHours() * 60 + event_3.start.getUTCMinutes();
+ if (event_3['start_m'] < 0) {
+ event_3['start_m'] = 0;
+ event_3['multiday'] = true;
+ }
+ event_3['end_m'] = event_3.end.getUTCHours() * 60 + event_3.end.getUTCMinutes();
+ if (event_3['end_m'] >= 24 * 60) {
+ event_3['end_m'] = 24 * 60 - 1;
+ event_3['multiday'] = true;
+ }
+ if (!event_3.start.getUTCHours() && !event_3.start.getUTCMinutes() && event_3.end.getUTCHours() == 23 && event_3.end.getUTCMinutes() == 59) {
+ event_3.whole_day_on_top = (event_3.non_blocking && event_3.non_blocking != '0');
+ }
+ }
+ // Skip events entirely on hidden weekends
+ if (this._hidden_weekend_event(event_3)) {
+ var node = this._children[n];
+ this.removeChild(n--);
+ node.destroy();
+ continue;
+ }
+ var event_start = new Date(event_3.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_3['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
+ */
+ et2_calendar_planner_row.prototype._hidden_weekend_event = function (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
+ */
+ et2_calendar_planner_row.prototype._time_to_position = function (time, start, end) {
+ var 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);
+ }
+ var wd_start = 60 * (parseInt('' + egw.preference('workdaystarts', 'calendar')) || 9);
+ var wd_end = 60 * (parseInt('' + egw.preference('workdayends', 'calendar')) || 17);
+ var 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
+ var weekend_count = 0;
+ var weekend_before = 0;
+ var partial_weekend = 0;
+ if (this.getParent().options.group_by !== 'month' && this.getParent() && !this.getParent().options.show_weekend) {
+ var 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.
+ */
+ et2_calendar_planner_row.prototype.resize = function () {
+ if (this.disabled || !this.div.is(':visible') || this.getParent().disabled) {
+ return;
+ }
+ var row = jQuery('
').appendTo(this.rows);
+ this._row_height = (parseInt(window.getComputedStyle(row[0]).getPropertyValue("height")) || 20);
+ row.remove();
+ // Resize & position all events
+ this.position_event();
+ };
+ et2_calendar_planner_row._attributes = {
+ start_date: {
+ name: "Start date",
+ type: "any"
+ },
+ end_date: {
+ name: "End date",
+ type: "any"
+ },
+ value: {
+ type: "any"
+ }
+ };
+ return et2_calendar_planner_row;
+}(et2_core_valueWidget_1.et2_valueWidget));
+exports.et2_calendar_planner_row = et2_calendar_planner_row;
+et2_core_widget_1.et2_register_widget(et2_calendar_planner_row, ["calendar-planner_row"]);
+//# sourceMappingURL=et2_widget_planner_row.js.map
\ No newline at end of file
diff --git a/calendar/js/et2_widget_timegrid.js b/calendar/js/et2_widget_timegrid.js
index 2fc678385e..889126f976 100644
--- a/calendar/js/et2_widget_timegrid.js
+++ b/calendar/js/et2_widget_timegrid.js
@@ -1,3 +1,4 @@
+"use strict";
/*
* Egroupware Calendar timegrid
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
@@ -7,12 +8,28 @@
* @author Nathan Gray
* @version $Id$
*/
-
-
+var __extends = (this && this.__extends) || (function () {
+ var extendStatics = function (d, b) {
+ extendStatics = Object.setPrototypeOf ||
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+ return extendStatics(d, b);
+ };
+ return function (d, b) {
+ extendStatics(d, b);
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+ };
+})();
+Object.defineProperty(exports, "__esModule", { value: true });
/*egw:uses
- /calendar/js/et2_widget_view.js;
+ /calendar/js/et2_widget_view.js;
*/
-
+var et2_core_widget_1 = require("../../api/js/etemplate/et2_core_widget");
+var et2_core_inheritance_1 = require("../../api/js/etemplate/et2_core_inheritance");
+var et2_widget_view_1 = require("./et2_widget_view");
+var et2_core_DOMWidget_1 = require("../../api/js/etemplate/et2_core_DOMWidget");
+var et2_dataview_view_grid_1 = require("../../api/js/etemplate/et2_dataview_view_grid");
/**
* Class which implements the "calendar-timegrid" XET-Tag for displaying a span of days
*
@@ -27,2300 +44,1840 @@
*
* @augments et2_calendar_view
*/
-var et2_calendar_timegrid = (function(){ "use strict"; return et2_calendar_view.extend([et2_IDetachedDOM, et2_IResizeable,et2_IPrint],
-{
- createNamespace: true,
-
- attributes: {
- value: {
- type: "any",
- description: "An array of events, indexed by date (Ymd format)."
- },
- day_start: {
- name: "Day start time",
- type: "string",
- default: parseInt(egw.preference('workdaystarts','calendar')) || 9,
- description: "Work day start time. If unset, this will default to the current user's preference"
- },
- day_end: {
- name: "Day end time",
- type: "string",
- default: parseInt(egw.preference('workdayends','calendar')) || 17,
- description: "Work day end time. If unset, this will default to the current user's preference"
- },
- show_weekend: {
- name: "Weekends",
- type: "boolean",
- default: egw.preference('days_in_weekview','calendar') != 5,
- description: "Display weekends. The date range should still include them for proper scrolling, but they just won't be shown."
- },
- granularity: {
- name: "Granularity",
- type: "integer",
- default: parseInt(egw.preference('interval','calendar')) || 30,
- description: "How many minutes per row, or 0 to display events as a list"
- },
- "onchange": {
- "name": "onchange",
- "type": "js",
- "default": et2_no_init,
- "description": "JS code which is executed when the date range changes."
- },
- "onevent_change": {
- "name": "onevent_change",
- "type": "js",
- "default": et2_no_init,
- "description": "JS code which is executed when an event changes."
- },
- height: {
- "default": '100%'
- }
- },
- /**
- * Constructor
- *
- * @memberOf et2_calendar_timegrid
- */
- init: function() {
- this._super.apply(this, arguments);
-
- // Main container
- this.div = jQuery(document.createElement("div"))
- .addClass("calendar_calTimeGrid")
- .addClass("calendar_TimeGridNoLabel");
-
- // Headers
- this.gridHeader = jQuery(document.createElement("div"))
- .addClass("calendar_calGridHeader")
- .appendTo(this.div);
- this.dayHeader = jQuery(document.createElement("div"))
- .appendTo(this.gridHeader);
-
- // Contains times / rows
- this.scrolling = jQuery(document.createElement('div'))
- .addClass("calendar_calTimeGridScroll")
- .appendTo(this.div)
- .append('
');
-
- // Contains days / columns
- this.days = jQuery(document.createElement("div"))
- .addClass("calendar_calDayCols")
- .appendTo(this.scrolling);
-
- // Used for owners
- this.owner = et2_createWidget('description',{},this);
-
- this._labelContainer = jQuery(document.createElement("label"))
- .addClass("et2_label et2_link")
- .appendTo(this.gridHeader);
-
- this.gridHover = jQuery('
');
-
- // List of dates in Ymd
- // The first one should be start_date, last should be end_date
- this.day_list = [];
- this.day_widgets = [];
-
- // Update timer, to avoid redrawing twice when changing start & end date
- this.update_timer = null;
-
- // Timer to re-scale time to fit
- this.resize_timer = null;
-
- this.setDOMNode(this.div[0]);
- },
- destroy: function() {
-
- // Stop listening to tab changes
- if(typeof framework !== 'undefined' && framework.getApplicationByName('calendar').tab)
- {
- jQuery(framework.getApplicationByName('calendar').tab.contentDiv).off('show.' + this.id);
- }
-
- this._super.apply(this, arguments);
-
- // Delete all old objects
- this._actionObject.clear();
- this._actionObject.unregisterActions();
- this._actionObject.remove();
- this._actionObject = null;
-
- this.div.off();
- this.div = null;
- this.gridHeader = null;
- this.dayHeader = null;
- this.days = null;
- this.scrolling = null;
- this._labelContainer = null;
-
- // Stop the resize timer
- if(this.resize_timer)
- {
- window.clearTimeout(this.resize_timer);
- }
- },
-
- doLoadingFinished: function() {
- this._super.apply(this, arguments);
-
- // Listen to tab show to make sure we scroll to the day start, not top
- if(typeof framework !== 'undefined' && framework.getApplicationByName('calendar').tab)
- {
- jQuery(framework.getApplicationByName('calendar').tab.contentDiv)
- .on('show.' + this.id, jQuery.proxy(
- function()
- {
- if(this.scrolling)
- {
- this.scrolling.scrollTop(this._top_time);
- }
- },this)
- );
- }
-
- // Need to get the correct internal sizing
- this.resize();
-
- this._drawGrid();
-
- // Actions may be set on a parent, so we need to explicitly get in here
- // and get ours
- this._link_actions(this.options.actions || this._parent.options.actions || []);
-
- // Automatically bind drag and resize for every event using jQuery directly
- // - no action system -
- var timegrid = this;
-
- /**
- * If user puts the mouse over an event, then we'll set up resizing so
- * they can adjust the length. Should be a little better on resources
- * than binding it for every calendar event, and we won't need exceptions
- * for planner view to resize horizontally.
- */
- this.div.on('mouseover', '.calendar_calEvent:not(.ui-resizable):not(.rowNoEdit)', function() {
- // Only resize in timegrid
- if(timegrid.options.granularity === 0) return;
-
- // Load the event
- timegrid._get_event_info(this);
- var that = this;
-
- //Resizable event handler
- jQuery(this).resizable
- ({
- distance: 10,
- // Grid matching preference
- grid: [10000,timegrid.rowHeight],
- autoHide: false,
- handles: 's,se',
- containment:'parent',
-
- /**
- * Triggered when the resizable is created.
- *
- * @param {event} event
- * @param {Object} ui
- */
- create:function(event, ui)
- {
- var resizeHelper = event.target.getAttribute('data-resize');
- if (resizeHelper == 'WD' || resizeHelper == 'WDS')
- {
- jQuery(this).resizable('destroy');
- }
- },
-
- /**
- * If dragging to resize an event, abort drag to create
- *
- * @param {jQuery.Event} event
- * @param {Object} ui
- */
- start: function(event, ui)
- {
- if(timegrid.drag_create.start)
- {
- // Abort drag to create, we're dragging to resize
- timegrid._drag_create_end({});
- }
- },
-
- /**
- * Triggered at the end of resizing the calEvent.
- *
- * @param {event} event
- * @param {Object} ui
- */
- stop:function(event, ui)
- {
- var e = new jQuery.Event('change');
- e.originalEvent = event;
- e.data = {duration: 0};
- var event_data = timegrid._get_event_info(this);
- var event_widget = timegrid.getWidgetById(event_data.widget_id);
- var sT = event_widget.options.value.start_m;
- if (typeof this.dropEnd != 'undefined' && this.dropEnd.length == 1)
- {
- var eT = (parseInt(timegrid._drop_data.hour) * 60) + parseInt(timegrid._drop_data.minute);
- e.data.duration = ((eT - sT)/60) * 3600;
-
- if(event_widget)
- {
- event_widget.options.value.end_m = eT;
- event_widget.options.value.duration = e.data.duration;
- }
- jQuery(this).trigger(e);
- event_widget._update(event_widget.options.value);
-
- // That cleared the resize handles, so remove for re-creation...
- if(jQuery(this).resizable('instance'))
- {
- jQuery(this).resizable('destroy');
- }
- }
- // Clear the helper, re-draw
- if(event_widget && event_widget._parent)
- {
- event_widget._parent.position_event(event_widget);
- }
- timegrid.div.children('.drop-hover').removeClass('.drop-hover');
- },
-
- /**
- * Triggered during the resize, on the drag of the resize handler
- *
- * @param {event} event
- * @param {Object} ui
- */
- resize:function(event, ui)
- {
- // Add a bit for better understanding - it will show _to_ the start,
- // covering the 'actual' target
- timegrid._get_time_from_position(ui.helper[0].getBoundingClientRect().left, ui.helper[0].getBoundingClientRect().bottom+5);
- timegrid.gridHover.hide();
- var drop = timegrid._drag_helper(this,ui.element[0]);
- if(drop && !drop.is(':visible'))
- {
- drop.get(0).scrollIntoView(false);
- }
- }
- });
- });
-
- // Customize and override some draggable settings
- this.div
- .on('dragcreate','.calendar_calEvent', function(event, ui) {
- jQuery(this).draggable('option','cancel','.rowNoEdit');
- // Act like you clicked the header, makes it easier to position
- // but put it to the side (-5) so we can still do the hover
- jQuery(this).draggable('option','cursorAt', {top: 5, left: -5});
- })
- .on('dragstart', '.calendar_calEvent', function(event,ui) {
- jQuery('.calendar_calEvent',ui.helper).width(jQuery(this).width())
- .height(jQuery(this).outerHeight())
- .css('top', '').css('left','')
- .appendTo(ui.helper);
- ui.helper.width(jQuery(this).width());
-
- // Cancel drag to create, we're dragging an existing event
- timegrid.drag_create.start = null;
- timegrid._drag_create_end();
- })
- .on('mousemove', function(event) {
- timegrid._get_time_from_position(event.clientX, event.clientY);
- })
- .on('mouseout', function(event) {
- if(timegrid.div.has(event.relatedTarget).length === 0)
- {
- timegrid.gridHover.hide();
- }
- })
- .on('mousedown', jQuery.proxy(this._mouse_down, this))
- .on('mouseup', jQuery.proxy(this._mouse_up, this));
-
- return true;
- },
-
- /**
- * Show the current time while dragging
- * Used for resizing as well as drag & drop
- *
- * @param {type} element
- * @param {type} helper
- * @param {type} height
- */
- _drag_helper: function(element, helper,height)
- {
- if(!element) return;
-
- element.dropEnd = this.gridHover;
-
- if(element.dropEnd.length)
- {
- this._drop_data = jQuery.extend({},element.dropEnd[0].dataset || {});
- }
-
- if (typeof element.dropEnd != 'undefined' && element.dropEnd.length)
- {
- // Make sure the target is visible in the scrollable day
- if(this.gridHover.is(':visible'))
- {
- if(this.scrolling.scrollTop() > 0 && this.scrolling.scrollTop() >= this.gridHover.position().top - this.rowHeight)
- {
- this.scrolling.scrollTop(this.gridHover.position().top-this.rowHeight);
- }
- else if (this.scrolling.scrollTop() + this.scrolling.height() <= this.gridHover.position().top + (2*this.rowHeight))
- {
- this.scrolling.scrollTop(this.scrolling.scrollTop() + this.rowHeight);
- }
- }
- var time = '';
- if(this._drop_data.whole_day)
- {
- time = this.egw().lang('Whole day');
- }
- else if (this.options.granularity === 0)
- {
- // No times, keep what's in the event
- // Add class to helper to keep formatting
- jQuery(helper).addClass('calendar_calTimeGridList');
- }
- else
- {
- time = jQuery.datepicker.formatTime(
- egw.preference("timeformat") === "12" ? "h:mmtt" : "HH:mm",
- {
- hour: element.dropEnd.attr('data-hour'),
- minute: element.dropEnd.attr('data-minute'),
- seconds: 0,
- timezone: 0
- },
- {"ampm": (egw.preference("timeformat") == "12")}
- );
- }
- element.innerHTML = '
'+time+'
';
- }
- else
- {
- element.innerHTML = '
';
- }
- jQuery(element).width(jQuery(helper).width());
- return element.dropEnd;
- },
-
- /**
- * Handler for dropping an event on the timegrid
- *
- * @param {type} timegrid
- * @param {type} event
- * @param {type} ui
- * @param {type} dropEnd
- */
- _event_drop: function(timegrid, event,ui, dropEnd) {
- var e = new jQuery.Event('change');
- e.originalEvent = event;
- e.data = {start: 0};
-
- if(typeof dropEnd != 'undefined' && dropEnd)
- {
- var drop_date = dropEnd.date||false;
-
- var event_data = timegrid._get_event_info(ui.draggable);
- var event_widget = timegrid.getWidgetById(event_data.widget_id);
- if(!event_widget)
- {
- // Widget was moved across weeks / owners
- event_widget = timegrid.getParent().getWidgetById(event_data.widget_id);
- }
- if(event_widget)
- {
- // Send full string to avoid rollover between months using set_month()
- event_widget._parent.date_helper.set_value(
- drop_date.substring(0,4)+'-'+drop_date.substring(4,6)+'-'+drop_date.substring(6,8)+
- 'T00:00:00Z'
- );
-
-
- // Make sure whole day events stay as whole day events by ignoring drop time
- if(event_data.app == 'calendar' && event_widget.options.value.whole_day)
- {
- event_widget._parent.date_helper.set_hours(0);
- event_widget._parent.date_helper.set_minutes(0);
- }
- else if (timegrid.options.granularity === 0)
- {
- // List, not time grid - keep time
- event_widget._parent.date_helper.set_hours(event_widget.options.value.start.getUTCHours());
- event_widget._parent.date_helper.set_minutes(event_widget.options.value.start.getUTCMinutes());
- }
- else
- {
- // Non-whole day events, and integrated apps, can change
- event_widget._parent.date_helper.set_hours(dropEnd.whole_day ? 0 : dropEnd.hour||0);
- event_widget._parent.date_helper.set_minutes(dropEnd.whole_day ? 0 : dropEnd.minute||0);
- }
-
- // Leave the helper there until the update is done
- var loading = ui.helper.clone(true).appendTo(jQuery('body'));
- // and add a loading icon so user knows something is happening
- if(jQuery('.calendar_timeDemo',loading).length == 0)
- {
- jQuery('.calendar_calEventHeader',loading).addClass('loading');
- }
- else
- {
- jQuery('.calendar_timeDemo',loading).after('
');
- }
-
- event_widget.recur_prompt(function(button_id) {
- if(button_id === 'cancel' || !button_id)
- {
- // Need to refresh the event with original info to clean up
- var app_id = event_widget.options.value.app_id ? event_widget.options.value.app_id : event_widget.options.value.id + (event_widget.options.value.recur_type ? ':'+event_widget.options.value.recur_date : '');
- egw().dataStoreUID('calendar::'+app_id,egw.dataGetUIDdata('calendar::'+app_id).data);
- loading.remove();
- return;
- }
- //Get infologID if in case if it's an integrated infolog event
- if (event_data.app === 'infolog')
- {
- // Duration - infologs are always non-blocking
- var duration = dropEnd.whole_day ? 86400-1 : (
- event_widget.options.value.whole_day ? (egw().preference('defaultlength','calendar')*60) : false);
-
- // If it is an integrated infolog event we need to edit infolog entry
- egw().json('stylite_infolog_calendar_integration::ajax_moveInfologEvent',
- [event_data.app_id, event_widget._parent.date_helper.getValue()||false,duration],
- function() {loading.remove();}
- ).sendRequest(true);
- }
- else
- {
- //Edit calendar event
-
- // Duration - check for whole day dropped on a time, change it to full days
- var duration = event_widget.options.value.whole_day && dropEnd.hour ?
- // Make duration whole days, less 1 second
- (Math.round((event_widget.options.value.end - event_widget.options.value.start) / (1000 * 86400)) * 86400) -1 :
- false;
- // Event (whole day or not) dropped on whole day section, change to whole day non blocking
- if(dropEnd.whole_day) duration = 'whole_day';
-
- // Send the update
- var _send = function(series_instance)
- {
- var start = new Date(event_widget._parent.date_helper.getValue());
-
- egw().json('calendar.calendar_uiforms.ajax_moveEvent', [
- button_id==='series' ? event_data.id : event_data.app_id,event_data.owner,
- start,
- timegrid.options.owner||egw.user('account_id'),
- duration,
- series_instance
- ],
- function() { loading.remove();}
- ).sendRequest(true);
- };
-
- // Check for modifying a series that started before today
- if (event_widget.options.value.recur_type && button_id === 'series')
- {
- event_widget.series_split_prompt(function(_button_id) {
- if (_button_id === et2_dialog.OK_BUTTON)
- {
- _send(event_widget.options.value.recur_date);
- }
- else
- {
- loading.remove();
- }
- });
- }
- else
- {
- _send(event_widget.options.value.recur_date);
- }
- }
- });
- }
- }
- },
-
- /**
- * Something changed, and the days need to be re-drawn. We wait a bit to
- * avoid re-drawing twice if start and end date both changed, then recreate
- * the days.
- * The whole grid is not regenerated because times aren't expected to change,
- * just the days.
- *
- * @param {boolean} [trigger=false] Trigger an event once things are done.
- * Waiting until invalidate completes prevents 2 updates when changing the date range.
- * @returns {undefined}
- */
- invalidate: function(trigger) {
-
- // Reset the list of days
- this.day_list = [];
-
- // Wait a bit to see if anything else changes, then re-draw the days
- if(this.update_timer)
- {
- window.clearTimeout(this.update_timer);
- }
- this.update_timer = window.setTimeout(jQuery.proxy(function() {
- this.widget.update_timer = null;
- window.clearTimeout(this.resize_timer);
- this.widget.loader.hide().show();
-
- // Update actions
- if(this.widget._actionManager)
- {
- this.widget._link_actions(this.widget._actionManager.children);
- }
-
- this.widget._drawDays();
- // We have to completely re-do times, as they may have changed in
- // scale to the point where more labels are needed / need to be removed
- this.widget._drawTimes();
- if(this.trigger)
- {
- this.widget.change();
- }
- // Hide loader
- window.setTimeout(jQuery.proxy(function() {this.loader.hide();},this.widget),200);
- },{widget:this,"trigger":trigger}),ET2_GRID_INVALIDATE_TIMEOUT);
- },
-
- detachFromDOM: function() {
- // Remove the binding to the change handler
- jQuery(this.div).off(".et2_calendar_timegrid");
-
- this._super.apply(this, arguments);
- },
-
- attachToDOM: function() {
- this._super.apply(this, arguments);
-
- // Add the binding for the event change handler
- jQuery(this.div).on("change.et2_calendar_timegrid", '.calendar_calEvent', this, function(e) {
- // Make sure function gets a reference to the widget
- var args = Array.prototype.slice.call(arguments);
- if(args.indexOf(this) == -1) args.push(this);
-
- return e.data.event_change.apply(e.data, args);
- });
-
- // Add the binding for the change handler
- jQuery(this.div).on("change.et2_calendar_timegrid", '*:not(.calendar_calEvent)', this, function(e) {
- return e.data.change.call(e.data, e, this);
- });
-
- // Catch resize and prevent it from bubbling further, triggering
- // etemplate's resize
- this.div.on('resize', this, function(e) {
- e.stopPropagation();
- });
- },
-
- getDOMNode: function(_sender) {
- if(_sender === this || !_sender)
- {
- return this.div ? this.div[0] : null;
- }
- else if (_sender.instanceOf(et2_calendar_daycol))
- {
- return this.days ? this.days[0] : null;
- }
- else if (_sender)
- {
- return this.gridHeader ? this.gridHeader[0] : null;
- }
- },
-
- set_disabled: function(disabled) {
- var old_value = this.options.disabled;
- this._super.apply(this, arguments);
- if(disabled)
- {
- this.loader.show();
- }
- else if (old_value !== disabled)
- {
- // Scroll to start of day - stops jumping in FF
- // For some reason on Chrome & FF this doesn't quite get the day start
- // to the top, so add 2px;
- this.scrolling.scrollTop(this._top_time+2);
- }
-
- },
-
- /**
- * Clear everything, and redraw the whole grid
- */
- _drawGrid: function() {
-
- this.div.css('height', this.options.height)
- .empty();
- this.loader.prependTo(this.div).show();
-
- // Draw in the horizontal - the times
- this._drawTimes();
-
- // Draw in the vertical - the days
- this.invalidate();
- },
-
- /**
- * Creates the DOM nodes for the times in the left column, and the horizontal
- * lines (mostly via CSS) that span the whole date range.
- */
- _drawTimes: function() {
- jQuery('.calendar_calTimeRow',this.div).remove();
-
- this.div.toggleClass('calendar_calTimeGridList', this.options.granularity === 0);
-
- this.gridHeader
- .attr('data-date', this.options.start_date)
- .attr('data-owner', this.options.owner)
- .append(this._labelContainer)
- .append(this.owner.getDOMNode())
- .append(this.dayHeader)
- .appendTo(this.div);
-
- // Max with 18 avoids problems when it's not shown
- var header_height = Math.max(this.gridHeader.outerHeight(true), 18);
-
- this.scrolling
- .appendTo(this.div)
- .off();
-
- // No time grid - list
- if(this.options.granularity === 0)
- {
- this.scrolling.css('height','100%');
- this.days.css('height', '100%');
- this.iterateOver(function(day) {
- day.resize();
- },this,et2_calendar_daycol);
- return;
- }
-
- var wd_start = 60*this.options.day_start;
- var wd_end = 60*this.options.day_end;
- var granularity = this.options.granularity;
- var totalDisplayMinutes = wd_end - wd_start;
- var rowsToDisplay = Math.ceil((totalDisplayMinutes+60)/granularity);
- var row_count = (1440 / this.options.granularity);
-
-
- this.scrolling
- .on('scroll', jQuery.proxy(this._scroll, this));
-
- // Percent
- var rowHeight = (100/rowsToDisplay).toFixed(1);
- // Pixels
- this.rowHeight = this.scrolling.height() / rowsToDisplay;
-
- // We need a reasonable bottom limit here, but resize will handle it
- // if we get too small
- if(this.rowHeight < 5 && this.div.is(':visible'))
- {
- if(this.rowHeight === 0)
- {
- // Something is not right...
- this.rowHeight = 5;
- }
- }
-
- // the hour rows
- var show = {
- 5 : [0,15,30,45],
- 10 : [0,30],
- 15 : [0,30],
- 45 : [0,15,30,45]
- };
- var html = '';
- var line_height = parseInt(this.div.css('line-height'));
- this._top_time = 0;
- for(var t = 0,i = 0; t < 1440; t += granularity,++i)
- {
- if(t <= wd_start && t + granularity > wd_start)
- {
- this._top_time = this.rowHeight * (i+1+(wd_start - (t+granularity))/granularity);
- }
- var working_hours = (t >= wd_start && t < wd_end) ? ' calendar_calWorkHours' : '';
- html += '
';
- // show time for full hours, always for 45min interval and at least on every 3 row
- var time = jQuery.datepicker.formatTime(
- egw.preference("timeformat") === "12" ? "h:mmtt" : "HH:mm",
- {
- hour: t / 60,
- minute: t % 60,
- seconds: 0,
- timezone: 0
- },
- {"ampm": (egw.preference("timeformat") === "12")}
- );
-
- var time_label = (typeof show[granularity] === 'undefined' ? t % 60 === 0 : show[granularity].indexOf(t % 60) !== -1) ? time : '';
- if(time_label && egw.preference("timeformat") == "12" && time_label.split(':')[0] < 10)
- {
- time_label =' ' + time_label;
- }
- html += '
'+time_label+"
\n";
- }
-
- // Set heights in pixels for scrolling
- jQuery('.calendar_calTimeLabels',this.scrolling)
- .empty()
- .height(this.rowHeight*i)
- .append(html);
- this.days.css('height', (this.rowHeight*i)+'px');
- this.gridHover.css('height', this.rowHeight);
-
- // Scroll to start of day
- this.scrolling.scrollTop(this._top_time);
- },
-
- /**
- * 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.
- *
- * We use a timeout to avoid doing it multiple times if redrawing or resizing.
- */
- resizeTimes: function() {
-
- // Hide resizing from user
- this.loader.show();
-
- // Wait a bit to see if anything else changes, then re-draw the times
- if(this.resize_timer)
- {
- window.clearTimeout(this.resize_timer);
- }
- // No point if it is just going to be redone completely
- if(this.update_timer) return;
-
- this.resize_timer = window.setTimeout(jQuery.proxy(function() {
- if(this._resizeTimes)
- {
- this.resize_timer = null;
-
- this._resizeTimes();
- }
- },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() {
-
- if(!this.div.is(':visible'))
- {
- return;
- }
- var wd_start = 60*this.options.day_start;
- var wd_end = 60*this.options.day_end;
- var totalDisplayMinutes = wd_end - wd_start;
- var rowsToDisplay = Math.ceil((totalDisplayMinutes+60)/this.options.granularity);
- var row_count = (1440 / this.options.granularity);
-
- var new_height = this.scrolling.height() / rowsToDisplay;
- var old_height = this.rowHeight;
- this.rowHeight = new_height;
-
- jQuery('.calendar_calTimeLabels', this.scrolling).height(this.rowHeight*row_count);
- this.days.css('height', this.options.granularity === 0 ?
- '100%' :
- (this.rowHeight*row_count)+'px'
- );
-
- // Scroll to start of day
- this._top_time = (wd_start * this.rowHeight) / this.options.granularity;
- // For some reason on Chrome & FF this doesn't quite get the day start
- // to the top, so add 2px;
- this.scrolling.scrollTop(this._top_time+2);
-
- if(this.rowHeight != old_height)
- {
- this.iterateOver(function(child) {
- if(child === this) return;
- child.resize();
- },this, et2_IResizeable);
- }
-
- this.loader.hide();
- },
-
- /**
- * Set up the needed day widgets to correctly display the selected date
- * range. First we calculate the needed dates, then we create any needed
- * widgets. Existing widgets are recycled rather than discarded.
- */
- _drawDays: function() {
- this.scrolling.append(this.days);
-
- // If day list is still empty, recalculate it from start & end date
- if(this.day_list.length === 0 && this.options.start_date && this.options.end_date)
- {
- this.day_list = this._calculate_day_list(this.options.start_date, this.options.end_date, this.options.show_weekend);
- }
- // For a single day, we show each owner in their own daycol
- this.daily_owner = this.day_list.length === 1 &&
- this.options.owner.length > 1 &&
- this.options.owner.length < (parseInt(egw.preference('day_consolidate','calendar')) || 6);
- var daycols_needed = this.daily_owner ? this.options.owner.length : this.day_list.length;
- var day_width = ( Math.min( jQuery(this.getInstanceManager().DOMContainer).width(),this.days.width())/daycols_needed);
- if(!day_width || !this.day_list)
- {
- // Hidden on another tab, or no days for some reason
- var dim = egw.getHiddenDimensions(this.days, false);
- day_width = ( dim.w /Math.max(daycols_needed,1));
- }
-
- // Create any needed widgets - otherwise, we'll just recycle
- // Add any needed day widgets (now showing more days)
- var add_index = 0;
- var before = true;
-
- while(daycols_needed > this.day_widgets.length)
- {
- var existing_index = this.day_widgets[add_index] && !this.daily_owner ?
- this.day_list.indexOf(this.day_widgets[add_index].options.date) :
- -1;
- before = existing_index > add_index;
-
- var day = et2_createWidget('calendar-daycol',{
- owner: this.options.owner,
- width: (before ? 0 : day_width) + "px"
- },this);
- if(this.isInTree())
- {
- day.doLoadingFinished();
- }
- if(existing_index != -1 && parseInt(this.day_list[add_index]) < parseInt(this.day_list[existing_index]))
- {
- this.day_widgets.unshift(day);
- jQuery(this.getDOMNode(day)).prepend(day.getDOMNode(day));
- }
- else
- {
- this.day_widgets.push(day);
- }
- add_index++;
- }
- // Remove any extra day widgets (now showing less)
- var delete_index = this.day_widgets.length - 1;
- before = false;
- while(this.day_widgets.length > daycols_needed)
- {
- // If we're going down to an existing one, just keep it for cool CSS animation
- while(delete_index > 1 && this.day_list.indexOf(this.day_widgets[delete_index].options.date) > -1)
- {
- delete_index--;
- before = true;
- }
- if(delete_index < 0) delete_index = 0;
-
- // Widgets that are before our date shrink, after just get pushed out
- if(before)
- {
- this.day_widgets[delete_index].set_width('0px');
- }
- this.day_widgets[delete_index].div.hide();
- this.day_widgets[delete_index].header.hide();
- this.day_widgets[delete_index].destroy();
- this.day_widgets.splice(delete_index--,1);
- }
-
- this.set_header_classes();
-
- // Create / update day widgets with dates and data
- for(var i = 0; i < this.day_widgets.length; i++)
- {
- day = this.day_widgets[i];
-
- // Position
- day.set_left((day_width * i) + 'px');
- day.title.removeClass('blue_title');
- if(this.daily_owner)
- {
- // Each 'day' is the same date, different user
- day.set_id(this.day_list[0]+'-'+this.options.owner[i]);
- day.set_date(this.day_list[0], false);
- day.set_owner(this.options.owner[i]);
- day.set_label(this._get_owner_name(this.options.owner[i]));
- day.title.addClass('blue_title');
- }
- else
- {
- // Show user name in day header even if only one
- if(this.day_list.length === 1)
- {
- day.set_label(this._get_owner_name(this.options.owner));
- day.title.addClass('blue_title');
- }
- else
- {
- // Go back to self-calculated date by clearing the label
- day.set_label('');
- }
- day.set_id(this.day_list[i]);
- day.set_date(this.day_list[i], this.value[this.day_list[i]] || false);
- day.set_owner(this.options.owner);
- }
- day.set_width(day_width + 'px');
- }
-
- // Adjust and scroll to start of day
- this.resizeTimes();
-
- // Don't hold on to value any longer, use the data cache for best info
- this.value = {};
-
- if(this.daily_owner)
- {
- this.set_label('');
- }
-
- // Handle not fully visible elements
- this._scroll();
-
- // TODO: Figure out how to do this with detached nodes
- /*
- var nodes = this.day_col.getDetachedNodes();
- var supportedAttrs = [];
- this.day_col.getDetachedAttributes(supportedAttrs);
- supportedAttrs.push("id");
-
- for(var i = 0; i < day_count; i++)
- {
- this.day_col.setDetachedAttributes(nodes.clone(),)
- }
- */
- },
-
- /**
- * Set header classes
- *
- */
- set_header_classes: function()
- {
- var day;
- for(var i = 0; i < this.day_widgets.length; i++)
- {
- day = this.day_widgets[i];
-
- // Classes
- if(app.calendar && app.calendar.state &&
- this.day_list[i] && parseInt(this.day_list[i].substr(4,2)) !== new Date(app.calendar.state.date).getUTCMonth()+1)
- {
- day.set_class('calendar_differentMonth');
- }
- else
- {
- day.set_class('');
- }
- }
- },
-
- /**
- * Update UI while scrolling within the selected time
- *
- * Toggles out of view indicators and adjusts not visible headers
- * @param {Event} event Scroll event
- */
- _scroll: function(event)
- {
- if(!this.day_widgets) return;
-
- // Loop through days, let them deal with it
- for(var day = 0; day < this.day_widgets.length; day++)
- {
- this.day_widgets[day]._out_of_view();
- }
- },
-
- /**
- * Calculate a list of days between start and end date, skipping weekends if
- * desired.
- *
- * @param {Date|string} start_date Date that et2_date widget can understand
- * @param {Date|string} end_date Date that et2_date widget can understand
- * @param {boolean} show_weekend If not showing weekend, Saturday and Sunday
- * will not be in the returned list.
- *
- * @returns {string[]} List of days in Ymd format
- */
- _calculate_day_list: function(start_date, end_date, show_weekend) {
-
- var day_list = [];
-
- this.date_helper.set_value(end_date);
- var end = this.date_helper.date.getTime();
- var i = 1;
- this.date_helper.set_value(new Date(start_date));
-
- do
- {
- if(show_weekend || !show_weekend && [0,6].indexOf(this.date_helper.date.getUTCDay()) === -1 || end_date === start_date)
- {
- day_list.push(''+this.date_helper.get_year() + sprintf('%02d',this.date_helper.get_month()) + sprintf('%02d',this.date_helper.get_date()));
- }
- this.date_helper.set_date(this.date_helper.get_date()+1);
- }
- // Limit it to 14 days to avoid infinite loops in case something is mis-set,
- // though the limit is more based on how wide the screen is
- while(end >= this.date_helper.date.getTime() && i++ <= 14)
-
- return day_list;
- },
-
- /**
- * Link the actions to the DOM nodes / widget bits.
- *
- * @param {object} actions {ID: {attributes..}+} map of egw action information
- */
- _link_actions: function(actions)
- {
- // Get the parent? Might be a grid row, might not. Either way, it is
- // just a container with no valid actions
- var objectManager = egw_getObjectManager(this.getInstanceManager().app,true,1);
- objectManager = objectManager.getObjectById(this.getInstanceManager().uniqueId,2) || objectManager;
- var parent = objectManager.getObjectById(this.id,1) || objectManager.getObjectById(this._parent.id,1) || objectManager;
- if(!parent)
- {
- debugger;
- 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.
- var widget_object = this._actionObject || parent.getObjectById(this.id);
- var aoi = new et2_action_object_impl(this,this.getDOMNode());
-
- for(var i = 0; i < parent.children.length; i++)
- {
- var 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
- var _invite_enabled = function(action, event, target)
- {
- var event = event.iface.getWidget();
- var timegrid = target.iface.getWidget() || false;
- if(event === timegrid || !event || !timegrid ||
- !event.options || !event.options.value.participants || !timegrid.options.owner
- )
- {
- return false;
- }
- var owner_match = false;
- var own_timegrid = event.getParent().getParent() === timegrid && !timegrid.daily_owner;
-
- for(var id in event.options.value.participants)
- {
- if(!timegrid.daily_owner)
- {
- if(timegrid.options.owner === id ||
- timegrid.options.owner.indexOf &&
- timegrid.options.owner.indexOf(id) >= 0)
- {
- owner_match = true;
- }
- }
- else
- {
- timegrid.iterateOver(function(col) {
- // Check scroll section or header section
- if(col.div.has(timegrid.gridHover).length || col.header.has(timegrid.gridHover).length)
- {
- owner_match = owner_match || col.options.owner.indexOf(id) !== -1;
- own_timegrid = (col === event.getParent());
- }
- }, this, et2_calendar_daycol);
- }
- }
- var enabled = !owner_match &&
- // Not inside its own timegrid
- !own_timegrid;
-
- 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')
- {
- var dropEnd = false;
- var helper = jQuery('.calendar_d-n-d_timeCounter',_data.ui.helper)[0];
- if(helper && helper.dropEnd && helper.dropEnd.length >= 1)
- if (typeof this.dropEnd !== 'undefined' && this.dropEnd.length >= 1)
- {
- dropEnd = helper.dropEnd[0].dataset || false;
- }
- this.getWidget()._event_drop.call(jQuery('.calendar_d-n-d_timeCounter',_data.ui.helper)[0],this.getWidget(),event, _data.ui, dropEnd);
- }
- var drag_listener = function(_event, ui) {
- aoi.getWidget()._drag_helper(jQuery('.calendar_d-n-d_timeCounter',ui.helper)[0],ui.helper[0],0);
- if(aoi.getWidget().daily_owner)
- {
- _invite_enabled(
- widget_object.getActionLink('invite').actionObj,
- event,
- widget_object
- );
- }
- };
- var 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'+widget_object.id, drag_listener);
- _data.ui.draggable.on('dragend.et2_timegrid'+widget_object.id, function() {
- _data.ui.draggable.off('drag.et2_timegrid' + widget_object.id);
- });
-
- // Remove formatting for out-of-view events (full day non-blocking)
- jQuery('.calendar_calEventHeader',_data.ui.helper).css('top','');
- jQuery('.calendar_calEventBody',_data.ui.helper).css('padding-top','');
-
- // 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('
');
- }
-
- 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'+widget_object.id);
- // Remove highlighted time square
- var timegrid = aoi.getWidget();
- timegrid.gridHover.hide();
- timegrid.scrolling.scrollTop(timegrid._top_time);
-
- // 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
- var action_links = this._get_action_links(actions);
-
- this._init_links_dnd(widget_object.manager, action_links);
-
- widget_object.updateActionLinks(action_links);
- },
-
- /**
- * Automatically add dnd support for linking
- *
- * @param {type} mgr
- * @param {type} actionLinks
- */
- _init_links_dnd: function(mgr,actionLinks) {
-
- if (this.options.readonly) return;
-
- var self = this;
-
- var drop_link = mgr.getActionById('egw_link_drop');
- var drop_change_participant = mgr.getActionById('change_participant');
- var drop_invite = mgr.getActionById('invite');
- var drag_action = mgr.getActionById('egw_link_drag');
-
- // Check if this app supports linking
- if(!egw.link_get_registry(this.dataStorePrefix || 'calendar', 'query') ||
- egw.link_get_registry(this.dataStorePrefix || 'calendar', 'title'))
- {
- if(drop_link)
- {
- drop_link.remove();
- if(actionLinks.indexOf(drop_link.id) >= 0)
- {
- actionLinks.splice(actionLinks.indexOf(drop_link.id),1);
- }
- }
- if(drag_action)
- {
- drag_action.remove();
- if(actionLinks.indexOf(drag_action.id) >= 0)
- {
- actionLinks.splice(actionLinks.indexOf(drag_action.id),1);
- }
- }
- return;
- }
- // Don't re-add
- if(drop_link == null)
- {
- // Create the drop action that links entries
- drop_link = mgr.addAction('drop', 'egw_link_drop', egw.lang('Create link'), egw.image('link'), function(action, source, target) {
-
- // Extract link IDs
- var links = [];
- var id = '';
- for(var i = 0; i < source.length; i++)
- {
- // Check for no ID (invalid) or same manager (dragging an event)
- if(!source[i].id) continue;
- if(source[i].manager === target.manager)
- {
- // Find the timegrid, could have dropped on an event
- var timegrid = target.iface.getWidget();
- while(target.parent && timegrid.instanceOf && !timegrid.instanceOf(et2_calendar_timegrid))
- {
- target = target.parent;
- timegrid = target.iface.getWidget();
- }
-
-
- if (timegrid && timegrid._drop_data)
- {
- timegrid._event_drop.call(source[i].iface.getDOMNode(),timegrid,null, action.ui,timegrid._drop_data);
- }
- timegrid._drop_data = false;
- // Ok, stop.
- return false;
- }
-
- id = source[i].id.split('::');
- links.push({app: id[0] == 'filemanager' ? 'link' : id[0], id: id[1]});
- }
- if(links.length && target && target.iface.getWidget() && target.iface.getWidget().instanceOf(et2_calendar_event))
- {
- // Link the entries
- egw.json(self.egw().getAppName()+".etemplate_widget_link.ajax_link.etemplate",
- target.id.split('::').concat([links]),
- function(result) {
- if(result)
- {
- this.egw().message('Linked');
- }
- },
- self,
- true,
- self
- ).sendRequest();
- }
- else if (links.length)
- {
- // Get date and time
- var params = jQuery.extend({},jQuery('.drop-hover[data-date]',target.iface.getDOMNode())[0].dataset || {});
-
- // Add link IDs
- var app_registry = egw.link_get_registry('calendar');
- params[app_registry.add_app] = [];
- params[app_registry.add_id] = [];
- for(var n in links)
- {
- params[app_registry.add_app].push( links[n].app);
- params[app_registry.add_id].push( links[n].id);
- }
- app.calendar.add(params);
- }
-
- },true);
-
- drop_link.acceptedTypes = ['default','link'];
- drop_link.hideOnDisabled = true;
-
- // Create the drop action for moving events between calendars
- var invite_action = function(action, source, target) {
-
- // Extract link IDs
- var links = [];
- var id = '';
- for(var i = 0; i < source.length; i++)
- {
- // Check for no ID (invalid) or same manager (dragging an event)
- if(!source[i].id) continue;
- if(source[i].manager === target.manager)
- {
-
- // Find the timegrid, could have dropped on an event
- var timegrid = target.iface.getWidget();
- while(target.parent && timegrid.instanceOf && !timegrid.instanceOf(et2_calendar_timegrid))
- {
- target = target.parent;
- timegrid = target.iface.getWidget();
- }
-
- // Leave the helper there until the update is done
- var loading = action.ui.helper.clone(true).appendTo(jQuery('body'));
-
- // and add a loading icon so user knows something is happening
- if(jQuery('.calendar_timeDemo',loading).length == 0)
- {
- jQuery('.calendar_calEventHeader',loading).addClass('loading');
- }
- else
- {
- jQuery('.calendar_timeDemo',loading).after('
');
- }
-
- var event_data = egw.dataGetUIDdata(source[i].id).data;
- et2_calendar_event.recur_prompt(event_data, function(button_id) {
- if(button_id === 'cancel' || !button_id)
- {
- return;
- }
- var add_owner = jQuery.extend([],timegrid.options.owner);
- if(timegrid.daily_owner)
- {
- timegrid.iterateOver(function(col) {
- if(col.div.has(timegrid.gridHover).length || col.header.has(timegrid.gridHover).length)
- {
- add_owner = col.options.owner;
- }
- }, this, et2_calendar_daycol);
- }
- egw().json('calendar.calendar_uiforms.ajax_invite', [
- button_id==='series' ? event_data.id : event_data.app_id,
- add_owner,
- action.id === 'change_participant' ?
- jQuery.extend([],source[i].iface.getWidget().getParent().options.owner) :
- []
- ],
- function() { loading.remove();}
- ).sendRequest(true);
- });
- // Ok, stop.
- return false;
- }
- }
- };
-
- drop_change_participant = mgr.addAction('drop', 'change_participant', egw.lang('Move to'), egw.image('participant'), invite_action,true);
- drop_change_participant.acceptedTypes = ['calendar'];
- drop_change_participant.hideOnDisabled = true;
-
- drop_invite = mgr.addAction('drop', 'invite', egw.lang('Invite'), egw.image('participant'), invite_action,true);
- drop_invite.acceptedTypes = ['calendar'];
- drop_invite.hideOnDisabled = true;
- }
- if(actionLinks.indexOf(drop_link.id) < 0)
- {
- actionLinks.push(drop_link.id);
- }
-
- actionLinks.push(drop_invite.id);
- actionLinks.push(drop_change_participant.id);
-
- // Don't re-add
- if(drag_action == null)
- {
- // Create drag action that allows linking
- drag_action = mgr.addAction('drag', 'egw_link_drag', egw.lang('link'), 'link', function(action, selected) {
- // Drag helper - list titles.
- // As we wanted to have a general defaul helper interface, we return null here and not using customize helper for links
- // TODO: Need to decide if we need to create a customized helper interface for links anyway
- //return helper;
- return null;
- },true);
- }
- // The timegrid itself is not draggable, so don't add a link.
- // The action is there for the children (events) to use
- if(false && actionLinks.indexOf(drag_action.id) < 0)
- {
- actionLinks.push(drag_action.id);
- }
- drag_action.set_dragType(['link','calendar']);
- },
-
- /**
- * 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: function(actions)
- {
- var action_links = [];
- // TODO: determine which actions are allowed without an action (empty actions)
- for(var i in actions)
- {
- var action = actions[i];
- if(action.type == 'drop')
- {
- action_links.push(typeof action.id != 'undefined' ? action.id : i);
- }
- }
- return action_links;
- },
-
- /**
- * Provide specific data to be displayed.
- * 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:
- * {
- * 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: function set_value(events)
- {
- if(typeof events !== 'object') return false;
-
- var use_days_sent = true;
-
- if(events.start_date)
- {
- use_days_sent = false;
- }
- if(events.end_date)
- {
- use_days_sent = false;
- }
-
- this._super.apply(this,arguments);
-
- if(use_days_sent)
- {
- var day_list = Object.keys(events);
- if(day_list.length)
- {
- this.set_start_date(day_list[0]);
- this.set_end_date(day_list[day_list.length-1]);
- }
-
-
- // Sub widgets actually get their own data from egw.data, so we'll
- // stick it there
- var consolidated = et2_calendar_view.is_consolidated(this.options.owner, this.day_list.length == 1 ? 'day' : 'week');
- for(var day in events)
- {
- var day_list = [];
- for(var i = 0; i < events[day].length; i++)
- {
- day_list.push(events[day][i].row_id);
- egw.dataStoreUID('calendar::'+events[day][i].row_id, events[day][i]);
- }
- // Might be split by user, so we have to check that too
- for(var i = 0; i < this.options.owner.length; i++)
- {
- var owner = consolidated ? this.options.owner : this.options.owner[i];
- var day_id = app.classes.calendar._daywise_cache_id(day,owner);
- egw.dataStoreUID(day_id, day_list);
- if(consolidated) break;
- }
- }
- }
-
- // Reset and calculate instead of just use the keys so we can get the weekend preference
- this.day_list = [];
-
- // None of the above changed anything, hide the loader
- if(!this.update_timer)
- {
- window.setTimeout(jQuery.proxy(function() {this.loader.hide();},this),200);
- }
- },
-
- /**
- * Set which user owns this. Owner is passed along to the individual
- * days.
- *
- * @param {number|number[]} _owner Account ID
- * @returns {undefined}
- */
- set_owner: function(_owner)
- {
- var old = this.options.owner || 0;
- this._super.apply(this, arguments);
-
- this.owner.set_label('');
- this.div.removeClass('calendar_TimeGridNoLabel');
-
- // Check to see if it's our own calendar, with just us showing
- if(typeof _owner == 'object' && _owner.length == 1)
- {
- var rowCount = 0;
- this._parent.iterateOver(function(widget) {
- if(!widget.disabled) rowCount++;
- },this, et2_calendar_timegrid);
- // Just us, show week number
- if(rowCount == 1 && _owner.length == 1 && _owner[0] == egw.user('account_id') || rowCount != 1) _owner = false;
- }
-
- var day_count = this.day_list.length ? this.day_list.length :
- this._calculate_day_list(this.options.start_date, this.options.end_date, this.options.show_weekend).length;
- if(typeof _owner == 'string' && isNaN(_owner))
- {
- this.set_label('');
- this.owner.set_value(this._get_owner_name(_owner));
-
- // Label is empty, but give extra space for the owner name
- this.div.removeClass('calendar_TimeGridNoLabel');
- }
- else if (!_owner || typeof _owner == 'object' && _owner.length > 1 ||
- // Single owner, single day
- _owner.length === 1 && day_count === 1
- )
- {
- // Don't show owners if more than one, show week number
- this.owner.set_value('');
- if(this.options.start_date)
- {
- this.set_label(egw.lang('wk') + ' ' +
- (app.calendar ? app.calendar.date.week_number(this.options.start_date) : '')
- );
- }
- }
- else
- {
- this.owner.options.application = 'api-accounts';
- this.owner.set_value(this._get_owner_name(_owner));
- this.set_label('');
- jQuery(this.getDOMNode(this.owner)).prepend(this.owner.getDOMNode());
- }
-
- 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);
- }
- },
-
- /**
- * Set a label for this week
- *
- * May conflict with owner, which is displayed when there's only one owner.
- *
- * @param {string} label
- */
- set_label: function(label)
- {
- this.options.label = label;
- this._labelContainer.html(label);
- this.gridHeader.prepend(this._labelContainer);
-
- // If it's a short label (eg week number), don't give it an extra line
- // but is empty, but give extra space for a single owner name
- this.div.toggleClass(
- 'calendar_TimeGridNoLabel',
- label.trim().length > 0 && label.trim().length <= 6 ||
- this.options.owner.length > 1
- );
- },
-
- /**
- * 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
- */
- set_granularity: function(minutes)
- {
- // Avoid < 0
- minutes = Math.max(0,minutes);
-
- if(this.options.granularity !== minutes)
- {
- if(this.options.granularity === 0 || minutes === 0)
- {
- this.options.granularity = minutes;
- // Need to re-do a bunch to make sure this is propagated
- this.invalidate();
- }
- else
- {
- this.options.granularity = minutes;
- this._drawTimes();
- }
- }
- else if (!this.update_timer)
- {
- this.resizeTimes();
- }
- },
-
- /**
- * Turn on or off the visibility of weekends
- *
- * @param {boolean} weekends
- */
- set_show_weekend: function(weekends)
- {
- weekends = weekends ? true : false;
- if(this.options.show_weekend !== weekends)
- {
- this.options.show_weekend = weekends;
- if(this.isAttached())
- {
- this.invalidate();
- }
- }
- },
-
- /**
- * Call change handler, if set
- */
- change: function() {
- if (this.onchange)
- {
- if(typeof this.onchange == 'function')
- {
- // Make sure function gets a reference to the widget
- var args = Array.prototype.slice.call(arguments);
- if(args.indexOf(this) == -1) args.push(this);
-
- return this.onchange.apply(this, args);
- } else {
- return (et2_compileLegacyJS(this.options.onchange, this, _node))();
- }
- }
- },
-
- /**
- * Call event change handler, if set
- *
- * @param {type} event
- * @param {type} dom_node
- */
- event_change: function(event, dom_node) {
- if (this.onevent_change)
- {
- var event_data = this._get_event_info(dom_node);
- var event_widget = this.getWidgetById(event_data.widget_id);
- et2_calendar_event.recur_prompt(event_data, jQuery.proxy(function(button_id, event_data) {
- // No need to continue
- if(button_id === 'cancel') return false;
-
- if(typeof this.onevent_change == 'function')
- {
- // Make sure function gets a reference to the widget
- var args = Array.prototype.slice.call(arguments);
-
- if(args.indexOf(event_widget) == -1) args.push(event_widget);
-
- // Put button ID in event
- event.button_id = button_id;
-
- return this.onevent_change.apply(this, [event, event_widget, button_id]);
- } else {
- return (et2_compileLegacyJS(this.options.onevent_change, event_widget, dom_node))();
- }
- },this));
- }
- return false;
- },
-
- get_granularity: function()
- {
- // get option, or user's preference
- if(typeof this.options.granularity === 'undefined')
- {
- this.options.granularity = egw.preference('interval','calendar') || 30;
- }
- return parseInt(this.options.granularity);
- },
-
- /**
- * Click handler calling custom handler set via onclick attribute to this.onclick
- *
- * This also handles all its own actions, including navigation. If there is
- * an event associated with the click, it will be found and passed to the
- * onclick function.
- *
- * @param {Event} _ev
- * @returns {boolean} Continue processing event (true) or stop (false)
- */
- click: function(_ev)
- {
- var result = true;
- if(this.options.readonly ) return;
-
- // Drag to create in progress
- if(this.drag_create.start !== null) return;
-
- // Is this click in the event stuff, or in the header?
- if(_ev.target.dataset.id || jQuery(_ev.target).parents('.calendar_calEvent').length)
- {
- // Event came from inside, maybe a calendar event
- var event = this._get_event_info(_ev.originalEvent.target);
- if(typeof this.onclick == 'function')
- {
- // Make sure function gets a reference to the widget, splice it in as 2. argument if not
- var args = Array.prototype.slice.call(arguments);
- if(args.indexOf(this) == -1) args.splice(1, 0, this);
-
- result = this.onclick.apply(this, args);
- }
-
- var event_node = jQuery(event.event_node);
- if(event.id && result && !this.disabled && !this.options.readonly &&
- // Permissions - opening will fail if we try
- event_node && !(event_node.hasClass('rowNoView'))
- )
- {
- if(event.widget_id && this.getWidgetById(event.widget_id))
- {
- this.getWidgetById(event.widget_id).recur_prompt();
- }
- else
- {
- et2_calendar_event.recur_prompt(event);
- }
-
- return false;
- }
- return result;
- }
- else if (this.gridHeader.is(_ev.target) && _ev.target.dataset ||
- this._labelContainer.is(_ev.target) && this.gridHeader[0].dataset)
- {
- app.calendar.update_state(jQuery.extend(
- {view: 'week'},
- this._labelContainer.is(_ev.target) ?
- this.gridHeader[0].dataset :
- _ev.target.dataset
- ));
- }
- else if (this.options.owner.length === 1 && jQuery(this.owner.getDOMNode()).is(_ev.target))
- {
- // Click on the owner in header, show just that owner
- app.calendar.update_state({owner: this.options.owner});
- }
- else if (this.dayHeader.has(_ev.target).length)
- {
- // Click on a day header - let day deal with it
- // First child is a selectAccount
- for(var i = 1; i < this._children.length; i++)
- {
- if(this._children[i].header && (
- this._children[i].header.has(_ev.target).length || this._children[i].header.is(_ev.target))
- )
- {
- return this._children[i].click(_ev);
- }
- }
- }
- // No time grid, click on a day
- else if (this.options.granularity === 0 &&
- (jQuery(_ev.target).hasClass('event_wrapper') || jQuery(_ev.target).hasClass('.calendar_calDayCol'))
- )
- {
- // Default handler to open a new event at the selected time
- var target = jQuery(_ev.target).hasClass('event_wrapper') ? _ev.target.parentNode : _ev.target;
- var options = {
- date: target.dataset.date || this.options.date,
- hour: target.dataset.hour || this._parent.options.day_start,
- minute: target.dataset.minute || 0,
- owner: this.options.owner
- };
- app.calendar.add(options);
- return false;
- }
- },
-
- /**
- * Mousedown handler to support drag to create
- *
- * @param {jQuery.Event} event
- */
- _mouse_down: function(event)
- {
- if(event.which !== 1) return;
-
- if (this.options.readonly) return;
-
- var start = jQuery.extend({},this.gridHover[0].dataset);
- if(start.date)
- {
- // Set parent for event
- if(this.daily_owner)
- {
- // Each 'day' is the same date, different user
- // Find the correct row so we know the parent
- var col = event.target.closest('.calendar_calDayCol');
- for(var i = 0; i < this._children.length && col; i++)
- {
- if(this._children[i].node === col)
- {
- this.drag_create.parent = this._children[i];
- break;
- }
- }
- }
- else
- {
- this.drag_create.parent = this.getWidgetById(start.date);
- }
-
- // Format date
- this.date_helper.set_year(start.date.substring(0,4));
- this.date_helper.set_month(start.date.substring(4,6));
- this.date_helper.set_date(start.date.substring(6,8));
- if(start.hour)
- {
- this.date_helper.set_hours(start.hour);
- }
- if(start.minute)
- {
- this.date_helper.set_minutes(start.minute);
- }
- start.date = this.date_helper.get_value();
-
- this.gridHover.css('cursor', 'ns-resize');
-
- // Start update
- var timegrid = this;
- this.div.on('mousemove.dragcreate', function()
- {
- if(timegrid.drag_create.event && timegrid.drag_create.parent && timegrid.drag_create.end)
- {
- var end = jQuery.extend({}, timegrid.gridHover[0].dataset);
- if(end.date)
- {
- timegrid.date_helper.set_year(end.date.substring(0,4));
- timegrid.date_helper.set_month(end.date.substring(4,6));
- timegrid.date_helper.set_date(end.date.substring(6,8));
- if(end.hour)
- {
- timegrid.date_helper.set_hours(end.hour);
- }
- if(end.minute)
- {
- timegrid.date_helper.set_minutes(end.minute);
- }
- timegrid.drag_create.end.date = timegrid.date_helper.get_value();
- }
- timegrid._drag_update_event();
- }
- });
- }
- return this._drag_create_start(start);
- },
-
- /**
- * Mouseup handler to support drag to create
- *
- * @param {jQuery.Event} event
- */
- _mouse_up: function(event)
- {
- if (this.options.readonly) return;
- var end = jQuery.extend({}, this.gridHover[0].dataset);
- if(end.date)
- {
- this.date_helper.set_year(end.date.substring(0,4));
- this.date_helper.set_month(end.date.substring(4,6));
- this.date_helper.set_date(end.date.substring(6,8));
- if(end.hour)
- {
- this.date_helper.set_hours(end.hour);
- }
- if(end.minute)
- {
- this.date_helper.set_minutes(end.minute);
- }
- end.date = this.date_helper.get_value();
- }
- this.div.off('mousemove.dragcreate');
- this.gridHover.css('cursor', '');
-
- return this._drag_create_end(end);
- },
-
- /**
- * 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} y
- * @returns {DOMNode[]} time node(s) for the given position
- */
- _get_time_from_position: function(x,y) {
-
- x = Math.round(x);
- y = Math.round(y);
-
- var path = [];
- var day = null;
- var time = null;
-
- var node = document.elementFromPoint(x,y);
- var $node = jQuery(node);
-
- // Ignore high level & non-time (grid itself, header parent & week label)
- if([this.node, this.gridHeader[0], this._labelContainer[0]].indexOf(node) !== -1 ||
- // Day labels
- this.gridHeader.has(node).length && !$node.hasClass("calendar_calDayColAllDay") && !$node.hasClass('calendar_calDayColHeader'))
- {
- return [];
- }
- for(var id in this.gridHover[0].dataset) {
- delete this.gridHover[0].dataset[id];
- }
- if(this.options.granularity == 0)
- {
- this.gridHover.css('height','');
- }
- while(node && node != this.node && node.tagName != 'BODY' && path.length < 10)
- {
- path.push(node);
- node.style.display = 'none';
- $node = jQuery(node);
- if($node.hasClass('calendar_calDayColHeader')) {
- for(var id in node.dataset) {
- this.gridHover[0].dataset[id] = node.dataset[id];
- }
- this.gridHover.css({
- position: 'absolute',
- top: '',
- bottom: '0px',
- // Use 100% height if we're hiding the day labels to avoid
- // any remaining space from the hidden labels
- height: $node.height() > parseInt($node.css('line-height')) ?
- $node.css('padding-bottom') : '100%'
- });
- day = node;
- this.gridHover
- .attr('data-non_blocking','true');
- break;
- }
- if($node.hasClass('calendar_calDayCol'))
- {
- day = node;
- this.gridHover
- .attr('data-date',day.dataset.date);
- }
- if($node.hasClass('calendar_calTimeRowTime'))
- {
- time = node;
- this.gridHover
- .attr('data-hour',time.dataset.hour)
- .attr('data-minute',time.dataset.minute);
- break;
- }
- node = document.elementFromPoint(x,y);
- }
- for(var i = 0; i < path.length; i++)
- {
- path[i].style.display = '';
- }
-
- if(!day)
- {
- return [];
- }
- this.gridHover
- .show()
- .appendTo(day);
- if(time)
- {
- this.gridHover
- .height(this.rowHeight)
- .position({my:'left top', at: 'left top', of: time});
- }
- this.gridHover.css('left','');
- return this.gridHover;
- },
-
- /**
- * Code for implementing et2_IDetachedDOM
- *
- * @param {array} _attrs array to add further attributes to
- */
- getDetachedAttributes: function(_attrs) {
- _attrs.push('start_date','end_date');
- },
-
- getDetachedNodes: function() {
- return [this.getDOMNode()];
- },
-
- setDetachedAttributes: function(_nodes, _values) {
- this.div = jQuery(_nodes[0]);
-
- if(_values.start_date)
- {
- this.set_start_date(_values.start_date);
- }
- if(_values.end_date)
- {
- this.set_end_date(_values.end_date);
- }
- },
-
- // Resizable interface
- /**
- * @param {boolean} [_too_small=null] Force the widget to act as if it was too small
- */
- resize: function (_too_small)
- {
- if(this.disabled || !this.div.is(':visible'))
- {
- return;
- }
-
- /*
- We expect the timegrid to be in a table with 0 or more other timegrids,
- 1 per row. We want each timegrid to be as large as possible, but space
- shared equally. Height can't be set to a percentage on the rows, because
- that doesn't work. However, if any timegrid is too small (1/2 hour < 1 line
- height), we change to showing only the working hours with no vertical
- scrollbar. Each week gets as much space as it needs, and all scroll together.
- */
- // How many rows?
- var rowCount = 0;
- this._parent.iterateOver(function(widget) {
- if(!widget.disabled) rowCount++;
- },this, et2_calendar_timegrid);
-
- // Take the whole tab height, or home portlet
- if(this.getInstanceManager().app === 'home')
- {
- var height = jQuery(this.getParent().getDOMNode()).parentsUntil('.et2_portlet').last().parent().innerHeight();
-
- // Allow for portlet header
- height -= jQuery('.ui-widget-header',this.div.parents('.egw_fw_ui_tab_content')).outerHeight(true);
- }
- else
- {
- var height = jQuery(this.getInstanceManager().DOMContainer).parent().innerHeight();
-
- // Allow for toolbar
- height -= jQuery('#calendar-toolbar',this.div.parents('.egw_fw_ui_tab_content')).outerHeight(true);
- }
-
- this.options.height = Math.floor(height / rowCount);
-
- // Allow for borders & padding
- this.options.height -= 2*((this.div.outerWidth(true) - this.div.innerWidth()) + parseInt(this.div.parent().css('padding-top')));
-
- // Calculate how much space is needed, and
- // if too small be bigger
- var needed = ((this.day_end - this.day_start) /
- (this.options.granularity / 60) * parseInt(this.div.css('line-height'))) +
- this.gridHeader.outerHeight();
- var too_small = needed > this.options.height && this.options.granularity != 0;
-
-
- if(this.getInstanceManager().app === 'home')
- {
- var modify_node = jQuery(this.getParent().getDOMNode()).parentsUntil('.et2_portlet').last();
- }
- else
- {
- var modify_node = jQuery(this.getInstanceManager().DOMContainer);
- }
- modify_node
- .css({
- 'overflow-y': too_small || _too_small ? 'auto' : 'hidden',
- 'overflow-x': 'hidden',
- 'height': too_small || _too_small ? height : '100%'
- });
- if(too_small || _too_small)
- {
- this.options.height = Math.max(this.options.height, needed);
- // Set all others to match
- if(!_too_small && rowCount > 1 && this._parent)
- {
- window.setTimeout(jQuery.proxy(function() {
- if(!this._parent) return;
- this._parent.iterateOver(function(widget) {
- if(!widget.disabled) widget.resize(true);
- },this, et2_calendar_timegrid);
- },this),1);
- return;
- }
- this.div.addClass('calendar_calTimeGridFixed');
- }
- else
- {
- this.div.removeClass('calendar_calTimeGridFixed');
- }
- this.div.css('height', this.options.height);
-
- // Re-do time grid
- if(!this.update_timer)
- {
- this.resizeTimes();
- }
-
- // Try to resize width, though animations cause problems
- var total_width = modify_node.parent().innerWidth() - this.days.position().left;
- // Space for todos, if there
- total_width -= jQuery(this.getInstanceManager().DOMContainer).siblings().has(':visible').not('#calendar-toolbar').outerWidth()
-
- var day_width = (total_width > 0 ? total_width : modify_node.width())/this.day_widgets.length;
- // update day widgets
- for(var i = 0; i < this.day_widgets.length; i++)
- {
- var day = this.day_widgets[i];
-
- // Position
- day.set_left((day_width * i) + 'px');
- day.set_width(day_width + 'px');
- }
- },
-
- /**
- * Set up for printing
- *
- * @return {undefined|Deferred} Return a jQuery Deferred object if not done setting up
- * (waiting for data)
- */
- beforePrint: function() {
-
- if(this.disabled || !this.div.is(':visible'))
- {
- return;
- }
-
- var height_check = this.div.height();
- this.div.css('max-height','17cm');
- if(this.div.height() != height_check)
- {
- this.div.height('17cm');
- this._resizeTimes();
- }
-
- // update day widgets, if not on single day view
- //
- // TODO: Find out why don't we update single day view
- // Let the single day view participate in print calculation.
- if(this.day_widgets.length > 0)
- {
- var day_width = (100 / this.day_widgets.length);
- for(var i = 0; i < this.day_widgets.length; i++)
- {
- var day = this.day_widgets[i];
-
- // Position
- day.set_left((i*day_width) + '%');
- day.set_width(day_width + '%');
- // For some reason the column's method does not set it correctly in Chrome
- day.header[0].style.width = day_width + '%';
- }
- }
-
- // Stop Firefox from scrolling the day to the top - this would break printing in Chrome
- if (navigator.userAgent.match(/(firefox|safari|iceweasel)/i) && !navigator.userAgent.match(/chrome/i))
- {
- var height = this.scrolling.scrollTop() + this.scrolling.height();
- this.scrolling
- // Disable scroll event, or it will recalculate out of view events
- .off('scroll')
- // Explicitly transform to the correct place
- .css({
- 'transform': 'translateY(-'+this.scrolling.scrollTop()+'px)',
- 'margin-bottom': '-'+this.scrolling.scrollTop()+'px',
- 'height': height+'px'
- });
- this.div.css({'height':'','max-height':''});
- }
- },
-
- /**
- * Reset after printing
- */
- afterPrint: function() {
- this.div.css('maxHeight','');
- this.scrolling.children().css({'transform':'', 'overflow':''});
- this.div.height(this.options.height);
- if (navigator.userAgent.match(/(firefox|safari|iceweasel)/i) && !navigator.userAgent.match(/chrome/i))
- {
- this._resizeTimes();
- this.scrolling
- // Re-enable out-of-view formatting on scroll
- .on('scroll', jQuery.proxy(this._scroll, this))
- // Remove translation
- .css({'transform':'', 'margin-bottom':''});
- }
- }
-});}).call(this);
-et2_register_widget(et2_calendar_timegrid, ["calendar-timegrid"]);
\ No newline at end of file
+var et2_calendar_timegrid = /** @class */ (function (_super) {
+ __extends(et2_calendar_timegrid, _super);
+ /**
+ * Constructor
+ *
+ * @memberOf et2_calendar_timegrid
+ */
+ function et2_calendar_timegrid(_parent, _attrs, _child) {
+ var _this =
+ // Call the inherited constructor
+ _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_calendar_timegrid._attributes, _child || {})) || this;
+ _this.daily_owner = false;
+ // Main container
+ _this.div = jQuery(document.createElement("div"))
+ .addClass("calendar_calTimeGrid")
+ .addClass("calendar_TimeGridNoLabel");
+ // Headers
+ _this.gridHeader = jQuery(document.createElement("div"))
+ .addClass("calendar_calGridHeader")
+ .appendTo(_this.div);
+ _this.dayHeader = jQuery(document.createElement("div"))
+ .appendTo(_this.gridHeader);
+ // Contains times / rows
+ _this.scrolling = jQuery(document.createElement('div'))
+ .addClass("calendar_calTimeGridScroll")
+ .appendTo(_this.div)
+ .append('
');
+ // Contains days / columns
+ _this.days = jQuery(document.createElement("div"))
+ .addClass("calendar_calDayCols")
+ .appendTo(_this.scrolling);
+ // Used for owners
+ _this.owner = et2_createWidget('description', {}, _this);
+ _this._labelContainer = jQuery(document.createElement("label"))
+ .addClass("et2_label et2_link")
+ .appendTo(_this.gridHeader);
+ _this.gridHover = jQuery('
');
+ // List of dates in Ymd
+ // The first one should be start_date, last should be end_date
+ _this.day_list = [];
+ _this.day_widgets = [];
+ // Timer to re-scale time to fit
+ _this.resize_timer = null;
+ _this.setDOMNode(_this.div[0]);
+ return _this;
+ }
+ et2_calendar_timegrid.prototype.destroy = function () {
+ // Stop listening to tab changes
+ if (typeof framework !== 'undefined' && framework.getApplicationByName('calendar').tab) {
+ jQuery(framework.getApplicationByName('calendar').tab.contentDiv).off('show.' + this.id);
+ }
+ _super.prototype.destroy.call(this);
+ // Delete all old objects
+ this._actionObject.clear();
+ this._actionObject.unregisterActions();
+ this._actionObject.remove();
+ this._actionObject = null;
+ this.div.off();
+ this.div = null;
+ this.gridHeader = null;
+ this.dayHeader = null;
+ this.days = null;
+ this.scrolling = null;
+ this._labelContainer = null;
+ // Stop the resize timer
+ if (this.resize_timer) {
+ window.clearTimeout(this.resize_timer);
+ }
+ };
+ et2_calendar_timegrid.prototype.doLoadingFinished = function () {
+ _super.prototype.doLoadingFinished.call(this);
+ // Listen to tab show to make sure we scroll to the day start, not top
+ if (typeof framework !== 'undefined' && framework.getApplicationByName('calendar').tab) {
+ jQuery(framework.getApplicationByName('calendar').tab.contentDiv)
+ .on('show.' + this.id, jQuery.proxy(function () {
+ if (this.scrolling) {
+ this.scrolling.scrollTop(this._top_time);
+ }
+ }, this));
+ }
+ // Need to get the correct internal sizing
+ this.resize();
+ this._drawGrid();
+ // Actions may be set on a parent, so we need to explicitly get in here
+ // and get ours
+ this._link_actions(this.options.actions || this.getParent().options.actions || []);
+ // Automatically bind drag and resize for every event using jQuery directly
+ // - no action system -
+ var timegrid = this;
+ /**
+ * If user puts the mouse over an event, then we'll set up resizing so
+ * they can adjust the length. Should be a little better on resources
+ * than binding it for every calendar event, and we won't need exceptions
+ * for planner view to resize horizontally.
+ */
+ this.div.on('mouseover', '.calendar_calEvent:not(.ui-resizable):not(.rowNoEdit)', function () {
+ // Only resize in timegrid
+ if (timegrid.options.granularity === 0)
+ return;
+ // Load the event
+ timegrid._get_event_info(this);
+ var that = this;
+ //Resizable event handler
+ jQuery(this).resizable({
+ distance: 10,
+ // Grid matching preference
+ grid: [10000, timegrid.rowHeight],
+ autoHide: false,
+ handles: 's,se',
+ containment: 'parent',
+ /**
+ * Triggered when the resizable is created.
+ *
+ * @param {event} event
+ * @param {Object} ui
+ */
+ create: function (event, ui) {
+ var resizeHelper = event.target.getAttribute('data-resize');
+ if (resizeHelper == 'WD' || resizeHelper == 'WDS') {
+ jQuery(this).resizable('destroy');
+ }
+ },
+ /**
+ * If dragging to resize an event, abort drag to create
+ *
+ * @param {jQuery.Event} event
+ * @param {Object} ui
+ */
+ start: function (event, ui) {
+ if (timegrid.drag_create.start) {
+ // Abort drag to create, we're dragging to resize
+ timegrid._drag_create_end({});
+ }
+ },
+ /**
+ * Triggered at the end of resizing the calEvent.
+ *
+ * @param {event} event
+ * @param {Object} ui
+ */
+ stop: function (event, ui) {
+ var e = new jQuery.Event('change');
+ e.originalEvent = event;
+ e.data = { duration: 0 };
+ var event_data = timegrid._get_event_info(this);
+ var event_widget = timegrid.getWidgetById(event_data.widget_id);
+ var sT = event_widget.options.value.start_m;
+ if (typeof this.dropEnd != 'undefined' && this.dropEnd.length == 1) {
+ var eT = (parseInt(timegrid._drop_data.hour) * 60) + parseInt(timegrid._drop_data.minute);
+ e.data.duration = ((eT - sT) / 60) * 3600;
+ if (event_widget) {
+ event_widget.options.value.end_m = eT;
+ event_widget.options.value.duration = e.data.duration;
+ }
+ jQuery(this).trigger(e);
+ event_widget._update(event_widget.options.value);
+ // That cleared the resize handles, so remove for re-creation...
+ if (jQuery(this).resizable('instance')) {
+ jQuery(this).resizable('destroy');
+ }
+ }
+ // Clear the helper, re-draw
+ if (event_widget && event_widget._parent) {
+ event_widget._parent.position_event(event_widget);
+ }
+ timegrid.div.children('.drop-hover').removeClass('.drop-hover');
+ },
+ /**
+ * Triggered during the resize, on the drag of the resize handler
+ *
+ * @param {event} event
+ * @param {Object} ui
+ */
+ resize: function (event, ui) {
+ // Add a bit for better understanding - it will show _to_ the start,
+ // covering the 'actual' target
+ timegrid._get_time_from_position(ui.helper[0].getBoundingClientRect().left, ui.helper[0].getBoundingClientRect().bottom + 5);
+ timegrid.gridHover.hide();
+ var drop = timegrid._drag_helper(this, ui.element[0]);
+ if (drop && !drop.is(':visible')) {
+ drop.get(0).scrollIntoView(false);
+ }
+ }
+ });
+ });
+ // Customize and override some draggable settings
+ this.div
+ .on('dragcreate', '.calendar_calEvent', function (event, ui) {
+ jQuery(this).draggable('option', 'cancel', '.rowNoEdit');
+ // Act like you clicked the header, makes it easier to position
+ // but put it to the side (-5) so we can still do the hover
+ jQuery(this).draggable('option', 'cursorAt', { top: 5, left: -5 });
+ })
+ .on('dragstart', '.calendar_calEvent', function (event, ui) {
+ jQuery('.calendar_calEvent', ui.helper).width(jQuery(this).width())
+ .height(jQuery(this).outerHeight())
+ .css('top', '').css('left', '')
+ .appendTo(ui.helper);
+ ui.helper.width(jQuery(this).width());
+ // Cancel drag to create, we're dragging an existing event
+ timegrid.drag_create.start = null;
+ timegrid._drag_create_end();
+ })
+ .on('mousemove', function (event) {
+ timegrid._get_time_from_position(event.clientX, event.clientY);
+ })
+ .on('mouseout', function (event) {
+ if (timegrid.div.has(event.relatedTarget).length === 0) {
+ timegrid.gridHover.hide();
+ }
+ })
+ .on('mousedown', jQuery.proxy(this._mouse_down, this))
+ .on('mouseup', jQuery.proxy(this._mouse_up, this));
+ return true;
+ };
+ et2_calendar_timegrid.prototype._createNamespace = function () {
+ return true;
+ };
+ /**
+ * Show the current time while dragging
+ * Used for resizing as well as drag & drop
+ *
+ * @param {type} element
+ * @param {type} helper
+ * @param {type} height
+ */
+ et2_calendar_timegrid.prototype._drag_helper = function (element, helper, height) {
+ if (!element)
+ return;
+ element.dropEnd = this.gridHover;
+ if (element.dropEnd.length) {
+ this._drop_data = jQuery.extend({}, element.dropEnd[0].dataset || {});
+ }
+ if (typeof element.dropEnd != 'undefined' && element.dropEnd.length) {
+ // Make sure the target is visible in the scrollable day
+ if (this.gridHover.is(':visible')) {
+ if (this.scrolling.scrollTop() > 0 && this.scrolling.scrollTop() >= this.gridHover.position().top - this.rowHeight) {
+ this.scrolling.scrollTop(this.gridHover.position().top - this.rowHeight);
+ }
+ else if (this.scrolling.scrollTop() + this.scrolling.height() <= this.gridHover.position().top + (2 * this.rowHeight)) {
+ this.scrolling.scrollTop(this.scrolling.scrollTop() + this.rowHeight);
+ }
+ }
+ var time = '';
+ if (this._drop_data.whole_day) {
+ time = this.egw().lang('Whole day');
+ }
+ else if (this.options.granularity === 0) {
+ // No times, keep what's in the event
+ // Add class to helper to keep formatting
+ jQuery(helper).addClass('calendar_calTimeGridList');
+ }
+ else {
+ // @ts-ignore
+ time = jQuery.datepicker.formatTime(egw.preference("timeformat") === "12" ? "h:mmtt" : "HH:mm", {
+ hour: element.dropEnd.attr('data-hour'),
+ minute: element.dropEnd.attr('data-minute'),
+ seconds: 0,
+ timezone: 0
+ }, { "ampm": (egw.preference("timeformat") == "12") });
+ }
+ element.innerHTML = '
' + time + '
';
+ }
+ else {
+ element.innerHTML = '
';
+ }
+ jQuery(element).width(jQuery(helper).width());
+ return element.dropEnd;
+ };
+ /**
+ * Handler for dropping an event on the timegrid
+ *
+ * @param {type} timegrid
+ * @param {type} event
+ * @param {type} ui
+ * @param {type} dropEnd
+ */
+ et2_calendar_timegrid.prototype._event_drop = function (timegrid, event, ui, dropEnd) {
+ var e = new jQuery.Event('change');
+ e.originalEvent = event;
+ e.data = { start: 0 };
+ if (typeof dropEnd != 'undefined' && dropEnd) {
+ var drop_date = dropEnd.date || false;
+ var event_data = timegrid._get_event_info(ui.draggable);
+ var event_widget = timegrid.getWidgetById(event_data.widget_id);
+ if (!event_widget) {
+ // Widget was moved across weeks / owners
+ event_widget = timegrid.getParent().getWidgetById(event_data.widget_id);
+ }
+ if (event_widget) {
+ // Send full string to avoid rollover between months using set_month()
+ event_widget._parent.date_helper.set_value(drop_date.substring(0, 4) + '-' + drop_date.substring(4, 6) + '-' + drop_date.substring(6, 8) +
+ 'T00:00:00Z');
+ // Make sure whole day events stay as whole day events by ignoring drop time
+ if (event_data.app == 'calendar' && event_widget.options.value.whole_day) {
+ event_widget._parent.date_helper.set_hours(0);
+ event_widget._parent.date_helper.set_minutes(0);
+ }
+ else if (timegrid.options.granularity === 0) {
+ // List, not time grid - keep time
+ event_widget._parent.date_helper.set_hours(event_widget.options.value.start.getUTCHours());
+ event_widget._parent.date_helper.set_minutes(event_widget.options.value.start.getUTCMinutes());
+ }
+ else {
+ // Non-whole day events, and integrated apps, can change
+ event_widget._parent.date_helper.set_hours(dropEnd.whole_day ? 0 : dropEnd.hour || 0);
+ event_widget._parent.date_helper.set_minutes(dropEnd.whole_day ? 0 : dropEnd.minute || 0);
+ }
+ // Leave the helper there until the update is done
+ var loading = ui.helper.clone(true).appendTo(jQuery('body'));
+ // and add a loading icon so user knows something is happening
+ if (jQuery('.calendar_timeDemo', loading).length == 0) {
+ jQuery('.calendar_calEventHeader', loading).addClass('loading');
+ }
+ else {
+ jQuery('.calendar_timeDemo', loading).after('
');
+ }
+ event_widget.recur_prompt(function (button_id) {
+ if (button_id === 'cancel' || !button_id) {
+ // Need to refresh the event with original info to clean up
+ var app_id = event_widget.options.value.app_id ? event_widget.options.value.app_id : event_widget.options.value.id + (event_widget.options.value.recur_type ? ':' + event_widget.options.value.recur_date : '');
+ egw().dataStoreUID('calendar::' + app_id, egw.dataGetUIDdata('calendar::' + app_id).data);
+ loading.remove();
+ return;
+ }
+ var duration;
+ //Get infologID if in case if it's an integrated infolog event
+ if (event_data.app === 'infolog') {
+ // Duration - infologs are always non-blocking
+ duration = dropEnd.whole_day ? 86400 - 1 : (event_widget.options.value.whole_day ? (egw().preference('defaultlength', 'calendar') * 60) : false);
+ // If it is an integrated infolog event we need to edit infolog entry
+ egw().json('stylite_infolog_calendar_integration::ajax_moveInfologEvent', [event_data.app_id, event_widget._parent.date_helper.getValue() || false, duration], function () { loading.remove(); }).sendRequest(true);
+ }
+ else {
+ //Edit calendar event
+ // Duration - check for whole day dropped on a time, change it to full days
+ duration = event_widget.options.value.whole_day && dropEnd.hour ?
+ // Make duration whole days, less 1 second
+ (Math.round((event_widget.options.value.end - event_widget.options.value.start) / (1000 * 86400)) * 86400) - 1 :
+ false;
+ // Event (whole day or not) dropped on whole day section, change to whole day non blocking
+ if (dropEnd.whole_day)
+ duration = 'whole_day';
+ // Send the update
+ var _send = function (series_instance) {
+ var start = new Date(event_widget._parent.date_helper.getValue());
+ egw().json('calendar.calendar_uiforms.ajax_moveEvent', [
+ button_id === 'series' ? event_data.id : event_data.app_id, event_data.owner,
+ start,
+ timegrid.options.owner || egw.user('account_id'),
+ duration,
+ series_instance
+ ], function () { loading.remove(); }).sendRequest(true);
+ };
+ // Check for modifying a series that started before today
+ if (event_widget.options.value.recur_type && button_id === 'series') {
+ event_widget.series_split_prompt(function (_button_id) {
+ if (_button_id === et2_dialog.OK_BUTTON) {
+ _send(event_widget.options.value.recur_date);
+ }
+ else {
+ loading.remove();
+ }
+ });
+ }
+ else {
+ _send(event_widget.options.value.recur_date);
+ }
+ }
+ });
+ }
+ }
+ };
+ /**
+ * Something changed, and the days need to be re-drawn. We wait a bit to
+ * avoid re-drawing twice if start and end date both changed, then recreate
+ * the days.
+ * The whole grid is not regenerated because times aren't expected to change,
+ * just the days.
+ *
+ * @param {boolean} [trigger=false] Trigger an event once things are done.
+ * Waiting until invalidate completes prevents 2 updates when changing the date range.
+ * @returns {undefined}
+ */
+ et2_calendar_timegrid.prototype.invalidate = function (trigger) {
+ // Reset the list of days
+ this.day_list = [];
+ // Wait a bit to see if anything else changes, then re-draw the days
+ if (this.update_timer) {
+ window.clearTimeout(this.update_timer);
+ }
+ this.update_timer = window.setTimeout(jQuery.proxy(function () {
+ this.widget.update_timer = null;
+ window.clearTimeout(this.resize_timer);
+ this.widget.loader.hide().show();
+ // Update actions
+ if (this.widget._actionManager) {
+ this.widget._link_actions(this.widget._actionManager.children);
+ }
+ this.widget._drawDays();
+ // We have to completely re-do times, as they may have changed in
+ // scale to the point where more labels are needed / need to be removed
+ this.widget._drawTimes();
+ if (this.trigger) {
+ this.widget.change();
+ }
+ // Hide loader
+ window.setTimeout(jQuery.proxy(function () { this.loader.hide(); }, this.widget), 200);
+ }, { widget: this, "trigger": trigger }), et2_dataview_view_grid_1.et2_dataview_grid.ET2_GRID_INVALIDATE_TIMEOUT);
+ };
+ et2_calendar_timegrid.prototype.detachFromDOM = function () {
+ // Remove the binding to the change handler
+ jQuery(this.div).off(".et2_calendar_timegrid");
+ return _super.prototype.detachFromDOM.call(this);
+ };
+ et2_calendar_timegrid.prototype.attachToDOM = function () {
+ var result = _super.prototype.attachToDOM.call(this);
+ // Add the binding for the event change handler
+ jQuery(this.div).on("change.et2_calendar_timegrid", '.calendar_calEvent', this, function (e) {
+ // Make sure function gets a reference to the widget
+ var args = Array.prototype.slice.call(arguments);
+ if (args.indexOf(this) == -1)
+ args.push(this);
+ return e.data.event_change.apply(e.data, args);
+ });
+ // Add the binding for the change handler
+ jQuery(this.div).on("change.et2_calendar_timegrid", '*:not(.calendar_calEvent)', this, function (e) {
+ return e.data.change.call(e.data, e, this);
+ });
+ // Catch resize and prevent it from bubbling further, triggering
+ // etemplate's resize
+ this.div.on('resize', this, function (e) {
+ e.stopPropagation();
+ });
+ return result;
+ };
+ et2_calendar_timegrid.prototype.getDOMNode = function (_sender) {
+ if (_sender === this || !_sender) {
+ return this.div ? this.div[0] : null;
+ }
+ else if (_sender.instanceOf(et2_calendar_daycol)) {
+ return this.days ? this.days[0] : null;
+ }
+ else if (_sender) {
+ return this.gridHeader ? this.gridHeader[0] : null;
+ }
+ };
+ et2_calendar_timegrid.prototype.set_disabled = function (disabled) {
+ var old_value = this.options.disabled;
+ _super.prototype.set_disabled.call(this, disabled);
+ if (disabled) {
+ this.loader.show();
+ }
+ else if (old_value !== disabled) {
+ // Scroll to start of day - stops jumping in FF
+ // For some reason on Chrome & FF this doesn't quite get the day start
+ // to the top, so add 2px;
+ this.scrolling.scrollTop(this._top_time + 2);
+ }
+ };
+ /**
+ * Clear everything, and redraw the whole grid
+ */
+ et2_calendar_timegrid.prototype._drawGrid = function () {
+ this.div.css('height', this.options.height)
+ .empty();
+ this.loader.prependTo(this.div).show();
+ // Draw in the horizontal - the times
+ this._drawTimes();
+ // Draw in the vertical - the days
+ this.invalidate();
+ };
+ /**
+ * Creates the DOM nodes for the times in the left column, and the horizontal
+ * lines (mostly via CSS) that span the whole date range.
+ */
+ et2_calendar_timegrid.prototype._drawTimes = function () {
+ jQuery('.calendar_calTimeRow', this.div).remove();
+ this.div.toggleClass('calendar_calTimeGridList', this.options.granularity === 0);
+ this.gridHeader
+ .attr('data-date', this.options.start_date)
+ .attr('data-owner', this.options.owner)
+ .append(this._labelContainer)
+ .append(this.owner.getDOMNode())
+ .append(this.dayHeader)
+ .appendTo(this.div);
+ // Max with 18 avoids problems when it's not shown
+ var header_height = Math.max(this.gridHeader.outerHeight(true), 18);
+ this.scrolling
+ .appendTo(this.div)
+ .off();
+ // No time grid - list
+ if (this.options.granularity === 0) {
+ this.scrolling.css('height', '100%');
+ this.days.css('height', '100%');
+ this.iterateOver(function (day) {
+ day.resize();
+ }, this, et2_calendar_daycol);
+ return;
+ }
+ var wd_start = 60 * this.options.day_start;
+ var wd_end = 60 * this.options.day_end;
+ var granularity = this.options.granularity;
+ var totalDisplayMinutes = wd_end - wd_start;
+ var rowsToDisplay = Math.ceil((totalDisplayMinutes + 60) / granularity);
+ var row_count = (1440 / this.options.granularity);
+ this.scrolling
+ .on('scroll', jQuery.proxy(this._scroll, this));
+ // Percent
+ var rowHeight = (100 / rowsToDisplay).toFixed(1);
+ // Pixels
+ this.rowHeight = this.scrolling.height() / rowsToDisplay;
+ // We need a reasonable bottom limit here, but resize will handle it
+ // if we get too small
+ if (this.rowHeight < 5 && this.div.is(':visible')) {
+ if (this.rowHeight === 0) {
+ // Something is not right...
+ this.rowHeight = 5;
+ }
+ }
+ // the hour rows
+ var show = {
+ 5: [0, 15, 30, 45],
+ 10: [0, 30],
+ 15: [0, 30],
+ 45: [0, 15, 30, 45]
+ };
+ var html = '';
+ var line_height = parseInt(this.div.css('line-height'));
+ this._top_time = 0;
+ for (var t = 0, i = 0; t < 1440; t += granularity, ++i) {
+ if (t <= wd_start && t + granularity > wd_start) {
+ this._top_time = this.rowHeight * (i + 1 + (wd_start - (t + granularity)) / granularity);
+ }
+ var working_hours = (t >= wd_start && t < wd_end) ? ' calendar_calWorkHours' : '';
+ html += '
';
+ // show time for full hours, always for 45min interval and at least on every 3 row
+ // @ts-ignore
+ var time = jQuery.datepicker.formatTime(egw.preference("timeformat") === "12" ? "h:mmtt" : "HH:mm", {
+ hour: t / 60,
+ minute: t % 60,
+ seconds: 0,
+ timezone: 0
+ }, { "ampm": (egw.preference("timeformat") === "12") });
+ var time_label = (typeof show[granularity] === 'undefined' ? t % 60 === 0 : show[granularity].indexOf(t % 60) !== -1) ? time : '';
+ if (time_label && egw.preference("timeformat") == "12" && time_label.split(':')[0] < 10) {
+ time_label = ' ' + time_label;
+ }
+ html += '
' + time_label + "
\n";
+ }
+ // Set heights in pixels for scrolling
+ jQuery('.calendar_calTimeLabels', this.scrolling)
+ .empty()
+ .height(this.rowHeight * i)
+ .append(html);
+ this.days.css('height', (this.rowHeight * i) + 'px');
+ this.gridHover.css('height', this.rowHeight);
+ // Scroll to start of day
+ this.scrolling.scrollTop(this._top_time);
+ };
+ /**
+ * 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.
+ *
+ * We use a timeout to avoid doing it multiple times if redrawing or resizing.
+ */
+ et2_calendar_timegrid.prototype.resizeTimes = function () {
+ // Hide resizing from user
+ this.loader.show();
+ // Wait a bit to see if anything else changes, then re-draw the times
+ if (this.resize_timer) {
+ window.clearTimeout(this.resize_timer);
+ }
+ // No point if it is just going to be redone completely
+ if (this.update_timer)
+ return;
+ this.resize_timer = window.setTimeout(jQuery.proxy(function () {
+ if (this._resizeTimes) {
+ this.resize_timer = null;
+ this._resizeTimes();
+ }
+ }, 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.
+ */
+ et2_calendar_timegrid.prototype._resizeTimes = function () {
+ if (!this.div.is(':visible')) {
+ return;
+ }
+ var wd_start = 60 * this.options.day_start;
+ var wd_end = 60 * this.options.day_end;
+ var totalDisplayMinutes = wd_end - wd_start;
+ var rowsToDisplay = Math.ceil((totalDisplayMinutes + 60) / this.options.granularity);
+ var row_count = (1440 / this.options.granularity);
+ var new_height = this.scrolling.height() / rowsToDisplay;
+ var old_height = this.rowHeight;
+ this.rowHeight = new_height;
+ jQuery('.calendar_calTimeLabels', this.scrolling).height(this.rowHeight * row_count);
+ this.days.css('height', this.options.granularity === 0 ?
+ '100%' :
+ (this.rowHeight * row_count) + 'px');
+ // Scroll to start of day
+ this._top_time = (wd_start * this.rowHeight) / this.options.granularity;
+ // For some reason on Chrome & FF this doesn't quite get the day start
+ // to the top, so add 2px;
+ this.scrolling.scrollTop(this._top_time + 2);
+ if (this.rowHeight != old_height) {
+ this.iterateOver(function (child) {
+ if (child === this)
+ return;
+ child.resize();
+ }, this, et2_IResizeable);
+ }
+ this.loader.hide();
+ };
+ /**
+ * Set up the needed day widgets to correctly display the selected date
+ * range. First we calculate the needed dates, then we create any needed
+ * widgets. Existing widgets are recycled rather than discarded.
+ */
+ et2_calendar_timegrid.prototype._drawDays = function () {
+ this.scrolling.append(this.days);
+ // If day list is still empty, recalculate it from start & end date
+ if (this.day_list.length === 0 && this.options.start_date && this.options.end_date) {
+ this.day_list = this._calculate_day_list(this.options.start_date, this.options.end_date, this.options.show_weekend);
+ }
+ // For a single day, we show each owner in their own daycol
+ this.daily_owner = this.day_list.length === 1 &&
+ this.options.owner.length > 1 &&
+ this.options.owner.length < (parseInt('' + egw.preference('day_consolidate', 'calendar')) || 6);
+ var daycols_needed = this.daily_owner ? this.options.owner.length : this.day_list.length;
+ var day_width = (Math.min(jQuery(this.getInstanceManager().DOMContainer).width(), this.days.width()) / daycols_needed);
+ if (!day_width || !this.day_list) {
+ // Hidden on another tab, or no days for some reason
+ var dim = egw.getHiddenDimensions(this.days, false);
+ day_width = (dim.w / Math.max(daycols_needed, 1));
+ }
+ // Create any needed widgets - otherwise, we'll just recycle
+ // Add any needed day widgets (now showing more days)
+ var add_index = 0;
+ var before = true;
+ while (daycols_needed > this.day_widgets.length) {
+ var existing_index = this.day_widgets[add_index] && !this.daily_owner ?
+ this.day_list.indexOf(this.day_widgets[add_index].options.date) :
+ -1;
+ before = existing_index > add_index;
+ var day = et2_createWidget('calendar-daycol', {
+ owner: this.options.owner,
+ width: (before ? 0 : day_width) + "px"
+ }, this);
+ if (this.isInTree()) {
+ day.doLoadingFinished();
+ }
+ if (existing_index != -1 && parseInt(this.day_list[add_index]) < parseInt(this.day_list[existing_index])) {
+ this.day_widgets.unshift(day);
+ jQuery(this.getDOMNode(day)).prepend(day.getDOMNode(day));
+ }
+ else {
+ this.day_widgets.push(day);
+ }
+ add_index++;
+ }
+ // Remove any extra day widgets (now showing less)
+ var delete_index = this.day_widgets.length - 1;
+ before = false;
+ while (this.day_widgets.length > daycols_needed) {
+ // If we're going down to an existing one, just keep it for cool CSS animation
+ while (delete_index > 1 && this.day_list.indexOf(this.day_widgets[delete_index].options.date) > -1) {
+ delete_index--;
+ before = true;
+ }
+ if (delete_index < 0)
+ delete_index = 0;
+ // Widgets that are before our date shrink, after just get pushed out
+ if (before) {
+ this.day_widgets[delete_index].set_width('0px');
+ }
+ this.day_widgets[delete_index].div.hide();
+ this.day_widgets[delete_index].header.hide();
+ this.day_widgets[delete_index].destroy();
+ this.day_widgets.splice(delete_index--, 1);
+ }
+ this.set_header_classes();
+ // Create / update day widgets with dates and data
+ for (var i = 0; i < this.day_widgets.length; i++) {
+ day = this.day_widgets[i];
+ // Position
+ day.set_left((day_width * i) + 'px');
+ day.title.removeClass('blue_title');
+ if (this.daily_owner) {
+ // Each 'day' is the same date, different user
+ day.set_id(this.day_list[0] + '-' + this.options.owner[i]);
+ day.set_date(this.day_list[0], false);
+ day.set_owner(this.options.owner[i]);
+ day.set_label(this._get_owner_name(this.options.owner[i]));
+ day.title.addClass('blue_title');
+ }
+ else {
+ // Show user name in day header even if only one
+ if (this.day_list.length === 1) {
+ day.set_label(this._get_owner_name(this.options.owner));
+ day.title.addClass('blue_title');
+ }
+ else {
+ // Go back to self-calculated date by clearing the label
+ day.set_label('');
+ }
+ day.set_id(this.day_list[i]);
+ day.set_date(this.day_list[i], this.value[this.day_list[i]] || false);
+ day.set_owner(this.options.owner);
+ }
+ day.set_width(day_width + 'px');
+ }
+ // Adjust and scroll to start of day
+ this.resizeTimes();
+ // Don't hold on to value any longer, use the data cache for best info
+ this.value = {};
+ if (this.daily_owner) {
+ this.set_label('');
+ }
+ // Handle not fully visible elements
+ this._scroll();
+ // TODO: Figure out how to do this with detached nodes
+ /*
+ var nodes = this.day_col.getDetachedNodes();
+ var supportedAttrs = [];
+ this.day_col.getDetachedAttributes(supportedAttrs);
+ supportedAttrs.push("id");
+
+ for(var i = 0; i < day_count; i++)
+ {
+ this.day_col.setDetachedAttributes(nodes.clone(),)
+ }
+ */
+ };
+ /**
+ * Set header classes
+ *
+ */
+ et2_calendar_timegrid.prototype.set_header_classes = function () {
+ var day;
+ for (var i = 0; i < this.day_widgets.length; i++) {
+ day = this.day_widgets[i];
+ // Classes
+ if (app.calendar && app.calendar.state &&
+ this.day_list[i] && parseInt(this.day_list[i].substr(4, 2)) !== new Date(app.calendar.state.date).getUTCMonth() + 1) {
+ day.set_class('calendar_differentMonth');
+ }
+ else {
+ day.set_class('');
+ }
+ }
+ };
+ /**
+ * Update UI while scrolling within the selected time
+ *
+ * Toggles out of view indicators and adjusts not visible headers
+ * @param {Event} event Scroll event
+ */
+ et2_calendar_timegrid.prototype._scroll = function (event) {
+ if (!this.day_widgets)
+ return;
+ // Loop through days, let them deal with it
+ for (var day = 0; day < this.day_widgets.length; day++) {
+ this.day_widgets[day]._out_of_view();
+ }
+ };
+ /**
+ * Calculate a list of days between start and end date, skipping weekends if
+ * desired.
+ *
+ * @param {Date|string} start_date Date that et2_date widget can understand
+ * @param {Date|string} end_date Date that et2_date widget can understand
+ * @param {boolean} show_weekend If not showing weekend, Saturday and Sunday
+ * will not be in the returned list.
+ *
+ * @returns {string[]} List of days in Ymd format
+ */
+ et2_calendar_timegrid.prototype._calculate_day_list = function (start_date, end_date, show_weekend) {
+ var day_list = [];
+ this.date_helper.set_value(end_date);
+ var end = this.date_helper.date.getTime();
+ var i = 1;
+ this.date_helper.set_value(new Date(start_date));
+ do {
+ if (show_weekend || !show_weekend && [0, 6].indexOf(this.date_helper.date.getUTCDay()) === -1 || end_date === start_date) {
+ day_list.push('' + this.date_helper.get_year() + sprintf('%02d', this.date_helper.get_month()) + sprintf('%02d', this.date_helper.get_date()));
+ }
+ this.date_helper.set_date(this.date_helper.get_date() + 1);
+ }
+ // Limit it to 14 days to avoid infinite loops in case something is mis-set,
+ // though the limit is more based on how wide the screen is
+ while (end >= this.date_helper.date.getTime() && i++ <= 14);
+ return day_list;
+ };
+ /**
+ * Link the actions to the DOM nodes / widget bits.
+ *
+ * @param {object} actions {ID: {attributes..}+} map of egw action information
+ */
+ et2_calendar_timegrid.prototype._link_actions = function (actions) {
+ // Get the parent? Might be a grid row, might not. Either way, it is
+ // just a container with no valid actions
+ var objectManager = egw_getObjectManager(this.getInstanceManager().app, true, 1);
+ objectManager = objectManager.getObjectById(this.getInstanceManager().uniqueId, 2) || objectManager;
+ var parent = objectManager.getObjectById(this.id, 1) || objectManager.getObjectById(this.getParent().id, 1) || objectManager;
+ if (!parent) {
+ debugger;
+ 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.
+ var widget_object = this._actionObject || parent.getObjectById(this.id);
+ var aoi = new et2_core_DOMWidget_1.et2_action_object_impl(this, this.getDOMNode(this)).getAOI();
+ for (var i = 0; i < parent.children.length; i++) {
+ var 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
+ var _invite_enabled = function (action, event, target) {
+ var event = event.iface.getWidget();
+ var timegrid = target.iface.getWidget() || false;
+ if (event === timegrid || !event || !timegrid ||
+ !event.options || !event.options.value.participants || !timegrid.options.owner) {
+ return false;
+ }
+ var owner_match = false;
+ var own_timegrid = event.getParent().getParent() === timegrid && !timegrid.daily_owner;
+ for (var id in event.options.value.participants) {
+ if (!timegrid.daily_owner) {
+ if (timegrid.options.owner === id ||
+ timegrid.options.owner.indexOf &&
+ timegrid.options.owner.indexOf(id) >= 0) {
+ owner_match = true;
+ }
+ }
+ else {
+ timegrid.iterateOver(function (col) {
+ // Check scroll section or header section
+ if (col.div.has(timegrid.gridHover).length || col.header.has(timegrid.gridHover).length) {
+ owner_match = owner_match || col.options.owner.indexOf(id) !== -1;
+ own_timegrid = (col === event.getParent());
+ }
+ }, this, et2_calendar_daycol);
+ }
+ }
+ var enabled = !owner_match &&
+ // Not inside its own timegrid
+ !own_timegrid;
+ 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') {
+ var dropEnd = false;
+ var helper = jQuery('.calendar_d-n-d_timeCounter', _data.ui.helper)[0];
+ if (helper && helper.dropEnd && helper.dropEnd.length >= 1)
+ if (typeof this.dropEnd !== 'undefined' && this.dropEnd.length >= 1) {
+ dropEnd = helper.dropEnd[0].dataset || false;
+ }
+ this.getWidget()._event_drop.call(jQuery('.calendar_d-n-d_timeCounter', _data.ui.helper)[0], this.getWidget(), event, _data.ui, dropEnd);
+ }
+ var drag_listener = function (_event, ui) {
+ aoi.getWidget()._drag_helper(jQuery('.calendar_d-n-d_timeCounter', ui.helper)[0], ui.helper[0], 0);
+ if (aoi.getWidget().daily_owner) {
+ _invite_enabled(widget_object.getActionLink('invite').actionObj, event, widget_object);
+ }
+ };
+ var 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' + widget_object.id, drag_listener);
+ _data.ui.draggable.on('dragend.et2_timegrid' + widget_object.id, function () {
+ _data.ui.draggable.off('drag.et2_timegrid' + widget_object.id);
+ });
+ // Remove formatting for out-of-view events (full day non-blocking)
+ jQuery('.calendar_calEventHeader', _data.ui.helper).css('top', '');
+ jQuery('.calendar_calEventBody', _data.ui.helper).css('padding-top', '');
+ // 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('
');
+ }
+ 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' + widget_object.id);
+ // Remove highlighted time square
+ var timegrid = aoi.getWidget();
+ timegrid.gridHover.hide();
+ timegrid.scrolling.scrollTop(timegrid._top_time);
+ // 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
+ var action_links = this._get_action_links(actions);
+ this._init_links_dnd(widget_object.manager, action_links);
+ widget_object.updateActionLinks(action_links);
+ };
+ /**
+ * Automatically add dnd support for linking
+ *
+ * @param {type} mgr
+ * @param {type} actionLinks
+ */
+ et2_calendar_timegrid.prototype._init_links_dnd = function (mgr, actionLinks) {
+ if (this.options.readonly)
+ return;
+ var self = this;
+ var drop_link = mgr.getActionById('egw_link_drop');
+ var drop_change_participant = mgr.getActionById('change_participant');
+ var drop_invite = mgr.getActionById('invite');
+ var drag_action = mgr.getActionById('egw_link_drag');
+ // Check if this app supports linking
+ if (!egw.link_get_registry(this.dataStorePrefix, 'query') ||
+ egw.link_get_registry(this.dataStorePrefix, 'title')) {
+ if (drop_link) {
+ drop_link.remove();
+ if (actionLinks.indexOf(drop_link.id) >= 0) {
+ actionLinks.splice(actionLinks.indexOf(drop_link.id), 1);
+ }
+ }
+ if (drag_action) {
+ drag_action.remove();
+ if (actionLinks.indexOf(drag_action.id) >= 0) {
+ actionLinks.splice(actionLinks.indexOf(drag_action.id), 1);
+ }
+ }
+ return;
+ }
+ // Don't re-add
+ if (drop_link == null) {
+ // Create the drop action that links entries
+ drop_link = mgr.addAction('drop', 'egw_link_drop', egw.lang('Create link'), egw.image('link'), function (action, source, target) {
+ // Extract link IDs
+ var links = [];
+ var id = '';
+ for (var i = 0; i < source.length; i++) {
+ // Check for no ID (invalid) or same manager (dragging an event)
+ if (!source[i].id)
+ continue;
+ if (source[i].manager === target.manager) {
+ // Find the timegrid, could have dropped on an event
+ var timegrid = target.iface.getWidget();
+ while (target.parent && timegrid.instanceOf && !timegrid.instanceOf(et2_calendar_timegrid)) {
+ target = target.parent;
+ timegrid = target.iface.getWidget();
+ }
+ if (timegrid && timegrid._drop_data) {
+ timegrid._event_drop.call(source[i].iface.getDOMNode(), timegrid, null, action.ui, timegrid._drop_data);
+ }
+ timegrid._drop_data = false;
+ // Ok, stop.
+ return false;
+ }
+ id = source[i].id.split('::');
+ links.push({ app: id[0] == 'filemanager' ? 'link' : id[0], id: id[1] });
+ }
+ if (links.length && target && target.iface.getWidget() && target.iface.getWidget().instanceOf(et2_calendar_event)) {
+ // Link the entries
+ egw.json(self.egw().getAppName() + ".etemplate_widget_link.ajax_link.etemplate", target.id.split('::').concat([links]), function (result) {
+ if (result) {
+ this.egw().message('Linked');
+ }
+ }, self, true, self).sendRequest();
+ }
+ else if (links.length) {
+ // Get date and time
+ var params = jQuery.extend({}, jQuery('.drop-hover[data-date]', target.iface.getDOMNode())[0].dataset || {});
+ // Add link IDs
+ var app_registry = egw.link_get_registry('calendar');
+ params[app_registry.add_app] = [];
+ params[app_registry.add_id] = [];
+ for (var n in links) {
+ params[app_registry.add_app].push(links[n].app);
+ params[app_registry.add_id].push(links[n].id);
+ }
+ app.calendar.add(params);
+ }
+ }, true);
+ drop_link.acceptedTypes = ['default', 'link'];
+ drop_link.hideOnDisabled = true;
+ // Create the drop action for moving events between calendars
+ var invite_action = function (action, source, target) {
+ // Extract link IDs
+ var links = [];
+ var id = '';
+ for (var i = 0; i < source.length; i++) {
+ // Check for no ID (invalid) or same manager (dragging an event)
+ if (!source[i].id)
+ continue;
+ if (source[i].manager === target.manager) {
+ // Find the timegrid, could have dropped on an event
+ var timegrid = target.iface.getWidget();
+ while (target.parent && timegrid.instanceOf && !timegrid.instanceOf(et2_calendar_timegrid)) {
+ target = target.parent;
+ timegrid = target.iface.getWidget();
+ }
+ // Leave the helper there until the update is done
+ var loading = action.ui.helper.clone(true).appendTo(jQuery('body'));
+ // and add a loading icon so user knows something is happening
+ if (jQuery('.calendar_timeDemo', loading).length == 0) {
+ jQuery('.calendar_calEventHeader', loading).addClass('loading');
+ }
+ else {
+ jQuery('.calendar_timeDemo', loading).after('
');
+ }
+ var event_data = egw.dataGetUIDdata(source[i].id).data;
+ et2_calendar_event.recur_prompt(event_data, function (button_id) {
+ if (button_id === 'cancel' || !button_id) {
+ return;
+ }
+ var add_owner = jQuery.extend([], timegrid.options.owner);
+ if (timegrid.daily_owner) {
+ timegrid.iterateOver(function (col) {
+ if (col.div.has(timegrid.gridHover).length || col.header.has(timegrid.gridHover).length) {
+ add_owner = col.options.owner;
+ }
+ }, this, et2_calendar_daycol);
+ }
+ egw().json('calendar.calendar_uiforms.ajax_invite', [
+ button_id === 'series' ? event_data.id : event_data.app_id,
+ add_owner,
+ action.id === 'change_participant' ?
+ jQuery.extend([], source[i].iface.getWidget().getParent().options.owner) :
+ []
+ ], function () { loading.remove(); }).sendRequest(true);
+ });
+ // Ok, stop.
+ return false;
+ }
+ }
+ };
+ drop_change_participant = mgr.addAction('drop', 'change_participant', egw.lang('Move to'), egw.image('participant'), invite_action, true);
+ drop_change_participant.acceptedTypes = ['calendar'];
+ drop_change_participant.hideOnDisabled = true;
+ drop_invite = mgr.addAction('drop', 'invite', egw.lang('Invite'), egw.image('participant'), invite_action, true);
+ drop_invite.acceptedTypes = ['calendar'];
+ drop_invite.hideOnDisabled = true;
+ }
+ if (actionLinks.indexOf(drop_link.id) < 0) {
+ actionLinks.push(drop_link.id);
+ }
+ actionLinks.push(drop_invite.id);
+ actionLinks.push(drop_change_participant.id);
+ // Don't re-add
+ if (drag_action == null) {
+ // Create drag action that allows linking
+ drag_action = mgr.addAction('drag', 'egw_link_drag', egw.lang('link'), 'link', function (action, selected) {
+ // Drag helper - list titles.
+ // As we wanted to have a general defaul helper interface, we return null here and not using customize helper for links
+ // TODO: Need to decide if we need to create a customized helper interface for links anyway
+ //return helper;
+ return null;
+ }, true);
+ }
+ // The timegrid itself is not draggable, so don't add a link.
+ // The action is there for the children (events) to use
+ if (false && actionLinks.indexOf(drag_action.id) < 0) {
+ actionLinks.push(drag_action.id);
+ }
+ drag_action.set_dragType(['link', 'calendar']);
+ };
+ /**
+ * 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}
+ */
+ et2_calendar_timegrid.prototype._get_action_links = function (actions) {
+ var action_links = [];
+ // TODO: determine which actions are allowed without an action (empty actions)
+ for (var i in actions) {
+ var action = actions[i];
+ if (action.type == 'drop') {
+ action_links.push(typeof action.id != 'undefined' ? action.id : i);
+ }
+ }
+ return action_links;
+ };
+ /**
+ * Provide specific data to be displayed.
+ * 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:
+ * {
+ * 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.
+ */
+ et2_calendar_timegrid.prototype.set_value = function (events) {
+ if (typeof events !== 'object')
+ return false;
+ var use_days_sent = true;
+ if (events.start_date) {
+ use_days_sent = false;
+ }
+ if (events.end_date) {
+ use_days_sent = false;
+ }
+ _super.prototype.set_value.call(this, events);
+ if (use_days_sent) {
+ var day_list = Object.keys(events);
+ if (day_list.length) {
+ this.set_start_date(day_list[0]);
+ this.set_end_date(day_list[day_list.length - 1]);
+ }
+ // Sub widgets actually get their own data from egw.data, so we'll
+ // stick it there
+ var consolidated = et2_widget_view_1.et2_calendar_view.is_consolidated(this.options.owner, this.day_list.length == 1 ? 'day' : 'week');
+ for (var day in events) {
+ var day_list_1 = [];
+ for (var i = 0; i < events[day].length; i++) {
+ day_list_1.push(events[day][i].row_id);
+ egw.dataStoreUID('calendar::' + events[day][i].row_id, events[day][i]);
+ }
+ // Might be split by user, so we have to check that too
+ for (var i = 0; i < this.options.owner.length; i++) {
+ var owner = consolidated ? this.options.owner : this.options.owner[i];
+ var day_id = CalendarApp._daywise_cache_id(day, owner);
+ egw.dataStoreUID(day_id, day_list_1);
+ if (consolidated)
+ break;
+ }
+ }
+ }
+ // Reset and calculate instead of just use the keys so we can get the weekend preference
+ this.day_list = [];
+ // None of the above changed anything, hide the loader
+ if (!this.update_timer) {
+ window.setTimeout(jQuery.proxy(function () { this.loader.hide(); }, this), 200);
+ }
+ };
+ /**
+ * Set which user owns this. Owner is passed along to the individual
+ * days.
+ *
+ * @param {number|number[]} _owner Account ID
+ * @returns {undefined}
+ */
+ et2_calendar_timegrid.prototype.set_owner = function (_owner) {
+ var old = this.options.owner || 0;
+ _super.prototype.set_owner.call(this, _owner);
+ this.owner.set_label('');
+ this.div.removeClass('calendar_TimeGridNoLabel');
+ // Check to see if it's our own calendar, with just us showing
+ if (typeof _owner == 'object' && _owner.length == 1) {
+ var rowCount = 0;
+ this.getParent().iterateOver(function (widget) {
+ if (!widget.disabled)
+ rowCount++;
+ }, this, et2_calendar_timegrid);
+ // Just us, show week number
+ if (rowCount == 1 && _owner.length == 1 && _owner[0] == egw.user('account_id') || rowCount != 1)
+ _owner = false;
+ }
+ var day_count = this.day_list.length ? this.day_list.length :
+ this._calculate_day_list(this.options.start_date, this.options.end_date, this.options.show_weekend).length;
+ // @ts-ignore
+ if (typeof _owner == 'string' && isNaN(_owner)) {
+ this.set_label('');
+ this.owner.set_value(this._get_owner_name(_owner));
+ // Label is empty, but give extra space for the owner name
+ this.div.removeClass('calendar_TimeGridNoLabel');
+ }
+ else if (!_owner || typeof _owner == 'object' && _owner.length > 1 ||
+ // Single owner, single day
+ _owner.length === 1 && day_count === 1) {
+ // Don't show owners if more than one, show week number
+ this.owner.set_value('');
+ if (this.options.start_date) {
+ this.set_label(egw.lang('wk') + ' ' +
+ (app.calendar ? app.calendar.date.week_number(this.options.start_date) : ''));
+ }
+ }
+ else {
+ this.owner.options.application = 'api-accounts';
+ this.owner.set_value(this._get_owner_name(_owner));
+ this.set_label('');
+ jQuery(this.getDOMNode(this.owner)).prepend(this.owner.getDOMNode());
+ }
+ 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);
+ }
+ };
+ /**
+ * Set a label for this week
+ *
+ * May conflict with owner, which is displayed when there's only one owner.
+ *
+ * @param {string} label
+ */
+ et2_calendar_timegrid.prototype.set_label = function (label) {
+ this.options.label = label;
+ this._labelContainer.html(label);
+ this.gridHeader.prepend(this._labelContainer);
+ // If it's a short label (eg week number), don't give it an extra line
+ // but is empty, but give extra space for a single owner name
+ this.div.toggleClass('calendar_TimeGridNoLabel', label.trim().length > 0 && label.trim().length <= 6 ||
+ this.options.owner.length > 1);
+ };
+ /**
+ * 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
+ */
+ et2_calendar_timegrid.prototype.set_granularity = function (minutes) {
+ // Avoid < 0
+ minutes = Math.max(0, minutes);
+ if (this.options.granularity !== minutes) {
+ if (this.options.granularity === 0 || minutes === 0) {
+ this.options.granularity = minutes;
+ // Need to re-do a bunch to make sure this is propagated
+ this.invalidate();
+ }
+ else {
+ this.options.granularity = minutes;
+ this._drawTimes();
+ }
+ }
+ else if (!this.update_timer) {
+ this.resizeTimes();
+ }
+ };
+ /**
+ * Turn on or off the visibility of weekends
+ *
+ * @param {boolean} weekends
+ */
+ et2_calendar_timegrid.prototype.set_show_weekend = function (weekends) {
+ weekends = weekends ? true : false;
+ if (this.options.show_weekend !== weekends) {
+ this.options.show_weekend = weekends;
+ if (this.isAttached()) {
+ this.invalidate();
+ }
+ }
+ };
+ /**
+ * Call change handler, if set
+ */
+ et2_calendar_timegrid.prototype.change = function () {
+ if (this.onchange) {
+ if (typeof this.onchange == 'function') {
+ // Make sure function gets a reference to the widget
+ var args = Array.prototype.slice.call(arguments);
+ if (args.indexOf(this) == -1)
+ args.push(this);
+ return this.onchange.apply(this, args);
+ }
+ else {
+ return (et2_compileLegacyJS(this.options.onchange, this, _node))();
+ }
+ }
+ };
+ /**
+ * Call event change handler, if set
+ *
+ * @param {type} event
+ * @param {type} dom_node
+ */
+ et2_calendar_timegrid.prototype.event_change = function (event, dom_node) {
+ if (this.onevent_change) {
+ var event_data = this._get_event_info(dom_node);
+ var event_widget = this.getWidgetById(event_data.widget_id);
+ et2_calendar_event.recur_prompt(event_data, jQuery.proxy(function (button_id, event_data) {
+ // No need to continue
+ if (button_id === 'cancel')
+ return false;
+ if (typeof this.onevent_change == 'function') {
+ // Make sure function gets a reference to the widget
+ var args = Array.prototype.slice.call(arguments);
+ if (args.indexOf(event_widget) == -1)
+ args.push(event_widget);
+ // Put button ID in event
+ event.button_id = button_id;
+ return this.onevent_change.apply(this, [event, event_widget, button_id]);
+ }
+ else {
+ return (et2_compileLegacyJS(this.options.onevent_change, event_widget, dom_node))();
+ }
+ }, this));
+ }
+ return false;
+ };
+ et2_calendar_timegrid.prototype.get_granularity = function () {
+ // get option, or user's preference
+ if (typeof this.options.granularity === 'undefined') {
+ this.options.granularity = egw.preference('interval', 'calendar') || 30;
+ }
+ return parseInt(this.options.granularity);
+ };
+ /**
+ * Click handler calling custom handler set via onclick attribute to this.onclick
+ *
+ * This also handles all its own actions, including navigation. If there is
+ * an event associated with the click, it will be found and passed to the
+ * onclick function.
+ *
+ * @param {Event} _ev
+ * @returns {boolean} Continue processing event (true) or stop (false)
+ */
+ et2_calendar_timegrid.prototype.click = function (_ev) {
+ var result = true;
+ if (this.options.readonly)
+ return;
+ // Drag to create in progress
+ if (this.drag_create.start !== null)
+ return;
+ // Is this click in the event stuff, or in the header?
+ if (_ev.target.dataset.id || jQuery(_ev.target).parents('.calendar_calEvent').length) {
+ // Event came from inside, maybe a calendar event
+ var event = this._get_event_info(_ev.originalEvent.target);
+ if (typeof this.onclick == 'function') {
+ // Make sure function gets a reference to the widget, splice it in as 2. argument if not
+ var args = Array.prototype.slice.call(arguments);
+ if (args.indexOf(this) == -1)
+ args.splice(1, 0, this);
+ result = this.onclick.apply(this, args);
+ }
+ var event_node = jQuery(event.event_node);
+ if (event.id && result && !this.disabled && !this.options.readonly &&
+ // Permissions - opening will fail if we try
+ event_node && !(event_node.hasClass('rowNoView'))) {
+ if (event.widget_id && this.getWidgetById(event.widget_id)) {
+ this.getWidgetById(event.widget_id).recur_prompt();
+ }
+ else {
+ et2_calendar_event.recur_prompt(event);
+ }
+ return false;
+ }
+ return result;
+ }
+ else if (this.gridHeader.is(_ev.target) && _ev.target.dataset ||
+ this._labelContainer.is(_ev.target) && this.gridHeader[0].dataset) {
+ app.calendar.update_state(jQuery.extend({ view: 'week' }, this._labelContainer.is(_ev.target) ?
+ this.gridHeader[0].dataset :
+ _ev.target.dataset));
+ }
+ else if (this.options.owner.length === 1 && jQuery(this.owner.getDOMNode()).is(_ev.target)) {
+ // Click on the owner in header, show just that owner
+ app.calendar.update_state({ owner: this.options.owner });
+ }
+ else if (this.dayHeader.has(_ev.target).length) {
+ // Click on a day header - let day deal with it
+ // First child is a selectAccount
+ for (var i = 1; i < this._children.length; i++) {
+ if (this._children[i].header && (this._children[i].header.has(_ev.target).length || this._children[i].header.is(_ev.target))) {
+ return this._children[i].click(_ev);
+ }
+ }
+ }
+ // No time grid, click on a day
+ else if (this.options.granularity === 0 &&
+ (jQuery(_ev.target).hasClass('event_wrapper') || jQuery(_ev.target).hasClass('.calendar_calDayCol'))) {
+ // Default handler to open a new event at the selected time
+ var target = jQuery(_ev.target).hasClass('event_wrapper') ? _ev.target.parentNode : _ev.target;
+ var options = {
+ date: target.dataset.date || this.options.date,
+ hour: target.dataset.hour || this._parent.options.day_start,
+ minute: target.dataset.minute || 0,
+ owner: this.options.owner
+ };
+ app.calendar.add(options);
+ return false;
+ }
+ };
+ /**
+ * Mousedown handler to support drag to create
+ *
+ * @param {jQuery.Event} event
+ */
+ et2_calendar_timegrid.prototype._mouse_down = function (event) {
+ if (event.which !== 1)
+ return;
+ if (this.options.readonly)
+ return;
+ var start = jQuery.extend({}, this.gridHover[0].dataset);
+ if (start.date) {
+ // Set parent for event
+ if (this.daily_owner) {
+ // Each 'day' is the same date, different user
+ // Find the correct row so we know the parent
+ var col = event.target.closest('.calendar_calDayCol');
+ for (var i = 0; i < this._children.length && col; i++) {
+ if (this._children[i].node === col) {
+ this.drag_create.parent = this._children[i];
+ break;
+ }
+ }
+ }
+ else {
+ this.drag_create.parent = this.getWidgetById(start.date);
+ }
+ // Format date
+ this.date_helper.set_year(start.date.substring(0, 4));
+ this.date_helper.set_month(start.date.substring(4, 6));
+ this.date_helper.set_date(start.date.substring(6, 8));
+ if (start.hour) {
+ this.date_helper.set_hours(start.hour);
+ }
+ if (start.minute) {
+ this.date_helper.set_minutes(start.minute);
+ }
+ start.date = this.date_helper.get_value();
+ this.gridHover.css('cursor', 'ns-resize');
+ // Start update
+ var timegrid = this;
+ this.div.on('mousemove.dragcreate', function () {
+ if (timegrid.drag_create.event && timegrid.drag_create.parent && timegrid.drag_create.end) {
+ var end = jQuery.extend({}, timegrid.gridHover[0].dataset);
+ if (end.date) {
+ timegrid.date_helper.set_year(end.date.substring(0, 4));
+ timegrid.date_helper.set_month(end.date.substring(4, 6));
+ timegrid.date_helper.set_date(end.date.substring(6, 8));
+ if (end.hour) {
+ timegrid.date_helper.set_hours(end.hour);
+ }
+ if (end.minute) {
+ timegrid.date_helper.set_minutes(end.minute);
+ }
+ timegrid.drag_create.end.date = timegrid.date_helper.get_value();
+ }
+ try {
+ timegrid._drag_update_event();
+ }
+ catch (e) {
+ timegrid._drag_create_end();
+ }
+ }
+ });
+ }
+ return this._drag_create_start(start);
+ };
+ /**
+ * Mouseup handler to support drag to create
+ *
+ * @param {jQuery.Event} event
+ */
+ et2_calendar_timegrid.prototype._mouse_up = function (event) {
+ if (this.options.readonly)
+ return;
+ var end = jQuery.extend({}, this.gridHover[0].dataset);
+ if (end.date) {
+ this.date_helper.set_year(end.date.substring(0, 4));
+ this.date_helper.set_month(end.date.substring(4, 6));
+ this.date_helper.set_date(end.date.substring(6, 8));
+ if (end.hour) {
+ this.date_helper.set_hours(end.hour);
+ }
+ if (end.minute) {
+ this.date_helper.set_minutes(end.minute);
+ }
+ end.date = this.date_helper.get_value();
+ }
+ this.div.off('mousemove.dragcreate');
+ this.gridHover.css('cursor', '');
+ return this._drag_create_end(end);
+ };
+ /**
+ * 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} y
+ * @returns {DOMNode[]} time node(s) for the given position
+ */
+ et2_calendar_timegrid.prototype._get_time_from_position = function (x, y) {
+ x = Math.round(x);
+ y = Math.round(y);
+ var path = [];
+ var day = null;
+ var time = null;
+ var node = document.elementFromPoint(x, y);
+ var $node = jQuery(node);
+ // Ignore high level & non-time (grid itself, header parent & week label)
+ if ([this.node, this.gridHeader[0], this._labelContainer[0]].indexOf(node) !== -1 ||
+ // Day labels
+ this.gridHeader.has(node).length && !$node.hasClass("calendar_calDayColAllDay") && !$node.hasClass('calendar_calDayColHeader')) {
+ return [];
+ }
+ for (var id in this.gridHover[0].dataset) {
+ delete this.gridHover[0].dataset[id];
+ }
+ if (this.options.granularity == 0) {
+ this.gridHover.css('height', '');
+ }
+ while (node && node != this.node && node.tagName != 'BODY' && path.length < 10) {
+ path.push(node);
+ node.style.display = 'none';
+ $node = jQuery(node);
+ if ($node.hasClass('calendar_calDayColHeader')) {
+ for (var id in node.dataset) {
+ this.gridHover[0].dataset[id] = node.dataset[id];
+ }
+ this.gridHover.css({
+ position: 'absolute',
+ top: '',
+ bottom: '0px',
+ // Use 100% height if we're hiding the day labels to avoid
+ // any remaining space from the hidden labels
+ height: $node.height() > parseInt($node.css('line-height')) ?
+ $node.css('padding-bottom') : '100%'
+ });
+ day = node;
+ this.gridHover
+ .attr('data-non_blocking', 'true');
+ break;
+ }
+ if ($node.hasClass('calendar_calDayCol')) {
+ day = node;
+ this.gridHover
+ .attr('data-date', day.dataset.date);
+ }
+ if ($node.hasClass('calendar_calTimeRowTime')) {
+ time = node;
+ this.gridHover
+ .attr('data-hour', time.dataset.hour)
+ .attr('data-minute', time.dataset.minute);
+ break;
+ }
+ node = document.elementFromPoint(x, y);
+ }
+ for (var i = 0; i < path.length; i++) {
+ path[i].style.display = '';
+ }
+ if (!day) {
+ return [];
+ }
+ this.gridHover
+ .show()
+ .appendTo(day);
+ if (time) {
+ this.gridHover
+ .height(this.rowHeight)
+ .position({ my: 'left top', at: 'left top', of: time });
+ }
+ this.gridHover.css('left', '');
+ return this.gridHover;
+ };
+ /**
+ * Code for implementing et2_IDetachedDOM
+ *
+ * @param {array} _attrs array to add further attributes to
+ */
+ et2_calendar_timegrid.prototype.getDetachedAttributes = function (_attrs) {
+ _attrs.push('start_date', 'end_date');
+ };
+ et2_calendar_timegrid.prototype.getDetachedNodes = function () {
+ return [this.getDOMNode(this)];
+ };
+ et2_calendar_timegrid.prototype.setDetachedAttributes = function (_nodes, _values) {
+ this.div = jQuery(_nodes[0]);
+ if (_values.start_date) {
+ this.set_start_date(_values.start_date);
+ }
+ if (_values.end_date) {
+ this.set_end_date(_values.end_date);
+ }
+ };
+ // Resizable interface
+ /**
+ * @param {boolean} [_too_small=null] Force the widget to act as if it was too small
+ */
+ et2_calendar_timegrid.prototype.resize = function (_too_small) {
+ if (this.disabled || !this.div.is(':visible')) {
+ return;
+ }
+ /*
+ We expect the timegrid to be in a table with 0 or more other timegrids,
+ 1 per row. We want each timegrid to be as large as possible, but space
+ shared equally. Height can't be set to a percentage on the rows, because
+ that doesn't work. However, if any timegrid is too small (1/2 hour < 1 line
+ height), we change to showing only the working hours with no vertical
+ scrollbar. Each week gets as much space as it needs, and all scroll together.
+ */
+ // How many rows?
+ var rowCount = 0;
+ this.getParent().iterateOver(function (widget) {
+ if (!widget.disabled)
+ rowCount++;
+ }, this, et2_calendar_timegrid);
+ // Take the whole tab height, or home portlet
+ if (this.getInstanceManager().app === 'home') {
+ var height = jQuery(this.getParent().getDOMNode(this)).parentsUntil('.et2_portlet').last().parent().innerHeight();
+ // Allow for portlet header
+ height -= jQuery('.ui-widget-header', this.div.parents('.egw_fw_ui_tab_content')).outerHeight(true);
+ }
+ else {
+ var height = jQuery(this.getInstanceManager().DOMContainer).parent().innerHeight();
+ // Allow for toolbar
+ height -= jQuery('#calendar-toolbar', this.div.parents('.egw_fw_ui_tab_content')).outerHeight(true);
+ }
+ this.options.height = Math.floor(height / rowCount);
+ // Allow for borders & padding
+ this.options.height -= 2 * ((this.div.outerWidth(true) - this.div.innerWidth()) + parseInt(this.div.parent().css('padding-top')));
+ // Calculate how much space is needed, and
+ // if too small be bigger
+ var needed = ((this.day_end - this.day_start) /
+ (this.options.granularity / 60) * parseInt(this.div.css('line-height'))) +
+ this.gridHeader.outerHeight();
+ var too_small = needed > this.options.height && this.options.granularity != 0;
+ if (this.getInstanceManager().app === 'home') {
+ var modify_node = jQuery(this.getParent().getDOMNode(this)).parentsUntil('.et2_portlet').last();
+ }
+ else {
+ var modify_node = jQuery(this.getInstanceManager().DOMContainer);
+ }
+ modify_node
+ .css({
+ 'overflow-y': too_small || _too_small ? 'auto' : 'hidden',
+ 'overflow-x': 'hidden',
+ 'height': too_small || _too_small ? height : '100%'
+ });
+ if (too_small || _too_small) {
+ this.options.height = Math.max(this.options.height, needed);
+ // Set all others to match
+ if (!_too_small && rowCount > 1 && this.getParent()) {
+ window.setTimeout(jQuery.proxy(function () {
+ if (!this._parent)
+ return;
+ this._parent.iterateOver(function (widget) {
+ if (!widget.disabled)
+ widget.resize(true);
+ }, this, et2_calendar_timegrid);
+ }, this), 1);
+ return;
+ }
+ this.div.addClass('calendar_calTimeGridFixed');
+ }
+ else {
+ this.div.removeClass('calendar_calTimeGridFixed');
+ }
+ this.div.css('height', this.options.height);
+ // Re-do time grid
+ if (!this.update_timer) {
+ this.resizeTimes();
+ }
+ // Try to resize width, though animations cause problems
+ var total_width = modify_node.parent().innerWidth() - this.days.position().left;
+ // Space for todos, if there
+ total_width -= jQuery(this.getInstanceManager().DOMContainer).siblings().has(':visible').not('#calendar-toolbar').outerWidth();
+ var day_width = (total_width > 0 ? total_width : modify_node.width()) / this.day_widgets.length;
+ // update day widgets
+ for (var i = 0; i < this.day_widgets.length; i++) {
+ var day = this.day_widgets[i];
+ // Position
+ day.set_left((day_width * i) + 'px');
+ day.set_width(day_width + 'px');
+ }
+ };
+ /**
+ * Set up for printing
+ *
+ * @return {undefined|Deferred} Return a jQuery Deferred object if not done setting up
+ * (waiting for data)
+ */
+ et2_calendar_timegrid.prototype.beforePrint = function () {
+ if (this.disabled || !this.div.is(':visible')) {
+ return;
+ }
+ var height_check = this.div.height();
+ this.div.css('max-height', '17cm');
+ if (this.div.height() != height_check) {
+ this.div.height('17cm');
+ this._resizeTimes();
+ }
+ // update day widgets, if not on single day view
+ //
+ // TODO: Find out why don't we update single day view
+ // Let the single day view participate in print calculation.
+ if (this.day_widgets.length > 0) {
+ var day_width = (100 / this.day_widgets.length);
+ for (var i = 0; i < this.day_widgets.length; i++) {
+ var day = this.day_widgets[i];
+ // Position
+ day.set_left((i * day_width) + '%');
+ day.set_width(day_width + '%');
+ // For some reason the column's method does not set it correctly in Chrome
+ day.header[0].style.width = day_width + '%';
+ }
+ }
+ // Stop Firefox from scrolling the day to the top - this would break printing in Chrome
+ if (navigator.userAgent.match(/(firefox|safari|iceweasel)/i) && !navigator.userAgent.match(/chrome/i)) {
+ var height = this.scrolling.scrollTop() + this.scrolling.height();
+ this.scrolling
+ // Disable scroll event, or it will recalculate out of view events
+ .off('scroll')
+ // Explicitly transform to the correct place
+ .css({
+ 'transform': 'translateY(-' + this.scrolling.scrollTop() + 'px)',
+ 'margin-bottom': '-' + this.scrolling.scrollTop() + 'px',
+ 'height': height + 'px'
+ });
+ this.div.css({ 'height': '', 'max-height': '' });
+ }
+ };
+ /**
+ * Reset after printing
+ */
+ et2_calendar_timegrid.prototype.afterPrint = function () {
+ this.div.css('maxHeight', '');
+ this.scrolling.children().css({ 'transform': '', 'overflow': '' });
+ this.div.height(this.options.height);
+ if (navigator.userAgent.match(/(firefox|safari|iceweasel)/i) && !navigator.userAgent.match(/chrome/i)) {
+ this._resizeTimes();
+ this.scrolling
+ // Re-enable out-of-view formatting on scroll
+ .on('scroll', jQuery.proxy(this._scroll, this))
+ // Remove translation
+ .css({ 'transform': '', 'margin-bottom': '' });
+ }
+ };
+ et2_calendar_timegrid._attributes = {
+ value: {
+ type: "any",
+ description: "An array of events, indexed by date (Ymd format)."
+ },
+ day_start: {
+ name: "Day start time",
+ type: "string",
+ default: parseInt('' + egw.preference('workdaystarts', 'calendar')) || 9,
+ description: "Work day start time. If unset, this will default to the current user's preference"
+ },
+ day_end: {
+ name: "Day end time",
+ type: "string",
+ default: parseInt('' + egw.preference('workdayends', 'calendar')) || 17,
+ description: "Work day end time. If unset, this will default to the current user's preference"
+ },
+ show_weekend: {
+ name: "Weekends",
+ type: "boolean",
+ // @ts-ignore
+ default: egw.preference('days_in_weekview', 'calendar') != 5,
+ description: "Display weekends. The date range should still include them for proper scrolling, but they just won't be shown."
+ },
+ granularity: {
+ name: "Granularity",
+ type: "integer",
+ default: parseInt('' + egw.preference('interval', 'calendar')) || 30,
+ description: "How many minutes per row, or 0 to display events as a list"
+ },
+ "onchange": {
+ "name": "onchange",
+ "type": "js",
+ "default": et2_no_init,
+ "description": "JS code which is executed when the date range changes."
+ },
+ "onevent_change": {
+ "name": "onevent_change",
+ "type": "js",
+ "default": et2_no_init,
+ "description": "JS code which is executed when an event changes."
+ },
+ height: {
+ "default": '100%'
+ }
+ };
+ return et2_calendar_timegrid;
+}(et2_widget_view_1.et2_calendar_view));
+exports.et2_calendar_timegrid = et2_calendar_timegrid;
+et2_core_widget_1.et2_register_widget(et2_calendar_timegrid, ["calendar-timegrid"]);
+//# sourceMappingURL=et2_widget_timegrid.js.map
\ No newline at end of file
diff --git a/calendar/js/et2_widget_view.js b/calendar/js/et2_widget_view.js
index da92bc6739..f199c6635d 100644
--- a/calendar/js/et2_widget_view.js
+++ b/calendar/js/et2_widget_view.js
@@ -1,3 +1,4 @@
+"use strict";
/*
* Egroupware
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
@@ -7,11 +8,22 @@
* @author Nathan Gray
* @version $Id$
*/
-
-/*egw:uses
- /etemplate/js/et2_core_valueWidget;
-*/
-
+var __extends = (this && this.__extends) || (function () {
+ var extendStatics = function (d, b) {
+ extendStatics = Object.setPrototypeOf ||
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+ return extendStatics(d, b);
+ };
+ return function (d, b) {
+ extendStatics(d, b);
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+ };
+})();
+Object.defineProperty(exports, "__esModule", { value: true });
+var et2_core_valueWidget_1 = require("../../api/js/etemplate/et2_core_valueWidget");
+var et2_core_inheritance_1 = require("../../api/js/etemplate/et2_core_inheritance");
/**
* Parent class for the various calendar views to reduce copied code
*
@@ -21,660 +33,546 @@
*
* @augments et2_valueWidget
*/
-var et2_calendar_view = (function(){ "use strict"; return et2_valueWidget.extend(
-{
- createNamespace: true,
-
- attributes: {
- 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"
- }
- },
-
- /**
- * Constructor
- *
- * @memberOf et2_calendar_view
- * @constructor
- */
- init: function init() {
- this._super.apply(this, arguments);
-
- // Used for its date calculations
- this.date_helper = et2_createWidget('date-time',{},null);
- this.date_helper.loadingFinished();
-
- this.loader = jQuery('
');
- 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: function destroy() {
- this._super.apply(this, arguments);
-
- // 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: function() {
- this._super.apply(this, arguments);
- this.loader.hide(0).prependTo(this.div);
- if(this.options.owner) this.set_owner(this.options.owner);
- },
-
- /**
- * 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: function 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: function get_start_date() {
- return new Date(this.options.start_date);
- },
-
- /**
- * Returns the current start date
- *
- * @returns {Date}
- *
- * @memberOf et2_calendar_view
- */
- get_end_date: function 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: function 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: function 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: function 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: function 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);
- }
- },
-
- /**
- * 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: function _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 = 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
- var 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: function _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: function(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: function()
- {
- 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: function()
- {
- 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: function(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;
- }
-
-});}).call(this);
-
-// Static class stuff
-jQuery.extend(et2_calendar_view,
-{
- /**
- * 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.
- */
- is_consolidated: function 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
- */
- owner_name_cache: {},
-
- 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
- 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;
- }
- }
-});
+var et2_calendar_view = /** @class */ (function (_super) {
+ __extends(et2_calendar_view, _super);
+ /**
+ * Constructor
+ *
+ */
+ function et2_calendar_view(_parent, _attrs, _child) {
+ var _this =
+ // Call the inherited constructor
+ _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_calendar_view._attributes, _child || {})) || this;
+ _this.dataStorePrefix = 'calendar';
+ _this.update_timer = null;
+ // Used for its date calculations
+ _this._date_helper = et2_createWidget('date-time', {}, null);
+ _this._date_helper.loadingFinished();
+ _this.loader = jQuery('
');
+ _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
+ };
+ return _this;
+ }
+ et2_calendar_view.prototype.destroy = function () {
+ _super.prototype.destroy.call(this);
+ // 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);
+ }
+ };
+ et2_calendar_view.prototype.doLoadingFinished = function () {
+ _super.prototype.doLoadingFinished.call(this);
+ 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
+ */
+ et2_calendar_view.prototype.invalidate = function (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
+ */
+ et2_calendar_view.prototype.get_start_date = function () {
+ return new Date(this.options.start_date);
+ };
+ /**
+ * Returns the current start date
+ *
+ * @returns {Date}
+ *
+ * @memberOf et2_calendar_view
+ */
+ et2_calendar_view.prototype.get_end_date = function () {
+ 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
+ */
+ et2_calendar_view.prototype.set_start_date = function (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
+ */
+ et2_calendar_view.prototype.set_end_date = function (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
+ */
+ et2_calendar_view.prototype.set_owner = function (_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.
+ */
+ et2_calendar_view.prototype.set_value = function (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);
+ }
+ };
+ Object.defineProperty(et2_calendar_view.prototype, "date_helper", {
+ get: function () {
+ return this._date_helper;
+ },
+ enumerable: true,
+ configurable: true
+ });
+ et2_calendar_view.prototype._createNamespace = function () {
+ 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
+ */
+ et2_calendar_view.prototype._get_owner_name = function (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 = 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
+ var 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}
+ */
+ et2_calendar_view.prototype._get_event_info = function (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)
+ */
+ et2_calendar_view.prototype._drag_create_start = function (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
+ */
+ et2_calendar_view.prototype._drag_create_event = function () {
+ 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();
+ }
+ };
+ et2_calendar_view.prototype._drag_update_event = function () {
+ 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)
+ */
+ et2_calendar_view.prototype._drag_create_end = function (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.
+ */
+ et2_calendar_view.is_consolidated = function (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'))));
+ };
+ /**
+ * Fetch and cache a list of the year's holidays
+ *
+ * @param {et2_calendar_timegrid} widget
+ * @param {string|numeric} year
+ * @returns {Array}
+ */
+ et2_calendar_view.get_holidays = function (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;
+ }
+ };
+ et2_calendar_view._attributes = {
+ owner: {
+ name: "Owner",
+ type: "any",
+ 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"
+ }
+ };
+ /**
+ * Cache to map owner & resource IDs to names, helps cut down on server requests
+ */
+ et2_calendar_view.owner_name_cache = {};
+ et2_calendar_view.holiday_cache = {};
+ return et2_calendar_view;
+}(et2_core_valueWidget_1.et2_valueWidget));
+exports.et2_calendar_view = et2_calendar_view;
+//# sourceMappingURL=et2_widget_view.js.map
\ No newline at end of file