Passed
Push — master ( 25f4d2...5b5caf )
by Jean-Christophe
11:34
created

FormModelViewerTrait   F

Complexity

Total Complexity 72

Size/Duplication

Total Lines 437
Duplicated Lines 0 %

Test Coverage

Coverage 63.04%

Importance

Changes 13
Bugs 3 Features 6
Metric Value
wmc 72
eloc 233
c 13
b 3
f 6
dl 0
loc 437
ccs 162
cts 257
cp 0.6304
rs 2.64

How to fix   Complexity   

Complex Class

Complex classes like FormModelViewerTrait 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.

While breaking up the class, it is a good idea to analyze how other classes use FormModelViewerTrait, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Ubiquity\controllers\crud\viewers\traits;
4
5
use Ajax\semantic\html\collections\form\HtmlFormField;
6
use Ajax\semantic\html\collections\form\HtmlFormInput;
7
use Ajax\semantic\html\elements\HtmlButton;
8
use Ajax\semantic\html\elements\HtmlIconGroups;
9
use Ajax\semantic\widgets\base\FieldAsTrait;
10
use Ajax\semantic\widgets\dataform\DataForm;
11
use Ajax\semantic\widgets\datatable\DataTable;
12
use Ajax\service\JArray;
13
use normalizer\UserNormalizer;
14
use Ubiquity\contents\validation\ValidatorsManager;
15
use Ubiquity\controllers\crud\EditMemberParams;
16
use Ubiquity\orm\DAO;
17
use Ubiquity\orm\OrmUtils;
18
use Ubiquity\orm\parser\Reflexion;
19
use Ajax\semantic\html\collections\form\HtmlFormCheckbox;
20
21
/**
22
 * Associated with a CRUDController class (part of ModelViewer)
23
 * Responsible of the display of the form
24
 * Ubiquity\controllers\crud\viewers\traits$FormModelViewerTrait
25
 * This class is part of Ubiquity
26
 *
27
 * @author jcheron <[email protected]>
28
 * @version 1.0.6
29
 * @property \Ajax\php\ubiquity\JsUtils $jquery
30
 */
