infolog/js/app.ts   F
last analyzed

Complexity

Total Complexity 114
Complexity/F 3.8

Size

Lines of Code 870
Function Count 30

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 503
dl 0
loc 870
rs 2
c 0
b 0
f 0
wmc 114
mnd 84
bc 84
fnc 30
bpm 2.8
cpm 3.8
noi 0

30 Functions

Rating   Name   Duplication   Size   Complexity  
B InfologApp.edit_actions 0 30 5
B InfologApp.observer 0 39 7
A InfologApp.infolog_print_preview_onload 0 23 2
A InfologApp.submit_if_not_empty 0 12 2
A InfologApp.setState 0 22 4
A InfologApp.show_details 0 15 5
A InfologApp.parent_query 0 23 3
A InfologApp.view_parent 0 18 2
A InfologApp.infolog_print_preview 0 8 1
A InfologApp.onchangeResponsible 0 12 2
B InfologApp.filter2_change 0 57 7
B InfologApp.filter_change 0 33 5
A InfologApp.add_link_sidemenu 0 7 1
F InfologApp.status_changed 0 74 18
A InfologApp._get_stylite 0 30 3
A InfologApp.toggleEncrypt 0 16 2
A InfologApp.getState 0 27 3
A InfologApp.getWindowTitle 0 9 2
A InfologApp.change_responsible 0 36 4
A InfologApp.printEncrypt 0 24 1
A InfologApp.timesheet_list 0 27 2
B InfologApp.add_email_from_ab 0 31 5
A InfologApp.has_parent 0 12 1
B InfologApp.et2_ready 0 61 5
A InfologApp.add_action_handler 0 18 2
A InfologApp.confirm_delete 0 25 5
B InfologApp.confirm_delete_2 0 25 6
B InfologApp.add_with_extras 0 46 7
A InfologApp.infolog_menu_print 0 11 1
A InfologApp.destroy 0 8 1

How to fix   Complexity   

Complexity

Complex classes like infolog/js/app.ts 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 - Infolog - Javascript UI
3
 *
4
 * @link: https://www.egroupware.org
5
 * @package infolog
6
 * @author Hadi Nategh	<hn-AT-stylite.de>
7
 * @copyright (c) 2008-13 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
8
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
9
 */
10
11
/*egw:uses
12
	/api/js/jsapi/egw_app.js
13
 */
14
15
import 'jquery';
16
import 'jqueryui';
17
import '../jsapi/egw_global';
18
import '../etemplate/et2_types';
19
20
import { EgwApp } from '../../api/js/jsapi/egw_app';
21
22
/**
23
 * UI for Infolog
24
 *
25
 * @augments AppJS
26
 */
