Completed
Push — 16.1 ( 3870d9...133628 )
by Nathan
25:03 queued 10:07
created

api/js/etemplate/et2_widget_radiobox.js   C

Complexity

Total Complexity 55
Complexity/F 2.04

Size

Lines of Code 421
Function Count 27

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
nc 1
dl 0
loc 421
rs 6.3333
c 0
b 0
f 0
wmc 55
mnd 2
bc 45
fnc 27
bpm 1.6666
cpm 2.037
noi 0

How to fix   Complexity   

Complexity

Complex classes like api/js/etemplate/et2_widget_radiobox.js 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 eTemplate2 - JS Radiobox 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
	/vendor/bower-asset/jquery/dist/jquery.js;
15
	et2_core_inputWidget;
16
*/
17
18
/**
19
 * Class which implements the "radiobox" XET-Tag
20
 *
21
 * A radio button belongs to same group by giving all buttons of a group same id!
22
 *
23
 * set_value iterates over all of them and (un)checks them depending on given value.
24
 *
25
 * @augments et2_inputWidget
26
 */
27
var et2_radiobox = (function(){ "use strict"; return et2_inputWidget.extend(
28
{
29
	attributes: {
30
		"set_value": {
31
			"name": "Set value",
32
			"type": "string",
33
			"default": "true",
34
			"description": "Value when selected"
35
		},
36
		"ro_true": {
37
			"name": "Read only selected",
38
			"type": "string",
39
			"default": "x",
40
			"description": "What should be displayed when readonly and selected"
41
		},
42
		"ro_false": {
43
			"name": "Read only unselected",
44
			"type": "string",
45
			"default": "",
46
			"description": "What should be displayed when readonly and not selected"
47
		}
48
	},
49
50
	legacyOptions: ["set_value", "ro_true", "ro_false"],
51
52
	/**
53
	 * Constructor
54
	 *
55
	 * @memberOf et2_radiobox
56
	 */
57
	init: function() {
58
		this._super.apply(this, arguments);
59
60
		this.input = null;
61
		this.id = "";
62
63
		this.createInputWidget();
64
	},
65
	transformAttributes: function(_attrs) {
66
		this._super.apply(this, arguments);
67
		var readonly = this.getArrayMgr('readonlys').getEntry(this.id);
68
		if(readonly && readonly[_attrs.set_value])
69
		{
70
			_attrs.readonly = readonly[_attrs.set_value];
71
		}
72
	},
73
74
	createInputWidget: function() {
75
		this.input = jQuery(document.createElement("input"))
76
			.val(this.options.set_value)
77
			.attr("type", "radio")
78
			.attr("disabled", this.options.readonly);
79
80
		this.input.addClass("et2_radiobox");
81
82
		this.setDOMNode(this.input[0]);
83
	},
84
85
	/**
86
	 * Overwritten to set different DOM level ids by appending set_value
87
	 *
88
	 * @param _id
89
	 */
90
	set_id: function(_id)
91
	{
92
		this._super.apply(this, arguments);
93
94
		this.dom_id = this.dom_id.replace('[]', '')+'-'+this.options.set_value;
95
		if (this.input) this.input.attr('id', this.dom_id);
96
	},
97
98
	/**
99
	 * Default for radio buttons is label after button
100
	 *
101
	 * @param _label String New label for radio button.  Use %s to locate the radio button somewhere else in the label
102
	 */
103
	set_label: function(_label) {
104
		if(_label.length > 0 && _label.indexOf('%s')==-1)
105
		{
106
			_label = '%s'+_label;
107
		}
108
		this._super.apply(this, [_label]);
109
	},
110
111
	/**
112
	 * Override default to match against set/unset value AND iterate over all siblings with same id
113
	 *
114
	 * @param {string} _value
115
	 */
116
	set_value: function(_value)
117
	{
118
		this.getRoot().iterateOver(function(radio)
119
		{
120
			if (radio.id == this.id)
121
			{
122
				radio.input.prop('checked', _value == radio.options.set_value);
123
			}
124
		}, this, et2_radiobox);
125
	},
126
127
	/**
128
	 * Override default to iterate over all siblings with same id
129
	 *
130
	 * @return {string}
131
	 */
132
	getValue: function()
133
	{
134
		var val = this.options.value;	// initial value, when form is loaded
135
		this.getRoot().iterateOver(function(radio)
136
		{
137
			if (radio.id == this.id && radio.input && radio.input.prop('checked'))
138
			{
139
				val = radio.options.set_value;
140
			}
141
		}, this, et2_radiobox);
142
143
		return val == this.options.set_value ? this.options.set_value : null;
144
	},
145
146
	/**
147
	 * Overridden from parent so if it's required, only 1 in a group needs a value
148
	 *
149
	 * @param {array} messages
150
	 * @returns {Boolean}
151
	 */
152
	isValid: function(messages) {
153
		var ok = true;
154
155
		// Check for required
156
		if (this.options && this.options.needed && !this.options.readonly && !this.disabled &&
157
			(this.getValue() == null || this.getValue().valueOf() == ''))
158
		{
159
			if(jQuery.isEmptyObject(this.getInstanceManager().getValues(this.getInstanceManager().widgetContainer)[this.id.replace('[]', '')]))
160
			{
161
				messages.push(this.egw().lang('Field must not be empty !!!'));
162
				ok = false;
163
			}
164
		}
165
		return ok;
166
	}
167
});}).call(this);
168
et2_register_widget(et2_radiobox, ["radio"]);
169
170
/**
171
 * @augments et2_valueWidget
172
 */
