addressbook/js/app.ts   F
last analyzed

Complexity

Total Complexity 183
Complexity/F 4.95

Size

Lines of Code 1324
Function Count 37

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 823
dl 0
loc 1324
rs 1.777
c 0
b 0
f 0
wmc 183
mnd 146
bc 146
fnc 37
bpm 3.9459
cpm 4.9459
noi 0

36 Functions

Rating   Name   Duplication   Size   Complexity  
A AddressbookApp.view_set_list 0 16 3
B AddressbookApp.show_custom_country 0 30 6
A AddressbookApp.adv_search 0 26 3
B AddressbookApp.add_cal 0 28 5
D AddressbookApp.setState 0 95 13
A AddressbookApp.showphones 0 19 1
A AddressbookApp.view 0 22 2
D AddressbookApp.addEmail 0 64 13
B AddressbookApp.rename_list 0 43 6
A AddressbookApp.getGeolocationConfig 0 14 2
A AddressbookApp.getWindowTitle 0 9 2
C AddressbookApp._confirmdialog_callback 0 57 9
B AddressbookApp.geoLocation_enabled 0 40 8
A AddressbookApp.can_merge 0 13 1
B AddressbookApp.adb_mail_vcard 0 37 5
A AddressbookApp.merge_mail 0 39 4
B AddressbookApp.geoLocationExec 0 28 6
B AddressbookApp.view_infolog 0 46 5
B AddressbookApp.add_new_list 0 36 6
B AddressbookApp.view_calendar 0 61 6
A AddressbookApp.is_share_enabled 0 22 3
B AddressbookApp.et2_ready 0 50 7
A AddressbookApp.add_task 0 20 2
A AddressbookApp.view_actions 0 24 2
A AddressbookApp.hidephones 0 19 1
A AddressbookApp.nm_compare_field 0 15 3
A AddressbookApp.getState 0 30 4
A AddressbookApp.account_change 0 37 3
C AddressbookApp.check_value 0 30 9
D AddressbookApp.geoLocationUrl 0 65 12
B AddressbookApp._copyvalues 0 20 7
C AddressbookApp.observer 0 68 10
B AddressbookApp.filter2_onchange 0 29 5
B AddressbookApp._add_new_list_prompt 0 50 5
A AddressbookApp.mailCheckbox 0 13 3
A AddressbookApp.destroy 0 8 1

How to fix   Complexity   

Complexity

Complex classes like addressbook/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 - Addressbook - Javascript UI
3
 *
4
 * @link: https://www.egroupware.org
5
 * @package addressbook
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 Addressbook
24
 *
25
 * @augments AppJS
26
 */