27
class InfologApp extends EgwApp
28
{
29
	readonly appname = 'infolog';
30
31
	/**
32
	 * Constructor
33
	 *
34
	 * @memberOf app.infolog
35
	 */
36
	constructor()
37
	{
38
		// call parent
39
		super();
40
	}
41
42
	/**
43
	 * Destructor
44
	 */
45
	destroy(_app)
46
	{
47
		// call parent
48
		super.destroy(_app);
49
	}
50
51
	/**
52
	 * This function is called when the etemplate2 object is loaded
53
	 * and ready.  If you must store a reference to the et2 object,
54
	 * make sure to clean it up in destroy().
55
	 *
56
	 * @param {etemplate2} _et2 newly ready object
57
	 * @param {string} _name template name
58
	 */
59
	et2_ready(_et2, _name)
60
	{
61
		// call parent
62
		super.et2_ready(_et2, _name);
63
64
		switch(_name)
65
		{
66
			case 'infolog.index':
67
				this.filter_change();
68
				// Show / hide descriptions according to details filter
69
				var nm = this.et2.getWidgetById('nm');
70
				var filter2 = nm.getWidgetById('filter2');
71
				this.show_details(filter2.value == 'all',nm.getDOMNode(nm));
72
				// Remove the rule added by show_details() if the template is removed
73
				jQuery(_et2.DOMContainer).on('clear', jQuery.proxy(function() {egw.css(this);}, '#' + nm.getDOMNode(nm).id + ' .et2_box.infoDes'));
74
75
				// Enable decrypt on hover
76
				if(this.egw.user('apps').stylite)
77
				{
78
					this._get_stylite(function() {this.mailvelopeAvailable(function() {app.stylite.decrypt_hover(nm);});});
79
				}
80
				break;
81
			case 'infolog.edit.print':
82
				if (this.et2.getArrayMgr('content').data.info_des.indexOf(this.begin_pgp_message) != -1)
83
				{
84
					this.mailvelopeAvailable(this.printEncrypt);
85
				}
86
				else
87
				{
88
					// Trigger print command if the infolog oppend for printing purpose
89
					this.infolog_print_preview_onload();
90
				}
91
				break;
92
			case 'infolog.edit':
93
				if (this.et2.getArrayMgr('content').data.info_des &&
94
					this.et2.getArrayMgr('content').data.info_des.indexOf(this.begin_pgp_message) != -1)
95
				{
96
					this._get_stylite(jQuery.proxy(function() {this.mailvelopeAvailable(jQuery.proxy(function() {
97
						this.toggleEncrypt();
98
99
						// Decrypt history on hover
100
						var history = this.et2.getWidgetById('history');
101
						app.stylite.decrypt_hover(history,'span');
102
						jQuery(history.getDOMNode(history))
103
							.tooltip('option','position',{my:'top left', at: 'top left', of: history.getDOMNode(history)});
104
105
					},this));},this));
106
					// This disables the diff in history
107
					var history = this.et2.getArrayMgr('content').getEntry('history');
108
					history['status-widgets'].De = 'description';
109
				}
110
				break;
111
		}
112
	}
113
114
	/**
115
	 * Observer method receives update notifications from all applications
116
	 *
117
	 * InfoLog currently reacts to timesheet updates, as it might show time-sums.
118
	 * @todo only trigger update, if times are shown
119
	 *
120
	 * @param {string} _msg message (already translated) to show, eg. 'Entry deleted'
121
	 * @param {string} _app application name
122
	 * @param {(string|number)} _id id of entry to refresh or null
123
	 * @param {string} _type either 'update', 'edit', 'delete', 'add' or null
124
	 * - update: request just modified data from given rows.  Sorting is not considered,
125
	 *		so if the sort field is changed, the row will not be moved.
126
	 * - edit: rows changed, but sorting may be affected.  Requires full reload.
127
	 * - delete: just delete the given rows clientside (no server interaction neccessary)
128
	 * - add: requires full reload for proper sorting
129
	 * @param {string} _msg_type 'error', 'warning' or 'success' (default)
130
	 * @param {object|null} _links app => array of ids of linked entries
131
	 * or null, if not triggered on server-side, which adds that info
132
	 */
133
	observer(_msg, _app, _id, _type, _msg_type, _links)
134
	{
135
		if (typeof _links != 'undefined')
136
		{
137
			if (typeof _links.infolog != 'undefined')
138
			{
139
				switch (_app)
140
				{
141
					case 'timesheet':
142
						var nm = this.et2 ? this.et2.getWidgetById('nm') : null;
143
						if (nm) nm.applyFilters();
144
						break;
145
				}
146
			}
147
		}
148
		// Refresh handler for Addressbook CRM view
149
		if (_app == 'infolog' && this.et2.getInstanceManager() && this.et2.getInstanceManager().app == 'addressbook' && this.et2.getInstanceManager().name == 'infolog.index')
150
		{
151
			this.et2._inst.refresh(_msg, _app, _id, _type);
152
		}
153
	}
154
155
	/**
156
	 * Retrieve the current state of the application for future restoration
157
	 *
158
	 * Reimplemented to add action/action_id from content set by server
159
	 * when eg. viewing infologs linked to contacts.
160
	 *
161
	 * @return {object} Application specific map representing the current state
162
	 */
163
	getState()
164
	{
165
		// call parent
166
		var state = super.getState();
167
		var nm : any = {};
168
169
		// Get index etemplate
170
		var et2 = etemplate2.getById('infolog-index');
171
		if(et2)
172
		{
173
			var content = et2.widgetContainer.getArrayMgr('content');
174
			nm = content && content.data && content.data.nm ? content.data.nm: {};
175
		}
176
177
		state.action = nm.action || null;
178
		state.action_id = nm.action_id || null;
179
180
		return state;
181
	}
182
183
	/**
184
	 * Set the application's state to the given state.
185
	 *
186
	 * Reimplemented to also reset action/action_id.
187
	 *
188
	 * @param {{name: string, state: object}|string} state Object (or JSON string) for a state.
189
	 *	Only state is required, and its contents are application specific.
190
	 *
191
	 * @return {boolean} false - Returns false to stop event propagation
192
	 */
193
	setState(state)
194
	{
195
		// as we have to set state.state.action, we have to set all other
196
		// for "No filter" favorite to work as expected
197
		var to_set = {col_filter: null, filter: '', filter2: '', cat_id: '', search: '', action: null};
198
		if (typeof state.state == 'undefined') state.state = {};
199
		for(var name in to_set)
200
		{
201
			if (typeof state.state[name] == 'undefined') state.state[name] = to_set[name];
202
		}
203
		return super.setState(state);
204
	}
205
206
	/**
207
	 * Enable or disable the date filter
208
	 *
209
	 * If the filter is set to something that needs dates, we enable the
210
	 * header_left template.  Otherwise, it is disabled.
211
	 */
212
	filter_change()
213
	{
214
		var filter = this.et2.getWidgetById('filter');
215
		var nm = this.et2.getWidgetById('nm');
216
		var dates = this.et2.getWidgetById('infolog.index.dates');
217
		if(nm && filter)
218
		{
219
			switch(filter.getValue())
220
			{
221
				case 'bydate':
222
				case 'duedate':
223
224
					if (filter && dates)
225
					{
226
						dates.set_disabled(false);
227
						window.setTimeout(function() {
228
							jQuery(dates.getWidgetById('startdate').getDOMNode()).find('input').focus();
229
						},0);
230
					}
231
					break;
232
				default:
233
					if (dates)
234
					{
235
						dates.set_disabled(true);
236
					}
237
					break;
238
			}
239
		}
240
	}
241
242
	/**
243
	 * show or hide the details of rows by selecting the filter2 option
244
	 * either 'all' for details or 'no_description' for no details
245
	 *
246
	 * @param {Event} event Change event
247
	 * @param {et2_nextmatch} nm The nextmatch widget that owns the filter
248
	 */
249
	filter2_change(event, nm)
250
	{
251
		var filter2 = nm.getWidgetById('filter2');
252
253
		if (nm && filter2)
254
		{
255
			// Show / hide descriptions
256
			this.show_details(filter2.get_value() === 'all', nm.getDOMNode(nm));
257
		}
258
259
		// Only change columns for a real user event, to avoid interfering with
260
		// favorites
261
		if (nm && filter2 && !nm.update_in_progress)
262
		{
263
			// Store selection as implicit preference
264
			egw.set_preference('infolog', nm.options.settings.columnselection_pref.replace('-details','')+'-details-pref', filter2.value);
265
266
			// Change preference location - widget is nextmatch
267
			nm.options.settings.columnselection_pref = nm.options.settings.columnselection_pref.replace('-details','') + (filter2.value == 'all' ? '-details' :'');
268
269
			// Load new preferences
270
			var colData = nm.columns.slice();
271
			for(var i = 0; i < nm.columns.length; i++) colData[i].visible=false;
272
273
			if(egw.preference(nm.options.settings.columnselection_pref,'infolog'))
274
			{
275
				nm.set_columns((<String>egw.preference(nm.options.settings.columnselection_pref,'infolog')).split(','));
276
			}
277
			nm._applyUserPreferences(nm.columns, colData);
278
279
			// Now apply them to columns
280
			for(var i = 0; i < colData.length; i++)
281
			{
282
				nm.dataview.getColumnMgr().columns[i].set_width(colData[i].width);
283
				nm.dataview.getColumnMgr().columns[i].set_visibility(colData[i].visible);
284
			}
285
			nm.dataview.getColumnMgr().updated = true;
286
287
			// Update page - set update_in_progress to true to avoid triggering
288
			// the change handler and looping if the user has a custom field
289
			// column change
290
			var in_progress = nm.update_in_progress;
291
			nm.update_in_progress = true;
292
			// Set the actual filter value here
293
			nm.activeFilters.filter2 = filter2.get_value();
294
			nm.dataview.updateColumns();
295
			nm.update_in_progress = in_progress;
296
		}
297
		return false;
298
	}
299
300
	/**
301
	 * Show or hide details by changing the CSS class
302
	 *
303
	 * @param {boolean} show
304
	 * @param {DOMNode} dom_node
305
	 */
306
	show_details(show, dom_node)
307
	{
308
		// Show / hide descriptions
309
        egw.css((dom_node && dom_node.id ? "#"+dom_node.id+' ' : '') + ".et2_box.infoDes","display:" + (show ? "block;" : "none;"));
310
		if (egwIsMobile())
311
		{
312
			var $select = jQuery('.infoDetails');
313
			(show)? $select.each(function(i,e){jQuery(e).hide();}): $select.each(function(i,e){jQuery(e).show();});
314
		}
315
	}
316
317
	confirm_delete_2(_action, _senders)
318
	{
319
		var children = false;
320
		var child_button = jQuery('#delete_sub').get(0) || jQuery('[id*="delete_sub"]').get(0);
321
		if(child_button)
322
		{
323
			for(var i = 0; i < _senders.length; i++)
324
			{
325
				if (jQuery(_senders[i].iface.node).hasClass('infolog_rowHasSubs'))
326
				{
327
					children = true;
328
					break;
329
				}
330
			}
331
			child_button.style.display = children ? 'block' : 'none';
332
		}
333
		var callbackDeleteDialog = function (button_id)
334
		{
335
			if (button_id == et2_dialog.YES_BUTTON )
336
			{
337
338
			}
339
		};
340
		et2_dialog.show_dialog(callbackDeleteDialog, this.egw.lang("Do you really want to DELETE this Rule"),this.egw.lang("Delete"), {},et2_dialog.BUTTONS_YES_NO_CANCEL, et2_dialog.WARNING_MESSAGE);
341
	}
342
343
	/**
344
	 * Confirm delete
345
	 * If entry has children, asks if you want to delete children too
346
	 *
347
	 *@param _action
348
	 *@param _senders
349
	 */
350
	confirm_delete(_action, _senders)
351
	{
352
		var children = false;
353
		var child_button = jQuery('#delete_sub').get(0) || jQuery('[id*="delete_sub"]').get(0);
354
		if(child_button)
355
		{
356
			for(var i = 0; i < _senders.length; i++)
357
			{
358
				if (jQuery(_senders[i].iface.getDOMNode()).hasClass('infolog_rowHasSubs'))
359
				{
360
					children = true;
361
					break;
362
				}
363
			}
364
			child_button.style.display = children ? 'block' : 'none';
365
		}
366
		nm_open_popup(_action, _senders);
367
	}
368
369
	/**
370
	 * Add email from addressbook
371
	 *
372
	 * @param ab_id
373
	 * @param info_cc
374
	 */
375
	add_email_from_ab(ab_id,info_cc)
376
	{
377
		var ab = <HTMLSelectElement>document.getElementById(ab_id);
378
379
		if (!ab || !ab.value)
380
		{
381
			jQuery("tr.hiddenRow").css("display", "table-row");
382
		}
383
		else
384
		{
385
			var cc = <HTMLInputElement>document.getElementById(info_cc);
386
387
			for(var i=0; i < ab.options.length && ab.options[i].value != ab.value; ++i) ;
388
389
			if (i < ab.options.length)
390
			{
391
				cc.value += (cc.value?', ':'')+ab.options[i].text.replace(/^.* <(.*)>$/,'$1');
392
				ab.value = '';
393
				// @ts-ignore
394
				ab.onchange();
395
				jQuery("tr.hiddenRow").css("display", "none");
396
			}
397
		}
398
		return false;
399
	}
400
401
	/**
402
	* If one of info_status, info_percent or info_datecompleted changed --> set others to reasonable values
403
	*
404
	* @param {string} changed_id id of changed element
405
	* @param {string} status_id
406
	* @param {string} percent_id
407
	* @param {string} datecompleted_id
408
	*/
409
	status_changed(changed_id, status_id, percent_id, datecompleted_id)
410
	{
411
		// Make sure this doesn't get executed while template is loading
412
		if(this.et2 == null || this.et2.getInstanceManager() == null) return;
413
414
		var status = <HTMLInputElement>document.getElementById(status_id);
415
		var percent = <HTMLInputElement>document.getElementById(percent_id);
416
		var datecompleted = <HTMLInputElement>document.getElementById(datecompleted_id+'[str]');
417
		if(!datecompleted)
418
		{
419
			datecompleted = <HTMLInputElement>jQuery('#'+datecompleted_id +' input').get(0);
420
		}
421
		var completed;
422
423
		switch(changed_id)
424
		{
425
			case status_id:
426
				completed = status.value == 'done' || status.value == 'billed';
427
				if (completed || status.value == 'not-started' ||
428
					(status.value == 'ongoing') != (parseFloat(percent.value) > 0 && parseFloat(percent.value) < 100))
429
				{
430
					if(completed)
431
					{
432
						percent.value = '100';
433
					}
434
					else if (status.value == 'not-started')
435
					{
436
						percent.value = '0';
437
					}
438
					else if (!completed && (parseInt(percent.value) == 0 || parseInt(percent.value) == 100))
439
					{
440
						percent.value = '10';
441
					}
442
				}
443
				break;
444
445
			case percent_id:
446
				completed = parseInt(percent.value) == 100;
447
				if (completed != (status.value == 'done' || status.value == 'billed') ||
448
					(status.value == 'not-started') != (parseInt(percent.value) == 0))
449
				{
450
					status.value = parseInt(percent.value) == 0 ? (jQuery('[value="not-started"]',status).length ?
451
						'not-started':'ongoing') : (parseInt(percent.value) == 100 ? 'done' : 'ongoing');
452
				}
453
				break;
454
455
			case datecompleted_id+'[str]':
456
			case datecompleted_id:
457
				completed = datecompleted.value != '';
458
				if (completed != (status.value == 'done' || status.value == 'billed'))
459
				{
460
					status.value = completed ? 'done' : 'not-started';
461
				}
462
				if (completed != (parseInt(percent.value) == 100))
463
				{
464
					percent.value = completed ? '100' : '0';
465
				}
466
				break;
467
		}
468
		if (!completed && datecompleted && datecompleted.value != '')
469
		{
470
			datecompleted.value = '';
471
		}
472
		else if (completed && datecompleted && datecompleted.value == '')
473
		{
474
			// todo: set current date in correct format
475
		}
476
	}
477
478
	/**
479
	 * handle "print" action from "Actions" selectbox in edit infolog window.
480
	 * check if the template is dirty then submit the template otherwise just open new window as print.
481
	 *
482
	 */
483
	edit_actions()
484
	{
485
		var widget = this.et2.getWidgetById('action');
486
		var template = this.et2._inst;
487
		if (template)
488
		{
489
			var id = template.widgetContainer.getArrayMgr('content').data['info_id'];
490
		}
491
		if (widget)
492
		{
493
			switch (widget.get_value())
494
			{
495
				case 'print':
496
					if (template.isDirty())
497
					{
498
						template.submit();
499
					}
500
					egw.open(id,'infolog','edit',{print:1});
501
					break;
502
				case 'ical':
503
					template.postSubmit();
504
					break;
505
				default:
506
					template.submit();
507
			}
508
		}
509
	}
510
511
	/**
512
	 * Open infolog entry for printing
513
	 *
514
	 * @param {aciton object} _action
515
	 * @param {object} _selected
516
	 */
517
	infolog_menu_print(_action, _selected)
518
	{
519
		var id = _selected[0].id.replace(/^infolog::/g,'');
520
		egw.open(id,'infolog','edit',{print:1});
521
	}
522
523
	/**
524
	 * Trigger print() onload window
525
	 */
526
	infolog_print_preview_onload()
527
	{
528
		var that = this;
529
		jQuery('#infolog-edit-print').bind('load',function(){
530
			var isLoadingCompleted = true;
531
			jQuery('#infolog-edit-print').bind("DOMSubtreeModified",function(event){
532
					isLoadingCompleted = false;
533
					jQuery('#infolog-edit-print').unbind("DOMSubtreeModified");
534
			});
535
			setTimeout(function() {
536
				isLoadingCompleted = false;
537
			}, 1000);
538
			var interval = setInterval(function(){
539
				if (!isLoadingCompleted)
540
				{
541
					clearInterval(interval);
542
					that.infolog_print_preview();
543
				}
544
			}, 100);
545
		});
546
	}
547
548
	/**
549
	 * Trigger print() function to print the current window
550
	 */
551
	infolog_print_preview()
552
	{
553
		this.egw.message(this.egw.lang('Printing...'));
554
		this.egw.window.print();
555
	}
556
557
	/**
558
	 *
559
	 */
560
	add_link_sidemenu()
561
	{
562
		egw.open('','infolog','add');
563
	}
564
565
	/**
566
	 * Wrapper so add -> New actions in the context menu can pass current
567
	 * filter values into new edit dialog
568
	 *
569
	 * @see add_with_extras
570
	 *
571
	 * @param {egwAction} action
572
	 * @param {egwActionObject[]} selected
573
	 */
574
	add_action_handler(action, selected)
575
	{
576
		var nm = action.getManager().data.nextmatch || false;
577
		if(nm)
578
		{
579
			this.add_with_extras(nm,action.id,
580
				nm.getArrayMgr('content').getEntry('action'),
581
				nm.getArrayMgr('content').getEntry('action_id')
582
			);
583
		}
584
	}
585
586
	/**
587
	 * Opens a new edit dialog with some extra url parameters pulled from
588
	 * standard locations.  Done with a function instead of hardcoding so
589
	 * the values can be updated if user changes them in UI.
590
	 *
591
	 * @param {et2_widget} widget Originating/calling widget
592
	 * @param _type string Type of infolog entry
593
	 * @param _action string Special action for new infolog entry
594
	 * @param _action_id string ID for special action
595
	 */
596
	add_with_extras(widget,_type, _action, _action_id)
597
	{
598
		// We use widget.getRoot() instead of this.et2 for the case when the
599
		// addressbook tab is viewing a contact + infolog list, there's 2 infolog
600
		// etemplates
601
		var nm = widget.getRoot().getWidgetById('nm');
602
		var nm_value = nm.getValue() || {};
603
604
		// It's important that all these keys are here, they override the link
605
		// registry.
606
		var action_id = nm_value.action_id ? nm_value.action_id : (_action_id != '0' ? _action_id : "") || "";
607
		if(typeof action_id == "object" && typeof action_id.length == "undefined")
608
		{
609
			// Need a real array here
610
			action_id = jQuery.map(action_id,function(val) {return val;});
611
		}
612
613
		// No action?  Try the linked filter, in case it's set
614
		if(!_action && !_action_id)
615
		{
616
			if(nm_value.col_filter && nm_value.col_filter.linked)
617
			{
618
				var split = nm_value.col_filter.linked.split(':') || '';
619
				_action = split[0] || '';
620
				action_id = split[1] || '';
621
			}
622
		}
623
		var extras = {
624
			type: _type || nm_value.col_filter.info_type || "task",
625
			cat_id: nm_value.cat_id || "",
626
			action: nm_value.action || _action || "",
627
			// egw_link can handle arrays; but server is expecting CSV
628
			action_id: typeof action_id.join != "undefined" ? action_id.join(',') : action_id
629
		};
630
		egw.open('','infolog','add',extras);
631
	}
632
633
	/**
634
	 * Get title in order to set it as document title
635
	 * @returns {string}
636
	 */
637
	getWindowTitle()
638
	{
639
		var widget = this.et2.getWidgetById('info_subject');
640
		if(widget) return widget.options.value;
641
	}
642
643
	/**
644
	 * View parent entry with all children
645
	 *
646
	 * @param {aciton object} _action
647
	 * @param {object} _selected
648
	 */
649
	view_parent(_action, _selected)
650
	{
651
		var data = egw.dataGetUIDdata(_selected[0].id);
652
		if (data && data.data && data.data.info_id_parent)
653
		{
654
			egw.link_handler(egw.link('/index.php', {
655
				menuaction: "infolog.infolog_ui.index",
656
				action: "sp",
657
				action_id: data.data.info_id_parent,
658
				ajax: "true"
659
			}), "infolog");
660
		}
661
	}
662
663
	/**
664
	 * Mess with the query for parent widget to exclude self
665
	 *
666
	 * @param {Object} request
667
	 * @param {et2_link_entry} widget
668
	 * @returns {boolean}
669
	 */
670
	parent_query(request, widget)
671
	{
672
		// No ID yet, no need to filter
673
		if(!widget.getRoot().getArrayMgr('content').getEntry('info_id'))
674
		{
675
			return true;
676
		}
677
		if(!request.options)
678
		{
679
			request.options = {};
680
		}
681
		// Exclude self from results - no app needed since it's just one app
682
		request.options.exclude = [widget.getRoot().getArrayMgr('content').getEntry('info_id')];
683
684
		return true;
685
	}
686
687
	/**
688
	 * View a list of timesheets for the linked infolog entry
689
	 *
690
	 * Only one infolog entry at a time is allowed, we just pick the first one
691
	 *
692
	 * @param {egwAction} _action
693
	 * @param {egwActionObject[]} _selected
694
	 */
695
	timesheet_list(_action, _selected)
696
	{
697
		var extras = {
698
			link_app: 'infolog',
699
			link_id: false
700
		};
701
		for(var i = 0; i < _selected.length; i++)
702
		{
703
			// Remove UID prefix for just contact_id
704
			var ids = _selected[i].id.split('::');
705
			ids.shift();
706
			ids = ids.join('::');
707
708
			extras.link_id = ids;
709
			break;
710
		}
711
712
		egw.open("","timesheet","list", extras, 'timesheet');
713
	}
714
715
	/**
716
	 * Go to parent entry
717
	 *
718
	 * @param {aciton object} _action
719
	 * @param {object} _selected
720
	 */
721
	has_parent(_action, _selected)
722
	{
723
		var data = egw.dataGetUIDdata(_selected[0].id);
724
725
		return data && data.data && data.data.info_id_parent > 0;
726
	}
727
728
	/**
729
	 * Submit template if widget has a value
730
	 *
731
	 * Used for project-selection to update pricelist items from server
732
	 *
733
	 * @param {DOMNode} _node
734
	 * @param {et2_widget} _widget
735
	 */
736
	submit_if_not_empty(_node, _widget)
737
	{
738
		if (_widget.get_value()) this.et2._inst.submit();
739
	}
740
741
	/**
742
	 * Toggle encryption
743
	 *
744
	 * @param {jQuery.Event} _event
745
	 * @param {et2_button} _widget
746
	 * @param {DOMNode} _node
747
	 */
748
	toggleEncrypt(_event, _widget, _node)
749
	{
750
		if (!this.egw.user('apps').stylite)
751
		{
752
			this.egw.message(this.egw.lang('InfoLog encryption requires EPL Subscription')+': <a href="http://www.egroupware.org/EPL">www.egroupware.org/EPL</a>');
753
			return;
754
		}
755
		this._get_stylite(function() {app.stylite.toggleEncrypt.call(app.stylite,_event,_widget,_node);});
756
	}
757
758
	/**
759
	 * Make sure stylite javascript is loaded, and call the given callback when it is
760
	 *
761
	 * @param {function} callback
762
	 * @param {object} attrs
763
	 *
764
	 */
765
	_get_stylite(callback : Function, attrs? : any[])
766
	{
767
		// use app object from etemplate2, which might be private and not just window.app
768
		var app = this.et2.getInstanceManager().app_obj;
769
770
		if (!app.stylite)
771
		{
772
			var self = this;
773
			egw_LAB.script('stylite/js/infolog-encryption.js?'+this.et2.getArrayMgr('content').data.encryption_ts).wait(function()
774
			{
775
				app.stylite = new app.classes.stylite;
776
				app.stylite.et2 = self.et2;
777
				if(callback)
778
				{
779
					callback.apply(app.stylite,attrs);
780
				}
781
			});
782
		}
783
		else
784
		{
785
			app.stylite.et2 = this.et2;
786
			callback.apply(app.stylite,attrs);
787
		}
788
	}
789
790
	/**
791
	 * OnChange callback for responsible
792
	 *
793
	 * @param {jQuery.Event} _event
794
	 * @param {et2_widget} _widget
795
	 */
796
	onchangeResponsible(_event, _widget)
797
	{
798
		if (app.stylite && app.stylite.onchangeResponsible)
799
		{
800
			app.stylite.onchangeResponsible.call(app.stylite, _event, _widget);
801
		}
802
	}
803
804
	/**
805
	 * Action handler for context menu change responsible action
806
	 *
807
	 * We populate the dialog with the current value.
808
	 *
809
	 * @param {egwAction} _action
810
	 * @param {egwActionObject[]} _selected
811
	 */
812
	change_responsible(_action, _selected)
813
	{
814
		var et2 = _selected[0].manager.data.nextmatch.getInstanceManager();
815
		var responsible = et2.widgetContainer.getWidgetById('responsible');
816
		if(responsible)
817
		{
818
			responsible.set_value([]);
819
			et2.widgetContainer.getWidgetById('responsible_action[title]').set_value('');
820
			et2.widgetContainer.getWidgetById('responsible_action[title]').set_class('');
821
			et2.widgetContainer.getWidgetById('responsible_action[ok]').set_disabled(_selected.length !== 1);
822
			et2.widgetContainer.getWidgetById('responsible_action[add]').set_disabled(_selected.length === 1)
823
			et2.widgetContainer.getWidgetById('responsible_action[delete]').set_disabled(_selected.length === 1)
824
		}
825
826
		if(_selected.length === 1)
827
		{
828
			var data = egw.dataGetUIDdata(_selected[0].id);
829
830
			if(responsible && data && data.data)
831
			{
832
				et2.widgetContainer.getWidgetById('responsible_action[title]').set_value(data.data.info_subject);
833
				et2.widgetContainer.getWidgetById('responsible_action[title]').set_class(data.data.sub_class)
834
				responsible.set_value(data.data.info_responsible);
835
			}
836
		}
837
838
		nm_open_popup(_action, _selected);
839
	}
840
841
	/**
842
	 * Handle encrypted info_desc for print purpose
843
	 * and triggers print action after decryption
844
	 *
845
	 * @param {Keyring} _keyring Mailvelope keyring to use
846
	 */
847
	printEncrypt(_keyring)
848
	{
849
		//this.mailvelopeAvailable(this.toggleEncrypt);
850
		var info_desc = this.et2.getWidgetById('info_des');
851
852
		var self = this;
853
		mailvelope.createDisplayContainer('#infolog-edit-print_info_des', info_desc.value, _keyring).then(function(_container)
854
		{
855
			var $info_des_dom = jQuery(self.et2.getWidgetById('info_des').getDOMNode());
856
//			$info_des_dom.children('iframe').height($info_des_dom.height());
857
			$info_des_dom.children('span').hide();
858
			//Trigger print action
859
			self.infolog_print_preview();
860
		},
861
		function(_err)
862
		{
863
			self.egw.message(_err, 'error');
864
		});
865
	}
866
867
}
868
869
app.classes.infolog = InfologApp;
870