Completed
Push — 16.1 ( 1e4888...9df24e )
by Ralf
12:06
created

➔ et2_valueWidget.extend._setup_filemanager   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 3
dl 0
loc 49
rs 9.2258
c 0
b 0
f 0
1
/**
2
 * EGroupware eTemplate2 - JS Custom fields object
3
 *
4
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
5
 * @package etemplate
6
 * @subpackage api
7
 * @link http://www.egroupware.org
8
 * @author Nathan Gray
9
 * @copyright Nathan Gray 2011
10
 * @version $Id$
11
 */
12
13
/*egw:uses
14
	lib/tooltip;
15
	/vendor/bower-asset/jquery/dist/jquery.js;
16
	et2_core_xml;
17
	et2_core_DOMWidget;
18
	et2_core_inputWidget;
19
*/
20
21
/**
22
 * @augments et2_dataview
23
 */
24
var et2_customfields_list = (function(){ "use strict"; return et2_valueWidget.extend([et2_IDetachedDOM, et2_IInput],
25
{
26
	attributes: {
27
		'customfields': {
28
			'name': 'Custom fields',
29
			'description': 'Auto filled',
30
			'type': 'any'
31
		},
32
		'fields': {
33
			'name': 'Custom fields',
34
			'description': 'Auto filled',
35
			'type': 'any'
36
		},
37
		'value': {
38
			'name': 'Custom fields',
39
			'description': 'Auto filled',
40
			'type': "any"
41
		},
42
		'type_filter': {
43
			'name': 'Field filter',
44
			"default": "",
45
			"type": "any", // String or array
46
			"description": "Filter displayed custom fields by their 'type2' attribute"
47
		},
48
		'private': {
49
			ignore: true,
50
			type: 'boolean'
51
		}
52
	},
53
54
	legacyOptions: ["type_filter","private", "fields"], // Field restriction & private done server-side
55
56
	prefix: '#',
57
58
	DEFAULT_ID: "custom_fields",
59
60
	/**
61
	 * Constructor
62
	 *
63
	 * @memberOf et2_customfields_list
64
	 */
65
	init: function() {
66
		// Some apps (infolog edit) don't give ID, so assign one to get settings
67
		if(!arguments[1].id) arguments[1].id = this.DEFAULT_ID;
68
69
		this._super.apply(this, arguments);
70
71
		// Allows server side to override prefix - not an attribute though
72
		if(typeof this.options.prefix != 'undefined') this.prefix = this.options.prefix;
73
74
		// Create the table body and the table
75
		this.tbody = jQuery(document.createElement("tbody"));
76
		this.table = jQuery(document.createElement("table"))
77
			.addClass("et2_grid et2_customfield_list");
78
		this.table.append(this.tbody);
79
80
		this.rows = {};
81
		this.widgets = {};
82
		this.detachedNodes = [];
83
		if(!this.options.fields) this.options.fields = {};
84
85
		if(this.options.type_filter && typeof this.options.type_filter == "string")
86
		{
87
			this.options.type_filter = this.options.type_filter.split(",");
88
		}
89
		if(this.options.type_filter)
90
		{
91
			var already_filtered = !jQuery.isEmptyObject(this.options.fields);
92
			for(var field_name in this.options.customfields)
93
			{
94
				// Already excluded?
95
				if(already_filtered && !this.options.fields[field_name]) continue;
96
97
				if(!this.options.customfields[field_name].type2 || this.options.customfields[field_name].type2.length == 0 ||
98
					this.options.customfields[field_name].type2 == '0')
99
				{
100
					// No restrictions
101
					this.options.fields[field_name] = true;
102
					continue;
103
				}
104
				var types = typeof this.options.customfields[field_name].type2 == 'string' ? this.options.customfields[field_name].type2.split(",") : this.options.customfields[field_name].type2;
105
				this.options.fields[field_name] = false;
106
				for(var i = 0; i < types.length; i++)
107
				{
108
					if(jQuery.inArray(types[i],this.options.type_filter) > -1)
109
					{
110
						this.options.fields[field_name] = true;
111
						continue;
0 ignored issues
show
Unused Code introduced by
This continue has no effect on the loop flow and can be removed.
Loading history...
112
					}
113
				}
114
			}
115
		}
116
117
		this.setDOMNode(this.table[0]);
118
	},
119
120
	destroy: function() {
121
		this._super.apply(this, arguments);
122
		this.rows = {};
123
		this.widgets = {};
124
		this.detachedNodes = [];
125
		this.tbody = null;
126
	},
127
128
	/**
129
	 * What does this do?  I don't know, but when everything is done the second
130
	 * time, this makes it work.  Otherwise, all custom fields are lost.
131
	 */
132
	assign: function(_obj) {
133
		this.loadFields();
134
	},
135
136
	getDOMNode: function(_sender) {
137
138
		// Check whether the _sender object exists inside the management array
139
		if(this.rows && _sender.id && this.rows[_sender.id])
140
		{
141
			return this.rows[_sender.id];
142
		}
143
144
		return this._super.apply(this, arguments);
145
	},
146
147
	/**
148
	 * Initialize widgets for custom fields
149
	 */
150
	loadFields: function() {
151
		if(!this.options || !this.options.customfields) return;
152
153
		// Already set up - avoid duplicates in nextmatch
154
		if(this._type == 'customfields-list' && !this.isInTree()) return;
155
		if(!jQuery.isEmptyObject(this.widgets)) return;
156
157
		// Check for global setting changes (visibility)
158
		var global_data = this.getArrayMgr("modifications").getRoot().getEntry('~custom_fields~');
159
		if(global_data && global_data.fields && !this.options.fields) this.options.fields = global_data.fields;
160
161
		// For checking app entries
162
		var apps = this.egw().link_app_list();
163
164
		// Create the table rows
165
		for(var field_name in this.options.customfields)
166
		{
167
			// Skip fields if we're filtering
168
			if(this._type != 'customfields-list' && !jQuery.isEmptyObject(this.options.fields) && !this.options.fields[field_name]) continue;
169
170
			var field = this.options.customfields[field_name];
171
172
			var id = this.prefix+field_name;
173
174
			// Need curlies around ID for nm row expansion
175
			if(this.id == '$row')
176
			{
177
				id = "{" + this.id + "}" + "["+this.prefix + field_name+"]";
178
			}
179
			else if (this.id != this.DEFAULT_ID)
180
			{
181
				// Prefix this ID to avoid potential ID collisions
182
				id = this.id + "["+id+"]";
183
			}
184
185
			// Avoid creating field twice
186
			if(!this.rows[id])
187
			{
188
189
				var row = jQuery(document.createElement("tr"))
190
					.appendTo(this.tbody)
191
					.addClass(this.id+'_'+id);
192
				var cf = jQuery(document.createElement("td"))
193
					.appendTo(row);
194
				if(!field.type) field.type = 'text";'
195
				var setup_function = '_setup_'+(apps[field.type] ? 'link_entry' : field.type.replace("-","_"));
196
197
				var attrs = {
198
					'id': 		id,
199
					'statustext':	field.help,
200
					'needed':	field.needed,
201
					'readonly':	this.getArrayMgr("readonlys").isReadOnly(id, null, this.options.readonly),
202
					'value':	this.options.value[this.prefix+field_name]
203
				};
204
				// Can't have a required readonly, it will warn & be removed later, so avoid the warning
205
				if(attrs.readonly === true) delete attrs.needed;
206
207
				if(this[setup_function]) {
208
					var no_skip = this[setup_function].call(this, field_name, field, attrs);
209
					if(!no_skip) continue;
210
				}
211
212
				if(this._type == 'customfields-list') {
213
					// No label, cust widget
214
					attrs.readonly = true;
215
					// Widget tooltips don't work in nextmatch because of the creation / binding separation
216
					// Set title to field label so browser will show something
217
					// Add field label & help as data attribute to row, so it can be stylied with CSS (title should be disabled)
218
					row.attr('title', field.label);
219
					row.attr('data-label', field.label);
220
					row.attr('data-field', field_name);
221
					row.attr('data-help', field.help);
222
					this.detachedNodes.push(row[0]);
223
				}
224
				else
225
				{
226
					// Label in first column, widget in 2nd
227
					cf.text(field.label + "");
228
					cf = jQuery(document.createElement("td"))
229
						.appendTo(row);
230
				}
231
				this.rows[id] = cf[0];
232
233
				// Set any additional attributes set in options, but not for widgets that pass actual options
234
				if(['select','radio','radiogroup','checkbox','button'].indexOf(field.type) == -1 && !jQuery.isEmptyObject(field.values))
235
				{
236
					var w = et2_registry[attrs.type ? attrs.type : field.type];
237
					for(var attr_name in field.values)
238
					{
239
						if (typeof w.prototype.attributes[attr_name] != "undefined")
240
						attrs[attr_name] = field.values[attr_name];
241
					}
242
				}
243
				// Create widget
244
				var widget = this.widgets[field_name] = et2_createWidget(attrs.type ? attrs.type : field.type, attrs, this);
245
			}
246
247
			// Field is not to be shown
248
			if(!this.options.fields || jQuery.isEmptyObject(this.options.fields) || this.options.fields[field_name] == true)
249
			{
250
				jQuery(this.rows[field_name]).show();
251
			}
252
			else
253
			{
254
				jQuery(this.rows[field_name]).hide();
255
			}
256
257
		}
258
	},
259
260
	/**
261
	 * Read needed info on available custom fields from various places it's stored.
262
	 */
263
	transformAttributes: function(_attrs) {
264
		this._super.apply(this, arguments);
265
266
		// Add in settings that are objects
267
268
		// Customized settings for this widget (unlikely)
269
		var data = this.getArrayMgr("modifications").getEntry(this.id);
270
		// Check for global settings
271
		var global_data = this.getArrayMgr("modifications").getRoot().getEntry('~custom_fields~', true);
272
		if(global_data)
273
		{
274
			for(var key in data)
275
			{
276
				// Don't overwrite fields with global values
277
				if(global_data[key] && key !== 'fields')
278
				{
279
					data[key] = jQuery.extend(true, {}, data[key], global_data[key]);
280
				}
281
			}
282
		}
283
		for(var key in data)
284
		{
285
			_attrs[key] = data[key];
286
		}
287
		for(var key in global_data)
288
		{
289
			if(typeof global_data[key] != 'undefined' && ! _attrs[key]) _attrs[key] = global_data[key];
290
		}
291
292
		if (this.id)
293
		{
294
			// Set the value for this element
295
			var contentMgr = this.getArrayMgr("content");
296
			if (contentMgr != null) {
297
				var val = contentMgr.getEntry(this.id);
298
				_attrs["value"] = {};
299
				if (val !== null)
300
				{
301
					if(this.id.indexOf(this.prefix) === 0 && typeof data.fields != 'undefined' && data.fields[this.id.replace(this.prefix,'')] === true)
302
					{
303
						_attrs['value'][this.id] = val;
304
					}
305
					else
306
					{
307
						// Only set the values that match desired custom fields
308
						for(var key in val)
309
						{
310
							if(key.indexOf(this.prefix) == 0) {
311
								_attrs["value"][key] = val[key];
312
							}
313
						}
314
					}
315
					//_attrs["value"] = val;
316
				}
317
				else
318
				{
319
					// Check for custom fields directly in record
320
					for(var key in _attrs.customfields)
321
					{
322
						_attrs["value"][this.prefix + key] = contentMgr.getEntry(this.prefix + key);
323
					}
324
				}
325
			}
326
		}
327
	},
328
329
	loadFromXML: function(_node) {
330
		this.loadFields();
331
332
		// Load the nodes as usual
333
		this._super.apply(this, arguments);
334
	},
335
336
	set_value: function(_value) {
337
		if(!this.options.customfields) return;
338
		for(var field_name in this.options.customfields)
339
		{
340
			// Skip fields if we're filtering
341
			if(!jQuery.isEmptyObject(this.options.fields) && !this.options.fields[field_name]) continue;
342
343
			// Make sure widget is created, and has the needed function
344
			if(!this.widgets[field_name] || !this.widgets[field_name].set_value) continue;
345
			var value = _value[this.prefix + field_name] ? _value[this.prefix + field_name] : null;
346
347
			// Check if ID was missing
348
			if(value == null && this.id == this.DEFAULT_ID && this.getArrayMgr("content").getEntry(this.prefix + field_name))
349
			{
350
				value = this.getArrayMgr("content").getEntry(this.prefix + field_name);
351
			}
352
353
			switch(this.options.customfields[field_name].type)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
354
			{
355
				case 'date':
356
					// Date custom fields are always in Y-m-d, which seldom matches user's preference
357
					// which fails when sent to date widget.  This is only used for nm rows, when possible
358
					// this is fixed server side
359
					if(value && isNaN(value))
360
					{
361
						value = jQuery.datepicker.parseDate("yy-mm-dd",value);
362
					}
363
					break;
364
			}
365
			this.widgets[field_name].set_value(value);
366
		}
367
	},
368
369
	/**
370
	 * et2_IInput so the custom field can be it's own widget.
371
	 */
372
	getValue: function() {
373
		// Not using an ID means we have to grab all the widget values, and put them where server knows to look
374
		if(this.id != this.DEFAULT_ID)
375
		{
376
			return null;
377
		}
378
		var value = {};
379
		for(var field_name in this.widgets)
380
		{
381
			if(this.widgets[field_name].getValue && !this.widgets[field_name].options.readonly)
382
			{
383
				value[this.prefix + field_name] = this.widgets[field_name].getValue();
384
			}
385
		}
386
		return value;
387
	},
388
389
	isDirty: function() {
390
		var dirty = true;
391
		for(var field_name in this.widgets)
392
		{
393
			if(this.widgets[field_name].isDirty)
394
			{
395
				dirty = dirty && this.widgets[field_name].isDirty();
396
			}
397
		}
398
		return dirty;
399
	},
400
401
	resetDirty: function() {
402
		for(var field_name in this.widgets)
403
		{
404
			if(this.widgets[field_name].resetDirty)
405
			{
406
				this.widgets[field_name].resetDirty();
407
			}
408
		}
409
	},
410
411
	isValid: function() {
412
		// Individual customfields will handle themselves
413
		return true;
414
	},
415
416
	/**
417
	 * Adapt provided attributes to match options for widget
418
	 *
419
	 * rows > 1 --> textarea, with rows=rows and cols=len
420
	 * !rows    --> input, with size=len
421
	 * rows = 1 --> input, with size=len, maxlength=len
422
	 */
423
	_setup_text: function(field_name, field, attrs) {
424
		// No label on the widget itself
425
		delete(attrs.label);
426
427
		field.type = 'textbox';
428
		attrs.rows = field.rows > 1 ? field.rows : null;
429
430
		if(field.len)
431
		{
432
			attrs.size = field.len;
433
			if (field.rows == 1) attrs.maxlength = field.len;
434
		}
435
		return true;
436
	},
437
	_setup_ajax_select: function(field_name, field, attrs) {
438
		var attributes = ['get_rows','get_title','id_field','template'];
439
		if(field.values)
440
		{
441
			for(var i = 0; i < attributes.length; i++)
442
			{
443
				if(typeof field.values[attributes[i]] !== 'undefined')
444
				{
445
					attrs[attributes[i]] = field.values[attributes[i]];
446
				}
447
			}
448
		}
449
		return true;
450
	},
451
	_setup_float: function(field_name, field, attrs) {
452
		// No label on the widget itself
453
		delete(attrs.label);
454
455
		field.type = 'float';
456
457
		if(field.len)
458
		{
459
			attrs.size = field.len;
460
		}
461
		return true;
462
	},
463
	_setup_select: function(field_name, field, attrs) {
464
		// No label on the widget itself
465
		delete(attrs.label);
466
467
		attrs.rows = field.rows;
468
		// select_options are now send from server-side incl. ones defined via a file in EGroupware root
469
		attrs.tags = field.tags;
470
471
		return true;
472
	},
473
	_setup_select_account: function(field_name, field, attrs) {
474
		attrs.empty_label = egw.lang('Select');
475
		return this._setup_select(field_name, field, attrs);
476
	},
477
478
	 _setup_date: function(field_name, field, attrs) {
479
		attrs.data_format = 'Y-m-d';
480
		return true;
481
	},
482
	_setup_date_time: function(field_name, field, attrs) {
483
		attrs.data_format = 'Y-m-d H:i:s';
484
		return true;
485
	},
486
	_setup_htmlarea: function(field_name, field, attrs) {
487
		attrs.config = field.config ? field.config : {};
488
		attrs.config.toolbarStartupExpanded = false;
489
		if(field.len)
490
		{
491
			attrs.config.width = field.len+'px';
492
		}
493
		attrs.config.height = (((field.rows > 0 && field.rows !='undefined') ? field.rows : 5) *16) +'px';
494
495
		// We have to push the config modifications into the modifications array, or they'll
496
		// be overwritten by the site config from the server
497
		var data = this.getArrayMgr("modifications").getEntry(this.prefix+field_name);
498
		if(data) jQuery.extend(data.config, attrs.config);
499
500
		return true;
501
	},
502
	_setup_radio: function(field_name, field, attrs) {
503
		// 'Empty' label will be first
504
		delete(attrs.label);
505
506
		if(field.values && field.values[''])
507
		{
508
			attrs.label = field.values[''];
509
			delete field.values[''];
510
		}
511
512
		field.type = 'radiogroup';
513
		attrs.options = field.values;
514
		return true;
515
	},
516
517
	_setup_checkbox: function(field_name, field, attrs) {
518
	 	// Read-only checkbox is just text
519
		if(attrs.readonly)
520
		{
521
			attrs.ro_true = field.label;
522
		}
523
		return true;
524
	},
525
526
	/**
527
	 * People set button attributes as
528
	 * label: javascript
529
	 */
530
	_setup_button: function(field_name, field, attrs) {
531
		// No label on the widget itself
532
		delete(attrs.label);
533
534
		attrs.label = field.label;
535
536
		if (this._type == 'customfields-list')
537
		{
538
			// No buttons in a list, it causes problems with detached nodes
539
			return false;
540
		}
541
		// Simple case, one widget for a custom field
542
		if(!field.values || typeof field.values != 'object' || Object.keys(field.values).length == 1)
543
		{
544
			for(var key in field.values)
545
			{
546
				attrs.label = key;
547
				attrs.onclick = field.values[key];
548
			}
549
			if (!attrs.label)
550
			{
551
				attrs.label = 'No "label=onclick" in values!';
552
				attrs.onclick = function(){ return false; };
553
			}
554
			return !attrs.readonly;
555
		}
556
		else
557
		{
558
			// Complicated case, a single custom field you get multiple widgets
559
			// Handle it all here, since this is the exception
560
			var row = jQuery('tr',this.tbody).last();
561
			var cf = jQuery('td',row);
562
			// Label in first column, widget in 2nd
563
			cf.text(field.label + "");
564
			cf = jQuery(document.createElement("td"))
565
				.appendTo(row);
566
567
			for(var key in field.values)
568
			{
569
				var button_attrs = jQuery.extend({},attrs);
570
				button_attrs.label = key;
571
				button_attrs.onclick = field.values[key];
572
				button_attrs.id = attrs.id + '_' + key;
573
574
				// This controls where the button is placed in the DOM
575
				this.rows[button_attrs.id] = cf[0];
576
577
				// Do not store in the widgets list, one name for multiple widgets would cause problems
578
				/*this.widgets[field_name] = */ et2_createWidget(attrs.type ? attrs.type : field.type, button_attrs, this);
579
			}
580
			return false;
581
		}
582
	},
583
	_setup_link_entry: function(field_name, field, attrs) {
584
		if(field.type === 'filemanager')
585
		{
586
			return this._setup_filemanager(field_name, field, attrs);
587
		}
588
		// No label on the widget itself
589
		delete(attrs.label);
590
591
		attrs.type = "link-entry";
592
		attrs.only_app = field.type;
593
		return true;
594
	},
595
596
	_setup_filemanager: function(field_name, field, attrs) {
597
		attrs.type = 'vfs-upload';
598
		delete(attrs.label);
599
600
		if (this._type == 'customfields-list')
601
		{
602
			// No special UI needed?
603
			return true;
604
		}
605
		else
606
		{
607
			// Complicated case, a single custom field you get multiple widgets
608
			// Handle it all here, since this is the exception
609
			var row = jQuery('tr',this.tbody).last();
610
			var cf = jQuery('td',row);
611
612
			// Label in first column, widget in 2nd
613
			cf.text(field.label + "");
614
			cf = jQuery(document.createElement("td"))
615
				.appendTo(row);
616
617
			// Create upload widget
618
			var widget = this.widgets[field_name] = et2_createWidget(attrs.type ? attrs.type : field.type, attrs, this);
619
620
			// This controls where the widget is placed in the DOM
621
			this.rows[attrs.id] = cf[0];
622
			jQuery(widget.getDOMNode(widget)).css('vertical-align','top');
623
624
			// Add a link to existing VFS file
625
			var select_attrs = jQuery.extend({},
626
				attrs,
627
				// Filemanager select
628
				{
629
					label: '',
630
					method: 'EGroupware\\Api\\Etemplate\\Widget\\Link::link_existing',
631
					method_id: attrs.path,
632
					button_label: egw.lang('Link')
633
				},{type: 'vfs-select'});
634
			select_attrs.id = attrs.id + '_vfs_select';
635
636
			// This controls where the button is placed in the DOM
637
			this.rows[select_attrs.id] = cf[0];
638
639
			// Do not store in the widgets list, one name for multiple widgets would cause problems
640
			widget = et2_createWidget(select_attrs.type, select_attrs, this);
641
			jQuery(widget.getDOMNode(widget)).css('vertical-align','top').prependTo(cf);
642
		}
643
		return false;
644
	},
645
646
	/**
647
	 * Set which fields are visible, by name
648
	 *
649
	 * Note: no # prefix on the name
650
	 *
651
	 */
652
	set_visible: function(_fields) {
653
		for(var name in _fields)
654
		{
655
			if(this.rows[this.prefix + name])
656
			{
657
				if(_fields[name])
658
				{
659
					jQuery(this.rows[this.prefix+name]).show();
660
				}
661
				else
662
				{
663
					jQuery(this.rows[this.prefix+name]).hide();
664
				}
665
			}
666
			this.options.fields[name] = _fields[name];
667
		}
668
	},
669
670
	/**
671
	 * Code for implementing et2_IDetachedDOM
672
	 */
673
	getDetachedAttributes: function(_attrs)
674
	{
675
		_attrs.push("value", "class");
676
	},
677
678
	getDetachedNodes: function()
679
	{
680
		return this.detachedNodes ? this.detachedNodes : [];
681
	},
682
683
	setDetachedAttributes: function(_nodes, _values)
684
	{
685
		// Individual widgets are detected and handled by the grid, but the interface is needed for this to happen
686
687
		// Show the row if there's a value, hide it if there is no value
688
		for(var i = 0; i < _nodes.length; i++)
689
		{
690
			// toggle() needs a boolean to do what we want
691
			var key = _nodes[i].getAttribute('data-field');
692
			jQuery(_nodes[i]).toggle(_values.fields[key] && _values.value[this.prefix + key]?true:false);
693
		}
694
	}
695
});}).call(this);
696
697
et2_register_widget(et2_customfields_list, ["customfields", "customfields-list"]);
698
699