31
trait FormModelViewerTrait {
32
	
33
	abstract public function getDataTableId();
34
	
35
	protected function relationMembersInForm($form, $instance, $className, $fields, $relations, &$fieldTypes) {
36
		foreach ( $relations as $field => $member ) {
37
			if (\array_search ( $field, $fields ) !== false) {
38
				unset($fieldTypes[$field]);
39
				if (OrmUtils::getAnnotationInfoMember ( $className, '#manyToOne', $member ) !== false) {
40
					$this->manyToOneFormField ( $form, $member, $className, $instance );
41
				} elseif (($annot = OrmUtils::getAnnotationInfoMember ( $className, '#oneToMany', $member )) !== false) {
42
					$fkClass=$annot['className'];
43
					if(OrmUtils::isManyToMany($fkClass)) {
44
						$this->oneToManyFormFieldDt($form, $member, $instance, $annot);
45
					}else{
46
						$this->oneToManyFormField ( $form, $member, $instance, $annot );
47
					}
48
				} elseif (($annot = OrmUtils::getAnnotationInfoMember ( $className, '#manyToMany', $member )) !== false) {
49
					$this->manyToManyFormField ( $form, $member, $instance, $annot );
50
				}
51
			}
52
		}
53
	}
54
	
55
	protected function manyToOneFormField($form, $member, $className, $instance) {
56
		$joinColumn = OrmUtils::getAnnotationInfoMember ( $className, '#joinColumn', $member );
57
		if ($joinColumn) {
58
			$fkObject = Reflexion::getMemberValue ( $instance, $member );
59
			$fkClass = $joinColumn ['className'];
60
			if ($fkObject === null) {
61
				$fkObject = new $fkClass ();
62
			}
63
			$fkId = OrmUtils::getFirstKey ( $fkClass );
64
			
65
			$fkIdGetter = 'get' . \ucfirst ( $fkId );
66
			if (\method_exists ( $fkObject, '__toString' ) && \method_exists ( $fkObject, $fkIdGetter )) {
67
				$fkField = $joinColumn ['name'];
68
				$fkValue = OrmUtils::getFirstKeyValue ( $fkObject );
69
				if (! Reflexion::setMemberValue ( $instance, $fkField, $fkValue )) {
70
					$instance->{$fkField} = OrmUtils::getFirstKeyValue ( $fkObject );
71
				}
72
				$attr = [ ];
73
				if (OrmUtils::isNullable ( $className, $member )) {
74
					$attr = [ 'jsCallback' => function ($elm) {
75
						$elm->getField ()->setClearable ( true );
76
					} ];
77
				} else {
78
					$attr = [ 'jsCallback' => function ($elm) {
79
						$elm->addRules ( [ 'empty' ] );
80
					} ];
81
				}
82
				$form->fieldAsDropDown ( $fkField, JArray::modelArray ( $this->controller->_getAdminData ()->getManyToOneDatas ( $fkClass, $instance, $member ), $fkIdGetter, '__toString' ), false, $attr );
83
				$form->setCaption ( $fkField, \ucfirst ( $member ) );
84
			}
85
		}
86
	}
87
	
88
	protected function oneToManyFormField(DataForm $form, $member, $instance, $annot) {
89
		$newField = $member . 'Ids';
90
		$fkClass = $annot ['className'];
91
		$fkId = OrmUtils::getFirstKey ( $fkClass );
92
		$fkIdGetter = 'get' . \ucfirst ( $fkId );
93
		$fkInstances = Reflexion::getMemberValue ( $instance, $member );//DAO::getOneToMany ( $instance, $member );
94
		$ids = \array_map ( function ($elm) use ($fkIdGetter) {
95
			return $elm->{$fkIdGetter} ();
96
		}, $fkInstances );
97
			$instance->{$newField} = \implode ( ",", $ids );
98
			$form->fieldAsDropDown ( $newField, JArray::modelArray ( $this->controller->_getAdminData ()->getOneToManyDatas ( $fkClass, $instance, $member ), $fkIdGetter, "__toString" ), true );
99
			$form->setCaption ( $newField, \ucfirst ( $member ) );
100
	}
101
102
	protected function oneToManyFormFieldDt(DataForm $form,$member, $instance, $annot){
103
		$newField = $member . 'Ids';
104
		$fkClass = $annot ['className'];
105
		$fkv=OrmUtils::getFirstKeyValue($instance);
106
		if($fkv!=null) {
107
			$fkInstances = DAO::getOneToMany($instance, $member);
108
		}
109
		$fields=OrmUtils::getManyToManyFieldsDt ( \get_class($instance),$fkClass );
110
		$fkInstances[]=new $fkClass();
111
		$relFields = OrmUtils::getFieldsInRelations_ ( $fkClass );
112
		$form->fieldAsDataTable( $newField, $fkClass,$fkInstances,$fields,['jsCallback'=>function(DataTable $dt) use($fkClass,$relFields,$fields,$newField){
113
			$this->dtManyField($dt,$fkClass,$relFields,$fields);
114
			$id=$dt->getIdentifier();
115
			$removeSelected='function updateCmb(){let cmb=$("#'.$id.' tbody tr:last-child div.dropdown");cmb.find("a[data-value]").removeClass("disabled");$("#'.$id.' tbody tr div.dropdown input").each(function(){if($(this).val()) cmb.find("[data-value="+$(this).val()+"]").addClass("disabled");});}';
116
			$this->jquery->execAtLast($removeSelected.'updateCmb();$("#'.$id.' tbody tr:last-child .dropdown").removeClass("disabled");$("#'.$id.' tbody tr:last-child").find("input._status").val("added");');
117
			$deleteJS=$this->jquery->execJSFromFile('@framework/js/delete',[],false);
118
			$this->jquery->click('._delete','$("[name='.$newField.']").val("updated");'.$deleteJS,true,false,false, "#$id");
119
			$this->jquery->change('tr:last-child input',$this->jquery->execJSFromFile('@framework/js/change',compact('id'),false),false,false,"#$id");
120
			$this->jquery->change('tr .dropdown input','updateCmb();',false,false,"#$id");
121
			$this->jquery->change('tr input','$("[name='.$newField.']").val("updated");if($(this).closest("tr").find("input._status").val()!=="added"){$(this).closest("tr").find("input._status").val("updated");}',false,false,"#$id");
122
123
		}] );
124
		$form->setCaption ( $newField, \ucfirst ( $member ) );
125
	}
126
127
	protected function dtManyField(DataTable $dt,$className,array $relations,array $fields){
128
		foreach ( $relations as $field => $member ) {
129
			if (\array_search($field, $fields) !== false) {
130
				if (OrmUtils::getAnnotationInfoMember($className, "#manyToOne", $member) !== false) {
131
					$this->dtManyFieldCmb($dt,$member,$className);
132
				}
133
			}
134
		}
135
	}
136
137
	protected function dtManyFieldCmb($dt, $member, $className) {
138
		$joinColumn = OrmUtils::getAnnotationInfoMember ( $className, "#joinColumn", $member );
139
		if ($joinColumn) {
140
			$fkClass = $joinColumn ['className'];
141
			$fkId = OrmUtils::getFirstKey ( $fkClass );
142
143
			$fkIdGetter = 'get' . \ucfirst ( $fkId );
144
			$fkField = $joinColumn ['name'];
145
146
			$attr = [ ];
147
			if (OrmUtils::isNullable ( $className, $member )) {
148
				$attr = [ 'jsCallback' => function ($elm) {
149
					$f=$elm->getField ();
150
					$f->setClearable ( true );
151
					$f->addClass('disabled');
152
				} ];
153
			} else {
154
				$attr = [ 'jsCallback' => function ($elm) {
155
					$elm->getField()->addClass('disabled');
156
				} ];
157
			}
158
			$dt->fieldAsDropDown ( $fkField, JArray::modelArray ( $this->controller->_getAdminData ()->getManyToOneDatas ( $fkClass, null, $member ), $fkIdGetter, '__toString' ), false, $attr );
159
		}
160
	}
161
	
162
	protected function manyToManyFormField(DataForm $form, $member, $instance, $annot) {
163
		$newField = $member . 'Ids';
164
		$fkClass = $annot ['targetEntity'];
165
		$fkId = OrmUtils::getFirstKey ( $fkClass );
166
		$fkIdGetter = 'get' . \ucfirst ( $fkId );
167
		$fkInstances = Reflexion::getMemberValue ( $instance, $member );//DAO::getManyToMany ( $instance, $member );
168
		$ids = \array_map ( function ($elm) use ($fkIdGetter) {
169
			return $elm->{$fkIdGetter} ();
170
		}, $fkInstances );
171
			$instance->{$newField} = \implode ( ',', $ids );
172
			$form->fieldAsDropDown ( $newField, JArray::modelArray ( $this->controller->_getAdminData ()->getManyToManyDatas ( $fkClass, $instance, $member ), $fkIdGetter, '__toString' ), true, [ 'jsCallback' => function ($elm) {
173
				$elm->getField ()->asSearch ();
174
			} ] );
175
				$form->setCaption ( $newField, \ucfirst ( $member ) );
176
	}
177
	
178
	/**
179
	 *
180
	 * @return \Ubiquity\controllers\crud\EditMemberParams[]
181
	 */
182
	public function getEditMemberParams() {
183
		return $this->defaultEditMemberParams ();
184
	}
185
	
186
	/**
187
	 *
188
	 * @param string $part
189
	 * @return \Ubiquity\controllers\crud\EditMemberParams
190
	 */
191
	protected function getEditMemberParams_($part) {
192
		$params = $this->getEditMemberParams ();
193
		if ($params && isset ( $params [$part] )) {
194
			return $params [$part];
195
		}
196
	}
197
	
198
	/**
199
	 *
200
	 * @return \Ubiquity\controllers\crud\EditMemberParams[]
201
	 */
202
	protected function defaultEditMemberParams() {
203
		return [ 'dataTable' => EditMemberParams::dataTable ( '#'.$this->getDataTableId () ),'dataElement' => EditMemberParams::dataElement () ];
204
	}
205
	
206
	/**
207
	 * Returns the form for adding or modifying an object
208
	 *
209
	 * @param string $identifier
210
	 * @param object $instance the object to add or modify
211
	 * @param ?string $updateUrl
212
	 * @return \Ajax\semantic\widgets\dataform\DataForm
213
	 */
214
	public function getForm($identifier, $instance, $updateUrl = 'updateModel') {
215
		$hasMessage=$this->formHasMessage();
216
		$form = $this->jquery->semantic ()->dataForm ( $identifier, $instance );
217
		$form->setLibraryId ( 'frmEdit' );
218
		$className = \get_class ( $instance );
219
		$fields = \array_unique ( $this->controller->_getAdminData ()->getFormFieldNames ( $className, $instance ) );
220
		$relFields = OrmUtils::getFieldsInRelations_ ( $className );
221
		
222
		$this->setFormFields_ ( $fields, $relFields );
223
		if($hasMessage) {
224
			\array_unshift($fields, '_message');
225
		}
226
		$form->setFields ( $fields );
227
		$fieldTypes = OrmUtils::getFieldTypes ( $className );
228
		$attrs=ValidatorsManager::getUIConstraints($instance);
229
230
		$this->relationMembersInForm ( $form, $instance, $className, $fields, $relFields ,$fieldTypes);
231
		OrmUtils::setFieldToMemberNames ( $fields, $relFields );
232
		$form->setCaptions ( $this->getFormCaptions ( $fields, $className, $instance ) );
233
		if($hasMessage) {
234
			$message = $this->getFormTitle($form, $instance);
235
			$form->setCaption('_message', $message ['subMessage']);
236
			$form->fieldAsMessage('_message', ['icon' => $message ['icon']]);
237
			$instance->_message = $message ['message'];
238
		}
239
240
		$this->setFormFieldsComponent ( $form, $fieldTypes,$attrs);
241
		$form->setSubmitParams ( $this->controller->_getBaseRoute () . '/' . $updateUrl, '#frm-add-update' );
242
		$form->onGenerateField ( [ $this,'onGenerateFormField' ] );
243
		return $form;
244
	}
245
	
246
	/**
247
	 * Returns a form for member editing
248
	 *
249
	 * @param string $identifier
250
	 * @param object $instance
251
	 * @param string $member
252
	 * @param string $td
253
	 * @param string $part
254
	 * @param ?string $updateUrl
255
	 * @return \Ajax\semantic\widgets\dataform\DataForm
256
	 */
257
	public function getMemberForm($identifier, $instance, $member, $td, $part, $updateUrl = '_updateMember') {
258
		$editMemberParams = $this->getEditMemberParams_ ( $part );
259
		
260
		$form = $this->jquery->semantic ()->dataForm ( $identifier, $instance );
261
		$form->on ( "dblclick", "", true, true );
262
		$form->setProperty ( "onsubmit", "return false;" );
263
		$form->setProperty('style','margin:0;');
264
		$form->addClass ( "_memberForm" );
265
		$className = \get_class ( $instance );
266
		$fields = [ "id",$member ];
267
		$relFields = OrmUtils::getFieldsInRelations_ ( $className );
268
		$hasRelations = $this->setFormFields_ ( $fields, $relFields );
269
		$form->setFields ( $fields );
270
		$fieldTypes = OrmUtils::getFieldTypes ( $className );
271
		$form->fieldAsHidden ( 0 );
272
		$attrs=ValidatorsManager::getUIConstraints($instance);
273
		$this->setMemberFormFieldsComponent ( $form, $fieldTypes ,$attrs);
274
		if ($hasRelations) {
275
			$this->relationMembersInForm ( $form, $instance, $className, $fields, $relFields,$fieldTypes );
276
		}
277
		$form->setCaptions ( [ "","" ] );
278
		$form->onGenerateField ( function (HtmlFormField $f, $nb) use ($identifier, $editMemberParams) {
279
			if ($nb == 1) {
280
				$f->setSize ( "mini" );
281
				if ($editMemberParams->getHasButtons ()) {
282
					$btO = HtmlButton::icon ( "btO", "check" )->addClass ( "green mini compact" )->onClick ( "\$('#" . $identifier . "').trigger('validate');", true, true );
283
					$btC = HtmlButton::icon ( "btC", "close" )->addClass ( "mini compact" )->onClick ( "\$('#" . $identifier . "').trigger('endEdit');" );
284
					$f->wrap ( "<div class='inline fields' style='margin:0;'>", [ $btO,$btC,"</div>" ] );
285
					if (! $editMemberParams->getHasPopup ()) {
286
						if (! ($f instanceof HtmlFormCheckbox)) {
287
							$f->setWidth ( 16 )->setProperty ( "style", "padding-left:0;" );
288
						}
289
					}
290
				}
291
				$f->on ( "keydown", "if(event.keyCode===27) {\$('#" . $identifier . "').trigger('endEdit');}" );
292
				$f->onClick ( "return false;", true, true );
293
			} else {
294
				$f->setProperty ( "style", "display: none;" );
295
			}
296
		} );
297
			$form->setSubmitParams ( $this->controller->_getBaseRoute () . "/$updateUrl/" . $member . "/" . $editMemberParams->getUpdateCallback (), "#" . $td, [ "attr" => "","hasLoader" => false,"jsCallback" => "$(self).remove();","jqueryDone" => "html" ] );
298
			if ($editMemberParams->getHasPopup ()) {
299
				$endEdit = "\$('#" . $identifier . "').html();\$('.popup').hide();\$('#" . $td . "').popup('destroy');";
300
				$validate = $endEdit;
301
			} else {
302
				$endEdit = "let td=\$('#" . $td . "');td.html(td.data('originalText'));";
303
				$validate = "";
304
			}
305
			$form->on ( "endEdit", $endEdit );
306
			$form->on ( "validate", "\$('#" . $identifier . "').form('submit');" . $validate );
307
			$this->jquery->execAtLast ( "$('form').find('input[type=text],textarea,select').filter(':visible:first').focus();" );
308
			return $form;
309
	}
310
	
311
	private function setFormFields_(&$fields, $relFields) {
312
		$hasRelations = false;
313
		$relFields = \array_flip ( $relFields );
314
		foreach ( $fields as $index => $field ) {
315
			if (isset ( $relFields [$field] )) {
316
				$fields [$index] = $relFields [$field];
317
				$hasRelations = true;
318
			}
319
		}
320
		return $hasRelations;
321
	}
322
	
323
	/**
324
	 * Returns an associative array defining form message title with keys "icon","message","subMessage"
325
	 *
326
	 * @param DataForm $form
327
	 * @param object $instance
328
	 * @return array the message title
329
	 */
330
	protected function getFormTitle($form, $instance) {
331
		$type = ($instance->_new) ? 'new' : 'edit';
332
		$messageInfos = [ 'new' => [ 'icon' => HtmlIconGroups::corner ( 'table ' . $this->style, 'plus ' . $this->style, 'big' ),'subMessage' => '&nbsp;New object creation' ],'edit' => [ 'icon' => HtmlIconGroups::corner ( 'table ' . $this->style, 'edit ' . $this->style, 'big' ),'subMessage' => '&nbsp;Editing an existing object' ] ];
333
		$message = $messageInfos [$type];
334
		$message ['message'] = '&nbsp;' . \get_class ( $instance );
335
		return $message;
336
	}
337
	
338
	/**
339
	 * Sets the components for each field
340
	 *
341
	 * @param DataForm $form
342
	 * @param array $fieldTypes associative array of field names (keys) and types (values)
343
	 * @param ?array $attributes
344
	 */
345
	public function setFormFieldsComponent(DataForm $form, $fieldTypes, $attributes = [ ]) {
346
		$this->setFormFieldsComponent_ ( $form, $fieldTypes, $attributes );
347
	}
348
	
349
	/**
350
	 * Sets the components for each field
351
	 *
352
	 * @param DataForm $form
353
	 * @param array $fieldTypes associative array of field names (keys) and types (values)
354
	 * @param array $attributes
355
	 */
356
	public function setMemberFormFieldsComponent(DataForm $form, $fieldTypes,$attributes=[]) {
357
		$this->setFormFieldsComponent_ ( $form, $fieldTypes ,$attributes);
358
	}
359
	
360
	protected function setFormFieldsComponent_(DataForm $form, $fieldTypes, $attributes = [ ]) {
361
		foreach ( $fieldTypes as $property => $type ) {
362
			$rules = $attributes[$property]??[];
363
			if($hasRules = \count($rules)>0){
364
				$form->setValidationParams(["on"=>"blur","inline"=>true]);
365
			}
366
			$noPName = false;
367
			$noPType = false;
368
			switch ($property) {
369
				case 'password' :
370
					$form->fieldAsInput ( $property, ['inputType'=>'password']+$rules );
371
					break;
372
				case 'email' :
373
				case 'mail' :
374
					$form->fieldAsInput ( $property,$rules);
375
					break;
376
				default :
377
					$noPName = true;
378
			}
379
			
380
			switch ($type) {
381
				case 'tinyint(1)' :
382
				case 'bool' :
383
				case 'boolean' :
384
					$form->fieldAsCheckbox ( $property, \array_diff($rules['rules']??[],['empty']));
385
					break;
386
				case 'int' :
387
				case 'integer' :
388
					$form->fieldAsInput ( $property, [ 'inputType' => 'number']+$rules );
389
					break;
390
				case 'date' :
391
					$form->fieldAsInput ( $property, [ 'inputType' => 'date']+$rules );
392
					break;
393
				case 'datetime' :
394
					$form->fieldAsInput ( $property, [ 'inputType' => 'datetime-local']+$rules );
395
					break;
396
				case 'text' :
397
					$form->fieldAsTextarea ( $property, $rules );
398
					break;
399
				default :
400
					$noPType = true;
401
			}
402
			if ($hasRules && $noPName && $noPType) {
403
				$form->fieldAsInput ( $property, $rules );
404
			}
405
		}
406
	}
407
	
408
	/**
409
	 * For doing something when $field is generated in form
410
	 *
411
	 * @param mixed $field
412
	 */
413
	public function onGenerateFormField($field, $nb,$name) {
414
		if ($field instanceof HtmlFormInput) {
415
			if ($field->getDataField ()->getProperty ( 'type' ) == "datetime-local") {
416
				$v = $field->getDataField ()->getProperty ( 'value' );
417
				$field->getDataField ()->setValue ( date ( "Y-m-d\TH:i:s", \strtotime ( $v ) ) );
418
			}
419
		}
420
		return;
421
	}
422
	
423
	/**
424
	 * Condition to determine if the edit or add form is modal for $model objects
425
	 *
426
	 * @param array $objects
427
	 * @param string $model
428
	 * @return boolean
429
	 */
430
	public function isModal($objects, $model) {
431
		return \count ( $objects ) > 5;
432
	}
433
	
434
	/**
435
	 * Returns the captions for form fields
436
	 *
437
	 * @param array $captions
438
	 * @param string $className
439
	 */
440
	public function getFormCaptions($captions, $className, $instance) {
441
		return \array_map ( 'ucfirst', $captions );
442
	}
443
	
444
	/**
445
	 * Returns the modal Title.
446
	 * @param object $instance
447
	 * @return string
448
	 */
449
	public function getFormModalTitle(object $instance):string{
450
		return \get_class ( $instance );
451
	}
452
	
453
	/**
454
	 * If true, display a message for editing or updating (default true).
455
	 * @return bool
456
	 */
457
	public function formHasMessage():bool{
458
		return true;
459
	}
460
	
461
	/**
462
	 * Hook for changing the edit/new modal buttons.
463
	 * @param HtmlButton $btOkay
464
	 * @param HtmlButton $btCancel
465
	 */
466
	public function onFormModalButtons(HtmlButton $btOkay,HtmlButton $btCancel):void{
467
		$btOkay->setValue ( "Validate modifications" );
468
	}
469
}
470
471