Completed
Push — master ( 10b954...5f25a8 )
by Klaus
22:53
created

de).change   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 1
b 0
f 0
1
/**
2
 * EGroupware - Calendar - Javascript UI
3
 *
4
 * @link http://www.egroupware.org
5
 * @package calendar
6
 * @author Hadi Nategh	<hn-AT-stylite.de>
7
 * @author Nathan Gray
8
 * @copyright (c) 2008-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 * @version $Id$
11
 */
12
13
/*egw:uses
14
	/etemplate/js/etemplate2.js;
15
	/calendar/js/et2_widget_owner.js;
16
	/calendar/js/et2_widget_timegrid.js;
17
	/calendar/js/et2_widget_planner.js;
18
	/vendor/bower-asset/jquery-touchswipe/jquery.touchSwipe.js;
19
*/
20
21
/**
22
 * UI for calendar
23
 *
24
 * Calendar has multiple different views of the same data.  All the templates
25
 * for the different view are loaded at the start, then the view objects
26
 * in app.classes.calendar.views are used to manage the different views.
27
 * update_state() is used to change the state between the different views, as
28
 * well as adjust only a single part of the state while keeping the rest unchanged.
29
 *
30
 * The event widgets (and the nextmatch) get the data from egw.data, and they
31
 * register update callbacks to automatically update when the data changes.  This
32
 * means that when we update something on the server, to update the UI we just need
33
 * to send back the new data and if the event widget still exists it will update
34
 * itself.  See calendar_uiforms->ajax_status().
35
 *
36
 * To reduce server calls, we also keep a map of day => event IDs.  This allows
37
 * us to quickly change views (week to day, for example) without requesting additional
38
 * data from the server.  We keep that map as long as only the date (and a few
39
 * others - see update_state()) changes.  If user or any of the other filters are
40
 * changed, we discard the daywise cache and ask the server for the filtered events.
41
 *
42
 * @augments AppJS
43
 */