173
var et2_radiobox_ro = (function(){ "use strict"; return et2_valueWidget.extend([et2_IDetachedDOM],
174
{
175
	attributes: {
176
		"set_value": {
177
			"name": "Set value",
178
			"type": "string",
179
			"default": "true",
180
			"description": "Value when selected"
181
		},
182
		"ro_true": {
183
			"name": "Read only selected",
184
			"type": "string",
185
			"default": "x",
186
			"description": "What should be displayed when readonly and selected"
187
		},
188
		"ro_false": {
189
			"name": "Read only unselected",
190
			"type": "string",
191
			"default": "",
192
			"description": "What should be displayed when readonly and not selected"
193
		},
194
		"label": {
195
			"name": "Label",
196
			"default": "",
197
			"type": "string"
198
		}
199
	},
200
201
	legacyOptions: ["set_value", "ro_true", "ro_false"],
202
203
	/**
204
	 * Constructor
205
	 *
206
	 * @memberOf et2_radiobox_ro
207
	 */
208
	init: function() {
209
		this._super.apply(this, arguments);
210
211
		this.value = "";
212
		this.span = jQuery(document.createElement("span"))
213
			.addClass("et2_radiobox");
214
215
		this.setDOMNode(this.span[0]);
216
	},
217
218
	/**
219
	 * Override default to match against set/unset value
220
	 *
221
	 * @param {string} _value
222
	 */
223
	set_value: function(_value) {
224
		this.value = _value;
225
		if(_value == this.options.set_value) {
226
			this.span.text(this.options.ro_true);
227
		} else {
228
			this.span.text(this.options.ro_false);
229
		}
230
	},
231
232
	set_label: function(_label) {
233
		// no label for ro radio, we show label of checked option as content
234
	},
235
236
	/**
237
	 * Code for implementing et2_IDetachedDOM
238
	 *
239
	 * @param {array} _attrs
240
	 */
241
	getDetachedAttributes: function(_attrs)
242
	{
243
		// Show label in nextmatch instead of just x
244
		this.options.ro_true = this.options.label;
245
		_attrs.push("value");
246
	},
247
248
	getDetachedNodes: function()
249
	{
250
		return [this.span[0]];
251
	},
252
253
	setDetachedAttributes: function(_nodes, _values)
254
	{
255
		this.span = jQuery(_nodes[0]);
256
		this.set_value(_values["value"]);
257
	}
258
});}).call(this);
259
et2_register_widget(et2_radiobox_ro, ["radio_ro"]);
260
261
262
/**
263
 * A group of radio buttons
264
 *
265
 * @augments et2_valueWidget
266
 */