27
class AddressbookApp extends EgwApp
28
{
29
	readonly appname = 'addressbook';
30
31
	/**
32
	 * Constructor
33
	 *
34
	 * @memberOf app.addressbook
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
58
	 */
59
	et2_ready(et2, name)
60
	{
61
		// r49769 let's CRM view run under currentapp == "addressbook", which causes
62
		// app.addressbook.et2_ready called before app.infolog.et2_ready and therefore
63
		// app.addressbook.et2 would point to infolog template, if we not stop here
64
		if (name.match(/^infolog|tracker\./)) return;
65
66
		// call parent
67
		super.et2_ready(et2, name);
68
69
		switch (name)
70
		{
71
			case 'addressbook.edit':
72
				var content = this.et2.getArrayMgr('content').data;
73
				if (typeof content.showsearchbuttons == 'undefined' || !content.showsearchbuttons)
74
				{
75
					this.show_custom_country(jQuery('select[id*="adr_one_countrycode"]').get(0));
76
					this.show_custom_country(jQuery('select[id*="adr_two_countrycode"]').get(0));
77
78
					// Instanciate infolog JS too - wrong app, so it won't be done automatically
79
					if(typeof window.app.infolog != 'object' && typeof window.app.classes['infolog'] == 'function')
80
					{
81
						window.app.infolog = new window.app.classes.infolog();
82
					}
83
				}
84
				// Call check value if the AB got opened with presets
85
				if (window.location.href.match(/&presets\[email\]/g) && content.presets_fields)
86
				{
87
					for(var i=0;i< content.presets_fields.length;i++)
88
					{
89
						this.check_value(this.et2.getWidgetById(content.presets_fields),0);
90
					}
91
				}
92
				break;
93
		}
94
95
		jQuery('select[id*="adr_one_countrycode"]').each(function() {
96
			app.addressbook.show_custom_country(this);
97
		});
98
		jQuery('select[id*="adr_two_countrycode"]').each(function() {
99
			app.addressbook.show_custom_country(this);
100
		});
101
	}
102
103
	/**
104
	 * Observer method receives update notifications from all applications
105
	 *
106
	 * App is responsible for only reacting to "messages" it is interested in!
107
	 *
108
	 * Addressbook checks for CRM view to update the displayed data if you edit
109
	 * that contact
110
	 *
111
	 * @param {string} _msg message (already translated) to show, eg. 'Entry deleted'
112
	 * @param {string} _app application name
113
	 * @param {(string|number)} _id id of entry to refresh or null
114
	 * @param {string} _type either 'update', 'edit', 'delete', 'add' or null
115
	 * - update: request just modified data from given rows.  Sorting is not considered,
116
	 *		so if the sort field is changed, the row will not be moved.
117
	 * - edit: rows changed, but sorting may be affected.  Requires full reload.
118
	 * - delete: just delete the given rows clientside (no server interaction neccessary)
119
	 * - add: requires full reload for proper sorting
120
	 * @param {string} _msg_type 'error', 'warning' or 'success' (default)
121
	 * @param {object|null} _links app => array of ids of linked entries
122
	 * or null, if not triggered on server-side, which adds that info
123
	 * @return {false|*} false to stop regular refresh, thought all observers are run
124
	 */
125
	observer(_msg, _app, _id, _type, _msg_type, _links)
126
	{
127
		// Edit to the current entry
128
		var state = this.getState();
129
		if(_app === 'addressbook' && state && state.type && state.type === 'view' && state.id === _id)
130
		{
131
			var content = egw.dataGetUIDdata('addressbook::'+_id);
132
			if (content.data)
133
			{
134
				var view = etemplate2.getById('addressbook-view');
135
				if(view)
136
				{
137
					view.widgetContainer._children[0].set_value({content:content.data});
138
				}
139
			}
140
			return false;
141
		}
142
		else if(_app === 'calendar')
143
		{
144
			// Event changed, update any [known] contacts participating
145
			var content = egw.dataGetUIDdata(_app+'::'+_id);
146
			if (content && content.data && content.data.participant_types && content.data.participant_types.c)
147
			{
148
				for(var contact in content.data.participant_types.c)
149
				{
150
					// Refresh handles checking to see if the contact is known,
151
					// and updating it directly
152
					egw.dataRefreshUID('addressbook::'+contact);
153
				}
154
				return true;
155
			}
156
			else if (!content)
157
			{
158
				// No data on the event, we'll have to reload if calendar column is visible
159
				// to get the updated information
160
				var nm = etemplate2.getById('addressbook-index').widgetContainer.getWidgetById('nm');
161
				var pref = nm ? nm._getPreferences() : false;
162
				if(pref && pref.visible.indexOf('calendar_calendar') > -1)
163
				{
164
					nm.refresh(null,'update');
165
				}
166
			}
167
		}
168
169
		return true;
170
	}
171
172
	/**
173
	 * Open CRM view
174
	 *
175
	 * @param _action
176
	 * @param _senders
177
	 */
178
	view(_action, _senders)
179
	{
180
		var index = _senders[0]._index;
181
		var id = _senders[0].id.split('::').pop();
182
		var extras : any = {
183
			index: index
184
		};
185
186
		// CRM list
187
		if(_action.id != 'view')
188
		{
189
			extras.crm_list = _action.id.replace('view-','');
190
		}
191
192
		this.egw.open(id, 'addressbook', 'view', extras, '_self', 'addressbook');
193
	}
194
195
	/**
196
	 * Set link filter for the already open & rendered  list
197
	 *
198
	 * @param {Object} filter Object with key / value pairs of filters to set
199
	 */
200
	view_set_list(filter)
201
	{
202
		// Find the infolog list
203
		var list = etemplate2.getById(
204
			jQuery(this.et2.getInstanceManager().DOMContainer).nextAll('.et2_container').attr('id')
205
		);
206
		var nm = list ? list.widgetContainer.getWidgetById('nm') : null;
207
		if(nm)
208
		{
209
			nm.applyFilters(filter);
210
		}
211
	}
212
213
	/**
214
	 * Run an action from CRM view toolbar
215
	 *
216
	 * @param {object} _action
217
	 */
218
	view_actions(_action)
219
	{
220
		var id = this.et2.getArrayMgr('content').data.id;
221
222
		switch(_action.id)
223
		{
224
			case 'open':
225
				this.egw.open(id, 'addressbook', 'edit');
226
				break;
227
			case 'copy':
228
				this.egw.open(id, 'addressbook', 'edit', { makecp: 1});
229
				break;
230
			case 'cancel':
231
				this.egw.open(null, 'addressbook', 'list', null, '_self', 'addressbook');
232
				break;
233
			default:	// submit all other buttons back to server
234
				this.et2._inst.submit();
235
				break;
236
		}
237
	}
238
239
	/**
240
	 * Open the calender to view the selected contacts
241
	 * @param {egwAction} _action
242
	 * @param {egwActionObject[]} _senders
243
	 */
244
	view_calendar(_action, _senders)
245
	{
246
		var extras : any = {
247
			filter: 'all',
248
			cat_id: '',
249
			owner: []
250
		};
251
		var orgs = [];
252
		for(var i = 0; i < _senders.length; i++)
253
		{
254
			// Remove UID prefix for just contact_id
255
			var ids = _senders[i].id.split('::');
256
			ids.shift();
257
			ids = ids.join('::');
258
259
			// Orgs need to get all the contact IDs first
260
			if (ids.substr(0,9) == 'org_name:')
261
			{
262
				orgs.push(ids);
263
			}
264
			else
265
			{
266
				// Check to see if this is a user account, we prefer to use
267
				// account ID in calendar
268
				var data = this.egw.dataGetUIDdata(_senders[i].id);
269
				if(data && data.data && data.data.account_id)
270
				{
271
					extras.owner.push(data.data.account_id);
272
				}
273
				else
274
				{
275
					extras.owner.push('c'+ids);
276
				}
277
			}
278
		}
279
280
		if(orgs.length > 0)
281
		{
282
			// Get organisation contacts, then show infolog list
283
			this.egw.json('addressbook.addressbook_ui.ajax_organisation_contacts',
284
				[orgs],
285
				function(contacts) {
286
					for(var i = 0; i < contacts.length; i++)
287
					{
288
						extras.owner.push('c'+contacts[i]);
289
					}
290
					extras.owner = extras.owner.join(',');
291
					this.egw.open('','calendar','list',extras,'calendar');
292
				},this,true,this
293
			).sendRequest();
294
		}
295
		else
296
		{
297
			extras.owner = extras.owner.join(',');
298
			egw.open('', 'calendar', 'list', extras, 'calendar');
299
		}
300
	}
301
	/**
302
	 * Add appointment or show calendar for selected contacts, call default nm_action after some checks
303
	 *
304
	 * @param _action
305
	 * @param _senders
306
	 */
307
	add_cal(_action, _senders)
308
	{
309
		if (!_senders[0].id.match(/^(?:addressbook::)?[0-9]+$/))
310
		{
311
			// send org-view requests to server
312
			_action.data.nm_action = "submit";
313
			nm_action(_action, _senders);
314
		}
315
		else
316
		{
317
			var ids = egw.user('account_id')+',';
318
			for (var i = 0; i < _senders.length; i++)
319
			{
320
				// Remove UID prefix for just contact_id
321
				var id = _senders[i].id.split('::');
322
				ids += "c" + id[1] + ((i < _senders.length - 1) ? "," : "");
323
			}
324
			var extra = {};
325
			extra[_action.data && _action.data.url && _action.data.url.indexOf('owner') > 0 ? 'owner' : 'participants'] = ids;
326
327
			// Use framework to add calendar entry
328
			egw.open('','calendar','add',extra);
329
		}
330
	}
331
332
	/**
333
	 * View infolog entries linked to selected contact
334
	 * @param {egwAction} _action Select action
335
	 * @param {egwActionObject[]} _senders Selected contact(s)
336
	 */
337
	view_infolog(_action, _senders)
338
	{
339
		var extras = {
340
			action: 'addressbook',
341
			action_id: [],
342
			action_title: _senders.length > 1 ? this.egw.lang('selected contacts') : ''
343
		};
344
		var orgs = [];
345
		for(var i = 0; i < _senders.length; i++)
346
		{
347
			// Remove UID prefix for just contact_id
348
			var ids = _senders[i].id.split('::');
349
			ids.shift();
350
			ids = ids.join('::');
351
352
			// Orgs need to get all the contact IDs first
353
			if (ids.substr(0,9) == 'org_name:')
354
			{
355
				orgs.push(ids);
356
			}
357
			else
358
			{
359
				extras.action_id.push(ids);
360
			}
361
		}
362
363
		if(orgs.length > 0)
364
		{
365
			// Get organisation contacts, then show infolog list
366
			this.egw.json('addressbook.addressbook_ui.ajax_organisation_contacts',
367
				[orgs],
368
				function(contacts) {
369
					extras.action_id = extras.action_id.concat(contacts);
370
					this.egw.open('','infolog','list',extras,'infolog');
371
				},this,true,this
372
			).sendRequest();
373
		}
374
		else
375
		{
376
			egw.open('', 'infolog', 'list', extras, 'infolog');
377
		}
378
	}
379
380
	/**
381
	 * Add task for selected contacts, call default nm_action after some checks
382
	 *
383
	 * @param _action
384
	 * @param _senders
385
	 */
386
	add_task(_action, _senders)
387
	{
388
		if (!_senders[0].id.match(/^(addressbook::)?[0-9]+$/))
389
		{
390
			// send org-view requests to server
391
			_action.data.nm_action = "submit";
392
		}
393
		else
394
		{
395
			// call nm_action's popup
396
			_action.data.nm_action = "popup";
397
		}
398
		nm_action(_action, _senders);
399
	}
400
401
	/**
402
	 * [More...] in phones clicked: copy allways shown phone numbers to phone popup
403
	 *
404
	 * @param {jQuery.event} _event
405
	 * @param {et2_widget} _widget
406
	 */
407
	showphones(_event, _widget)
408
	{
409
		this._copyvalues({
410
			tel_home: 'tel_home2',
411
			tel_work: 'tel_work2',
412
			tel_cell: 'tel_cell2',
413
			tel_fax:  'tel_fax2'
414
		});
415
		jQuery('table.editphones').css('display','inline');
416
417
		_event.stopPropagation();
418
		return false;
419
	}
420
421
	/**
422
	 * [OK] in phone popup clicked: copy phone numbers back to always shown ones
423
	 *
424
	 * @param {jQuery.event} _event
425
	 * @param {et2_widget} _widget
426
	 */
427
	hidephones(_event, _widget)
428
	{
429
		this._copyvalues({
430
			tel_home2: 'tel_home',
431
			tel_work2: 'tel_work',
432
			tel_cell2: 'tel_cell',
433
			tel_fax2:  'tel_fax'
434
		});
435
		jQuery('table.editphones').css('display','none');
436
437
		_event.stopPropagation();
438
		return false;
439
	}
440
441
	/**
442
	 * Copy content of multiple fields
443
	 *
444
	 * @param {object} what object with src: dst pairs
445
	 */
446
	_copyvalues(what)
447
	{
448
		for(var name in what)
449
		{
450
			var src = this.et2.getWidgetById(name);
451
			var dst = this.et2.getWidgetById(what[name]);
452
			if (src && dst) dst.set_value(src.get_value ? src.get_value() : src.value);
453
		}
454
		// change tel_prefer according to what
455
		var tel_prefer = this.et2.getWidgetById('tel_prefer');
456
		if (tel_prefer)
457
		{
458
			var val = tel_prefer.get_value ? tel_prefer.get_value() : tel_prefer.value;
459
			if (typeof what[val] != 'undefined') tel_prefer.set_value(what[val]);
460
		}
461
	}
462
463
	/**
464
	 * Callback function to create confirm dialog for duplicates contacts
465
	 *
466
	 * @param {object} _data includes duplicates contacts information
467
	 *
468
	 */
469
	_confirmdialog_callback(_data)
470
	{
471
		var confirmdialog = function(_title, _value, _buttons, _egw_or_appname?)
472
		{
473
			return et2_createWidget("dialog",
474
			{
475
				callback(_buttons, _value)
476
				{
477
					if (_buttons == et2_dialog.OK_BUTTON)
478
					{
479
						var id = '';
480
						var content = this.template.widgetContainer.getArrayMgr('content').data;
481
						for (var row in _value.grid)
482
						{
483
							if (_value.grid[row].confirm == "true" && typeof content.grid !='undefined')
484
							{
485
								id = this.options.value.content.grid[row].confirm;
486
								egw.open(id, 'addressbook');
487
488
							}
489
						}
490
					}
491
				},
492
				title: _title||egw.lang('Input required'),
493
				buttons: _buttons||et2_dialog.BUTTONS_OK_CANCEL,
494
				value: {
495
					content: {
496
						grid: _value
497
					}
498
				},
499
				template: egw.webserverUrl+'/addressbook/templates/default/dupconfirmdialog.xet'
500
			}, et2_dialog._create_parent(_egw_or_appname));
501
		};
502
503
		if (_data.msg && _data.doublicates)
504
		{
505
			var content = [];
506
507
			for(var id in _data.doublicates)
508
			{
509
				content.push({"confirm":id,"name":_data.doublicates[id]});
510
			}
511
			confirmdialog(this.egw.lang('Duplicate warning'),content,et2_dialog.BUTTONs_OK_CANCEL);
512
		}
513
		if (typeof _data.fileas_options == 'object' && this.et2)
514
		{
515
			var selbox = this.et2.getWidgetById('fileas_type');
516
			if (selbox)
517
			{
518
				selbox.set_select_options(_data.fileas_sel_options);
519
			}
520
		}
521
	}
522
523
	/**
524
	 * Callback if certain fields get changed
525
	 *
526
	 * @param {widget} widget widget
527
	 * @param {string} own_id Current AB id
528
	 */
529
	check_value(widget, own_id)
530
	{
531
		// if we edit an account, call account_change to let it do it's stuff too
532
		if (this.et2.getWidgetById('account_lid'))
533
		{
534
			this.account_change(null, widget);
535
		}
536
537
		var values = this.et2._inst.getValues(this.et2);
538
539
		if (widget.id.match(/n_/))
540
		{
541
			var value = '';
542
			if (values.n_prefix) value += values.n_prefix+" ";
543
			if (values.n_given)  value += values.n_given+" ";
544
			if (values.n_middle) value += values.n_middle+" ";
545
			if (values.n_family) value += values.n_family+" ";
546
			if (values.n_suffix) value += values.n_suffix;
547
548
			var name = this.et2.getWidgetById("n_fn");
549
			if (typeof name != 'undefined')	name.set_value(value);
550
		}
551
		egw.json('addressbook.addressbook_ui.ajax_check_values', [values, widget.id, own_id],this._confirmdialog_callback,this,true,this).sendRequest();
552
	}
553
554
	show_custom_country(selectbox)
555
	{
556
		if(!selectbox) return;
557
		var custom_field_name = selectbox.id.replace("countrycode", "countryname");
558
		var custom_field = <HTMLInputElement>document.getElementById(custom_field_name);
559
		if(custom_field && selectbox.value == "-custom-") {
560
			custom_field.style.display = "inline";
561
		}
562
		else if (custom_field)
563
		{
564
			if((selectbox.value == "" || selectbox.value == null) && custom_field.value != "")
565
			{
566
				selectbox.value = "-custom-";
567
				// Chosen needs this to update
568
				jQuery(selectbox).trigger("liszt:updated");
569
570
				custom_field.style.display = "inline";
571
			}
572
			else
573
			{
574
				custom_field.style.display = "none";
575
			}
576
		}
577
		var region = this.et2.getWidgetById(selectbox.name.replace('countrycode', 'region'));
578
		if (region)
579
		{
580
			region.set_country_code(selectbox.value);
581
			region.options.select_options = {};
582
			region.transformAttributes(region.options);
583
		}
584
	}
585
586
	/**
587
	 * Add a new mailing list.  If any contacts are selected, they will be added.
588
	 *
589
	 * @param {egwAction} owner
590
	 * @param {egwActionObject[]} selected
591
	 */
592
	add_new_list(owner, selected?)
593
	{
594
		if(!owner || typeof owner == 'object')
595
		{
596
			var filter = this.et2.getWidgetById('filter');
597
			owner = filter.getValue()||egw.preference('add_default','addressbook');
598
		}
599
		var contacts = [];
600
		if(selected && selected[0] && selected[0].getAllSelected())
601
		{
602
			// Action says all contacts selected, better ask the server for _all_ the IDs
603
			var fetching = fetchAll(selected, this.et2.getWidgetById('nm'), jQuery.proxy(
604
				function(contacts) {
605
					this._add_new_list_prompt(owner, contacts);
606
				}, this));
607
			if (fetching) return;
608
		}
609
		if(selected && selected.length)
610
		{
611
			for(var i = 0; i < selected.length; i++)
612
			{
613
				// Remove UID prefix for just contact_id
614
				var ids = selected[i].id.split('::');
615
				ids.shift();
616
				ids = ids.join('::');
617
				contacts.push(ids);
618
			}
619
		}
620
		this._add_new_list_prompt(owner, contacts);
621
	}
622
623
	/**
624
	 * Ask the user for a name, then create a new list with the provided contacts
625
	 * in it.
626
	 *
627
	 * @param {int} owner
628
	 * @param {String[]} contacts
629
	 */
630
	_add_new_list_prompt(owner, contacts)
631
	{
632
		var lists = this.et2.getWidgetById('filter2');
633
		et2_dialog.show_prompt(
634
			function(button, name) {
635
				if(button == et2_dialog.OK_BUTTON)
636
				{
637
					egw.json('addressbook.addressbook_ui.ajax_set_list',[0, name, owner, contacts],
638
						function(result)
639
						{
640
							if(typeof result == 'object') return; // This response not for us
641
							// Update list
642
							if(result)
643
							{
644
								lists.options.select_options.unshift({value:result,label:name});
645
								lists.set_select_options(lists.options.select_options);
646
647
								// Set to new list so they can see it easily
648
								lists.set_value(result);
649
								// Call cahnge event manually after setting the value
650
								// Not sure why our selectbox does not trigger change event
651
								jQuery(lists.node).change();
652
							}
653
							// Add to actions
654
							var addressbook_actions = egw_getActionManager('addressbook',false);
655
							var dist_lists = null;
656
							if(addressbook_actions && (dist_lists = addressbook_actions.getActionById('to_list')))
657
							{
658
								var id = 'to_list_' + result;
659
								var action = dist_lists.addAction(
660
									'popup',
661
									id,
662
									name
663
								);
664
								action.updateAction({group: 1});
665
							}
666
						}
667
					).sendRequest(true);
668
				}
669
			},
670
			this.egw.lang('Name for the distribution list'),
671
			this.egw.lang('Add a new list')
672
		);
673
	}
674
675
	/**
676
	 * Rename the current distribution list selected in the nextmatch filter2
677
	 *
678
	 * Differences from add_new_list are in the dialog, parameters sent, and how the
679
	 * response is dealt with
680
	 *
681
	 * @param {egwAction} action Action selected in context menu (rename)
682
	 * @param {egwActionObject[]} selected The selected row(s).  Not used for this.
683
	 */
684
	rename_list(action, selected)
685
	{
686
		var lists = this.et2.getWidgetById('filter2');
687
		var list = lists.getValue() || 0;
688
		var value = null;
689
		for(var i = 0; i < lists.options.select_options.length; i++)
690
		{
691
			if(lists.options.select_options[i].value == list)
692
			{
693
				value = lists.options.select_options[i];
694
			}
695
		}
696
		et2_dialog.show_prompt(
697
			function(button, name) {
698
				if(button == et2_dialog.OK_BUTTON)
699
				{
700
					egw.json('addressbook.addressbook_ui.ajax_set_list',[list, name],
701
						function(result)
702
						{
703
							if(typeof result == 'object') return; // This response not for us
704
							// Update list
705
							if(result)
706
							{
707
								value.label = name;
708
								lists.set_select_options(lists.options.select_options);
709
							}
710
						}
711
					).sendRequest(true);
712
				}
713
			},
714
			this.egw.lang('Name for the distribution list'),
715
			this.egw.lang('Rename list'),
716
			value.label
717
		);
718
	}
719
720
	/**
721
	 * OnChange for distribution list selectbox
722
	 */
723
	filter2_onchange()
724
	{
725
		var filter = this.et2.getWidgetById('filter');
726
		var filter2 = this.et2.getWidgetById('filter2');
727
		var widget = this.et2.getWidgetById('nm');
728
		var filter2_val = filter2.get_value();
729
730
		if(filter2_val == 'add')
731
		{
732
			this.add_new_list(typeof widget == 'undefined' ? this.et2.getWidgetById('filter').value : widget.header.filter.get_value());
733
			filter2.set_value('');
734
		}
735
		// automatic switch to accounts addressbook or all addressbooks depending on distribution list is a group
736
		else if (filter2_val && (filter2_val < 0) !== (filter.get_value() === '0'))
737
		{
738
			// Change filter & filter2 at the same time
739
			widget.applyFilters({
740
				filter: filter2_val < 0 ? '0' : '',
741
				filter2: filter2_val
742
			});
743
			// Don't get rows here, let applyFilters() do it
744
			return false;
745
		}
746
747
		return true;
748
	}
749
750
	/**
751
	 * Method to enable actions by comparing a field with given value
752
	 */
753
	nm_compare_field()
754
	{
755
		var field = this.et2.getWidgetById('filter2');
756
		if (field) var val = field.get_value();
757
		if (val)
758
		{
759
			return nm_compare_field;
760
		}
761
		else
762
		{
763
			return false;
764
		}
765
	}
766
767
	/**
768
	 * Apply advanced search filters to index nextmatch
769
	 *
770
	 * @param {object} filters
771
	 */
772
	adv_search(filters)
773
	{
774
		var index = window.opener.etemplate2.getById('addressbook-index');
775
		if(!index)
776
		{
777
			alert('Could not find index');
778
			egw(window).close();
779
			return false;
780
		}
781
		var nm = index.widgetContainer.getWidgetById('nm');
782
		if(!index)
783
		{
784
			window.opener.egw.message('Could not find list', 'error');
785
			egw(window).close();
786
			return false;
787
		}
788
		// Reset filters first
789
		nm.activeFilters = {};
790
		nm.applyFilters(filters);
791
		return false;
792
	}
793
794
	/**
795
	 * Mail vCard
796
	 *
797
	 * @param {object} _action
798
	 * @param {array} _elems
799
	 */
800
	adb_mail_vcard(_action, _elems)
801
	{
802
		var link = {'preset[type]':[], 'preset[file]':[]};
803
		var content = {data:{files:{file:[], type:[]}}};
804
		var nm = this.et2.getWidgetById('nm');
805
		if(fetchAll(_elems, nm, jQuery.proxy(function(ids) {
806
			this.adb_mail_vcard(_action, ids.map(function(num) {return {id:'addressbook::'+num};}));
807
		}, this)))
808
		{
809
			return;
810
		}
811
812
		for (var i = 0; i < _elems.length; i++)
813
		{
814
			var idToUse = _elems[i].id;
815
			var idToUseArray = idToUse.split('::');
816
			idToUse = idToUseArray[1];
817
			link['preset[type]'].push("text/vcard; charset="+(egw.preference('vcard_charset', 'addressbook') || 'utf-8'));
818
			link['preset[file]'].push("vfs://default/apps/addressbook/"+idToUse+"/.entry");
819
			content.data.files.file.push("vfs://default/apps/addressbook/"+idToUse+"/.entry");
820
			content.data.files.type.push("text/vcard; charset="+(egw.preference('vcard_charset', 'addressbook') || 'utf-8'));
821
		}
822
		egw.openWithinWindow("mail", "setCompose", content, link, /mail.mail_compose.compose/);
823
824
		for (var index in content)
825
		{
826
			if (content[index].file.length > 0)
827
			{
828
				egw.message(egw.lang('%1 contact(s) added as %2', content[index].file.length, egw.lang(index)));
829
				return;
830
			}
831
		}
832
833
	}
834
835
	/**
836
	 * Action function to set business or private mail checkboxes to user preferences
837
	 *
838
	 * @param {egwAction} action Action user selected.
839
	 */
840
	mailCheckbox(action)
841
	{
842
		var preferences = {
843
			business: action.getManager().getActionById('email_business').checked ? true : false,
844
			private: action.getManager().getActionById('email_home').checked ? true : false
845
		};
846
		this.egw.set_preference('addressbook','preferredMail', preferences);
847
	}
848
849
	/**
850
	 * Action function to add the email address (business or home) of the selected
851
	 * contacts to a compose email popup window.
852
	 *
853
	 * Uses the egw API to handle the opening of the popup.
854
	 *
855
	 * @param {egwAction} action Action user selected.  Should have ID of either
856
	 *  'email_business' or 'email_home', from server side definition of actions.
857
	 * @param {egwActionObject[]} selected Selected rows
858
	 */
859
	addEmail(action, selected)
860
	{
861
		// Check for all selected.
862
		var nm = this.et2.getWidgetById('nm');
863
		if(fetchAll(selected, nm, jQuery.proxy(function(ids) {
864
			// fetchAll() returns just the ID, no prefix, so map it to match normal selected
865
			this.addEmail(action, ids.map(function(num) {return {id:'addressbook::'+num};}));
866
		}, this)))
867
		{
868
			// Need more IDs, will use the above callback when they're ready.
869
			return;
870
		}
871
872
		// Go through selected & pull email addresses from data
873
		var emails = [];
874
		for(var i = 0; i < selected.length; i++)
875
		{
876
			// Pull data from global cache
877
			var data = egw.dataGetUIDdata(selected[i].id) || {data:{}};
878
879
			var email_business = data.data[action.getManager().getActionById('email_business').checked ? 'email' : ''];
880
			var email = data.data[action.getManager().getActionById('email_home').checked ? 'email_home' : ''];
881
			// prefix email with full name
882
			var personal = data.data.n_fn || '';
883
			if (personal.match(/[^a-z0-9. -]/i)) personal = '"'+personal.replace(/"/, '\\"')+'"';
884
885
			//remove comma in personal as it will confilict with mail content comma seperator in the process
886
			personal = personal.replace(/,/g,'');
887
888
			if(email_business)
889
			{
890
				emails.push((personal?personal+' <':'')+email_business+(personal?'>':''));
891
			}
892
			if(email)
893
			{
894
				emails.push((personal?personal+' <':'')+email+(personal?'>':''));
895
			}
896
		}
897
		switch (action.id)
898
		{
899
			case "add_to_to":
900
				egw.open_link('mailto:' + emails.join(',').replace(/&/g, '__AMPERSAND__'));
901
				break;
902
			case "add_to_cc":
903
				egw.open_link('mailto:' + '?cc='  + emails.join(',').replace(/&/g, '__AMPERSAND__'));
904
				//egw.mailto('mailto:');
905
				break;
906
			case "add_to_bcc":
907
				egw.open_link('mailto:' + '?bcc=' + emails.join(',').replace(/&/g, '__AMPERSAND__'));
908
				break;
909
		}
910
911
		return false;
912
	}
913
914
	/**
915
	 * Merge the selected contacts into the target document.
916
	 *
917
	 * Normally we let the framework handle this, but in addressbook we want to
918
	 * interfere and customize things a little to ask about saving to infolog.
919
	 *
920
	 * @param {egwAction} action - The document they clicked
921
	 * @param {egwActionObject[]} selected - Rows selected
922
	 */
923
	merge_mail(action, selected, target)
924
	{
925
		// Special processing for email documents - ask about infolog
926
		if(action && action.data && selected.length > 1)
927
		{
928
			var callback = function(button, value) {
929
				if(button == et2_dialog.OK_BUTTON)
930
				{
931
					var _action = jQuery.extend(true, {}, action);
932
					if(value.infolog)
933
					{
934
						_action.data.menuaction += '&to_app=infolog&info_type='+value.info_type;
935
					}
936
					nm_action(_action, selected, target);
937
				}
938
			};
939
			et2_createWidget("dialog",{
940
				callback: callback,
941
				title: action.caption,
942
				buttons: et2_dialog.BUTTONS_OK_CANCEL,
943
				type: et2_dialog.QUESTION_MESSAGE,
944
				template: egw.webserverUrl+'/addressbook/templates/default/mail_merge_dialog.xet',
945
				value: {content: {info_type: 'email'}, sel_options: this.et2.getArrayMgr('sel_options').data}
946
			});
947
		}
948
		else
949
		{
950
			// Normal processing for only one contact selected
951
			return nm_action(action, selected, target);
952
		}
953
	}
954
955
	/**
956
	 * Retrieve the current state of the application for future restoration
957
	 *
958
	 * Overridden from parent to handle viewing a contact.  In this case state
959
	 * will be {contact_id: #}
960
	 *
961
	 * @return {object} Application specific map representing the current state
962
	 */
963
	getState()
964
	{
965
		// Most likely we're in the list view
966
		var state = super.getState();
967
968
		if(jQuery.isEmptyObject(state))
969
		{
970
			// Not in a list view.  Try to find contact ID
971
			var etemplates = etemplate2.getByApplication('addressbook');
972
			for(var i = 0; i < etemplates.length; i++)
973
			{
974
				var content = etemplates[i].widgetContainer.getArrayMgr("content");
975
				if(content && content.getEntry('id'))
976
				{
977
					state = {app: 'addressbook', id: content.getEntry('id'), type: 'view'};
978
					break;
979
				}
980
			}
981
		}
982
983
		return state;
984
	}
985
986
	/**
987
	 * Set the application's state to the given state.
988
	 *
989
	 * Overridden from parent to stop the contact view's infolog nextmatch from
990
	 * being changed.
991
	 *
992
	 * @param {{name: string, state: object}|string} state Object (or JSON string) for a state.
993
	 *	Only state is required, and its contents are application specific.
994
	 *
995
	 * @return {boolean} false - Returns false to stop event propagation
996
	 */
997
	setState(state, template?)
998
	{
999
		var current_state = this.getState();
1000
1001
		// State should be an object, not a string, but we'll parse
1002
		if(typeof state == "string")
1003
		{
1004
			if(state.indexOf('{') != -1 || state =='null')
1005
			{
1006
				state = JSON.parse(state);
1007
			}
1008
		}
1009
1010
1011
		// Redirect from view to list - parent would do this, but infolog nextmatch stops it
1012
		if(current_state.app && current_state.id && (typeof state.state == 'undefined' || typeof state.state.app == 'undefined'))
1013
		{
1014
			// Redirect to list
1015
			// 'blank' is the special name for no filters, send that instead of the nice translated name
1016
			var safe_name = jQuery.isEmptyObject(state) || jQuery.isEmptyObject(state.state||state.filter) ? 'blank' : state.name.replace(/[^A-Za-z0-9-_]/g, '_');
1017
			egw.open('',this.appname,'list',{'favorite': safe_name},this.appname);
1018
			return false;
1019
		}
1020
		else if (jQuery.isEmptyObject(state))
1021
		{
1022
			// Regular handling first to clear everything but advanced search
1023
			super.setState(state);
1024
1025
			// Clear advanced search, which is in session and etemplate
1026
			egw.json('addressbook.addressbook_ui.ajax_clear_advanced_search',[], function() {
1027
				framework.setWebsiteTitle('addressbook','');
1028
				var index = etemplate2.getById('addressbook-index');
1029
				if(index && index.widgetContainer)
1030
				{
1031
					var nm = index.widgetContainer.getWidgetById('nm');
1032
					if(nm)
1033
					{
1034
						nm.applyFilters({
1035
							advanced_search: false
1036
						});
1037
					}
1038
				}
1039
			},this).sendRequest(true);
1040
			return false;
1041
		}
1042
		else if (state.state.grouped_view)
1043
		{
1044
			// Deal with grouped views that are not valid (not in list of options)
1045
			// by faking viewing that organisation
1046
			var index = etemplate2.getById('addressbook-index');
1047
			if(index && index.widgetContainer)
1048
			{
1049
				var grouped = index.widgetContainer.getWidgetById('grouped_view');
1050
				var options : any[];
1051
				if(grouped && grouped.options && grouped.options.select_options)
1052
				{
1053
					options = grouped.options.select_options;
1054
				}
1055
1056
				// Check to see if it's not there
1057
				if(options && (options.find &&
1058
					!options.find(function(e) {console.log(e); return e.value === state.state.grouped_view;}) ||
1059
					typeof options.find === 'undefined' && !options[state.state.grouped_view]
1060
				))
1061
				{
1062
					window.setTimeout(function() {
1063
						app.addressbook.setState(state);
1064
					}, 500);
1065
					var nm = index.widgetContainer.getWidgetById('nm');
1066
					var action = nm.controller._actionManager.getActionById('view_org');
1067
					var senders = [{_context: {_widget: nm}}];
1068
					return nm_action(action, senders, {}, {ids:[state.state.grouped_view]});
1069
				}
1070
			}
1071
		}
1072
1073
		// Make sure advanced search is false if not set, this clears any
1074
		// currently set advanced search
1075
		if(typeof state.state.advanced_search === 'undefined')
1076
		{
1077
			state.state.advanced_search = false;
1078
		}
1079
		return super.setState(state);
1080
	}
1081
1082
	/**
1083
	 * Field changed, call server validation
1084
	 *
1085
	 * @param {jQuery.Event} _ev
1086
	 * @param {et2_button} _widget
1087
	 */
1088
	account_change(_ev, _widget)
1089
	{
1090
		switch(_widget.id)
1091
		{
1092
			case 'account_lid':
1093
			case 'n_family':
1094
			case 'n_given':
1095
			case 'account_passwd':
1096
			case 'account_passwd_2':
1097
				var values = this.et2._inst.getValues(this.et2);
1098
				var data = {
1099
					account_id: this.et2.getArrayMgr('content').data.account_id,
1100
					account_lid: values.account_lid,
1101
					account_firstname: values.n_given,
1102
					account_lastname: values.n_family,
1103
					account_email: values.email,
1104
					account_passwd: values.account_passwd,
1105
					account_passwd_2: values.account_passwd_2
1106
				};
1107
1108
				this.egw.message('');
1109
				this.egw.json('admin_account::ajax_check', [data, _widget.id], function(_msg)
1110
				{
1111
					if (_msg && typeof _msg == 'string')
1112
					{
1113
						egw(window).message(_msg, 'error');	// context get's lost :(
1114
						_widget.getDOMNode().focus();
1115
					}
1116
				}, this).sendRequest();
1117
				break;
1118
		}
1119
	}
1120
1121
	/**
1122
	 * Get title in order to set it as document title
1123
	 * @returns {string}
1124
	 */
1125
	getWindowTitle()
1126
	{
1127
		var widget = this.et2.getWidgetById('n_fn');
1128
		if(widget) return widget.options.value;
1129
	}
1130
1131
	/**
1132
	 * Enable/Disable geolocation action items in contextmenu base on address availabilty
1133
	 *
1134
	 * @param {egwAction} _action
1135
	 * @param {egwActionObject[]} _selected selected rows
1136
	 * @returns {boolean} return false if no address found
1137
	 */
1138
	geoLocation_enabled(_action, _selected)
1139
	{
1140
		// multiple selection is not supported
1141
		if (_selected.length>1) return false;
1142
1143
		var url = this.getGeolocationConfig();
1144
1145
		// exit if no url or invalide url given
1146
		if (!url || typeof url === 'undefined' || typeof url !== 'string')
1147
		{
1148
			egw.debug('warn','no url or invalid url given as geoLocationUrl');
1149
			return false;
1150
		}
1151
		var content = egw.dataGetUIDdata(_selected[0].id);
1152
1153
		// Selected, but data not found
1154
		if(!content || typeof content.data === 'undefined') return false;
1155
1156
		var type = _action.id === 'business'?'one':'two';
1157
		var addrs = [
1158
			content.data['adr_'+type+'_street'],
1159
			content.data['adr_'+type+'_locality'],
1160
			content.data['adr_'+type+'_postalcode']
1161
		];
1162
1163
		var fields = '';
1164
		// Replcae placeholders with acctual values
1165
		for (var i=0;i < addrs.length; i++)
1166
		{
1167
			fields += addrs[i] ? addrs[i] : '';
1168
		}
1169
		return (url !== '' && fields !== '')?true:false;
1170
	}
1171
1172
	/**
1173
	 * Generate a geo location URL based on geolocation_url in
1174
	 * site configuration
1175
	 *
1176
	 * @param {object} _dest_data
1177
	 * @param {string} _dest_type type of destination address ('one'| 'two')
1178
	 * @param {object} _src_data address data to be used as source contact data|coordination object
1179
	 * @param {string} _src_type type of source address ('browser'|'one'|'two')
1180
	 * @returns {Boolean|string} return url and return false if no address
1181
	 */
1182
	geoLocationUrl(_dest_data, _dest_type,_src_data, _src_type)
1183
	{
1184
		var dest_type = _dest_type || 'one';
1185
		var url = this.getGeolocationConfig();
1186
1187
		// exit if no url or invalide url given
1188
		if (!url || typeof url === 'undefined' || typeof url !== 'string')
1189
		{
1190
			egw.debug('warn','no url or invalid url given as geoLocationUrl');
1191
			return false;
1192
		}
1193
1194
		// array of placeholders with their representing values
1195
		var	addrs = [
1196
1197
			[ // source address
1198
				{id:'r0',val:_src_type === 'browser'?_src_data.latitude:_src_data['adr_'+_src_type+'_street']},
1199
				{id:'t0',val:_src_type === 'browser'?_src_data.longitude:_src_data['adr_'+_src_type+'_locality']},
1200
				{id:'c0',val:_src_type === 'browser'?'':_src_data['adr_'+_src_type+'_countrycode']},
1201
				{id:'z0',val:_src_type === 'browser'?'':_src_data['adr_'+_src_type+'_postalcode']}
1202
			],
1203
			[ // destination address
1204
				{id:'r1',val:_dest_data['adr_'+dest_type+'_street']},
1205
				{id:'t1',val:_dest_data['adr_'+dest_type+'_locality']},
1206
				{id:'c1',val:_dest_data['adr_'+dest_type+'_countrycode']},
1207
				{id:'z1',val:_dest_data['adr_'+dest_type+'_postalcode']}
1208
			]
1209
		];
1210
1211
		var src_param : any = url.match(/{{%rs=.*%rs}}/ig);
1212
		if (src_param[0])
1213
		{
1214
			src_param = src_param[0].replace(/{{%rs=/,'');
1215
			src_param = src_param.replace(/%rs}}/,'');
1216
			url = url.replace(/{{%rs=.*%rs}}/, src_param);
1217
		}
1218
1219
		var d_param = url.match(/{{%d=.*%d}}/ig);
1220
		if (d_param[0])
1221
		{
1222
			d_param = d_param[0].replace(/{{%d=/,'');
1223
			d_param = d_param.replace(/%d}}/,'');
1224
			url = url.replace(/{{%d=.*%d}}/, d_param);
1225
		}
1226
1227
		// Replcae placeholders with acctual values
1228
		for (var j=0;j<addrs.length;j++)
1229
		{
1230
			for (var i=0;i < addrs[j].length; i++)
1231
			{
1232
				url = url.replace('%'+addrs[j][i]['id'], addrs[j][i]['val']? addrs[j][i]['val'] : "");
1233
			}
1234
		}
1235
		return url !== ''? url : false;
1236
	}
1237
1238
	/**
1239
	 * Open a popup base on selected address in provided map
1240
	 *
1241
	 * @param {object} _action
1242
	 * @param {object} _selected
1243
	 */
1244
	geoLocationExec(_action, _selected)
1245
	{
1246
		var content = egw.dataGetUIDdata(_selected[0].id);
1247
		var geolocation_src = egw.preference('geolocation_src','addressbook');
1248
		var self = this;
1249
1250
		if (geolocation_src === 'browser' && navigator.geolocation)
1251
		{
1252
			navigator.geolocation.getCurrentPosition(function(position){
1253
				if (position && position.coords)
1254
				{
1255
					var url = self.geoLocationUrl(content.data,_action.id === 'business'?'one':'two', position.coords, 'browser');
1256
					window.open(url,'_blank');
1257
				}
1258
			});
1259
		}
1260
		else
1261
		{
1262
			egw.json('addressbook.addressbook_ui.ajax_get_contact', [egw.user('account_id')],function(_data){
1263
				var url = self.geoLocationUrl(content.data,_action.id === 'business'?'one':'two', _data, geolocation_src === 'browser'?'one':geolocation_src);
1264
				window.open(url,'_blank');
1265
			}).sendRequest();
1266
1267
		}
1268
	}
1269
1270
	/**
1271
	 * Get geolocation_url stored in config|default url
1272
	 *
1273
	 * @returns {String}
1274
	 */
1275
	getGeolocationConfig()
1276
	{
1277
		// This default url should be identical to the first value of geolocation_url array
1278
		// defined in addressbook_hooks::config
1279
		var default_url = 'https://maps.here.com/directions/drive{{%rs=/%rs}}%r0,%t0,%z0,%c0{{%d=/%d}}%r1,%t1,%z1+%c1';
1280
		var geo_url = egw.config('geolocation_url');
1281
		if (geo_url) geo_url = geo_url[0];
1282
		return geo_url || default_url;
1283
	}
1284
1285
	/**
1286
	 * Check to see if the selection contains at most one account
1287
	 *
1288
	 * @param {egwAction} action
1289
	 * @param {egwActionObject[]} selected Selected rows
1290
	 */
1291
	can_merge(action, selected)
1292
	{
1293
		return selected.filter(function (row) {
1294
			var data = egw.dataGetUIDdata(row.id);
1295
			return data && data.data.account_id;
1296
		}).length <= 1;
1297
	}
1298
1299
	/**
1300
	 * Check if the share action is enabled for this entry
1301
	 * This only works for single contacts
1302
	 *
1303
	 * @param {egwAction} _action
1304
	 * @param {egwActionObject[]} _entries
1305
	 * @param {egwActionObject} _target
1306
	 * @returns {boolean} if action is enabled
1307
	 */
1308
	is_share_enabled(_action, _entries, _target)
1309
	{
1310
		var enabled = true;
1311
		for( var i = 0; i < _entries.length; i++)
1312
		{
1313
			let id = _entries[i].id.split('::');
1314
			if(isNaN(id[1]))
1315
			{
1316
				return false;
1317
			}
1318
		}
1319
		return enabled;
1320
	}
1321
}
1322
1323
app.classes.addressbook = AddressbookApp;
1324