44
app.classes.calendar = (function(){ "use strict"; return AppJS.extend(
45
{
46
	/**
47
	 * application name
48
	 */
49
	appname: 'calendar',
50
51
	/**
52
	 * etemplate for the sidebox filters
53
	 */
54
	sidebox_et2: null,
55
56
	/**
57
	 * Current internal state
58
	 *
59
	 * If you need to change state, you can pass just the fields to change to
60
	 * update_state().
61
	 */
62
	state: {
63
		date: new Date(),
64
		view: egw.preference('saved_states','calendar') ? egw.preference('saved_states','calendar').view : egw.preference('defaultcalendar','calendar') || 'day',
65
		owner: egw.user('account_id')
66
	},
67
68
	/**
69
	 * These are the keys we keep to set & remember the status, others are discarded
70
	 */
71
	states_to_save: ['owner','status_filter','filter','cat_id','view','sortby','planner_view','weekend'],
72
73
	// If you are in one of these views and select a date in the sidebox, the view
74
	// will change as needed to show the date.  Other views will only change the
75
	// date in the current view.
76
	sidebox_changes_views: ['day','week','month'],
77
78
	// Calendar allows other apps to hook into the sidebox.  We keep these etemplates
79
	// up to date as state is changed.
80
	sidebox_hooked_templates: [],
81
82
	// List of queries in progress, to prevent home from requesting the same thing
83
	_queries_in_progress: [],
84
85
	// Calendar-wide autorefresh
86
	_autorefresh_timer: null,
87
88
	/**
89
	 * Constructor
90
	 *
91
	 * @memberOf app.calendar
92
	 */
93
	init: function()
94
	{
95
		// categories have nothing to do with calendar, but eT2 objects loads calendars app.js
96
		if (window.framework && framework.applications.calendar.browser &&
97
			framework.applications.calendar.browser.currentLocation.match('menuaction=preferences\.preferences_categories_ui\.index'))
98
		{
99
			this._super.apply(this, arguments);
100
			return;
101
		}
102
		else// make calendar object available, even if not running in top window, as sidebox does
103
		if (window.top !== window && !egw(window).is_popup() && window.top.app.calendar)
104
		{
105
			window.app.calendar = window.top.app.calendar;
106
			return;
107
		}
108
		else if (window.top == window && !egw(window).is_popup())
109
		{
110
			// Show loading div
111
			egw.loading_prompt(
112
				this.appname,true,egw.lang('please wait...'),
113
				typeof framework !== 'undefined' ? framework.applications.calendar.tab.contentDiv : false,
114
				egwIsMobile()?'horizental':'spinner'
115
			);
116
		}
117
118
		// call parent
119
		this._super.apply(this, arguments);
120
121
		// Scroll
122
		jQuery(jQuery.proxy(this._scroll,this));
123
		jQuery.extend(this.state, this.egw.preference('saved_states','calendar'));
124
	},
125
126
	/**
127
	 * Destructor
128
	 */
129
	destroy: function()
130
	{
131
		// call parent
132
		this._super.apply(this, arguments);
133
134
		// remove top window reference
135
		if (window.top !== window && window.top.app.calendar === this)
136
		{
137
			delete window.top.app.calendar;
138
		}
139
		jQuery('body').off('.calendar');
140
141
		if(this.sidebox_et2)
142
		{
143
			var date = this.sidebox_et2.getWidgetById('date');
144
			jQuery(window).off('resize.calendar'+date.dom_id);
145
		}
146
		this.sidebox_hooked_templates = null;
147
148
		egw_unregisterGlobalShortcut(jQuery.ui.keyCode.PAGE_UP, false, false, false);
149
		egw_unregisterGlobalShortcut(jQuery.ui.keyCode.PAGE_DOWN, false, false, false);
150
151
		// Stop autorefresh
152
		if(this._autorefresh_timer)
153
		{
154
			window.clearInterval(this._autorefresh_timer);
155
			this._autorefresh_timer = null;
156
		}
157
	},
158
159
	/**
160
	 * This function is called when the etemplate2 object is loaded
161
	 * and ready.  If you must store a reference to the et2 object,
162
	 * make sure to clean it up in destroy().
163
	 *
164
	 * @param {etemplate2} _et2 newly ready et2 object
165
	 * @param {string} _name name of template
166
	 */
167
	et2_ready: function(_et2, _name)
168
	{
169
		// call parent
170
		this._super.apply(this, arguments);
171
172
		// Avoid many problems with home
173
		if(_et2.app !== 'calendar' || _name == 'admin.categories.index')
174
		{
175
			egw.loading_prompt(this.appname,false);
176
			return;
177
		}
178
179
		// Re-init sidebox, since it was probably initialized too soon
180
		var sidebox = jQuery('#favorite_sidebox_'+this.appname);
181
		if(sidebox.length == 0 && egw_getFramework() != null)
182
		{
183
			var egw_fw = egw_getFramework();
184
			sidebox= jQuery('#favorite_sidebox_'+this.appname,egw_fw.sidemenuDiv);
185
		}
186
187
		var content = this.et2.getArrayMgr('content');
188
189
		switch (_name)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
190
		{
191
			case 'calendar.sidebox':
192
				this.sidebox_et2 = _et2.widgetContainer;
193
				this.sidebox_hooked_templates.push(this.sidebox_et2);
194
				jQuery(_et2.DOMContainer).hide();
195
196
				// Set client side holiday cache for this year
197
				egw.window.et2_calendar_view.holiday_cache[content.data.year] = content.data.holidays;
198
				delete content.data.holidays;
199
				delete content.data.year;
200
201
				this._setup_sidebox_filters();
202
203
				this.state = content.data;
204
				break;
205
206
			case 'calendar.edit':
207
				if (typeof content.data['conflicts'] == 'undefined')
208
				{
209
					//Check if it's fallback from conflict window or it's from edit window
210
					if (content.data['button_was'] != 'freetime')
211
					{
212
						this.set_enddate_visibility();
213
						this.check_recur_type();
214
						this.edit_start_change();
215
						this.et2.getWidgetById('recur_exception').set_disabled(!content.data.recur_exception ||
216
							typeof content.data.recur_exception[0] == 'undefined');
217
					}
218
					else
219
					{
220
						this.freetime_search();
221
					}
222
					//send Syncronus ajax request to the server to unlock the on close entry
223
					//set onbeforeunload with json request to send request when the window gets close by X button
224
					if (content.data.lock_token)
225
					{
226
						window.onbeforeunload = function () {
227
							this.egw.json('calendar.calendar_uiforms.ajax_unlock',
228
							[content.data.id, content.data.lock_token],null,true,null,null).sendRequest(true);
229
						};
230
					}
231
				}
232
				this.alarm_custom_date();
233
234
				// If title is pre-filled for a new (no ID) event, highlight it
235
				if(content.data && !content.data.id && content.data.title)
236
				{
237
					this.et2.getWidgetById('title').input.select();
238
				}
239
				break;
240
241
			case 'calendar.freetimesearch':
242
				this.set_enddate_visibility();
243
				break;
244
			case 'calendar.list':
245
				// Wait until _et2_view_init is done
246
				window.setTimeout(jQuery.proxy(function() {
247
					this.filter_change();
248
				},this),0);
249
				break;
250
		}
251
252
		// Record the templates for the views so we can switch between them
253
		this._et2_view_init(_et2,_name);
254
	},
255
256
	/**
257
	 * Observer method receives update notifications from all applications
258
	 *
259
	 * App is responsible for only reacting to "messages" it is interested in!
260
	 *
261
	 * Calendar binds listeners to the data cache, so if the data is updated, the widget
262
	 * will automatically update itself.
263
	 *
264
	 * @param {string} _msg message (already translated) to show, eg. 'Entry deleted'
265
	 * @param {string} _app application name
266
	 * @param {(string|number)} _id id of entry to refresh or null
267
	 * @param {string} _type either 'update', 'edit', 'delete', 'add' or null
268
	 * - update: request just modified data from given rows.  Sorting is not considered,
269
	 *		so if the sort field is changed, the row will not be moved.
270
	 * - edit: rows changed, but sorting may be affected.  Requires full reload.
271
	 * - delete: just delete the given rows clientside (no server interaction neccessary)
272
	 * - add: requires full reload for proper sorting
273
	 * @param {string} _msg_type 'error', 'warning' or 'success' (default)
274
	 * @param {object|null} _links app => array of ids of linked entries
275
	 * or null, if not triggered on server-side, which adds that info
276
	 * @return {false|*} false to stop regular refresh, thought all observers are run
277
	 */
278
	observer: function(_msg, _app, _id, _type, _msg_type, _links)
279
	{
280
		var do_refresh = false;
281
		if(this.state.view === 'listview')
282
		{
283
			app.classes.calendar.views.listview.etemplates[0].widgetContainer.getWidgetById('nm').refresh(_id,_type);
284
		}
285
		switch(_app)
286
		{
287
			case 'infolog':
288
				jQuery('.calendar_calDayTodos')
289
					.find('a')
290
					.each(function(i,a){
291
						var match = a.href.split(/&info_id=/);
292
						if (match && typeof match[1] !="undefined")
293
						{
294
							if (match[1]== _id)	do_refresh = true;
295
						}
296
					});
297
298
				// Unfortunately we do not know what type this infolog is here,
299
				// but we can tell if it's turned off entirely
300
				if(egw.preference('calendar_integration','infolog') !== '0')
301
				{
302
					if (jQuery('div [data-app="infolog"][data-app_id="'+_id+'"]').length > 0) do_refresh = true;
303
					switch (_type)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
304
					{
305
						case 'add':
306
							do_refresh = true;
307
							break;
308
					}
309
				}
310
				if (do_refresh)
311
				{
312
					// Discard cache
313
					this._clear_cache();
314
315
					// Calendar is the current application, refresh now
316
					if(framework.activeApp.appName === this.appname)
317
					{
318
						this.setState({state: this.state});
319
					}
320
					// Bind once to trigger a refresh when tab is activated again
321
					else if(framework.applications.calendar && framework.applications.calendar.tab &&
322
						framework.applications.calendar.tab.contentDiv)
323
					{
324
						jQuery(framework.applications.calendar.tab.contentDiv)
325
							.off('show.calendar')
326
							.one('show.calendar',
327
								jQuery.proxy(function() {this.setState({state: this.state});},this)
328
							);
329
					}
330
				}
331
				break;
332
			case 'calendar':
333
				// Regular refresh
334
				var event = false;
335
				if(_id)
336
				{
337
					event = egw.dataGetUIDdata('calendar::'+_id);
338
				}
339
				if(event && event.data && event.data.date || _type === 'delete')
340
				{
341
					// Intelligent refresh without reloading everything
342
					var recurrences = Object.keys(egw.dataSearchUIDs(new RegExp('^calendar::'+_id+':')));
343
					var ids = event && event.data && event.data.recur_type && typeof _id === 'string' && _id.indexOf(':') < 0 || recurrences.length ?
344
						recurrences :
345
						['calendar::'+_id];
346
347
					if(_type === 'delete')
348
					{
349
						for(var i in ids)
350
						{
351
							egw.dataStoreUID(ids[i], null);
352
						}
353
					}
354
					// Updates are handled by events themselves through egw.data
355
					else if (_type !== 'update')
356
					{
357
						this._update_events(this.state, ids);
358
					}
359
					return false;
360
				}
361
				else
362
				{
363
					this._clear_cache();
364
365
					// Force redraw to current state
366
					this.setState({state: this.state});
367
					return false;
368
				}
369
				break;
0 ignored issues
show
Unused Code introduced by
This break statement is unnecessary and may be removed.
Loading history...
370
			default:
371
				return undefined;
372
		}
373
	},
374
375
	/**
376
	 * Link hander for jDots template to just reload our iframe, instead of reloading whole admin app
377
	 *
378
	 * @param {String} _url
379
	 * @return {boolean|string} true, if linkHandler took care of link, false for default processing or url to navigate to
380
	 */
381
	linkHandler: function(_url)
382
	{
383
		if (_url == 'about:blank' || _url.match('menuaction=preferences\.preferences_categories_ui\.index'))
384
		{
385
			return false;
386
		}
387
		if (_url.match('menuaction=calendar\.calendar_uiviews\.'))
388
		{
389
			var view = _url.match(/calendar_uiviews\.([^&?]+)/);
390
			view = view && view.length > 1 ? view[1] : null;
391
392
			// Get query
393
			var q = {};
394
			_url.split('?')[1].split('&').forEach(function(i){
395
				q[i.split('=')[0]]=unescape(i.split('=')[1]);
396
			});
397
			delete q.ajax;
398
			delete q.menuaction;
399
			if(!view && q.view || q.view != view && view == 'index') view = q.view;
400
401
			// No specific view requested, looks like a reload from framework
402
			if(this.sidebox_et2 && typeof view === 'undefined')
403
			{
404
				this._clear_cache();
405
				this.setState({state: this.state});
406
				return false;
407
			}
408
409
			if (this.sidebox_et2 && typeof app.classes.calendar.views[view] == 'undefined' && view != 'index')
410
			{
411
				if(q.owner)
412
				{
413
					q.owner = q.owner.split(',');
414
					q.owner = q.owner.reduce(function(p,c) {if(p.indexOf(c)<0) p.push(c);return p;},[]);
415
					q.owner = q.owner.join(',');
416
				}
417
				q.menuaction = 'calendar.calendar_uiviews.index';
418
				this.sidebox_et2.getWidgetById('iframe').set_src(egw.link('/index.php',q));
419
				jQuery(this.sidebox_et2.parentNode).show();
420
				return true;
421
			}
422
			// Known AJAX view
423
			else if(app.classes.calendar.views[view])
424
			{
425
				// Reload of known view?
426
				if(view == 'index')
427
				{
428
					var pref = this.egw.preference('saved_states','calendar');
429
					view = pref.view || 'day';
430
				}
431
				// View etemplate not loaded
432
				if(typeof app.classes.calendar.views[view].etemplates[0] == 'string')
433
				{
434
					return _url + '&ajax=true';
435
				}
436
				// Already loaded, we'll just apply any variables to our current state
437
				var set = jQuery.extend({view: view},q);
438
				this.update_state(set);
439
				return true;
440
			}
441
		}
442
		else if (this.sidebox_et2)
443
		{
444
			var iframe = this.sidebox_et2.getWidgetById('iframe');
445
			if(!iframe) return false;
446
			iframe.set_src(_url);
447
			jQuery(this.sidebox_et2.parentNode).show();
448
			// Hide other views
449
			for(var _view in app.classes.calendar.views)
450
			{
451
				for(var i = 0; i < app.classes.calendar.views[_view].etemplates.length; i++)
452
				{
453
					jQuery(app.classes.calendar.views[_view].etemplates[i].DOMContainer).hide();
454
				}
455
			}
456
			this.state.view = '';
457
			return true;
458
		}
459
		// can not load our own index page, has to be done by framework
460
		return false;
461
	},
462
463
	/**
464
	 * Handle actions from the toolbar
465
	 *
466
	 * @param {egwAction} action Action from the toolbar
467
	 */
468
	toolbar_action: function toolbar_action(action)
469
	{
470
		// Most can just provide state change data
471
		if(action.data && action.data.state)
472
		{
473
			var state = jQuery.extend({},action.data.state);
474
			if(state.view == 'planner' && app.calendar.state.view != 'planner') {
475
				state.planner_view = app.calendar.state.view;
476
			}
477
			this.update_state(state);
478
		}
479
		// Special handling
480
		switch(action.id)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
481
		{
482
			case 'add':
483
				return egw.open(null,"calendar","add");
484
			case 'weekend':
485
				this.update_state({weekend: action.checked});
486
				break;
487
			case 'today':
488
				var tempDate = new Date();
489
				var today = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate(),0,-tempDate.getTimezoneOffset(),0);
490
				var change = {date: today.toJSON()};
491
				app.calendar.update_state(change);
492
				break;
493
			case 'next':
494
			case 'previous':
495
				var delta = action.id == 'previous' ? -1 : 1;
496
				var view = app.classes.calendar.views[app.calendar.state.view] || false;
497
				var start = new Date(app.calendar.state.date);
498
				if (view)
499
				{
500
					start = view.scroll(delta);
501
					app.calendar.update_state({date:app.calendar.date.toString(start)});
502
				}
503
				break;
504
		}
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
505
	},
506
507
	/**
508
	 * Set the app header
509
	 *
510
	 * Because the toolbar takes some vertical space and has some horizontal space,
511
	 * we don't use the system app header, but our own that is in the toolbar
512
	 *
513
	 * @param {string} header Text to display
514
	 */
515
	set_app_header: function(header) {
516
		var template = etemplate2.getById('calendar-toolbar');
517
		var widget = template ? template.widgetContainer.getWidgetById('app_header') : false;
518
		if(widget)
519
		{
520
			widget.set_value(header);
521
			egw_app_header('','calendar');
522
		}
523
		else
524
		{
525
			egw_app_header(header,'calendar');
526
		}
527
	},
528
529
	/**
530
	 * Setup and handle sortable calendars.
531
	 *
532
	 * You can only sort calendars if there is more than one owner, and the calendars
533
	 * are not combined (many owners, multi-week or month views)
534
	 * @returns {undefined}
535
	 */
536
	_sortable: function() {
537
		// Calender current state
538
		var state = this.getState();
539
		// Day / month sortables
540
		var daily = jQuery('#calendar-view_view .calendar_calGridHeader > div:first');
541
		var weekly = jQuery('#calendar-view_view tbody');
542
		if(state.view == 'day')
543
		{
544
			var sortable = daily;
545
			if(weekly.sortable('instance')) weekly.sortable('disable');
546
		}
547
		else
548
		{
549
			var sortable = weekly;
550
			if(daily.sortable('instance')) daily.sortable('disable');
551
		}
552
		if(!sortable.sortable('instance'))
553
		{
554
			sortable.sortable({
555
				cancel: "#divAppboxHeader, .calendar_calWeekNavHeader, .calendar_calDayColHeader, .calendar_plannerHeader",
556
				handle: '.calendar_calGridHeader',
557
				//placeholder: "srotable_cal_wk_ph",
558
				axis:"y",
559
				revert: true,
560
				helper:"clone",
561
				create: function ()
562
				{
563
					var $sortItem = jQuery(this);
564
				},
565
				start: function (event, ui)
566
				{
567
					jQuery('.calendar_calTimeGrid',ui.helper).css('position', 'absolute');
568
					// Put owners into row IDs
569
					app.classes.calendar.views[app.calendar.state.view].etemplates[0].widgetContainer.iterateOver(function(widget) {
570
						if(widget.options.owner && !widget.disabled)
571
						{
572
							widget.div.parents('tr').attr('data-owner',widget.options.owner);
573
						}
574
						else
575
						{
576
							widget.div.parents('tr').removeAttr('data-owner');
577
						}
578
					},this,et2_calendar_timegrid);
579
				},
580
				stop: function ()
581
				{
582
				},
583
				update: function ()
584
				{
585
					var state = app.calendar.getState();
586
					if (state && typeof state.owner !== 'undefined')
587
					{
588
						var sortedArr = sortable.sortable('toArray', {attribute:"data-owner"});
589
						// No duplicates, no empties
590
						sortedArr = sortedArr.filter(function(value, index, self) {
591
							return value !== '' && self.indexOf(value) === index;
592
						});
593
594
						var parent = null;
595
						var children = [];
596
						if(state.view == 'day')
597
						{
598
							// If in day view, the days need to be re-ordered, avoiding
599
							// the current sort order
600
							app.classes.calendar.views.day.etemplates[0].widgetContainer.iterateOver(function(widget) {
601
								var idx = sortedArr.indexOf(widget.options.owner);
602
								// Move the event holding div
603
								widget.set_left((parseInt(widget.options.width) * idx) + 'px');
604
								// Re-order the children, or it won't stay
605
								parent = widget._parent;
606
								children.splice(idx,0,widget);
607
							},this,et2_calendar_daycol);
608
						}
609
						else
610
						{
611
							// Re-order the children, or it won't stay
612
							app.classes.calendar.views.day.etemplates[0].widgetContainer.iterateOver(function(widget) {
613
								parent = widget._parent;
614
								var idx = sortedArr.indexOf(widget.options.owner);
615
								children.splice(idx,0,widget);
616
								widget.resize();
617
							},this,et2_calendar_timegrid);
618
						}
619
						parent._children.sort(function(a,b) {
620
							return children.indexOf(a) - children.indexOf(b);
621
						});
622
						// Directly update, since there is no other changes needed,
623
						// and we don't want the current sort order applied
624
						app.calendar.state.owner = sortedArr;
625
					}
626
				}
627
			});
628
		}
629
630
		// Enable or disable
631
		if(state.owner.length > 1 && (
632
			state.view == 'day' && state.owner.length < parseInt(egw.preference('day_consolidate','calendar')) ||
633
			state.view == 'week' && state.owner.length < parseInt(egw.preference('week_consolidate','calendar'))
634
		))
635
		{
636
			sortable.sortable('enable')
637
				.sortable("refresh")
638
				.disableSelection();
639
			var options = {};
640
			switch (state.view)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
641
			{
642
				case "day":
643
					options = {
644
						placeholder:"srotable_cal_day_ph",
645
						axis:"x",
646
						handle: '> div:first',
647
						helper: function(event, element) {
648
							var scroll = element.parentsUntil('.calendar_calTimeGrid').last().next();
649
							var helper = jQuery(document.createElement('div'))
650
								.append(element.clone())
651
								.css('height',scroll.parent().css('height'))
652
								.css('background-color','white')
653
								.css('width', element.css('width'));
654
							return helper;
655
						}
656
					};
657
					sortable.sortable('option', options);
658
					break;
659
				case "week":
660
					options = {
661
						placeholder:"srotable_cal_wk_ph",
662
						axis:"y",
663
						handle: '.calendar_calGridHeader',
664
						helper: 'clone'
665
					};
666
					sortable.sortable('option', options);
667
					break;
668
			}
669
		}
670
		else
671
		{
672
			sortable.sortable('disable');
673
		}
674
	},
675
676
	/**
677
	 * Bind scroll event
678
	 * When the user scrolls, we'll move enddate - startdate days
679
	 */
680
	_scroll: function() {
681
		/**
682
		 * Function we can pass all this off to
683
		 *
684
		 * @param {String} direction up, down, left or right
685
		 * @param {number} delta Integer for how many we're moving, should be +/- 1
686
		 */
687
		var scroll_animate = function(direction, delta)
688
		{
689
			// Scrolling too fast?
690
			if(app.calendar._scroll_disabled) return;
691
692
			// Find the template
693
			var id = jQuery(this).closest('.et2_container').attr('id');
694
			if(id)
695
			{
696
				var template = etemplate2.getById(id);
697
			}
698
			else
699
			{
700
				template = app.classes.calendar.views[app.calendar.state.view].etemplates[0];
701
			}
702
			if(!template) return;
703
704
			// Prevent scrolling too fast
705
			app.calendar._scroll_disabled = true;
706
707
			// Animate the transition, if possible
708
			var widget = null;
709
			template.widgetContainer.iterateOver(function(w) {
710
				if (w.getDOMNode() == this) widget = w;
711
			},this,et2_widget);
712
			if(widget == null)
713
			{
714
				template.widgetContainer.iterateOver(function(w) {
715
					widget = w;
716
				},this, et2_calendar_timegrid);
717
				if(widget == null) return;
718
			}
719
			/* Disabled
720
			 *
721
			// We clone the nodes so we can animate the transition
722
			var original = jQuery(widget.getDOMNode()).closest('.et2_grid');
723
			var cloned = original.clone(true).attr("id","CLONE");
724
725
			// Moving this stuff around scrolls things around too
726
			// We need this later
727
			var scrollTop = jQuery('.calendar_calTimeGridScroll',original).scrollTop();
728
729
			// This is to hide the scrollbar
730
			var wrapper = original.parent();
731
			if(direction == "right" || direction == "left")
732
			{
733
				original.css({"display":"inline-block","width":original.width()+"px"});
734
				cloned.css({"display":"inline-block","width":original.width()+"px"});
735
			}
736
			else
737
			{
738
				original.css("height",original.height() + "px");
739
				cloned.css("height",original.height() + "px");
740
			}
741
			var original_size = {height: wrapper.parent().css('height'), width: wrapper.parent().css('width')};
742
			wrapper.parent().css({overflow:'hidden', height:original.outerHeight()+"px", width:original.outerWidth() + "px"});
743
			wrapper.height(direction == "up" || direction == "down" ? 2 * original.outerHeight()  : original.outerHeight());
744
			wrapper.width(direction == "left" || direction == "right" ? 2 * original.outerWidth() : original.outerWidth());
745
746
			// Re-scroll to previous to avoid "jumping"
747
			jQuery('.calendar_calTimeGridScroll',original).scrollTop(scrollTop);
748
			switch(direction)
749
			{
750
				case "up":
751
				case "left":
752
					// Scrolling up
753
					// Apply the reverse quickly, then let it animate as the changes are
754
					// removed, leaving things where they should be.
755
756
					original.parent().append(cloned);
757
					// Makes it jump to destination
758
					wrapper.css({
759
						"transition-duration": "0s",
760
						"transition-delay": "0s",
761
						"transform": direction == "up" ? "translateY(-50%)" : "translateX(-50%)"
762
					});
763
					// Stop browser from caching style by forcing reflow
764
					if(wrapper[0]) wrapper[0].offsetHeight;
765
766
					wrapper.css({
767
						"transition-duration": "",
768
						"transition-delay": ""
769
					});
770
					break;
771
				case "down":
772
				case "right":
773
					// Scrolling down
774
					original.parent().prepend(cloned);
775
					break;
776
			}
777
			// Scroll clone to match to avoid "jumping"
778
			jQuery('.calendar_calTimeGridScroll',cloned).scrollTop(scrollTop);
779
780
			// Remove
781
			var remove = function() {
782
				// Starting animation
783
				wrapper.addClass("calendar_slide");
784
				var translate = direction == "down" ? "translateY(-50%)" : (direction == "right" ? "translateX(-50%)" : "");
785
				wrapper.css({"transform": translate});
786
				window.setTimeout(function() {
787
788
					cloned.remove();
789
790
					// Makes it jump to destination
791
					wrapper.css({
792
						"transition-duration": "0s",
793
						"transition-delay": "0s"
794
					});
795
796
					// Clean up from animation
797
					wrapper
798
						.removeClass("calendar_slide")
799
						.css({"transform": '',height: '', width:'',overflow:''});
800
					wrapper.parent().css({overflow: '', width: original_size.width, height: original_size.height});
801
					original.css("display","");
802
					if(wrapper.length)
803
					{
804
						wrapper[0].offsetHeight;
805
					}
806
					wrapper.css({
807
						"transition-duration": "",
808
						"transition-delay": ""
809
					});
810
811
					// Re-scroll to start of day
812
					template.widgetContainer.iterateOver(function(w) {
813
						w.resizeTimes();
814
					},this, et2_calendar_timegrid);
815
816
					window.setTimeout(function() {
817
						if(app.calendar)
818
						{
819
							app.calendar._scroll_disabled = false;
820
						}
821
					}, 100);
822
				},2000);
823
			}
824
			// If detecting the transition end worked, we wouldn't need to use a timeout.
825
			window.setTimeout(remove,100);
826
			*/
827
		   window.setTimeout(function() {
828
				if(app.calendar)
829
				{
830
					app.calendar._scroll_disabled = false;
831
				}
832
			}, 2000);
833
			// Get the view to calculate - this actually loads the new data
834
			// Using a timeout make it a little faster (in Chrome)
835
			window.setTimeout(function() {
836
				var view = app.classes.calendar.views[app.calendar.state.view] || false;
837
				var start = new Date(app.calendar.state.date);
838
				if (view && view.etemplates.indexOf(template) !== -1)
839
				{
840
					start = view.scroll(delta);
841
					app.calendar.update_state({date:app.calendar.date.toString(start)});
842
				}
843
				else
844
				{
845
					// Home - always 1 week
846
					// TODO
847
					return false;
848
				}
849
			},0);
850
		};
851
852
		// Bind only once, to the whole thing
853
		/* Disabled
854
		jQuery('body').off('.calendar')
855
			//.on('wheel','.et2_container:#calendar-list,#calendar-sidebox)',
856
			.on('wheel.calendar','.et2_container .calendar_calTimeGrid, .et2_container .calendar_plannerWidget',
857
				function(e)
858
				{
859
					// Consume scroll if in the middle of something
860
					if(app.calendar._scroll_disabled) return false;
861
862
					// Ignore if they're going the other way
863
					var direction = e.originalEvent.deltaY > 0 ? 1 : -1;
864
					var at_bottom = direction !== -1;
865
					var at_top = direction !== 1;
866
867
					jQuery(this).children(":not(.calendar_calGridHeader)").each(function() {
868
						// Check for less than 2px from edge, as sometimes we can't scroll anymore, but still have
869
						// 2px left to go
870
						at_bottom = at_bottom && Math.abs(this.scrollTop - (this.scrollHeight - this.offsetHeight)) <= 2;
871
					}).each(function() {
872
						at_top = at_top && this.scrollTop === 0;
873
					});
874
					if(!at_bottom && !at_top) return;
875
876
					e.preventDefault();
877
878
					scroll_animate.call(this, direction > 0 ? "down" : "up", direction);
879
880
					return false;
881
				}
882
			);
883
		*/
884
		if(typeof framework !== 'undefined' && framework.applications.calendar && framework.applications.calendar.tab)
885
		{
886
			jQuery(framework.applications.calendar.tab.contentDiv)
887
				.swipe('destroy');
888
889
			jQuery(framework.applications.calendar.tab.contentDiv)
890
				.swipe({
891
					//Generic swipe handler for all directions
892
					swipe:function(event, direction, distance, duration, fingerCount) {
893
						if(direction == "up" || direction == "down")
894
						{
895
							if(fingerCount <= 1) return;
896
							var at_bottom = direction !== -1;
897
							var at_top = direction !== 1;
898
899
							jQuery(this).children(":not(.calendar_calGridHeader)").each(function() {
900
								// Check for less than 2px from edge, as sometimes we can't scroll anymore, but still have
901
								// 2px left to go
902
								at_bottom = at_bottom && Math.abs(this.scrollTop - (this.scrollHeight - this.offsetHeight)) <= 2;
903
							}).each(function() {
904
								at_top = at_top && this.scrollTop === 0;
905
							});
906
						}
907
908
						var delta = direction == "down" || direction == "right" ? -1 : 1;
909
						// But we animate in the opposite direction to the swipe
910
						var opposite = {"down": "up", "up": "down", "left": "right", "right": "left"};
911
						direction = opposite[direction];
912
						scroll_animate.call(jQuery(event.target).closest('.calendar_calTimeGrid, .calendar_plannerWidget')[0], direction, delta);
913
						return false;
914
					},
915
					allowPageScroll: jQuery.fn.swipe.pageScroll.VERTICAL,
916
					threshold: 100,
917
					fallbackToMouseEvents: false,
918
					triggerOnTouchEnd: false
919
				});
920
921
			// Page up & page down
922
			egw_registerGlobalShortcut(jQuery.ui.keyCode.PAGE_UP, false, false, false, function() {
923
				if(app.calendar.state.view == 'listview')
924
				{
925
					return false;
926
				}
927
				scroll_animate.call(this,"up", -1);
928
				return true;
929
			});
930
			egw_registerGlobalShortcut(jQuery.ui.keyCode.PAGE_DOWN, false, false, false, function() {
931
				if(app.calendar.state.view == 'listview')
932
				{
933
					return false;
934
				}
935
				scroll_animate.call(this,"down", 1);
936
				return true;
937
			});
938
		}
939
	},
940
941
	/**
942
	 * Handler for changes generated by internal user interactions, like
943
	 * drag & drop inside calendar and resize.
944
	 *
945
	 * @param {Event} event
946
	 * @param {et2_calendar_event} widget Widget for the event
947
	 * @param {string} dialog_button - 'single', 'series', or 'exception', based on the user's answer
948
	 *	in the popup
949
	 * @returns {undefined}
950
	 */
951
	event_change: function(event, widget, dialog_button)
952
	{
953
		// Add loading spinner - not visible if the body / gradient is there though
954
		widget.div.addClass('loading');
955
956
		// Integrated infolog event
957
		//Get infologID if in case if it's an integrated infolog event
958
		if (widget.options.value.app == 'infolog')
959
		{
960
			// If it is an integrated infolog event we need to edit infolog entry
961
			egw().json(
962
				'stylite_infolog_calendar_integration::ajax_moveInfologEvent',
963
				[widget.options.value.app_id, widget.options.value.start, widget.options.value.duration],
964
				// Remove loading spinner
965
				function() {if(widget.div) widget.div.removeClass('loading');}
966
			).sendRequest();
967
		}
968
		else
969
		{
970
			var _send = function() {
971
				egw().json(
972
					'calendar.calendar_uiforms.ajax_moveEvent',
973
					[
974
						dialog_button == 'exception' ? widget.options.value.app_id : widget.options.value.id,
975
						widget.options.value.owner,
976
						widget.options.value.start,
977
						widget.options.value.owner,
978
						widget.options.value.duration,
979
						dialog_button == 'series' ? widget.options.value.start : null
980
					],
981
					// Remove loading spinner
982
					function() {if(widget && widget.div) widget.div.removeClass('loading');}
983
				).sendRequest(true);
984
			};
985
			if(dialog_button == 'series' && widget.options.value.recur_type)
986
			{
987
				widget.series_split_prompt(function(_button_id)
988
					{
989
						if (_button_id == et2_dialog.OK_BUTTON)
990
						{
991
							_send();
992
						}
993
					}
994
				);
995
			}
996
			else
997
			{
998
				_send();
999
			}
1000
		}
1001
	},
1002
1003
	/**
1004
	 * open the freetime search popup
1005
	 *
1006
	 * @param {string} _link
1007
	 */
1008
	freetime_search_popup: function(_link)
1009
	{
1010
		this.egw.open_link(_link,'ft_search','700x500') ;
1011
	},
1012
1013
	/**
1014
	 * send an ajax request to server to set the freetimesearch window content
1015
	 *
1016
	 */
1017
	freetime_search: function()
1018
	{
1019
		var content = this.et2.getArrayMgr('content').data;
1020
		content['start'] = this.et2.getWidgetById('start').get_value();
1021
		content['end'] = this.et2.getWidgetById('end').get_value();
1022
		content['duration'] = this.et2.getWidgetById('duration').get_value();
1023
1024
		var request = this.egw.json('calendar.calendar_uiforms.ajax_freetimesearch', [content],null,null,null,null);
1025
		request.sendRequest();
1026
	},
1027
1028
	/**
1029
	 * Function for disabling the recur_data multiselect box
1030
	 *
1031
	 */
1032
	check_recur_type: function()
1033
	{
1034
		var recurType = this.et2.getWidgetById('recur_type');
1035
		var recurData = this.et2.getWidgetById('recur_data');
1036
1037
		if(recurType && recurData)
1038
		{
1039
			recurData.set_disabled(recurType.get_value() != 2 && recurType.get_value() != 4);
1040
		}
1041
	},
1042
1043
	/**
1044
	 * Actions for when the user changes the event start date in edit dialog
1045
	 * 
1046
	 * @returns {undefined}
1047
	 */
1048
	edit_start_change: function(input, widget)
1049
	{
1050
		if(!widget)
1051
		{
1052
			widget = etemplate2.getById('calendar-edit').widgetContainer.getWidgetById('start');
1053
		}
1054
		
1055
		// Update settings for querying participants
1056
		this.edit_update_participant(widget);
1057
1058
		// Update recurring date limit, if not set it can't be before start
1059
		if(widget)
1060
		{
1061
			var recur_end = widget.getRoot().getWidgetById('recur_enddate');
1062
			if(recur_end && !recur_end.getValue())
1063
			{
1064
				recur_end.set_min(widget.getValue());
1065
			}
1066
		}
1067
	},
1068
1069
	/**
1070
	 * Show/Hide end date, for both edit and freetimesearch popups,
1071
	 * based on if "use end date" selected or not.
1072
	 *
1073
	 */
1074
	set_enddate_visibility: function()
1075
	{
1076
		var duration = this.et2.getWidgetById('duration');
1077
		var start = this.et2.getWidgetById('start');
1078
		var end = this.et2.getWidgetById('end');
1079
		var content = this.et2.getArrayMgr('content').data;
1080
1081
		if (typeof duration != 'undefined' && typeof end != 'undefined')
1082
		{
1083
			end.set_disabled(duration.get_value()!=='');
1084
1085
			// Only set end date if not provided, adding seconds fails with DST
1086
			if (!end.disabled && !content.end)
1087
			{
1088
				end.set_value(start.get_value());
1089
				if (typeof content.duration != 'undefined') end.set_value("+"+content.duration);
1090
			}
1091
		}
1092
		this.edit_update_participant(start);
1093
	},
1094
1095
	/**
1096
	 * Update query parameters for participants
1097
	 *
1098
	 * This allows for resource conflict checking
1099
	 *
1100
	 * @param {DOMNode|et2_widget} input Either the input node, or the widget
1101
	 * @param {et2_widget} [widget] If input is an input node, widget will have
0 ignored issues
show
Documentation Bug introduced by
The parameter [widget] does not exist. Did you maybe mean widget instead?
Loading history...
1102
	 *	the widget, otherwise it will be undefined.
1103
	 */
1104
	edit_update_participant: function(input, widget)
1105
	{
1106
		if(typeof widget === 'undefined') widget = input;
1107
		var content = widget.getInstanceManager().getValues(widget.getRoot());
1108
		var participant = widget.getRoot().getWidgetById('participant');
1109
1110
		participant.set_autocomplete_params({exec:{
1111
			start: content.start,
1112
			end: content.end,
1113
			duration: content.duration,
1114
			whole_day: content.whole_day,
1115
		}});
1116
	},
1117
1118
	/**
1119
	 * handles actions selectbox in calendar edit popup
1120
	 *
1121
	 * @param {mixed} _event
1122
	 * @param {et2_base_widget} widget "actions selectBox" in edit popup window
1123
	 */
1124
	actions_change: function(_event, widget)
1125
	{
1126
		var event = this.et2.getArrayMgr('content').data;
1127
		if (widget)
1128
		{
1129
			var id = this.et2.getArrayMgr('content').data['id'];
1130
			switch (widget.get_value())
1131
			{
1132
				case 'print':
1133
					this.egw.open_link('calendar.calendar_uiforms.edit&cal_id='+id+'&print=1','_blank','700x700');
1134
					break;
1135
				case 'mail':
1136
					this.egw.json('calendar.calendar_uiforms.ajax_custom_mail', [event, !event['id'], false],null,null,null,null).sendRequest();
1137
					this.et2._inst.submit();
1138
					break;
1139
				case 'sendrequest':
1140
					this.egw.json('calendar.calendar_uiforms.ajax_custom_mail', [event, !event['id'], true],null,null,null,null).sendRequest();
1141
					this.et2._inst.submit();
1142
					break;
1143
				case 'infolog':
1144
					this.egw.open_link('infolog.infolog_ui.edit&action=calendar&action_id='+(jQuery.isPlainObject(event)?event['id']:event),'_blank','700x600','infolog');
1145
					this.et2._inst.submit();
1146
					break;
1147
				case 'ical':
1148
					this.et2._inst.postSubmit();
1149
					break;
1150
				default:
1151
					this.et2._inst.submit();
1152
			}
1153
		}
1154
	},
1155
1156
	/**
1157
	 * open mail compose popup window
1158
	 *
1159
	 * @param {Array} vars
1160
	 * @todo need to provide right mail compose from server to custom_mail function
1161
	 */
1162
	custom_mail: function (vars)
1163
	{
1164
		this.egw.open_link(this.egw.link("/index.php",vars),'_blank','700x700');
1165
	},
1166
1167
	/**
1168
	 * control delete_series popup visibility
1169
	 *
1170
	 * @param {et2_widget} widget
1171
	 * @param {Array} exceptions an array contains number of exception entries
1172
	 *
1173
	 */
1174
	delete_btn: function(widget,exceptions)
1175
	{
1176
		var content = this.et2.getArrayMgr('content').data;
1177
1178
		if (exceptions)
1179
		{
1180
			var buttons = [
1181
				{
1182
					button_id: 'keep',
1183
					title: this.egw.lang('All exceptions are converted into single events.'),
1184
					text: this.egw.lang('Keep exceptions'),
1185
					id: 'button[delete_keep_exceptions]',
1186
					image: 'keep', "default":true
1187
				},
1188
				{
1189
					button_id: 'delete',
1190
					title: this.egw.lang('The exceptions are deleted together with the series.'),
1191
					text: this.egw.lang('Delete exceptions'),
1192
					id: 'button[delete_exceptions]',
1193
					image: 'delete'
1194
				},
1195
				{
1196
					button_id: 'cancel',
1197
					text: this.egw.lang('Cancel'),
1198
					id: 'dialog[cancel]',
1199
					image: 'cancel'
1200
				}
1201
1202
			];
1203
			var self = this;
1204
			et2_dialog.show_dialog
1205
			(
1206
					function(_button_id)
1207
					{
1208
						if (_button_id != 'dialog[cancel]')
1209
						{
1210
							widget.getRoot().getWidgetById('delete_exceptions').set_value(_button_id == 'button[delete_exceptions]');
1211
							widget.getInstanceManager().submit('button[delete]');
1212
							return true;
1213
						}
1214
						else
1215
						{
1216
							return false;
1217
						}
1218
					},
1219
					this.egw.lang("Do you want to keep the series exceptions in your calendar?"),
1220
					this.egw.lang("This event is part of a series"), {}, buttons , et2_dialog.WARNING_MESSAGE
1221
			);
1222
		}
1223
		else if (content['recur_type'] !== 0)
1224
		{
1225
			et2_dialog.confirm(widget,'Delete this series of recuring events','Delete Series');
1226
		}
1227
		else
1228
		{
1229
			et2_dialog.confirm(widget,'Delete this event','Delete');
1230
		}
1231
	},
1232
1233
	/**
1234
	 * On change participant event, try to set add button status based on
1235
	 * participant field value. Additionally, disable/enable quantity field
1236
	 * if there's none resource value or there are more than one resource selected.
1237
	 *
1238
	 */
1239
	participantOnChange: function ()
1240
	{
1241
		var add = this.et2.getWidgetById('add');
1242
		var quantity = this.et2.getWidgetById('quantity');
1243
		var participant = this.et2.getWidgetById('participant');
1244
1245
		// array of participants
1246
		var value = participant.get_value();
1247
1248
		add.set_readonly(value.length <= 0);
1249
1250
		quantity.set_readonly(false);
1251
1252
		// number of resources
1253
		var nRes = 0;
1254
1255
		for (var i=0;i<value.length;i++)
1256
		{
1257
			if (!value[i].match(/\D/ig) || nRes)
1258
			{
1259
				quantity.set_readonly(true);
1260
				quantity.set_value(1);
1261
			}
1262
			nRes++;
1263
		}
1264
	},
1265
1266
	/**
1267
	 * print_participants_status(egw,widget)
1268
	 * Handle to apply changes from status in print popup
1269
	 *
1270
	 * @param {mixed} _event
1271
	 * @param {et2_base_widget} widget widget "status" in print popup window
1272
	 *
1273
	 */
1274
	print_participants_status: function(_event, widget)
1275
	{
1276
		if (widget && window.opener)
1277
		{
1278
			//Parent popup window
1279
			var editPopWindow = window.opener;
1280
1281
			if (editPopWindow)
1282
			{
1283
				//Update paretn popup window
1284
				editPopWindow.etemplate2.getByApplication('calendar')[0].widgetContainer.getWidgetById(widget.id).set_value(widget.get_value());
1285
			}
1286
			this.et2._inst.submit();
1287
1288
			editPopWindow.opener.egw_refresh('status changed','calendar');
1289
		}
1290
		else if (widget)
1291
		{
1292
			window.egw_refresh(this.egw.lang('The original popup edit window is closed! You need to close the print window and reopen the entry again.'),'calendar');
1293
		}
1294
	},
1295
1296
	/**
1297
	 * Handles to select freetime, and replace the selected one on Start,
1298
	 * and End date&time in edit calendar entry popup.
1299
	 *
1300
	 * @param {mixed} _event
1301
	 * @param {et2_base_widget} _widget widget "select button" in freetime search popup window
1302
	 *
1303
	 */
1304
	freetime_select: function(_event, _widget)
1305
	{
1306
		if (_widget)
1307
		{
1308
			var content = this.et2._inst.widgetContainer.getArrayMgr('content').data;
1309
			// Make the Id from selected button by checking the index
1310
			var selectedId = _widget.id.match(/^select\[([0-9])\]$/i)[1];
1311
1312
			var sTime = this.et2.getWidgetById(selectedId+'start');
1313
1314
			//check the parent window is still open before to try to access it
1315
			if (window.opener && sTime)
1316
			{
1317
				var editWindowObj = window.opener.etemplate2.getByApplication('calendar')[0];
1318
				if (typeof editWindowObj != "undefined")
1319
				{
1320
					var startTime = editWindowObj.widgetContainer.getWidgetById('start');
1321
					var endTime = editWindowObj.widgetContainer.getWidgetById('end');
1322
					if (startTime && endTime)
1323
					{
1324
						startTime.set_value(sTime.get_value());
1325
						endTime.set_value(sTime.get_value());
1326
						endTime.set_value('+'+content['duration']);
1327
					}
1328
				}
1329
			}
1330
			else
1331
			{
1332
				alert(this.egw.lang('The original calendar edit popup is closed!'));
1333
			}
1334
		}
1335
		egw(window).close();
1336
	},
1337
1338
	/**
1339
	 * show/hide the filter of nm list in calendar listview
1340
	 *
1341
	 */
1342
	filter_change: function()
1343
	{
1344
		var view = app.classes.calendar.views['listview'].etemplates[0].widgetContainer || false;
1345
		var filter = view ? view.getWidgetById('nm').getWidgetById('filter') : null;
1346
		var dates = view ? view.getWidgetById('calendar.list.dates') : null;
1347
1348
		// Update state when user changes it
1349
		if(filter)
1350
		{
1351
			app.calendar.state.filter = filter.getValue();
1352
			// Change sort order for before - this is just the UI, server does the query
1353
			if(app.calendar.state.filter == 'before')
1354
			{
1355
				view.getWidgetById('nm').sortBy('cal_start',false, false);
1356
			}
1357
			else
1358
			{
1359
				view.getWidgetById('nm').sortBy('cal_start',true, false);
1360
			}
1361
		}
1362
		else
1363
		{
1364
			delete app.calendar.state.filter;
1365
		}
1366
		if (filter && dates)
1367
		{
1368
			dates.set_disabled(filter.value !== "custom");
1369
			if (filter.value == "custom" && !this.state_update_in_progress)
1370
			{
1371
				// Copy state dates over, without causing [another] state update
1372
				var actual = this.state_update_in_progress;
1373
				this.state_update_in_progress = true;
1374
				view.getWidgetById('startdate').set_value(app.calendar.state.first);
1375
				view.getWidgetById('enddate').set_value(app.calendar.state.last);
1376
				this.state_update_in_progress = actual;
1377
1378
				jQuery(view.getWidgetById('startdate').getDOMNode()).find('input').focus();
1379
			}
1380
		}
1381
	},
1382
1383
	/**
1384
	 * Application links from non-list events
1385
	 *
1386
	 * @param {egwAction} _action
1387
	 * @param {egwActionObject[]} _events
1388
	 */
1389
	action_open: function(_action, _events)
1390
	{
1391
		var id = _events[0].id.split('::');
1392
		if(_action.data.open)
1393
		{
1394
			var open = JSON.parse(_action.data.open) || {};
1395
			var extra = open.extra || '';
1396
1397
			extra = extra.replace(/(\$|%24)app/,id[0]).replace(/(\$|%24)id/,id[1]);
1398
1399
			// Get a little smarter with the context
1400
			if(!extra)
1401
			{
1402
				var context = {};
1403
				if(egw.dataGetUIDdata(_events[0].id) && egw.dataGetUIDdata(_events[0].id).data)
1404
				{
1405
					// Found data in global cache
1406
					context = egw.dataGetUIDdata(_events[0].id).data;
1407
					extra = {};
1408
				}
1409
				else if (_events[0].iface.getWidget() && _events[0].iface.getWidget().instanceOf(et2_valueWidget))
1410
				{
1411
					// Able to extract something from the widget
1412
					context = _events[0].iface.getWidget().getValue ?
1413
						_events[0].iface.getWidget().getValue() :
1414
						_events[0].iface.getWidget().options.value || {};
1415
					extra = {};
1416
				}
1417
				// Try to pull whatever we can from the event
1418
				else if (jQuery.isEmptyObject(context) && _action.menu_context && (_action.menu_context.event.target))
1419
				{
1420
					var target = _action.menu_context.event.target;
1421
					while(target != null && target.parentNode && jQuery.isEmptyObject(target.dataset))
1422
					{
1423
						target = target.parentNode;
1424
					}
1425
1426
					context = extra = target.dataset;
1427
				}
1428
				if(context.date) extra.date = context.date;
1429
				if(context.app) extra.app = context.app;
1430
				if(context.app_id) extra.app_id = context.app_id;
1431
			}
1432
1433
			this.egw.open(open.id_data||'',open.app,open.type,extra ? extra : context);
0 ignored issues
show
Bug introduced by
The variable context does not seem to be initialized in case !extra on line 1400 is false. Are you sure this can never be the case?
Loading history...
1434
		}
1435
		else if (_action.data.url)
1436
		{
1437
			var url = _action.data.url;
1438
			url = url.replace(/(\$|%24)app/,id[0]).replace(/(\$|%24)id/,id[1]);
1439
			this.egw.open_link(url);
1440
		}
1441
	},
1442
1443
	/**
1444
	 * Context menu action (on a single event) in non-listview to generate ical
1445
	 *
1446
	 * Since nextmatch is all ready to handle that, we pass it through
1447
	 *
1448
	 * @param {egwAction} _action
1449
	 * @param {egwActionObject[]} _events
1450
	 */
1451
	ical: function(_action, _events)
1452
	{
1453
		// Send it through nextmatch
1454
		_action.data.nextmatch = etemplate2.getById('calendar-list').widgetContainer.getWidgetById('nm');
1455
		var ids = {ids:[]};
1456
		for(var i = 0; i < _events.length; i++)
1457
		{
1458
			ids.ids.push(_events[i].id);
1459
		}
1460
		nm_action(_action, _events, null, ids);
1461
	},
1462
1463
	/**
1464
	 * Change status (via AJAX)
1465
	 *
1466
	 * @param {egwAction} _action
1467
	 * @param {egwActionObject} _events
1468
	 */
1469
	status: function(_action, _events)
1470
	{
1471
		// Should be a single event, but we'll do it for all
1472
		for(var i = 0; i < _events.length; i++)
1473
		{
1474
			var event_widget = _events[i].iface.getWidget() || false;
1475
			if(!event_widget) continue;
1476
1477
			event_widget.recur_prompt(jQuery.proxy(function(button_id,event_data) {
1478
				switch(button_id)
1479
				{
1480
					case 'exception':
1481
						egw().json(
1482
							'calendar.calendar_uiforms.ajax_status',
1483
							[event_data.app_id, egw.user('account_id'), _action.data.id]
1484
						).sendRequest(true);
1485
						break;
1486
					case 'series':
1487
					case 'single':
1488
						egw().json(
1489
							'calendar.calendar_uiforms.ajax_status',
1490
							[event_data.id, egw.user('account_id'), _action.data.id]
1491
						).sendRequest(true);
1492
						break;
1493
					case 'cancel':
1494
					default:
1495
						break;
1496
				}
1497
			},this));
1498
		}
1499
1500
	},
1501
1502
	/**
1503
	 * this function try to fix ids which are from integrated apps
1504
	 *
1505
	 * @param {egwAction} _action
1506
	 * @param {egwActionObject[]} _senders
1507
	 */
1508
	cal_fix_app_id: function(_action, _senders)
1509
	{
1510
		var app = 'calendar';
1511
		var id = _senders[0].id;
1512
		var matches = id.match(/^(?:calendar::)?([0-9]+)(:([0-9]+))?$/);
1513
		if (matches)
1514
		{
1515
			id = matches[1];
1516
		}
1517
		else
1518
		{
1519
			matches = id.match(/^([a-z_-]+)([0-9]+)/i);
1520
			if (matches)
1521
			{
1522
				app = matches[1];
1523
				id = matches[2];
1524
			}
1525
		}
1526
		var backup_url = _action.data.url;
1527
1528
		_action.data.url = _action.data.url.replace(/(\$|%24)id/,id);
1529
		_action.data.url = _action.data.url.replace(/(\$|%24)app/,app);
1530
1531
		nm_action(_action, _senders,false,{ids:[id]});
1532
1533
		_action.data.url = backup_url;	// restore url
1534
	},
1535
1536
	/**
1537
	 * Open calendar entry, taking into accout the calendar integration of other apps
1538
	 *
1539
	 * calendar_uilist::get_rows sets var js_calendar_integration object
1540
	 *
1541
	 * @param _action
1542
	 * @param _senders
1543
	 *
1544
	 */
1545
	cal_open: function(_action, _senders)
1546
	{
1547
		// Try for easy way - find a widget
1548
		if(_senders[0].iface.getWidget)
1549
		{
1550
			var widget = _senders[0].iface.getWidget();
1551
			return widget.recur_prompt();
1552
		}
1553
1554
		// Nextmatch in list view does not have a widget, but we can pull
1555
		// the data by ID
1556
		// Check for series
1557
		var id = _senders[0].id;
1558
		var data = egw.dataGetUIDdata(id);
1559
		if (data && data.data)
1560
		{
1561
			et2_calendar_event.recur_prompt(data.data);
1562
			return;
1563
		}
1564
		var matches = id.match(/^(?:calendar::)?([0-9]+):([0-9]+)$/);
1565
1566
		// Check for other app integration data sent from server
1567
		var backup = _action.data;
1568
		if(_action.parent.data && _action.parent.data.nextmatch)
1569
		{
1570
			var js_integration_data = _action.parent.data.nextmatch.options.settings.js_integration_data || this.et2.getArrayMgr('content').data.nm.js_integration_data;
1571
			if(typeof js_integration_data == 'string')
1572
			{
1573
				js_integration_data = JSON.parse(js_integration_data);
1574
			}
1575
		}
1576
		matches = id.match(/^calendar::([a-z_-]+)([0-9]+)/i);
1577
		if (matches && js_integration_data && js_integration_data[matches[1]])
1578
		{
1579
			var app = matches[1];
1580
			_action.data.url = window.egw_webserverUrl+'/index.php?';
1581
			var get_params = js_integration_data[app].edit;
1582
			get_params[js_integration_data[app].edit_id] = matches[2];
1583
			for(var name in get_params)
1584
				_action.data.url += name+"="+encodeURIComponent(get_params[name])+"&";
1585
1586
			if (js_integration_data[app].edit_popup)
1587
			{
1588
				egw.open_link(_action.data.url,'_blank',js_integration_data[app].edit_popup,app);
1589
1590
				_action.data = backup;	// restore url, width, height, nm_action
1591
				return;
1592
			}
1593
		}
1594
		else
1595
		{
1596
			// Other app integration using link registry
1597
			var data = egw.dataGetUIDdata(_senders[0].id);
1598
			if(data && data.data)
1599
			{
1600
				return egw.open(data.data.app_id, data.data.app, 'edit');
1601
			}
1602
		}
1603
		// Regular, single event
1604
		egw.open(id.replace(/^calendar::/g,''),'calendar','edit');
1605
	},
1606
1607
	/**
1608
	 * Delete (a single) calendar entry over ajax.
1609
	 *
1610
	 * Used for the non-list views
1611
	 *
1612
	 * @param {egwAction} _action
1613
	 * @param {egwActionObject} _events
1614
	 */
1615
	delete: function(_action, _events)
1616
	{
1617
		// Should be a single event, but we'll do it for all
1618
		for(var i = 0; i < _events.length; i++)
1619
		{
1620
			var event_widget = _events[i].iface.getWidget() || false;
1621
			if(!event_widget) continue;
1622
1623
			event_widget.recur_prompt(jQuery.proxy(function(button_id,event_data) {
1624
				switch(button_id)
1625
				{
1626
					case 'exception':
1627
						egw().json(
1628
							'calendar.calendar_uiforms.ajax_delete',
1629
							[event_data.app_id]
1630
						).sendRequest(true);
1631
						break;
1632
					case 'series':
1633
					case 'single':
1634
						egw().json(
1635
							'calendar.calendar_uiforms.ajax_delete',
1636
							[event_data.id]
1637
						).sendRequest(true);
1638
						break;
1639
					case 'cancel':
1640
					default:
1641
						break;
1642
				}
1643
			},this));
1644
		}
1645
	},
1646
1647
	/**
1648
	 * Delete calendar entry, asking if you want to delete series or exception
1649
	 *
1650
	 * Used for nextmatch
1651
	 *
1652
	 * @param _action
1653
	 * @param _senders
1654
	 */
1655
	cal_delete: function(_action, _senders)
1656
	{
1657
		var backup = _action.data;
1658
		var matches = false;
1659
1660
		// Loop so we ask if any of the selected entries is part of a series
1661
		for(var i = 0; i < _senders.length; i++)
1662
		{
1663
			var id = _senders[i].id;
1664
			if(!matches)
1665
			{
1666
				matches = id.match(/^(?:calendar::)?([0-9]+):([0-9]+)$/);
1667
			}
1668
		}
1669
		if (matches)
1670
		{
1671
			var popup = jQuery('#calendar-list_delete_popup').get(0);
1672
			if (typeof popup != 'undefined')
1673
			{
1674
				// nm action - show popup
1675
				nm_open_popup(_action,_senders);
1676
			}
1677
			return;
1678
		}
1679
1680
		nm_action(_action, _senders);
1681
	},
1682
1683
	/**
1684
	 * Confirmation dialog for moving a series entry
1685
	 *
1686
	 * @param {object} _DOM
1687
	 * @param {et2_widget} _button button Save | Apply
1688
	 */
1689
	move_edit_series: function(_DOM,_button)
1690
	{
1691
		var content = this.et2.getArrayMgr('content').data;
1692
		var start_date = this.et2.getWidgetById('start').get_value();
1693
		var end_date = this.et2.getWidgetById('end').get_value();
1694
		var whole_day = this.et2.getWidgetById('whole_day');
1695
		var duration = ''+this.et2.getWidgetById('duration').get_value();
1696
		var is_whole_day = whole_day && whole_day.get_value() == whole_day.options.selected_value;
1697
		var button = _button;
1698
		var that = this;
1699
1700
		var instance_date = window.location.search.match(/date=(\d{4}-\d{2}-\d{2}(?:.+Z)?)/);
1701
		if(instance_date && instance_date.length && instance_date[1])
1702
		{
1703
			instance_date = new Date(unescape(instance_date[1]));
1704
			instance_date.setUTCMinutes(instance_date.getUTCMinutes() +instance_date.getTimezoneOffset());
1705
		}
1706
		if (typeof content != 'undefined' && content.id != null &&
1707
			typeof content.recur_type != 'undefined' && content.recur_type != null && content.recur_type != 0
1708
		)
1709
		{
1710
			if (content.start != start_date ||
1711
				content.whole_day != is_whole_day ||
1712
				(duration && ''+content.duration != duration ||
1713
				// End date might ignore seconds, and be 59 seconds off for all day events
1714
				!duration && Math.abs(new Date(end_date) - new Date(content.end)) > 60000)
1715
			)
1716
			{
1717
				et2_calendar_event.series_split_prompt(
1718
					content, instance_date, function(_button_id)
1719
					{
1720
						if (_button_id == et2_dialog.OK_BUTTON)
1721
						{
1722
							that.et2._inst.submit(button);
1723
1724
						}
1725
					}
1726
				);
1727
			}
1728
			else
1729
			{
1730
				return true;
1731
			}
1732
		}
1733
		else
1734
		{
1735
			return true;
1736
		}
1737
	},
1738
1739
	/**
1740
	 * Sidebox merge
1741
	 *
1742
	 * Manage the state and pass the request to the correct place.  Since the nextmatch
1743
	 * and the sidebox have different ideas of the 'current' timespan (sidebox
1744
	 * always has a start and end date) we need to call merge on the nextmatch
1745
	 * if the current view is listview, so the user gets the results they expect.
1746
	 *
1747
	 * @param {Event} event UI event
1748
	 * @param {et2_widget} widget Should be the merge selectbox
1749
	 */
1750
	sidebox_merge: function(event, widget)
1751
	{
1752
		if(!widget || !widget.getValue()) return false;
1753
1754
		if(this.state.view == 'listview')
1755
		{
1756
			// If user is looking at the list, pretend they used the context
1757
			// menu and process it through the nextmatch
1758
			var nm = etemplate2.getById('calendar-list').widgetContainer.getWidgetById('nm') || false;
1759
			var selected = nm ? nm.controller._objectManager.getSelectedLinks() : [];
1760
			var action = nm.controller._actionManager.getActionById('document_'+widget.getValue());
1761
			if(nm && (!selected || !selected.length))
1762
			{
1763
				nm.controller._selectionMgr.selectAll(true);
1764
			}
1765
			if(action && selected)
1766
			{
1767
				action.execute(selected);
1768
			}
1769
		}
1770
		else
1771
		{
1772
			// Set the hidden inputs to the current time span & submit
1773
			widget.getRoot().getWidgetById('first').set_value(app.calendar.state.first);
1774
			widget.getRoot().getWidgetById('last').set_value(app.calendar.state.last);
1775
			widget.getInstanceManager().postSubmit();
1776
		}
1777
		window.setTimeout(function() {widget.set_value('');},100);
1778
1779
		return false;
1780
	},
1781
1782
	/**
1783
	 * Method to set state for JSON requests (jdots ajax_exec or et2 submits can NOT use egw.js script tag)
1784
	 *
1785
	 * @param {object} _state
1786
	 */
1787
	set_state: function(_state)
1788
	{
1789
		if (typeof _state == 'object')
1790
		{
1791
			// If everything is loaded, handle the changes
1792
			if(this.sidebox_et2 !== null)
1793
			{
1794
				this.update_state(_state);
1795
			}
1796
			else
1797
			{
1798
				// Things aren't loaded yet, just set it
1799
				this.state = _state;
1800
			}
1801
		}
1802
	},
1803
1804
	/**
1805
	 * Change only part of the current state.
1806
	 *
1807
	 * The passed state options (filters) are merged with the current state, so
1808
	 * this is the one that should be used for most calls, as setState() requires
1809
	 * the complete state.
1810
	 *
1811
	 * @param {Object} _set New settings
1812
	 */
1813
	update_state: function update_state(_set)
1814
	{
1815
		// Make sure we're running in top window
1816
		if(window !== window.top && window.top.app.calendar)
1817
		{
1818
			return window.top.app.calendar.update_state(_set);
1819
		}
1820
		if(this.state_update_in_progress) return;
1821
1822
		var changed = [];
1823
		var new_state = jQuery.extend({}, this.state);
1824
		if (typeof _set === 'object')
1825
		{
1826
			for(var s in _set)
1827
			{
1828
				if (new_state[s] !== _set[s] && (typeof new_state[s] == 'string' || typeof new_state[s] !== 'string' && new_state[s]+'' !== _set[s]+''))
1829
				{
1830
					changed.push(s + ': ' + new_state[s] + ' -> ' + _set[s]);
1831
					new_state[s] = _set[s];
1832
				}
1833
			}
1834
		}
1835
		if(changed.length && !this.state_update_in_progress)
1836
		{
1837
			// This activates calendar app if you call setState from a different app
1838
			// such as home.  If we change state while not active, sizing is wrong.
1839
			if(typeof framework !== 'undefined' && framework.applications.calendar && framework.applications.calendar.hasSideboxMenuContent)
1840
			{
1841
				framework.setActiveApp(framework.applications.calendar);
1842
			}
1843
1844
			console.log('Calendar state changed',changed.join("\n"));
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1845
			// Log
1846
			this.egw.debug('navigation','Calendar state changed', changed.join("\n"));
1847
			this.setState({state: new_state});
1848
		}
1849
	},
1850
1851
	/**
1852
	 * Return state object defining current view
1853
	 *
1854
	 * Called by favorites to query current state.
1855
	 *
1856
	 * @return {object} description
1857
	 */
1858
	getState: function getState()
1859
	{
1860
		var state = jQuery.extend({},this.state);
1861
1862
		if (!state)
1863
		{
1864
			var egw_script_tag = document.getElementById('egw_script_id');
1865
			state = egw_script_tag.getAttribute('data-calendar-state');
1866
			state = state ? JSON.parse(state) : {};
1867
		}
1868
1869
		// Don't store current user in state to allow admins to create favourites for all
1870
		// Should make no difference for normal users.
1871
		if(state.owner == egw.user('account_id'))
1872
		{
1873
			// 0 is always the current user, so if an admin creates a default favorite,
1874
			// it will work for other users too.
1875
			state.owner = 0;
1876
		}
1877
1878
		// Keywords are only for list view
1879
		if(state.view == 'listview')
1880
		{
1881
			var listview = app.classes.calendar.views.listview.etemplates[0] &&
1882
				app.classes.calendar.views.listview.etemplates[0].widgetContainer &&
1883
				app.classes.calendar.views.listview.etemplates[0].widgetContainer.getWidgetById('nm');
1884
			if(listview && listview.activeFilters && listview.activeFilters.search)
1885
			{
1886
				state.keywords = listview.activeFilters.search;
1887
			}
1888
		}
1889
1890
		// Don't store date or first and last
1891
		delete state.date;
1892
		delete state.first;
1893
		delete state.last;
1894
		delete state.startdate;
1895
		delete state.enddate;
1896
		delete state.start_date;
1897
		delete state.end_date;
1898
1899
		return state;
1900
	},
1901
1902
	/**
1903
	 * Set a state previously returned by getState
1904
	 *
1905
	 * Called by favorites to set a state saved as favorite.
1906
	 *
1907
	 * @param {object} state containing "name" attribute to be used as "favorite" GET parameter to a nextmatch
1908
	 */
1909
	setState: function setState(state)
1910
	{
1911
		// State should be an object, not a string, but we'll parse
1912
		if(typeof state == "string")
1913
		{
1914
			if(state.indexOf('{') != -1 || state =='null')
1915
			{
1916
				state = JSON.parse(state);
1917
			}
1918
		}
1919
		if(typeof state.state !== 'object' || !state.state.view)
1920
		{
1921
			state.state = {view: 'week'};
1922
		}
1923
		// States with no name (favorites other than No filters) default to
1924
		// today.  Applying a favorite should keep the current date.
1925
		if(!state.state.date)
1926
		{
1927
			state.state.date = state.name ? this.state.date : new Date();
1928
		}
1929
		if(typeof state.state.weekend == 'undefined')
1930
		{
1931
			state.state.weekend = true;
1932
		}
1933
1934
		// Hide other views
1935
		var view = app.classes.calendar.views[state.state.view];
1936
		for(var _view in app.classes.calendar.views)
1937
		{
1938
			if(state.state.view != _view && app.classes.calendar.views[_view])
1939
			{
1940
				for(var i = 0; i < app.classes.calendar.views[_view].etemplates.length; i++)
1941
				{
1942
					if(typeof app.classes.calendar.views[_view].etemplates[i] !== 'string' &&
1943
						view.etemplates.indexOf(app.classes.calendar.views[_view].etemplates[i]) == -1)
1944
					{
1945
						jQuery(app.classes.calendar.views[_view].etemplates[i].DOMContainer).hide();
1946
					}
1947
				}
1948
			}
1949
		}
1950
		if(this.sidebox_et2)
1951
		{
1952
			jQuery(this.sidebox_et2.getInstanceManager().DOMContainer).hide();
1953
		}
1954
1955
		// Check for valid cache
1956
		var cachable_changes = ['date','weekend','view','days','planner_view','sortby'];
1957
		var keys = jQuery.unique(Object.keys(this.state).concat(Object.keys(state.state)));
1958
		for(var i = 0; i < keys.length; i++)
1959
		{
1960
			var s = keys[i];
1961
			if (this.state[s] !== state.state[s])
1962
			{
1963
				if(cachable_changes.indexOf(s) === -1)
1964
				{
1965
					// Expire daywise cache
1966
					var daywise = egw.dataKnownUIDs(app.classes.calendar.DAYWISE_CACHE_ID);
1967
1968
					// Can't delete from here, as that would disconnect the existing widgets listening
1969
					for(var i = 0; i < daywise.length; i++)
1970
					{
1971
						egw.dataStoreUID(app.classes.calendar.DAYWISE_CACHE_ID + '::' + daywise[i],null);
1972
					}
1973
					break;
1974
				}
1975
			}
1976
		}
1977
1978
		// Check for a supported client-side view
1979
		if(app.classes.calendar.views[state.state.view] &&
1980
			// Check that the view is instanciated
1981
			typeof app.classes.calendar.views[state.state.view].etemplates[0] !== 'string' && app.classes.calendar.views[state.state.view].etemplates[0].widgetContainer
1982
		)
1983
		{
1984
			// Doing an update - this includes the selected view, and the sidebox
1985
			// We set a flag to ignore changes from the sidebox which would
1986
			// cause infinite loops.
1987
			this.state_update_in_progress = true;
1988
1989
			// Sanitize owner so it's always an array
1990
			if(state.state.owner === null || !state.state.owner ||
1991
				(typeof state.state.owner.length != 'undefined' && state.state.owner.length == 0)
1992
			)
1993
			{
1994
				state.state.owner = undefined;
1995
			}
1996
			switch(typeof state.state.owner)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
1997
			{
1998
				case 'undefined':
1999
					state.state.owner = [this.egw.user('account_id')];
2000
					break;
2001
				case 'string':
2002
					state.state.owner = state.state.owner.split(',');
2003
					break;
2004
				case 'number':
2005
					state.state.owner = [state.state.owner];
2006
					break;
2007
				case 'object':
2008
					// An array-like Object or an Array?
2009
					if(!state.state.owner.filter)
2010
					{
2011
						state.state.owner = jQuery.map(state.state.owner, function(owner) {return owner;});
2012
					}
2013
			}
2014
			// Remove duplicates
2015
			state.state.owner = state.state.owner.filter(function(value, index, self) {
2016
				return self.indexOf(value) === index;
2017
			});
2018
			// Make sure they're all strings
2019
			state.state.owner = state.state.owner.map(function(owner) { return ''+owner;});
2020
			// Keep sort order
2021
			if(typeof this.state.owner === 'object')
2022
			{
2023
				var owner = [];
2024
				this.state.owner.forEach(function(key) {
2025
					var found = false;
2026
					state.state.owner = state.state.owner.filter(function(item) {
2027
						if(!found && item == key) {
2028
							owner.push(item);
2029
							found = true;
2030
							return false;
2031
						} else
2032
							return true;
2033
					});
2034
				});
2035
				// Add in any new owners
2036
				state.state.owner = owner.concat(state.state.owner);
2037
			}
2038
			if (state.state.owner.indexOf('0') >= 0)
2039
			{
2040
				state.state.owner[state.state.owner.indexOf('0')] = this.egw.user('account_id');
2041
			}
2042
2043
			// Show the correct number of grids
2044
			var grid_count = 0;
2045
			switch(state.state.view)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
2046
			{
2047
				case 'day':
2048
					grid_count = 1;
2049
					break;
2050
				case 'day4':
2051
				case 'week':
2052
					grid_count = state.state.owner.length >= parseInt(this.egw.preference('week_consolidate','calendar')) ? 1 : state.state.owner.length;
2053
					break;
2054
				case 'weekN':
2055
					grid_count = parseInt(this.egw.preference('multiple_weeks','calendar')) || 3;
2056
					break;
2057
				// Month is calculated individually for the month
2058
			}
2059
2060
			var grid = view.etemplates[0].widgetContainer.getWidgetById('view');
2061
2062
			// Show the templates for the current view
2063
			// Needs to be visible while updating so sizing works
2064
			for(var i = 0; i < view.etemplates.length; i++)
2065
			{
2066
				jQuery(view.etemplates[i].DOMContainer).show();
2067
			}
2068
2069
			/*
2070
			If the count is different, we need to have the correct number
2071
			If the count is > 1, it's either because there are multiple date spans (weekN, month) and we need the correct span
2072
			per row, or there are multiple owners and we need the correct owner per row.
2073
			*/
2074
			if(grid)
2075
			{
2076
				// Show loading div to hide redrawing
2077
				egw.loading_prompt(
2078
					this.appname,true,egw.lang('please wait...'),
2079
					typeof framework !== 'undefined' ? framework.applications.calendar.tab.contentDiv : false,
2080
					egwIsMobile()?'horizental':'spinner'
2081
				);
2082
2083
				var loading = false;
2084
2085
2086
				var value = [];
2087
				state.state.first = view.start_date(state.state).toJSON();
2088
				// We'll modify this one, so it needs to be a new object
2089
				var date = new Date(state.state.first);
2090
2091
				// Hide all but the first day header
2092
				jQuery(grid.getDOMNode()).toggleClass(
2093
					'hideDayColHeader',
2094
					state.state.view == 'week' || state.state.view == 'day4'
2095
				);
2096
2097
				// Determine the different end date & varying values
2098
				switch(state.state.view)
2099
				{
2100
					case 'month':
2101
						var end = state.state.last = view.end_date(state.state);
2102
						grid_count = Math.ceil((end - date) / (1000 * 60 * 60 * 24) / 7);
2103
						// fall through
2104
					case 'weekN':
2105
						for(var week = 0; week < grid_count; week++)
2106
						{
2107
							var val = {
2108
								id: app.classes.calendar._daywise_cache_id(date,state.state.owner),
2109
								start_date: date.toJSON(),
2110
								end_date: new Date(date.toJSON()),
2111
								owner: state.state.owner
2112
							};
2113
							val.end_date.setUTCHours(24*7-1);
2114
							val.end_date.setUTCMinutes(59);
2115
							val.end_date.setUTCSeconds(59);
2116
							val.end_date = val.end_date.toJSON();
2117
							value.push(val);
2118
							date.setUTCHours(24*7);
2119
						}
2120
						state.state.last=val.end_date;
2121
						break;
2122
					case 'day':
2123
						var end = state.state.last = view.end_date(state.state).toJSON();
2124
							value.push({
2125
							id: app.classes.calendar._daywise_cache_id(date,state.state.owner),
2126
								start_date: state.state.first,
2127
								end_date: state.state.last,
2128
								owner: view.owner(state.state)
2129
							});
2130
						break;
2131
					default:
2132
						var end = state.state.last = view.end_date(state.state).toJSON();
2133
						for(var owner = 0; owner < grid_count && owner < state.state.owner.length; owner++)
0 ignored issues
show
Bug introduced by
The variable owner does not seem to be initialized in case typeof this.state.owner === "object" on line 2021 is false. Are you sure this can never be the case?
Loading history...
introduced by
This code is unreachable and can thus be removed without consequences.
Loading history...
2134
						{
2135
							var _owner = grid_count > 1 ? state.state.owner[owner] || 0 : state.state.owner;
2136
							value.push({
2137
								id: app.classes.calendar._daywise_cache_id(date,_owner),
2138
								start_date: date,
2139
								end_date: end,
2140
								owner: _owner
2141
							});
2142
						}
2143
						break;
2144
				}
2145
				// If we have cached data for the timespan, pass it along
2146
				// Single day with multiple owners still needs owners split to satisfy
2147
				// caching keys, otherwise they'll fetch & cache consolidated
2148
				if(state.state.view == 'day' && state.state.owner.length < parseInt(this.egw.preference('day_consolidate','calendar')))
2149
				{
2150
					var day_value = [];
2151
					for(var i = 0; i < state.state.owner.length; i++)
2152
					{
2153
						day_value.push({
2154
							start_date: state.state.first,
2155
							end_date: state.state.last,
2156
							owner: state.state.owner[i]
2157
						});
2158
					}
2159
					loading = this._need_data(day_value,state.state);
2160
				}
2161
				else
2162
				{
2163
					loading = this._need_data(value,state.state);
2164
				}
2165
2166
				var row_index = 0;
2167
2168
				// Find any matching, existing rows - they can be kept
2169
				grid.iterateOver(function(widget) {
2170
					for(var i = 0; i < value.length; i++)
2171
					{
2172
						if(widget.id == value[i].id)
2173
						{
2174
							// Keep it, but move it
2175
							if(i > row_index)
2176
							{
2177
								for(var j = i-row_index; j > 0; j--)
2178
								{
2179
									// Move from the end to the start
2180
									grid._children.unshift(grid._children.pop());
2181
2182
									// Swap DOM nodes
2183
									var a = grid._children[0].getDOMNode().parentNode.parentNode;
2184
									var a_scroll = jQuery('.calendar_calTimeGridScroll',a).scrollTop();
2185
									var b = grid._children[1].getDOMNode().parentNode.parentNode;
2186
									a.parentNode.insertBefore(a,b);
2187
2188
									// Moving nodes changes scrolling, so set it back
2189
									var a_scroll = jQuery('.calendar_calTimeGridScroll',a).scrollTop(a_scroll);
2190
								}
2191
							}
2192
							else if (row_index > i)
2193
							{
2194
								// Swap DOM nodes
2195
								var a = grid._children[row_index].getDOMNode().parentNode.parentNode;
2196
								var a_scroll = jQuery('.calendar_calTimeGridScroll',a).scrollTop();
2197
								var b = grid._children[i].getDOMNode().parentNode.parentNode;
2198
2199
								// Simple scroll forward, put top on the bottom
2200
								// This makes it faster if they scroll back next
2201
								if(i==0 && row_index == 1)
2202
								{
2203
									jQuery(b).appendTo(b.parentNode);
2204
									grid._children.push(grid._children.shift());
2205
								}
2206
								else
2207
								{
2208
									grid._children.splice(i,0,widget);
2209
									grid._children.splice(row_index+1,1);
2210
									a.parentNode.insertBefore(a,b);
2211
								}
2212
2213
								// Moving nodes changes scrolling, so set it back
2214
								var a_scroll = jQuery('.calendar_calTimeGridScroll',a).scrollTop(a_scroll);
2215
							}
2216
							break;
2217
						}
2218
					}
2219
					row_index++;
2220
				},this,et2_calendar_view);
2221
				row_index = 0;
2222
2223
				// Set rows that need it
2224
				grid.iterateOver(function(widget) {
2225
					if(row_index < value.length)
2226
					{
2227
						widget.set_disabled(false);
2228
					}
2229
					else
2230
					{
2231
						widget.set_disabled(true);
2232
						return;
2233
					}
2234
					if(widget.set_show_weekend)
2235
					{
2236
						widget.set_show_weekend(view.show_weekend(state.state));
2237
					}
2238
					if(widget.set_granularity)
2239
					{
2240
						if(widget.loader) widget.loader.show();
2241
						widget.set_granularity(view.granularity(state.state));
2242
					}
2243
					if(widget.id == value[row_index].id &&
2244
						widget.get_end_date().getUTCFullYear() == value[row_index].end_date.substring(0,4) &&
2245
						widget.get_end_date().getUTCMonth()+1 == value[row_index].end_date.substring(5,7) &&
2246
						widget.get_end_date().getUTCDate() == value[row_index].end_date.substring(8,10)
2247
					)
2248
					{
2249
						// Do not need to re-set this row, but we do need to re-do
2250
						// the times, as they may have changed
2251
						widget.resizeTimes();
2252
2253
						// Hide loader
2254
						widget.loader.hide();
2255
						row_index++;
2256
						return;
2257
					}
2258
					if(widget.set_value)
2259
					{
2260
						widget.set_value(value[row_index++]);
2261
					}
2262
				},this, et2_calendar_view);
2263
			}
2264
			else if(state.state.view !== 'listview')
2265
			{
2266
				// Simple, easy case - just one widget for the selected time span. (planner)
2267
				// Update existing view's special attribute filters, defined in the view list
2268
				for(var updater in view)
2269
				{
2270
					if(typeof view[updater] === 'function')
2271
					{
2272
						var value = view[updater].call(this,state.state);
2273
						if(updater === 'start_date') state.state.first = this.date.toString(value);
2274
						if(updater === 'end_date') state.state.last = this.date.toString(value);
2275
2276
						// Set value
2277
						for(var i = 0; i < view.etemplates.length; i++)
2278
						{
2279
							view.etemplates[i].widgetContainer.iterateOver(function(widget) {
2280
								if(typeof widget['set_'+updater] === 'function')
2281
								{
2282
									widget['set_'+updater](value);
2283
								}
2284
							}, this, et2_calendar_view);
2285
						}
2286
					}
2287
				}
2288
				var value = [{start_date: state.state.first, end_date: state.state.last}];
2289
				loading = this._need_data(value,state.state);
2290
			}
2291
			// Include first & last dates in state, mostly for server side processing
2292
			if(state.state.first && state.state.first.toJSON) state.state.first = state.state.first.toJSON();
2293
			if(state.state.last && state.state.last.toJSON) state.state.last = state.state.last.toJSON();
2294
2295
			// Toggle todos
2296
			if((state.state.view == 'day' || this.state.view == 'day') && jQuery(view.etemplates[0].DOMContainer).is(':visible'))
2297
			{
2298
				if(state.state.view == 'day' && state.state.owner.length === 1 && !isNaN(state.state.owner) && state.state.owner[0] >= 0 && !egwIsMobile())
2299
				{
2300
					view.etemplates[0].widgetContainer.iterateOver(function(w) {
2301
						w.set_width(jQuery(view.etemplates[0].DOMContainer).width() * 0.69);
2302
					},this,et2_calendar_timegrid);
2303
2304
					jQuery(view.etemplates[1].DOMContainer).css({"left":"69%", "height":(jQuery(framework.tabsUi.activeTab.contentDiv).height()-30)+'px'});
2305
					// TODO: Maybe some caching here
2306
					this.egw.jsonq('calendar_uiviews::ajax_get_todos', [state.state.date, state.state.owner[0]], function(data) {
2307
						this.getWidgetById('label').set_value(data.label||'');
2308
						this.getWidgetById('todos').set_value({content:data.todos||''});
2309
					},view.etemplates[1].widgetContainer);
2310
					view.etemplates[0].resize();
2311
				}
2312
				else
2313
				{
2314
					jQuery(app.classes.calendar.views.day.etemplates[1].DOMContainer).css("left","100%");
2315
					jQuery(app.classes.calendar.views.day.etemplates[1].DOMContainer).hide();
2316
					jQuery(app.classes.calendar.views.day.etemplates[0].DOMContainer).css("width","100%");
2317
					view.etemplates[0].widgetContainer.iterateOver(function(w) {
2318
						w.set_width('100%');
2319
					},this,et2_calendar_timegrid);
2320
				}
2321
			}
2322
			else if(jQuery(view.etemplates[0].DOMContainer).is(':visible'))
2323
			{
2324
				jQuery(view.etemplates[0].DOMContainer).css("width","");
2325
				view.etemplates[0].widgetContainer.iterateOver(function(w) {
2326
					w.set_width('100%');
2327
				},this,et2_calendar_timegrid);
2328
			}
2329
2330
			// List view (nextmatch) has slightly different fields
2331
			if(state.state.view === 'listview')
2332
			{
2333
				state.state.startdate = state.state.date;
2334
				if(state.state.startdate.toJSON)
2335
				{
2336
					state.state.startdate = state.state.startdate.toJSON();
2337
				}
2338
2339
				if(state.state.end_date)
2340
				{
2341
					state.state.enddate = state.state.end_date;
2342
				}
2343
				if(state.state.enddate && state.state.enddate.toJSON)
2344
				{
2345
					state.state.enddate = state.state.enddate.toJSON();
2346
				}
2347
				state.state.col_filter = {participant: state.state.owner};
2348
				state.state.search = state.state.keywords ? state.state.keywords : state.state.search;
2349
2350
2351
				var nm = view.etemplates[0].widgetContainer.getWidgetById('nm');
2352
2353
				// 'Custom' filter needs an end date
2354
				if(nm.activeFilters.filter === 'custom' && !state.state.end_date)
2355
				{
2356
					state.state.enddate = state.state.last;
2357
				}
2358
				if(state.state.enddate && state.state.startdate && state.state.startdate > state.state.enddate)
2359
				{
2360
					state.state.enddate = state.state.startdate;
2361
				}
2362
				nm.applyFilters(state.state);
2363
2364
				// Try to keep last value up to date with what's in nextmatch
2365
				if(nm.activeFilters.enddate)
2366
				{
2367
					this.state.last = nm.activeFilters.enddate;
2368
				}
2369
				// Updates the display of start & end date
2370
				this.filter_change();
2371
			}
2372
			else
2373
			{
2374
				// Turn off nextmatch's automatic stuff - it won't work while it
2375
				// is hidden, and can cause an infinite loop as it tries to layout.
2376
				// (It will automatically re-start when shown)
2377
				try
2378
				{
2379
					var nm = app.classes.calendar.views.listview.etemplates[0].widgetContainer.getWidgetById('nm');
2380
					nm.controller._grid.doInvalidate = false;
2381
				} catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
2382
				// Other views do not search
2383
				delete state.state.keywords;
2384
			}
2385
			this.state = jQuery.extend({},state.state);
2386
2387
			/* Update re-orderable calendars */
2388
			this._sortable();
2389
2390
			/* Update sidebox widgets to show current value*/
2391
			if(this.sidebox_hooked_templates.length)
2392
			{
2393
				for(var j = 0; j < this.sidebox_hooked_templates.length; j++)
2394
				{
2395
					var sidebox = this.sidebox_hooked_templates[j];
2396
					// Remove any destroyed or not valid templates
2397
					if(!sidebox.getInstanceManager || !sidebox.getInstanceManager())
2398
					{
2399
						this.sidebox_hooked_templates.splice(j,1,0);
2400
						continue;
2401
					}
2402
					sidebox.iterateOver(function(widget) {
2403
						if(widget.id == 'view')
2404
						{
2405
							// View widget has a list of state settings, which require special handling
2406
							for(var i = 0; i < widget.options.select_options.length; i++)
2407
							{
2408
								var option_state = JSON.parse(widget.options.select_options[i].value) || [];
2409
								var match = true;
2410
								for(var os_key in option_state)
2411
								{
2412
									// Sometimes an optional state variable is not yet defined (sortby, days, etc)
2413
									match = match && (option_state[os_key] == this.state[os_key] || typeof this.state[os_key] == 'undefined');
2414
								}
2415
								if(match)
2416
								{
2417
									widget.set_value(widget.options.select_options[i].value);
2418
									return;
2419
								}
2420
							}
2421
						}
2422
						else if (widget.id == 'keywords')
2423
						{
2424
							widget.set_value('');
2425
						}
2426
						else if(typeof state.state[widget.id] !== 'undefined' && state.state[widget.id] != widget.getValue())
2427
						{
2428
							// Update widget.  This may trigger an infinite loop of
2429
							// updates, so we do it after changing this.state and set a flag
2430
							try
2431
							{
2432
								widget.set_value(state.state[widget.id]);
2433
							}
2434
							catch(e)
2435
							{
2436
								widget.set_value('');
2437
							}
2438
						}
2439
						else if (widget.instanceOf(et2_inputWidget) && typeof state.state[widget.id] == 'undefined')
2440
						{
2441
							// No value, clear it
2442
							widget.set_value('');
2443
						}
2444
					},this,et2_valueWidget);
2445
				}
2446
			}
2447
2448
			// If current state matches a favorite, hightlight it
2449
			this.highlight_favorite();
2450
2451
			// Update app header
2452
			this.set_app_header(view.header(state.state));
2453
2454
			// Reset auto-refresh timer
2455
			this._set_autorefresh();
2456
2457
			// Sidebox is updated, we can clear the flag
2458
			this.state_update_in_progress = false;
2459
2460
			// Update saved state in preferences
2461
			var save = {};
2462
			for(var i = 0; i < this.states_to_save.length; i++)
2463
			{
2464
				save[this.states_to_save[i]] = this.state[this.states_to_save[i]];
2465
			}
2466
			egw.set_preference('calendar','saved_states', save);
2467
2468
			// Trigger resize to get correct sizes, as they may have sized while
2469
			// hidden
2470
			for(var i = 0; i < view.etemplates.length; i++)
2471
			{
2472
				view.etemplates[i].resize();
2473
			}
2474
2475
			// If we need to fetch data from the server, it will hide the loader
2476
			// when done but if everything is in the cache, hide from here.
2477
			if(!loading)
2478
			{
2479
				window.setTimeout(jQuery.proxy(function() {
2480
2481
					egw.loading_prompt(this.appname,false);
2482
				},this),500);
2483
			}
2484
2485
			return;
2486
		}
2487
		// old calendar state handling on server-side (incl. switching to and from listview)
2488
		var menuaction = 'calendar.calendar_uiviews.index';
2489
		if (typeof state.state != 'undefined' && (typeof state.state.view == 'undefined' || state.state.view == 'listview'))
2490
		{
2491
			if (state.name)
2492
			{
2493
				// 'blank' is the special name for no filters, send that instead of the nice translated name
2494
				state.state.favorite = jQuery.isEmptyObject(state) || jQuery.isEmptyObject(state.state||state.filter) ? 'blank' : state.name.replace(/[^A-Za-z0-9-_]/g, '_');
2495
				// set date for "No Filter" (blank) favorite to todays date
2496
				if (state.state.favorite == 'blank')
2497
					state.state.date = jQuery.datepicker.formatDate('yymmdd', new Date);
2498
			}
2499
			menuaction = 'calendar.calendar_uilist.listview';
2500
			state.state.ajax = 'true';
2501
			// check if we already use et2 / are in listview
2502
			if (this.et2 || etemplate2 && etemplate2.getByApplication('calendar'))
2503
			{
2504
				// current calendar-code can set regular calendar states only via a server-request :(
2505
				// --> check if we only need to set something which can be handeled by nm internally
2506
				// or we need a redirect
2507
				// ToDo: pass them via nm's get_rows call to server (eg. by passing state), so we dont need a redirect
2508
				var current_state = this.getState();
2509
				var need_redirect = false;
2510
				for(var attr in current_state)
2511
				{
2512
					switch(attr)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
2513
					{
2514
						case 'cat_id':
2515
						case 'owner':
2516
						case 'filter':
2517
							if (state.state[attr] != current_state[attr])
2518
							{
2519
								need_redirect = true;
2520
								// reset of attributes managed on server-side
2521
								if (state.state.favorite === 'blank')
2522
								{
2523
									switch(attr)
2524
									{
2525
										case 'cat_id':
2526
											state.state.cat_id = 0;
2527
											break;
2528
										case 'owner':
2529
											state.state.owner = egw.user('account_id');
2530
											break;
2531
										case 'filter':
2532
											state.state.filter = 'default';
2533
											break;
2534
									}
2535
								}
2536
								break;
2537
							}
2538
							break;
2539
2540
						case 'view':
2541
							// "No filter" (blank) favorite: if not in listview --> stay in that view
2542
							if (state.state.favorite === 'blank' && current_state.view != 'listview')
2543
							{
2544
								menuaction = 'calendar.calendar_uiviews.index';
2545
								delete state.state.ajax;
2546
								need_redirect = true;
2547
							}
2548
					}
2549
				}
2550
				if (!need_redirect)
2551
				{
2552
					return this._super.apply(this, [state]);
2553
				}
2554
			}
2555
		}
2556
		// setting internal state now, that linkHandler does not intercept switching from listview to any old view
2557
		this.state = jQuery.extend({},state.state);
2558
		if(this.sidebox_et2)
2559
		{
2560
			jQuery(this.sidebox_et2.getInstanceManager().DOMContainer).show();
2561
		}
2562
2563
		var query = jQuery.extend({menuaction: menuaction},state.state||{});
2564
2565
		// prepend an owner 0, to reset all owners and not just set given resource type
2566
		if(typeof query.owner != 'undefined')
2567
		{
2568
			query.owner = '0,'+ (typeof query.owner == 'object' ? query.owner.join(',') : (''+query.owner).replace('0,',''));
2569
		}
2570
2571
		this.egw.open_link(this.egw.link('/index.php',query), 'calendar');
2572
2573
		// Stop the normal bubbling if this is called on click
2574
		return false;
2575
	},
2576
2577
	/**
2578
	 * Check to see if any of the selected is an event widget
2579
	 * Used to separate grid actions from event actions
2580
	 *
2581
	 * @param {egwAction} _action
2582
	 * @param {egwActioObject[]} _selected
2583
	 * @returns {boolean} Is any of the selected an event widget
2584
	 */
2585
	is_event: function(_action, _selected)
2586
	{
2587
		var is_widget = false;
2588
		for(var i = 0; i < _selected.length; i++)
2589
		{
2590
			if(_selected[i].iface.getWidget() && _selected[i].iface.getWidget().instanceOf(et2_calendar_event))
2591
			{
2592
				is_widget = true;
2593
			}
2594
2595
			// Also check classes, usually indicating permission
2596
			if(_action.data && _action.data.enableClass)
2597
			{
2598
				is_widget = is_widget && (jQuery( _selected[i].iface.getDOMNode()).hasClass(_action.data.enableClass));
2599
			}
2600
			if(_action.data && _action.data.disableClass)
2601
			{
2602
				is_widget = is_widget && !(jQuery( _selected[i].iface.getDOMNode()).hasClass(_action.data.disableClass));
2603
			}
2604
2605
		}
2606
		return is_widget;
2607
	},
2608
2609
	/**
2610
	 * Enable/Disable custom Date-time for set Alarm
2611
	 *
2612
	 * @param {egw object} _egw
2613
	 * @param {widget object} _widget new_alarm[options] selectbox
2614
	 */
2615
	alarm_custom_date: function (_egw,_widget)
2616
	{
2617
		var alarm_date = this.et2.getWidgetById('new_alarm[date]');
2618
		var alarm_options = _widget || this.et2.getWidgetById('new_alarm[options]');
2619
		var start = this.et2.getWidgetById('start');
2620
2621
		if (alarm_date && alarm_options
2622
					&& start)
2623
		{
2624
			if (alarm_options.get_value() != '0')
2625
			{
2626
				alarm_date.set_class('calendar_alarm_date_display');
2627
			}
2628
			else
2629
			{
2630
				alarm_date.set_class('');
2631
			}
2632
			var startDate = typeof start.get_value != 'undefined'?start.get_value():start.value;
2633
			if (startDate)
2634
			{
2635
				var date = new Date(startDate);
2636
				date.setTime(date.getTime() - 1000 * parseInt(alarm_options.get_value()));
2637
				alarm_date.set_value(date);
2638
			}
2639
		}
2640
	},
2641
2642
	/**
2643
	 * Set alarm options based on WD/Regular event user preferences
2644
	 * Gets fired by wholeday checkbox
2645
	 *
2646
	 * @param {egw object} _egw
2647
	 * @param {widget object} _widget whole_day checkbox
2648
	 */
2649
	set_alarmOptions_WD: function (_egw,_widget)
2650
	{
2651
		var alarm = this.et2.getWidgetById('alarm');
2652
		if (!alarm) return;	// no default alarm
2653
		var content = this.et2.getArrayMgr('content').data;
2654
		var start = this.et2.getWidgetById('start');
2655
		var self= this;
2656
		var time = alarm.cells[1][0].widget;
2657
		var event = alarm.cells[1][1].widget;
2658
		// Convert a seconds of time to a translated label
2659
		var _secs_to_label = function (_secs)
2660
		{
2661
			var label='';
2662
			if (_secs <= 3600)
2663
			{
2664
				label = self.egw.lang('%1 minutes', _secs/60);
2665
			}
2666
			else if(_secs <= 86400)
2667
			{
2668
				label = self.egw.lang('%1 hours', _secs/3600);
2669
			}
2670
			return label;
2671
		};
2672
		if (typeof content['alarm'][1]['default'] == 'undefined')
2673
		{
2674
			// user deleted alarm --> nothing to do
2675
		}
2676
		else
2677
		{
2678
			var def_alarm = this.egw.preference(_widget.get_value() === "true" ?
2679
				'default-alarm-wholeday' : 'default-alarm', 'calendar');
2680
			if (!def_alarm && def_alarm !== 0)	// no alarm
2681
			{
2682
				jQuery('#calendar-edit_alarm > tbody :nth-child(1)').hide();
2683
			}
2684
			else
2685
			{
2686
				jQuery('#calendar-edit_alarm > tbody :nth-child(1)').show();
2687
				start.set_hours(0);
2688
				start.set_minutes(0);
2689
				time.set_value(start.get_value());
2690
				time.set_value('-'+(60 * def_alarm));
2691
				event.set_value(_secs_to_label(60 * def_alarm));
2692
			}
2693
		}
2694
	},
2695
2696
2697
	/**
2698
	 * Clear all calendar data from egw.data cache
2699
	 */
2700
	_clear_cache: function() {
2701
		// Full refresh, clear the caches
2702
		var events = egw.dataKnownUIDs('calendar');
2703
		for(var i = 0; i < events.length; i++)
2704
		{
2705
			egw.dataDeleteUID('calendar::' + events[i]);
2706
		}
2707
		var daywise = egw.dataKnownUIDs(app.classes.calendar.DAYWISE_CACHE_ID);
2708
		for(var i = 0; i < daywise.length; i++)
2709
		{
2710
			// Empty to clear existing widgets
2711
			egw.dataStoreUID(app.classes.calendar.DAYWISE_CACHE_ID + '::' + daywise[i], null);
2712
		}
2713
	},
2714
2715
	/**
2716
	 * Take the date range(s) in the value and decide if we need to fetch data
2717
	 * for the date ranges, or if they're already cached fill them in.
2718
	 *
2719
	 * @param {Object} value
2720
	 * @param {Object} state
2721
	 *
2722
	 * @return {boolean} Data was requested
2723
	 */
2724
	_need_data: function(value, state)
2725
	{
2726
		var need_data = false;
2727
2728
		// Determine if we're showing multiple owners seperate or consolidated
2729
		var seperate_owners = false;
2730
		var last_owner = value.length ? value[0].owner || 0 : 0;
2731
		for(var i = 0; i < value.length && !seperate_owners; i++)
2732
		{
2733
			seperate_owners = seperate_owners || (last_owner !== value[i].owner);
2734
		}
2735
2736
		for(var i = 0; i < value.length; i++)
2737
		{
2738
			var t = new Date(value[i].start_date);
2739
			var end = new Date(value[i].end_date);
2740
			do
2741
			{
2742
				// Cache is by date (and owner, if seperate)
2743
				var date = t.getUTCFullYear() + sprintf('%02d',t.getUTCMonth()+1) + sprintf('%02d',t.getUTCDate());
2744
				var cache_id = app.classes.calendar._daywise_cache_id(date, seperate_owners && value[i].owner ? value[i].owner : state.owner||false);
2745
2746
				if(egw.dataHasUID(cache_id))
2747
				{
2748
					var c = egw.dataGetUIDdata(cache_id);
2749
					if(c.data && c.data !== null)
2750
					{
2751
						// There is data, pass it along now
2752
						value[i][date] = [];
2753
						for(var j = 0; j < c.data.length; j++)
2754
						{
2755
							if(egw.dataHasUID('calendar::'+c.data[j]))
2756
							{
2757
								value[i][date].push(egw.dataGetUIDdata('calendar::'+c.data[j]).data);
2758
							}
2759
							else
2760
							{
2761
								need_data = true;
2762
							}
2763
						}
2764
					}
2765
					else
2766
					{
2767
						need_data = true;
2768
						// Assume it's empty, if there is data it will be filled later
2769
						egw.dataStoreUID(cache_id, []);
2770
					}
2771
				}
2772
				else
2773
				{
2774
					need_data = true;
2775
					// Assume it's empty, if there is data it will be filled later
2776
					egw.dataStoreUID(cache_id, []);
2777
				}
2778
				t.setUTCDate(t.getUTCDate() + 1);
2779
			}
2780
			while(t < end);
2781
2782
			// Some data is missing for the current owner, go get it
2783
			if(need_data && seperate_owners)
2784
			{
2785
				this._fetch_data(
2786
					jQuery.extend({}, state, {owner: value[i].owner}),
2787
					this.sidebox_et2 ? null : this.et2.getInstanceManager()
2788
				);
2789
				need_data = false;
2790
			}
2791
		}
2792
2793
		// Some data was missing, go get it
2794
		if(need_data && !seperate_owners)
2795
		{
2796
			this._fetch_data(
2797
				state,
2798
				this.sidebox_et2 ? null : this.et2.getInstanceManager()
2799
			);
2800
		}
2801
2802
		return need_data;
2803
	},
2804
2805
	/**
2806
	 * Use the egw.data system to get data from the calendar list for the
2807
	 * selected time span.
2808
	 *
2809
	 * As long as the other filters are the same (category, owner, status) we
2810
	 * cache the data.
2811
	 *
2812
	 * @param {Object} state
2813
	 * @param {etemplate2} [instance] If the full calendar app isn't loaded
0 ignored issues
show
Documentation Bug introduced by
The parameter [instance] does not exist. Did you maybe mean instance instead?
Loading history...
2814
	 *	(home app), pass a different instance to use it to get the data
2815
	 * @param {number} [start] Result offset.  Internal use only
0 ignored issues
show
Documentation Bug introduced by
The parameter [start] does not exist. Did you maybe mean start instead?
Loading history...
2816
	 */
2817
	_fetch_data: function(state, instance, start)
2818
	{
2819
		if(!this.sidebox_et2 && !instance)
2820
		{
2821
			return;
2822
		}
2823
2824
		if(typeof start === 'undefined')
2825
		{
2826
			start = 0;
2827
		}
2828
2829
		// Category needs to be false if empty, not an empty array or string
2830
		var cat_id = state.cat_id ? state.cat_id : false;
2831
		if(cat_id && typeof cat_id.join != 'undefined')
2832
		{
2833
			if(cat_id.join('') == '') cat_id = false;
2834
		}
2835
2836
		var query = jQuery.extend({}, {
2837
			get_rows: 'calendar.calendar_uilist.get_rows',
2838
			row_id:'row_id',
2839
			startdate:state.first ||  state.date,
2840
			enddate:state.last,
2841
			// Participant must be an array or it won't work
2842
			col_filter: {participant: (typeof state.owner == 'string' || typeof state.owner == 'number' ? [state.owner] : state.owner)},
2843
			filter:'custom', // Must be custom to get start & end dates
2844
			status_filter: state.status_filter,
2845
			cat_id: cat_id,
2846
			csv_export: false
2847
		});
2848
		// Show ajax loader
2849
		if(typeof framework !== 'undefined')
2850
		{
2851
			framework.applications.calendar.sidemenuEntry.showAjaxLoader();
2852
		}
2853
2854
		// Already in progress?
2855
		var query_string = JSON.stringify(query);
2856
		if(this._queries_in_progress.indexOf(query_string) != -1)
2857
		{
2858
			return;
2859
		}
2860
		this._queries_in_progress.push(query_string);
2861
2862
		this.egw.dataFetch(
2863
			instance ? instance.etemplate_exec_id :
2864
				this.sidebox_et2.getInstanceManager().etemplate_exec_id,
2865
			{start: start, num_rows:400},
2866
			query,
2867
			this.id,
2868
			function calendar_handleResponse(data) {
2869
				var idx = this._queries_in_progress.indexOf(query_string);
2870
				if(idx >= 0)
2871
				{
2872
					this._queries_in_progress.splice(idx,1);
2873
				}
2874
				//console.log(data);
2875
2876
				// Look for any updated select options
2877
				if(data.rows && data.rows.sel_options && this.sidebox_et2)
2878
				{
2879
					for(var field in data.rows.sel_options)
2880
					{
2881
						var widget = this.sidebox_et2.getWidgetById(field);
2882
						if(widget && widget.set_select_options)
2883
						{
2884
							// Merge in new, update label of existing
2885
							for(var i in data.rows.sel_options[field])
2886
							{
2887
								var found = false;
2888
								var option = data.rows.sel_options[field][i];
2889
								for(var j in widget.options.select_options)
2890
								{
2891
									if(option.value == widget.options.select_options[j].value)
2892
									{
2893
										widget.options.select_options[j].label = option.label;
2894
										found = true;
2895
										break;
2896
									}
2897
								}
2898
								if(!found)
2899
								{
2900
									if(!widget.options.select_options.push)
2901
									{
2902
										widget.options.select_options = [];
2903
									}
2904
									widget.options.select_options.push(option);
2905
								}
2906
							}
2907
							var in_progress = app.calendar.state_update_in_progress;
2908
							app.calendar.state_update_in_progress = true;
2909
							widget.set_select_options(widget.options.select_options);
2910
							widget.set_value(widget.getValue());
2911
2912
							// If updating owner, update listview participants as well
2913
							// This lets us _add_ to the options, normal nm behaviour will replace.
2914
							if(field == 'owner')
2915
							{
2916
								try {
2917
									var participant = app.classes.calendar.views.listview.etemplates[0].widgetContainer.getWidgetById('nm').getWidgetById('participant');
2918
									if(participant)
2919
									{
2920
										participant.options.select_options = widget.options.select_options;
2921
										participant.set_select_options(widget.options.select_options);
2922
									}
2923
								} catch(e) {debugger;}
0 ignored issues
show
Debugging Code introduced by
debugger looks like debug code. Are you sure you do not want to remove it?
Loading history...
2924
							}
2925
2926
							app.calendar.state_update_in_progress = in_progress;
2927
						}
2928
					}
2929
				}
2930
2931
				if(data.order && data.total)
2932
				{
2933
					this._update_events(state, data.order);
2934
				}
2935
2936
				// More rows?
2937
				if(data.order.length + start < data.total)
2938
				{
2939
					// Wait a bit, let UI do something.
2940
					window.setTimeout( function() {
2941
						app.calendar._fetch_data(state, instance, start + data.order.length);
2942
					}, 100);
2943
				}
2944
				// Hide AJAX loader
2945
				else if(typeof framework !== 'undefined')
2946
				{
2947
					framework.applications.calendar.sidemenuEntry.hideAjaxLoader();
2948
					egw.loading_prompt('calendar',false)
2949
2950
				}
2951
			}, this,null
2952
		);
2953
	},
2954
2955
	/**
2956
	 * We have a list of calendar UIDs of events that need updating.
2957
	 *
2958
	 * The event data should already be in the egw.data cache, we just need to
2959
	 * figure out where they need to go, and update the needed parent objects.
2960
	 *
2961
	 * Already existing events will have already been updated by egw.data
2962
	 * callbacks.
2963
	 *
2964
	 * @param {Object} state Current state for update, used to determine what to update
2965
	 * @param data
2966
	 */
2967
	_update_events: function(state, data) {
2968
		var updated_days = {};
2969
2970
		// Events can span for longer than we are showing
2971
		var first = new Date(state.first);
2972
		var last = new Date(state.last);
2973
		var bounds = {
2974
			first: ''+first.getUTCFullYear() + sprintf('%02d',first.getUTCMonth()+1) + sprintf('%02d',first.getUTCDate()),
2975
			last: ''+last.getUTCFullYear() + sprintf('%02d',last.getUTCMonth()+1) + sprintf('%02d',last.getUTCDate())
2976
		};
2977
		// Seperate owners, or consolidated?
2978
		var multiple_owner = typeof state.owner != 'string' &&
2979
			state.owner.length > 1 &&
2980
			(state.view == 'day' && state.owner.length < parseInt(this.egw.preference('day_consolidate','calendar')) ||
2981
			state.view == 'week' && state.owner.length < parseInt(this.egw.preference('week_consolidate','calendar')));
2982
2983
2984
		for(var i = 0; i < data.length; i++)
2985
		{
2986
			var record = this.egw.dataGetUIDdata(data[i]);
2987
			if(record && record.data)
2988
			{
2989
				if(typeof updated_days[record.data.date] === 'undefined')
2990
				{
2991
					// Check to make sure it's in range first, record.data.date is start date
2992
					// and could be before our start
2993
					if(record.data.date >= bounds.first && record.data.date <= bounds.last)
2994
					{
2995
						updated_days[record.data.date] = [];
2996
					}
2997
				}
2998
				if(typeof updated_days[record.data.date] != 'undefined')
2999
				{
3000
					// Copy, to avoid unwanted changes by reference
3001
					updated_days[record.data.date].push(record.data.row_id);
3002
				}
3003
3004
				// Check for multi-day events listed once
3005
				// Date must stay a string or we might cause problems with nextmatch
3006
				var dates = {
3007
					start: typeof record.data.start === 'string' ? record.data.start : record.data.start.toJSON(),
3008
					end: typeof record.data.end === 'string' ? record.data.end : record.data.end.toJSON()
3009
				};
3010
				if(dates.start.substr(0,10) !== dates.end.substr(0,10))
3011
				{
3012
					var end = new Date(Math.min(new Date(record.data.end), new Date(state.last)));
3013
					end.setUTCHours(23);
3014
					end.setUTCMinutes(59);
3015
					end.setUTCSeconds(59);
3016
					var t = new Date(Math.max(new Date(record.data.start), new Date(state.first)));
3017
3018
					do
3019
					{
3020
						var expanded_date = ''+t.getUTCFullYear() + sprintf('%02d',t.getUTCMonth()+1) + sprintf('%02d',t.getUTCDate());
3021
						if(typeof(updated_days[expanded_date]) === 'undefined')
3022
						{
3023
							// Check to make sure it's in range first, expanded_date could be after our end
3024
							if(expanded_date >= bounds.first && expanded_date <= bounds.last)
3025
							{
3026
								updated_days[expanded_date] = [];
3027
							}
3028
						}
3029
						if(record.data.date !== expanded_date && typeof updated_days[expanded_date] !== 'undefined')
3030
						{
3031
							// Copy, to avoid unwanted changes by reference
3032
							updated_days[expanded_date].push(record.data.row_id);
3033
						}
3034
						t.setUTCDate(t.getUTCDate() + 1);
3035
					}
3036
					while(end >= t)
3037
				}
3038
			}
3039
		}
3040
3041
		// Now we know which days changed, so we pass it on
3042
		for(var day in updated_days)
3043
		{
3044
			// Might be split by user, so we have to check that too
3045
			for(var i = 0; i < (typeof state.owner == 'object' ? state.owner.length : 1); i++)
3046
			{
3047
				var owner = multiple_owner ? state.owner[i] : state.owner;
3048
				var cache_id = app.classes.calendar._daywise_cache_id(day, owner);
3049
				if(egw.dataHasUID(cache_id))
3050
				{
3051
					// Don't lose any existing data, just append
3052
					var c = egw.dataGetUIDdata(cache_id);
3053
					if(c.data && c.data !== null)
3054
					{
3055
						// Avoid duplicates
3056
						var data = c.data.concat(updated_days[day]).filter(function(value, index, self) {
3057
							return self.indexOf(value) === index;
3058
						});
3059
						this.egw.dataStoreUID(cache_id,data);
3060
					}
3061
				}
3062
				else
3063
				{
3064
					this.egw.dataStoreUID(cache_id, updated_days[day]);
3065
				}
3066
				if(!multiple_owner) break;
3067
			}
3068
		}
3069
3070
		egw.loading_prompt(this.appname,false);
3071
	},
3072
3073
	/**
3074
	 * Some handy date calculations
3075
	 * All take either a Date object or full date with timestamp (Z)
3076
	 */
3077
	date: {
3078
		toString: function(date)
3079
		{
3080
			// Ensure consistent formatting using UTC, avoids problems with comparison
3081
			// and timezones
3082
			if(typeof date === 'string') date = new Date(date);
3083
			return date.getUTCFullYear() +'-'+
3084
				sprintf("%02d",date.getUTCMonth()+1) + '-'+
3085
				sprintf("%02d",date.getUTCDate()) + 'T'+
3086
				sprintf("%02d",date.getUTCHours()) + ':'+
3087
				sprintf("%02d",date.getUTCMinutes()) + ':'+
3088
				sprintf("%02d",date.getUTCSeconds()) + 'Z';
3089
		},
3090
3091
		/**
3092
		* Formats one or two dates (range) as long date (full monthname), optionaly with a time
3093
		*
3094
		* Take care of any timezone issues before you pass the dates in.
3095
		*
3096
		* @param {Date} first first date
3097
		* @param {Date} last =0 last date for range, or false for a single date
3098
		* @param {boolean} display_time =false should a time be displayed too
3099
		* @param {boolean} display_day =false should a day-name prefix the date, eg. monday June 20, 2006
3100
		* @return string with formatted date
3101
		*/
3102
		long_date: function(first, last, display_time, display_day)
3103
		{
3104
			if(!first) return '';
3105
			if(typeof first === 'string')
3106
			{
3107
				first = new Date(first);
3108
			}
3109
			var first_format = new Date(first.valueOf() + first.getTimezoneOffset() * 60 * 1000);
3110
3111
			if(typeof last == 'string' && last)
3112
			{
3113
				last = new Date(last);
3114
			}
3115
			if(!last || typeof last !== 'object')
3116
			{
3117
				 last = false;
3118
			}
3119
			if(last)
3120
			{
3121
				var last_format = new Date(last.valueOf() + last.getTimezoneOffset() * 60 * 1000);
3122
			}
3123
3124
			if(!display_time) display_time = false;
3125
			if(!display_day) display_day = false;
3126
3127
			var range = '';
3128
3129
			var datefmt = egw.preference('dateformat');
3130
			var timefmt = egw.preference('timeformat') === '12' ? 'h:i a' : 'H:i';
3131
3132
			var month_before_day = datefmt[0].toLowerCase() == 'm' ||
3133
				datefmt[2].toLowerCase() == 'm' && datefmt[4] == 'd';
3134
3135
			if (display_day)
3136
			{
3137
				range = jQuery.datepicker.formatDate('DD',first_format)+(datefmt[0] != 'd' ? ' ' : ', ');
3138
			}
3139
			for (var i = 0; i < 5; i += 2)
3140
			{
3141
				switch(datefmt[i])
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
3142
				{
3143
					case 'd':
3144
						range += first.getUTCDate()+ (datefmt[1] == '.' ? '.' : '');
3145
						if (last && (first.getUTCMonth() != last.getUTCMonth() || first.getUTCFullYear() != last.getUTCFullYear()))
3146
						{
3147
							if (!month_before_day)
3148
							{
3149
								range += jQuery.datepicker.formatDate('MM',first_format);
3150
							}
3151
							if (first.getFullYear() != last.getFullYear() && datefmt[0] != 'Y')
3152
							{
3153
								range += (datefmt[0] != 'd' ? ', ' : ' ') + first.getFullYear();
3154
							}
3155
							if (display_time)
3156
							{
3157
								range += ' '+jQuery.datepicker.formatDate(dateTimeFormat(timefmt),first_format);
3158
							}
3159
							if (!last)
3160
							{
3161
								return range;
3162
							}
3163
							range += ' - ';
3164
3165
							if (first.getFullYear() != last.getFullYear() && datefmt[0] == 'Y')
3166
							{
3167
								range += last.getUTCFullYear() + ', ';
3168
							}
3169
3170
							if (month_before_day)
3171
							{
3172
								range += jQuery.datepicker.formatDate('MM',last_format);
0 ignored issues
show
Bug introduced by
The variable last_format does not seem to be initialized in case last on line 3119 is false. Are you sure the function formatDate handles undefined variables?
Loading history...
3173
							}
3174
						}
3175
						else if (last)
3176
						{
3177
							if (display_time)
3178
							{
3179
								range += ' '+jQuery.datepicker.formatDate(dateTimeFormat(timefmt),last_format);
3180
							}
3181
							if(last)
3182
							{
3183
								range += ' - ';
3184
							}
3185
						}
3186
						if(last)
3187
						{
3188
							range += ' ' + last.getUTCDate() + (datefmt[1] == '.' ? '.' : '');
3189
						}
3190
						break;
3191
					case 'm':
3192
					case 'M':
3193
						range += ' '+jQuery.datepicker.formatDate('MM',month_before_day || !last ? first_format : last_format) + ' ';
3194
						break;
3195
					case 'Y':
3196
						if (datefmt[0] != 'm')
3197
						{
3198
							range += ' ' + (datefmt[0] == 'Y' ? first.getUTCFullYear()+(datefmt[2] == 'd' ? ', ' : ' ') : last.getUTCFullYear()+' ');
3199
						}
3200
						break;
3201
				}
3202
			}
3203
			if (display_time && last)
3204
			{
3205
				 range += ' '+jQuery.datepicker.formatDate(dateTimeFormat(timefmt),last_format);
3206
			}
3207
			if (datefmt[4] == 'Y' && datefmt[0] == 'm')
3208
			{
3209
				 range += ', ' + last.getUTCFullYear();
3210
			}
3211
			return range;
3212
		},
3213
		/**
3214
		* Calculate iso8601 week-number, which is defined for Monday as first day of week only
3215
		*
3216
		* We adjust the day, if user prefs want a different week-start-day
3217
		*
3218
		* @param {string|Date} _date
3219
		* @return string
3220
		*/
3221
		week_number: function(_date)
3222
		{
3223
			var d = new Date(_date);
3224
			var day = d.getUTCDay();
3225
3226
3227
			// if week does not start Monday and date is Sunday --> add one day
3228
			if (egw.preference('weekdaystarts','calendar') != 'Monday' && !day)
3229
			{
3230
				d.setUTCDate(d.getUTCDate() + 1);
3231
			}
3232
			// if week does start Saturday and $time is Saturday --> add two days
3233
			else if (egw.preference('weekdaystarts','calendar') == 'Saturday' && day == 6)
3234
			{
3235
				d.setUTCDate(d.getUTCDate() + 2);
3236
			}
3237
3238
			return jQuery.datepicker.iso8601Week(new Date(d.valueOf() + d.getTimezoneOffset() * 60 * 1000));
3239
		},
3240
		start_of_week: function(date)
3241
		{
3242
			var d = new Date(date);
3243
			var day = d.getUTCDay();
3244
			var diff = 0;
3245
			switch(egw.preference('weekdaystarts','calendar'))
3246
			{
3247
				case 'Saturday':
3248
					diff = day === 6 ? 0 : day === 0 ? -1 : -(day + 1);
3249
					break;
3250
				case 'Monday':
3251
					diff = day === 0 ? -6 : 1-day;
3252
					break;
3253
				case 'Sunday':
3254
				default:
3255
					diff = -day;
3256
			}
3257
			d.setUTCDate(d.getUTCDate() + diff);
3258
			return d;
3259
		},
3260
		end_of_week: function(date)
3261
		{
3262
			var d = app.calendar.date.start_of_week(date);
3263
			d.setUTCDate(d.getUTCDate() + 6);
3264
			return d;
3265
		}
3266
	},
3267
3268
	/**
3269
	 * The sidebox filters use some non-standard and not-exposed options.  They
3270
	 * are set up here.
3271
	 *
3272
	 */
3273
	_setup_sidebox_filters: function()
3274
	{
3275
		// Further date customizations
3276
		var date_widget = this.sidebox_et2.getWidgetById('date');
3277
		if(date_widget)
3278
		{
3279
			// Dynamic resize of sidebox calendar to fill sidebox
3280
			var preferred_width = jQuery('#calendar-sidebox_date .ui-datepicker-inline').outerWidth();
3281
			var font_ratio = 12 / parseFloat(jQuery('#calendar-sidebox_date .ui-datepicker-inline').css('font-size'));
3282
			var calendar_resize = function() {
3283
				try {
3284
					var percent = 1+((jQuery(date_widget.getDOMNode()).width() - preferred_width) / preferred_width);
3285
					percent *= font_ratio;
3286
					jQuery('#calendar-sidebox_date .ui-datepicker-inline')
3287
						.css('font-size',(percent*100)+'%');
3288
3289
					// Position go and today
3290
					var buttons = jQuery('#calendar-sidebox_date .ui-datepicker-header a span');
3291
					if(today.length && go_button.length)
3292
					{
3293
						go_button.position({my: 'left+8px center', at: 'right center-1',of: jQuery('#calendar-sidebox_date .ui-datepicker-year')});
3294
						today.css({
3295
							'left': (buttons.first().offset().left + buttons.last().offset().left)/2 - Math.ceil(today.outerWidth(true)/2),
3296
							'top': go_button.css('top')
3297
						});
3298
						buttons.position({my: 'center', at: 'center', of: go_button})
3299
							.css('left', '');
3300
					}
3301
				} catch (e){
3302
					// Resize didn't work
3303
				}
3304
			};
3305
3306
			var datepicker = date_widget.input_date.datepicker("option", {
3307
				showButtonPanel:	false,
3308
				onChangeMonthYear: function(year, month, inst)
3309
				{
3310
					// Update month button label
3311
					var go_button = date_widget.getRoot().getWidgetById('header_go');
3312
					if(go_button)
3313
					{
3314
						var temp_date = new Date(year, month-1, 1,0,0,0);
3315
						//temp_date.setUTCMinutes(temp_date.getUTCMinutes() + temp_date.getTimezoneOffset());
3316
						go_button.btn.attr('title',egw.lang(date('F',temp_date)));
3317
3318
						// Store current _displayed_ date in date button for clicking
3319
						temp_date.setUTCMinutes(temp_date.getUTCMinutes() - temp_date.getTimezoneOffset());
3320
						go_button.btn.attr('data-date', temp_date.toJSON());
3321
					}
3322
					window.setTimeout(calendar_resize,0);
3323
				},
3324
				// Mark holidays
3325
				beforeShowDay: function (date)
3326
				{
3327
					var holidays = et2_calendar_view.get_holidays({day_class_holiday: function() {}}, date.getFullYear());
3328
					var day_holidays = holidays[''+date.getFullYear() +
3329
						sprintf("%02d",date.getMonth()+1) +
3330
						sprintf("%02d",date.getDate())];
3331
					var css_class = '';
3332
					var tooltip = '';
3333
					if(typeof day_holidays !== 'undefined' && day_holidays.length)
3334
					{
3335
						for(var i = 0; i < day_holidays.length; i++)
3336
						{
3337
							if (typeof day_holidays[i]['birthyear'] !== 'undefined')
3338
							{
3339
								css_class +='calendar_calBirthday ';
3340
							}
3341
							else
3342
							{
3343
								css_class += 'calendar_calHoliday ';
3344
							}
3345
							tooltip += day_holidays[i]['name'] + "\n";
3346
						}
3347
					}
3348
					return [true, css_class, tooltip];
3349
				}
3350
			});
3351
3352
			// Clickable week numbers
3353
			date_widget.input_date.on('mouseenter','.ui-datepicker-week-col', function() {
3354
					jQuery(this).siblings().find('a').addClass('ui-state-hover');
3355
				})
3356
				.on('mouseleave','.ui-datepicker-week-col', function() {
3357
					jQuery(this).siblings().find('a').removeClass('ui-state-hover');
3358
				})
3359
				.on('click', '.ui-datepicker-week-col', function() {
3360
					var view = app.calendar.state.view;
3361
					var days = app.calendar.state.days;
3362
3363
					// Avoid a full state update, we just want the calendar to update
3364
					// Directly update to avoid change event from the sidebox calendar
3365
					var date = new Date(this.nextSibling.dataset.year,this.nextSibling.dataset.month,this.nextSibling.firstChild.textContent,0,0,0);
3366
					date.setUTCMinutes(date.getUTCMinutes() - date.getTimezoneOffset());
3367
					date = app.calendar.date.toString(date);
3368
3369
					// Set to week view, if in one of the views where we change view
3370
					if(app.calendar.sidebox_changes_views.indexOf(view) >= 0)
3371
					{
3372
						app.calendar.update_state({view: 'week', date: date, days: days});
3373
					}
3374
					else if (view == 'planner')
3375
					{
3376
						// Clicked a week, show just a week
3377
						app.calendar.update_state({date: date, planner_view: 'week'});
3378
					}
3379
					else if (view == 'listview')
3380
					{
3381
						app.calendar.update_state({
3382
							date: date,
3383
							end_date: app.calendar.date.toString(app.classes.calendar.views.week.end_date({date:date})),
3384
							filter: 'week'
3385
						});
3386
					}
3387
					else
3388
					{
3389
						app.calendar.update_state({date: date});
3390
					}
3391
				});
3392
3393
3394
			// Set today button
3395
			var today = jQuery('#calendar-sidebox_header_today');
3396
			today.attr('title',egw.lang('today'));
3397
3398
			// Set go button
3399
			var go_button = date_widget.getRoot().getWidgetById('header_go');
3400
			if(go_button && go_button.btn)
3401
			{
3402
				go_button = go_button.btn;
3403
				var temp_date = new Date(date_widget.get_value());
3404
				temp_date.setUTCDate(1);
3405
				temp_date.setUTCMinutes(temp_date.getUTCMinutes() + temp_date.getTimezoneOffset());
3406
3407
				go_button.attr('title', egw.lang(date('F',temp_date)));
3408
				// Store current _displayed_ date in date button for clicking
3409
				temp_date.setUTCMinutes(temp_date.getUTCMinutes() - temp_date.getTimezoneOffset());
3410
				go_button.attr('data-date', temp_date.toJSON());
3411
3412
			}
3413
		}
3414
3415
		jQuery(window).on('resize.calendar'+date_widget.dom_id,calendar_resize).trigger('resize');
0 ignored issues
show
Bug introduced by
The variable calendar_resize does not seem to be initialized in case date_widget on line 3277 is false. Are you sure the function on handles undefined variables?
Loading history...
3416
3417
		// Avoid wrapping owner icons if user has group + search
3418
		var button = jQuery('#calendar-sidebox_owner ~ span.et2_clickable');
3419
		if(button.length == 1)
3420
		{
3421
			button.parent().css('margin-right',button.outerWidth(true)+2);
3422
			button.parent().parent().css('white-space','nowrap');
3423
		}
3424
		jQuery(window).on('resize.calendar-owner', function() {
3425
			var preferred_width = jQuery('#calendar-et2_target').children().first().outerWidth()||0;
3426
			if(app.calendar && app.calendar.sidebox_et2)
3427
			{
3428
				var owner = app.calendar.sidebox_et2.getWidgetById('owner');
3429
				if(preferred_width && owner.input.hasClass("chzn-done"))
3430
				{
3431
					owner.input.next().css('width',preferred_width);
3432
				}
3433
			}
3434
		});
3435
	},
3436
3437
	/**
3438
	 * Record view templates so we can quickly switch between them.
3439
	 *
3440
	 * @param {etemplate2} _et2 etemplate2 template that was just loaded
3441
	 * @param {String} _name Name of the template
3442
	 */
3443
	_et2_view_init: function(_et2, _name)
3444
	{
3445
		var hidden = typeof this.state.view !== 'undefined';
3446
		var all_loaded = this.sidebox_et2 !== null;
3447
3448
		// Avoid home portlets using our templates, and get them right
3449
		if(_et2.uniqueId.indexOf('portlet') === 0) return;
3450
3451
		// Flag to make sure we don't hide non-view templates
3452
		var view_et2 = false;
3453
3454
		for(var view in app.classes.calendar.views)
3455
		{
3456
			var index = app.classes.calendar.views[view].etemplates.indexOf(_name);
3457
			if(index > -1)
3458
			{
3459
				view_et2 = true;
3460
				app.classes.calendar.views[view].etemplates[index] = _et2;
3461
				// If a template disappears, we want to release it
3462
				jQuery(_et2.DOMContainer).one('clear',jQuery.proxy(function() {
3463
					this.view.etemplates[this.index] = _name;
3464
				},jQuery.extend({},{view: app.classes.calendar.views[view], index: ""+index, name: _name})));
3465
3466
				if(this.state.view === view)
3467
				{
3468
					hidden = false;
3469
				}
3470
			}
3471
			app.classes.calendar.views[view].etemplates.forEach(function(et) {all_loaded = all_loaded && typeof et !== 'string';});
3472
		}
3473
3474
		// Add some extras to the nextmatch so it can keep the dates in sync with
3475
		// those in the sidebox calendar.  Care must be taken to not trigger any
3476
		// sort of refresh or update, as that may resulte in infinite loops so these
3477
		// are only used for the 'week' and 'month' filters, and we just update the
3478
		// date range
3479
		if(_name == 'calendar.list')
3480
		{
3481
			var nm = _et2.widgetContainer.getWidgetById('nm');
3482
			if(nm)
3483
			{
3484
				// Avoid unwanted refresh immediately after load
3485
				nm.controller._grid.doInvalidate = false;
3486
3487
				// Preserve pre-set search
3488
				if(nm.activeFilters.search)
3489
				{
3490
					this.state.keywords = nm.activeFilters.search;
3491
				}
3492
				// Bind to keep search up to date
3493
				jQuery(nm.getWidgetById('search').getDOMNode()).on('change', function() {
3494
					app.calendar.state.search = jQuery('input',this).val();
3495
				});
3496
				nm.set_startdate = jQuery.proxy(function(date) {
3497
					this.state.first = this.date.toString(new Date(date));
3498
				},this);
3499
				nm.set_enddate = jQuery.proxy(function(date) {
3500
					this.state.last = this.date.toString(new Date(date));
3501
				},this);
3502
			}
3503
		}
3504
3505
		// Start hidden, except for current view
3506
		if(view_et2)
3507
		{
3508
			if(hidden)
3509
			{
3510
				jQuery(_et2.DOMContainer).hide();
3511
			}
3512
		}
3513
		else
3514
		{
3515
			var app_name = _name.split('.')[0];
3516
			if(app_name && app_name != 'calendar' && egw.app(app_name))
3517
			{
3518
				// A template from another application?  Keep it up to date as state changes
3519
				this.sidebox_hooked_templates.push(_et2.widgetContainer);
3520
				// If it leaves (or reloads) remove it
3521
				jQuery(_et2.DOMContainer).one('clear',jQuery.proxy(function() {
3522
					if(app.calendar)
3523
					{
3524
						app.calendar.sidebox_hooked_templates.splice(this,1,0);
3525
					}
3526
				},this.sidebox_hooked_templates.length -1));
3527
			}
3528
		}
3529
		if(all_loaded)
3530
		{
3531
			jQuery(window).trigger('resize');
3532
			this.setState({state:this.state});
3533
3534
			// Hide loader after 1 second as a fallback, it will also be hidden
3535
			// after loading is complete.
3536
			window.setTimeout(jQuery.proxy(function() {
3537
				egw.loading_prompt(this.appname,false);
3538
			}, this),1000);
3539
3540
			// Start calendar-wide autorefresh timer to include more than just nm
3541
			this._set_autorefresh();
3542
		}
3543
	},
3544
3545
	/**
3546
	 * Set a refresh timer that works for the current view.
3547
	 * The nextmatch goes into an infinite loop if we let it autorefresh while
3548
	 * hidden.
3549
	 */
3550
	_set_autorefresh: function() {
3551
		// Listview not loaded
3552
		if(typeof app.classes.calendar.views.listview.etemplates[0] == 'string') return;
3553
3554
		var nm = app.classes.calendar.views.listview.etemplates[0].widgetContainer.getWidgetById('nm');
3555
		// nextmatch missing
3556
		if(!nm) return;
3557
3558
		var refresh_preference = "nextmatch-" + nm.options.settings.columnselection_pref + "-autorefresh";
3559
		var time = this.egw.preference(refresh_preference, 'calendar');
3560
3561
		if(this.state.view == 'listview' && time)
3562
		{
3563
			nm._set_autorefresh(time);
3564
			return;
3565
		}
3566
		else
3567
		{
3568
			window.clearInterval(nm._autorefresh_timer);
3569
		}
3570
		var self = this;
3571
		var refresh = function() {
3572
			// Deleted events are not coming properly, so clear it all
3573
			self._clear_cache();
3574
			// Force redraw to current state
3575
			self.setState({state: self.state});
3576
3577
			// This is a fast update, but misses deleted events
3578
			//app.calendar._fetch_data(app.calendar.state);
3579
		};
3580
3581
		// Start / update timer
3582
		if (this._autorefresh_timer)
3583
		{
3584
			window.clearInterval(this._autorefresh_timer);
3585
			this._autorefresh_timer = null;
3586
		}
3587
		if(time > 0)
3588
		{
3589
			this._autorefresh_timer = setInterval(jQuery.proxy(refresh, this), time * 1000);
3590
		}
3591
3592
		// Bind to tab show/hide events, so that we don't bother refreshing in the background
3593
		jQuery(nm.getInstanceManager().DOMContainer.parentNode).on('hide.calendar', jQuery.proxy(function(e) {
3594
			// Stop
3595
			window.clearInterval(this._autorefresh_timer);
3596
			jQuery(e.target).off(e);
3597
3598
			if(!time) return;
3599
3600
			// If the autorefresh time is up, bind once to trigger a refresh
3601
			// (if needed) when tab is activated again
3602
			this._autorefresh_timer = setTimeout(jQuery.proxy(function() {
3603
				// Check in case it was stopped / destroyed since
3604
				if(!this._autorefresh_timer) return;
3605
3606
				jQuery(nm.getInstanceManager().DOMContainer.parentNode).one('show.calendar',
3607
					// Important to use anonymous function instead of just 'this.refresh' because
3608
					// of the parameters passed
3609
					jQuery.proxy(function() {refresh();},this)
3610
				);
3611
			},this), time*1000);
3612
		},this));
3613
		jQuery(nm.getInstanceManager().DOMContainer.parentNode).on('show.calendar', jQuery.proxy(function(e) {
3614
			// Start normal autorefresh timer again
3615
			this._set_autorefresh(this.egw.preference(refresh_preference, 'calendar'));
3616
			jQuery(e.target).off(e);
3617
		},this));
3618
	},
3619
3620
	/**
3621
	 * Super class for the different views.
3622
	 *
3623
	 * Each separate view overrides what it needs
3624
	 */
3625
	View: {
3626
		// List of etemplates to show for this view
3627
		etemplates: ['calendar.view'],
3628
3629
		/**
3630
		 * Translated label for header
3631
		 * @param {Object} state
3632
		 * @returns {string}
3633
		 */
3634
		header: function(state) {
3635
			var formatDate = new Date(state.date);
3636
			formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000);
3637
			return app.calendar.View._owner(state) + date(egw.preference('dateformat'),formatDate);
3638
		},
3639
3640
		/**
3641
		 * If one owner, get the owner text
3642
		 *
3643
		 * @param {object} state
3644
		 */
3645
		_owner: function(state) {
3646
			var owner = '';
3647
			if(state.owner.length && state.owner.length == 1 && app.calendar.sidebox_et2)
3648
			{
3649
				var own = app.calendar.sidebox_et2.getWidgetById('owner').getDOMNode();
3650
				if(own.selectedIndex >= 0)
3651
				{
3652
					owner = own.options[own.selectedIndex].innerHTML + ": ";
3653
				}
3654
			}
3655
			return owner;
3656
		},
3657
3658
		/**
3659
		 * Get the start date for this view
3660
		 * @param {Object} state
3661
		 * @returns {Date}
3662
		 */
3663
		start_date: function(state) {
3664
			var d = state.date ? new Date(state.date) : new Date();
3665
			d.setUTCHours(0);
3666
			d.setUTCMinutes(0);
3667
			d.setUTCSeconds(0);
3668
			d.setUTCMilliseconds(0);
3669
			return d;
3670
		},
3671
		/**
3672
		 * Get the end date for this view
3673
		 * @param {Object} state
3674
		 * @returns {Date}
3675
		 */
3676
		end_date: function(state) {
3677
			var d = state.date ? new Date(state.date) : new Date();
3678
			d.setUTCHours(23);
3679
			d.setUTCMinutes(59);
3680
			d.setUTCSeconds(59);
3681
			d.setUTCMilliseconds(0);
3682
			return d;
3683
		},
3684
		/**
3685
		 * Get the owner for this view
3686
		 *
3687
		 * This is always the owner from the given state, we use a function
3688
		 * to trigger setting the widget value.
3689
		 *
3690
		 * @param {number[]|String} state state.owner List of owner IDs, or a comma seperated list
3691
		 * @returns {number[]|String}
3692
		 */
3693
		owner: function(state) {
3694
			return state.owner || 0;
3695
		},
3696
		/**
3697
		 * Should the view show the weekends
3698
		 *
3699
		 * @param {object} state
3700
		 * @returns {boolean} Current preference to show 5 or 7 days in weekview
3701
		 */
3702
		show_weekend: function(state)
3703
		{
3704
			return state.weekend;
3705
		},
3706
		/**
3707
		 * How big or small are the displayed time chunks?
3708
		 *
3709
		 * @param {object} state
3710
		 */
3711
		granularity: function(state) {
3712
			var list = egw.preference('use_time_grid','calendar');
3713
			if(list === 0 || typeof list === 'undefined')
3714
			{
3715
				return parseInt(egw.preference('interval','calendar')) || 30;
3716
			}
3717
			if(typeof list == 'string') list = list.split(',');
3718
			if(!list.indexOf && jQuery.isPlainObject(list))
3719
			{
3720
				list = jQuery.map(list, function(el) { return el; });
3721
			}
3722
			return list.indexOf(state.view) >= 0 ?
3723
				0 :
3724
				parseInt(egw.preference('interval','calendar')) || 30;
3725
		},
3726
		extend: function(sub)
3727
		{
3728
			return jQuery.extend({},this,{_super:this},sub);
3729
		},
3730
		/**
3731
		 * Determines the new date after scrolling.  The default is 1 week.
3732
		 *
3733
		 * @param {number} delta Integer for how many 'ticks' to move, positive for
3734
		 *	forward, negative for backward
3735
		 * @returns {Date}
3736
		 */
3737
		scroll: function(delta)
3738
		{
3739
			var d = new Date(app.calendar.state.date);
3740
			d.setUTCDate(d.getUTCDate() + (7 * delta));
3741
			return d;
3742
		}
3743
	}
3744
});}).call(this);
3745
3746
3747
jQuery.extend(app.classes.calendar,{
3748
3749
	/**
3750
	 * This is the data cache prefix for the daywise event index cache
3751
	 * Daywise cache IDs look like: calendar_daywise::20150101 and
3752
	 * contain a list of event IDs for that day (or empty array)
3753
	 */
3754
	DAYWISE_CACHE_ID: 'calendar_daywise',
3755
3756
3757
	/**
3758
	 * Create a cache ID for the daywise cache
3759
	 *
3760
	 * @param {String|Date} date If a string, date should be in Ymd format
3761
	 * @param {String|integer|String[]} owner
3762
	 * @returns {String} Cache ID
3763
	 */
3764
	_daywise_cache_id: function(date, owner)
3765
	{
3766
		if(typeof date === 'object')
3767
		{
3768
			date =  date.getUTCFullYear() + sprintf('%02d',date.getUTCMonth()+1) + sprintf('%02d',date.getUTCDate());
3769
		}
3770
3771
	// If the owner is not set, 0, or the current user, don't bother adding it
3772
		var _owner = (owner && owner.toString() != '0') ? owner.toString() : '';
3773
		if(_owner == egw.user('account_id'))
3774
		{
3775
			_owner = '';
3776
		}
3777
		return app.classes.calendar.DAYWISE_CACHE_ID+'::'+date+(_owner ? '-' + _owner : '');
3778
	},
3779
3780
	/**
3781
	* Etemplates and settings for the different views.  Some (day view)
3782
	* use more than one template, some use the same template as others,
3783
	* most need different handling for their various attributes.
3784
	*
3785
	* Not using the standard Class.extend here because it hides the members,
3786
	* and we want to be able to look inside them.  This is done seperately instead
3787
	* of inside the normal object to allow access to the View object.
3788
	*/
3789
	views: {
3790
		day: app.classes.calendar.prototype.View.extend({
3791
			header: function(state) {
3792
				return app.calendar.View.header.call(this, state);
3793
			},
3794
			etemplates: ['calendar.view','calendar.todo'],
3795
			start_date: function(state) {
3796
				var d = app.calendar.View.start_date.call(this, state);
3797
				state.date = app.calendar.date.toString(d);
3798
				return d;
3799
			},
3800
			show_weekend: function(state) {
3801
				state.days = '1';
3802
				state.weekend = 'true';
3803
				return app.calendar.View.show_weekend.call(this,state);
3804
			},
3805
			scroll: function(delta)
3806
			{
3807
				var d = new Date(app.calendar.state.date);
3808
				d.setUTCDate(d.getUTCDate() + (delta));
3809
				return d;
3810
			}
3811
		}),
3812
		day4: app.classes.calendar.prototype.View.extend({
3813
			header: function(state) {
3814
				return app.calendar.View.header.call(this, state);
3815
			},
3816
			end_date: function(state) {
3817
				var d = app.calendar.View.end_date.call(this,state);
3818
				state.days = '4';
3819
				d.setUTCHours(24*4-1);
3820
				d.setUTCMinutes(59);
3821
				d.setUTCSeconds(59);
3822
				d.setUTCMilliseconds(0);
3823
				return d;
3824
			},
3825
			show_weekend: function(state) {
3826
				state.weekend = 'true';
3827
				return true;
3828
			},
3829
			scroll: function(delta)
3830
			{
3831
				var d = new Date(app.calendar.state.date);
3832
				d.setUTCDate(d.getUTCDate() + (4 * delta));
3833
				return d;
3834
			}
3835
		}),
3836
		week: app.classes.calendar.prototype.View.extend({
3837
			header: function(state) {
3838
				var end_date = state.last;
3839
				if(!app.classes.calendar.views.week.show_weekend(state))
3840
				{
3841
					end_date = new Date(state.last);
3842
					end_date.setUTCDate(end_date.getUTCDate() - 2);
3843
				}
3844
				return app.calendar.View._owner(state) + app.calendar.egw.lang('Week') + ' ' +
3845
					app.calendar.date.week_number(state.first) + ': ' +
3846
					app.calendar.date.long_date(state.first, end_date);
3847
			},
3848
			start_date: function(state) {
3849
				return app.calendar.date.start_of_week(app.calendar.View.start_date.call(this,state));
3850
			},
3851
			end_date: function(state) {
3852
				var d = app.calendar.date.start_of_week(state.date || new Date());
3853
				// Always 7 days, we just turn weekends on or off
3854
				d.setUTCHours(24*7-1);
3855
				d.setUTCMinutes(59);
3856
				d.setUTCSeconds(59);
3857
				d.setUTCMilliseconds(0);
3858
				return d;
3859
			}
3860
		}),
3861
		weekN: app.classes.calendar.prototype.View.extend({
3862
			header: function(state) {
3863
				return  app.calendar.View._owner(state) + app.calendar.egw.lang('Week') + ' ' +
3864
					app.calendar.date.week_number(state.first) + ' - ' +
3865
					app.calendar.date.week_number(state.last) + ': ' +
3866
					app.calendar.date.long_date(state.first, state.last);
3867
			},
3868
			start_date: function(state) {
3869
				return app.calendar.date.start_of_week(app.calendar.View.start_date.call(this,state));
3870
			},
3871
			end_date: function(state) {
3872
				state.days = '' + (state.days >= 5 ? state.days : egw.preference('days_in_weekview','calendar') || 7);
3873
3874
				var d = app.calendar.date.start_of_week(app.calendar.View.start_date.call(this,state));
3875
				// Always 7 days, we just turn weekends on or off
3876
				d.setUTCHours(24*7*(parseInt(this.egw.preference('multiple_weeks','calendar')) || 3)-1);
3877
				return d;
3878
			}
3879
		}),
3880
		month: app.classes.calendar.prototype.View.extend({
3881
			header: function(state)
3882
			{
3883
				var formatDate = new Date(state.date);
3884
				formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000);
3885
				return app.calendar.View._owner(state) + app.calendar.egw.lang(date('F',formatDate)) + ' ' + date('Y',formatDate);
3886
			},
3887
			start_date: function(state) {
3888
				var d = app.calendar.View.start_date.call(this,state);
3889
				d.setUTCDate(1);
3890
				return app.calendar.date.start_of_week(d);
3891
			},
3892
			end_date: function(state) {
3893
				var d = app.calendar.View.end_date.call(this,state);
3894
				d = new Date(d.getFullYear(),d.getUTCMonth() + 1, 1,0,-d.getTimezoneOffset(),0);
3895
				d.setUTCSeconds(d.getUTCSeconds()-1);
3896
				return app.calendar.date.end_of_week(d);
3897
			},
3898
			granularity: function(state) {
3899
				// Always a list, not a grid
3900
				return 0;
3901
			},
3902
			scroll: function(delta)
3903
			{
3904
				var d = new Date(app.calendar.state.date);
3905
				d.setUTCMonth(d.getUTCMonth() + delta);
3906
				return d;
3907
			}
3908
		}),
3909
3910
		planner: app.classes.calendar.prototype.View.extend({
3911
			header: function(state) {
3912
				var startDate = new Date(state.first);
3913
				startDate = new Date(startDate.valueOf() + startDate.getTimezoneOffset() * 60 * 1000);
3914
3915
				var endDate = new Date(state.last);
3916
				endDate = new Date(endDate.valueOf() + endDate.getTimezoneOffset() * 60 * 1000);
3917
				return app.calendar.View._owner(state) + date(egw.preference('dateformat'),startDate) +
3918
					(startDate == endDate ? '' : ' - ' + date(egw.preference('dateformat'),endDate));
3919
			},
3920
			etemplates: ['calendar.planner'],
3921
			group_by: function(state) {
3922
				return state.sortby ? state.sortby : 0;
3923
			},
3924
			// Note: Planner uses the additional value of planner_view to determine
3925
			// the start & end dates using other view's functions
3926
			start_date: function(state) {
3927
				// Start here, in case we can't find anything better
3928
				var d = app.calendar.View.start_date.call(this, state);
3929
3930
				if(state.sortby && state.sortby === 'month')
3931
				{
3932
					d.setUTCDate(1);
3933
				}
3934
				else if (state.planner_view && app.classes.calendar.views[state.planner_view])
3935
				{
3936
					d = app.classes.calendar.views[state.planner_view].start_date.call(this,state);
3937
				}
3938
				else
3939
				{
3940
					d = app.calendar.date.start_of_week(d);
3941
					d.setUTCHours(0);
3942
					d.setUTCMinutes(0);
3943
					d.setUTCSeconds(0);
3944
					d.setUTCMilliseconds(0);
3945
					return d;
3946
				}
3947
				return d;
3948
			},
3949
			end_date: function(state) {
3950
3951
				var d = app.calendar.View.end_date.call(this, state);
3952
				if(state.sortby && state.sortby === 'month')
3953
				{
3954
					d.setUTCDate(0);
3955
					d.setUTCFullYear(d.getUTCFullYear() + 1);
3956
				}
3957
				else if (state.planner_view && app.classes.calendar.views[state.planner_view])
3958
				{
3959
					d = app.classes.calendar.views[state.planner_view].end_date.call(this,state);
3960
				}
3961
				else if (state.days)
3962
				{
3963
					// This one comes from a grid view, but we'll use it
3964
					d.setUTCDate(d.getUTCDate() + parseInt(state.days)-1);
3965
					delete state.days;
3966
				}
3967
				else
3968
				{
3969
					d = app.calendar.date.end_of_week(d);
3970
				}
3971
				return d;
3972
			},
3973
			scroll: function(delta)
3974
			{
3975
				if(app.calendar.state.planner_view)
3976
				{
3977
					return app.classes.calendar.views[app.calendar.state.planner_view].scroll.call(this,delta);
3978
				}
3979
				var d = new Date(app.calendar.state.date);
3980
3981
				// Yearly view, grouped by month - scroll 1 month
3982
				if(app.calendar.state.sortby === 'month')
3983
				{
3984
					d.setUTCMonth(d.getUTCMonth() + delta);
3985
					d.setUTCDate(1);
3986
					d.setUTCHours(0);
3987
					d.setUTCMinutes(0);
3988
					return d;
3989
				}
3990
				// Need to set the day count, or auto date ranging takes over and
3991
				// makes things buggy
3992
				if(app.calendar.state.first && app.calendar.state.last)
3993
				{
3994
					var diff = new Date(app.calendar.state.last)  - new Date(app.calendar.state.first);
3995
					var days = Math.round(diff / (1000*3600*24));
3996
				}
3997
				d.setUTCDate(d.getUTCDate() + (days*delta));
0 ignored issues
show
Bug introduced by
The variable days does not seem to be initialized in case app.calendar.state.first...app.calendar.state.last on line 3992 is false. Are you sure this can never be the case?
Loading history...
3998
				if(days > 8)
3999
				{
4000
					d = app.calendar.date.start_of_week(d);
4001
				}
4002
				return d;
4003
			}
4004
		}),
4005
4006
		listview: app.classes.calendar.prototype.View.extend({
4007
			header: function(state)
4008
			{
4009
				var startDate = new Date(state.first || state.date);
4010
				startDate = new Date(startDate.valueOf() + startDate.getTimezoneOffset() * 60 * 1000);
4011
				var start_check = ''+startDate.getFullYear() + startDate.getMonth() + startDate.getDate();
4012
4013
				var endDate = new Date(state.last || state.date);
4014
				endDate = new Date(endDate.valueOf() + endDate.getTimezoneOffset() * 60 * 1000);
4015
				var end_check = ''+endDate.getFullYear() + endDate.getMonth() + endDate.getDate();
4016
				return app.calendar.View._owner(state) +
4017
					date(egw.preference('dateformat'),startDate) +
4018
					(start_check == end_check ? '' : ' - ' + date(egw.preference('dateformat'),endDate));
4019
			},
4020
			etemplates: ['calendar.list']
4021
		})
4022
	}}
4023
);
4024