267
var et2_radioGroup = (function(){ "use strict"; return et2_valueWidget.extend([et2_IDetachedDOM],
268
{
269
	attributes: {
270
		"label": {
271
			"name": "Label",
272
			"default": "",
273
			"type": "string",
274
			"description": "The label is displayed above the list of radio buttons. The label can contain variables, as descript for name. If the label starts with a '@' it is replaced by the value of the content-array at this index (with the '@'-removed and after expanding the variables).",
275
			"translate": true
276
		},
277
		"value": {
278
			"name": "Value",
279
			"type": "string",
280
			"default": "true",
281
			"description": "Value for each radio button"
282
		},
283
		"ro_true": {
284
			"name": "Read only selected",
285
			"type": "string",
286
			"default": "x",
287
			"description": "What should be displayed when readonly and selected"
288
		},
289
		"ro_false": {
290
			"name": "Read only unselected",
291
			"type": "string",
292
			"default": "",
293
			"description": "What should be displayed when readonly and not selected"
294
		},
295
		"options": {
296
			"name": "Radio options",
297
			"type": "any",
298
			"default": {},
299
			"description": "Options for radio buttons.  Should be {value: label, ...}"
300
		},
301
		"needed": {
302
			"name":	"Required",
303
			"default": false,
304
			"type": "boolean",
305
			"description": "If required, the user must select one of the options before the form can be submitted"
306
		}
307
	},
308
309
	createNamespace: false,
310
311
	/**
312
	 * Constructor
313
	 *
314
	 * @param parent
315
	 * @param attrs
316
	 * @memberOf et2_radioGroup
317
	 */
318
	init: function(parent, attrs) {
319
		this._super.apply(this, arguments);
320
		this.node = jQuery(document.createElement("div"))
321
			.addClass("et2_vbox")
322
			.addClass("et2_box_widget");
323
		if(this.options.needed)
324
		{
325
			// This isn't strictly allowed, but it works
326
			this.node.attr("required","required");
327
		}
328
		this.setDOMNode(this.node[0]);
329
330
		// The supported widget classes array defines a whitelist for all widget
331
		// classes or interfaces child widgets have to support.
332
		this.supportedWidgetClasses = [et2_radiobox,et2_radiobox_ro];
333
	},
334
335
	set_value: function(_value) {
336
		this.value = _value;
337
		for (var i = 0; i < this._children.length; i++)
338
		{
339
			var radio = this._children[i];
340
			radio.set_value(_value);
341
		}
342
	},
343
344
	getValue: function() {
345
		return jQuery("input:checked", this.getDOMNode()).val();
346
	},
347
348
	/**
349
	 * Set a bunch of radio buttons
350
	 *
351
	 * @param {object} _options object with value: label pairs
352
	 */
353
	set_options: function(_options) {
354
		// Call the destructor of all children
355
		for (var i = this._children.length - 1; i >= 0; i--)
356
		{
357
			this._children[i].free();
358
		}
359
		this._children = [];
360
		// create radio buttons for each option
361
		for(var key in _options)
362
		{
363
			var attrs = {
364
				// Add index so radios work properly
365
				"id": (this.options.readonly ? this.id : this.id + "[" +  "]"),
366
				set_value: key,
367
				label: _options[key],
368
				ro_true: this.options.ro_true,
369
				ro_false: this.options.ro_false,
370
				readonly: this.options.readonly
371
			};
372
			// Can't have a required readonly, it will warn & be removed later, so avoid the warning
373
			if(attrs.readonly === false)
374
			{
375
				attrs.needed = this.options.needed;
376
			}
377
			var radio = et2_createWidget("radio", attrs, this);
378
		}
379
		this.set_value(this.value);
380
	},
381
382
	/**
383
	 * Set a label on the group of radio buttons
384
	 *
385
	 * @param {string} _value
386
	 */
387
	set_label: function(_value) {
388
		// Abort if ther was no change in the label
389
		if (_value == this.label)
390
		{
391
			return;
392
		}
393
394
		if (_value)
395
		{
396
			// Create the label container if it didn't exist yet
397
			if (this._labelContainer == null)
398
			{
399
				this._labelContainer = jQuery(document.createElement("label"));
400
				this.getSurroundings().insertDOMNode(this._labelContainer[0]);
401
			}
402
403
			// Clear the label container.
404
			this._labelContainer.empty();
405
406
			// Create the placeholder element and set it
407
			var ph = document.createElement("span");
408
			this.getSurroundings().setWidgetPlaceholder(ph);
409
410
			this._labelContainer
411
				.append(document.createTextNode(_value))
412
				.append(ph);
413
		}
414
		else
415
		{
416
			// Delete the labelContainer from the surroundings object
417
			if (this._labelContainer)
418
			{
419
				this.getSurroundings().removeDOMNode(this._labelContainer[0]);
420
			}
421
			this._labelContainer = null;
422
		}
423
	},
424
425
	/**
426
	 * Code for implementing et2_IDetachedDOM
427
	 * This doesn't need to be implemented.
428
	 * Individual widgets are detected and handled by the grid, but the interface is needed for this to happen
429
	 *
430
	 * @param {object} _attrs
431
	 */
432
	getDetachedAttributes: function(_attrs)
433
	{
434
	},
435
436
	getDetachedNodes: function()
437
	{
438
		return [this.getDOMNode()];
439
	},
440
441
	setDetachedAttributes: function(_nodes, _values)
442
	{
443
	}
444
445
});}).call(this);
446
// No such tag as 'radiogroup', but it needs something
447
et2_register_widget(et2_radioGroup, ["radiogroup"]);
448