Completed
Push — 16.1 ( e8b4e7...3bc699 )
by Nathan
13:31
created

app.js ➔ ... ➔ AppJS.extend._update_events   F

Complexity

Conditions 26
Paths 220

Size

Total Lines 107

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 26
c 1
b 0
f 0
nc 220
nop 2
dl 0
loc 107
rs 3.9894

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like app.js ➔ ... ➔ AppJS.extend._update_events often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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