Completed
Push — master ( 847185...5c7ff3 )
by Jean-Christophe
03:16
created

DataTable::run()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 11
nc 16
nop 1
1
<?php
2
3
namespace Ajax\semantic\widgets\datatable;
4
5
use Ajax\common\Widget;
6
use Ajax\JsUtils;
7
use Ajax\semantic\html\collections\table\HtmlTable;
8
use Ajax\semantic\html\elements\HtmlInput;
9
use Ajax\semantic\html\collections\menus\HtmlPaginationMenu;
10
use Ajax\semantic\html\modules\checkbox\HtmlCheckbox;
11
use Ajax\semantic\html\base\constants\Direction;
12
use Ajax\service\JArray;
13
use Ajax\semantic\widgets\base\InstanceViewer;
14
use Ajax\semantic\html\collections\table\traits\TableTrait;
15
use Ajax\semantic\html\elements\HtmlLabel;
16
17
/**
18
 * DataTable widget for displaying list of objects
19
 * @version 1.0
20
 * @author jc
21
 * @since 2.2
22
 *
23
 */
24
class DataTable extends Widget {
25
	use TableTrait,DataTableFieldAsTrait;
26
	protected $_searchField;
27
	protected $_urls;
28
	protected $_pagination;
29
	protected $_hasCheckboxes;
30
	protected $_compileParts;
31
	protected $_deleteBehavior;
32
	protected $_editBehavior;
33
	protected $_visibleHover=false;
34
	protected $_hasCheckedMessage=false;
35
	protected $_targetSelector;
36
	protected $_checkedMessage;
37
	protected $_checkedClass;
38
39
	public function __construct($identifier,$model,$modelInstance=NULL) {
40
		parent::__construct($identifier, $model,$modelInstance);
41
		$this->_init(new InstanceViewer($identifier), "table", new HtmlTable($identifier, 0,0), false);
42
		$this->_urls=[];
43
	}
44
45
	public function run(JsUtils $js){
46
		if($this->_hasCheckboxes && isset($js)){
47
			$this->_runCheckboxes($js);
48
		}
49
		if($this->_visibleHover){
50
			$js->execOn("mouseover", "#".$this->identifier." tr", "$(event.target).closest('tr').find('.visibleover').css('visibility', 'visible');",["preventDefault"=>false,"stopPropagation"=>true]);
51
			$js->execOn("mouseout", "#".$this->identifier." tr", "$(event.target).closest('tr').find('.visibleover').css('visibility', 'hidden');",["preventDefault"=>false,"stopPropagation"=>true]);
52
		}
53
		if(\is_array($this->_deleteBehavior))
54
			$this->_generateBehavior("delete",$this->_deleteBehavior, $js);
55
		if(\is_array($this->_editBehavior))
56
			$this->_generateBehavior("edit",$this->_editBehavior,$js);
57
		return parent::run($js);
58
	}
59
60
	protected function _runCheckboxes(JsUtils $js){
61
		$checkedMessageCall="";
62
		if($this->_hasCheckedMessage){
63
			$msg=$this->getCheckedMessage();
64
			$checkedMessageFunction="function updateChecked(){var msg='".$msg[0]."',count=\$('#{$this->identifier} [name=\"selection[]\"]:checked').length,all=\$('#{$this->identifier} [name=\"selection[]\"]').length;
65
			if(count==1) msg='".$msg[1]."';
66
						else if(count>1) msg='".$msg["other"]."';
67
						\$('#checked-count-".$this->identifier."').contents().filter(function() {return this.nodeType == 3;}).each(function(){this.textContent = msg.replace('{count}',count);});
68
								\$('#toolbar-{$this->identifier} .visibleOnChecked').toggle(count>0);}\$('#toolbar-".$this->identifier." .visibleOnChecked').hide();";
69
			$checkedMessageCall="updateChecked();";
70
			if(isset($this->_checkedClass)){
71
				$checkedMessageCall.="$(this).closest('tr').toggleClass('".$this->_checkedClass."',$(this).prop('checked'));";
72
			}
73
			$js->exec($checkedMessageFunction,true);
74
		}
75
		$js->execOn("change", "#".$this->identifier." [name='selection[]']", "
76
				var \$parentCheckbox=\$('#ck-main-ck-{$this->identifier}'),\$checkbox=\$('#{$this->identifier} [name=\"selection[]\"]'),allChecked=true,allUnchecked=true;
77
				\$checkbox.each(function() {if($(this).prop('checked')){allUnchecked = false;}else{allChecked = false;}});
78
				if(allChecked) {\$parentCheckbox.checkbox('set checked');}else if(allUnchecked){\$parentCheckbox.checkbox('set unchecked');}else{\$parentCheckbox.checkbox('set indeterminate');};".$checkedMessageCall);
79
	}
80
81
	protected function _generateBehavior($op,$params,JsUtils $js){
82
		if(isset($this->_urls[$op])){
83
			$params=\array_merge($params,["attr"=>"data-ajax"]);
84
			$js->getOnClick("#".$this->identifier." ._".$op, $this->_urls[$op],$this->getTargetSelector(),$params);
85
		}
86
	}
87
88
	/**
89
	 * {@inheritDoc}
90
	 * @see \Ajax\semantic\html\collections\table\TableTrait::getTable()
91
	 */
92
	protected function getTable() {
93
		return $this->content["table"];
94
	}
95
96
97
	public function compile(JsUtils $js=NULL,&$view=NULL){
98
		if(!$this->_generated){
99
			$this->_instanceViewer->setInstance($this->_model);
100
			$captions=$this->_instanceViewer->getCaptions();
101
102
			$table=$this->content["table"];
103
104
			if($this->_hasCheckboxes){
105
				$this->_generateMainCheckbox($captions);
106
			}
107
108
			$table->setRowCount(0, \sizeof($captions));
109
			$table->setHeaderValues($captions);
110
			if(isset($this->_compileParts))
111
				$table->setCompileParts($this->_compileParts);
112
113
			if(isset($this->_searchField) && isset($js)){
114
				if(isset($this->_urls["refresh"]))
115
					$this->_searchField->postOn("change", $this->_urls["refresh"],"{'s':$(this).val()}","#".$this->identifier." tbody",["preventDefault"=>false,"jqueryDone"=>"replaceWith"]);
116
			}
117
118
			$this->_generateContent($table);
119
120
			if($this->_hasCheckboxes && $table->hasPart("thead")){
121
					$table->getHeader()->getCell(0, 0)->addClass("no-sort");
122
			}
123
124
			if(isset($this->_pagination) && $this->_pagination->getVisible()){
125
				$this->_generatePagination($table);
126
			}
127
			if(isset($this->_toolbar)){
128
				$this->_setToolbarPosition($table, $captions);
129
			}
130
			$this->content=JArray::sortAssociative($this->content, [PositionInTable::BEFORETABLE,"table",PositionInTable::AFTERTABLE]);
131
			$this->_compileForm();
132
			$this->_generated=true;
133
		}
134
		return parent::compile($js,$view);
135
	}
136
137
	private function _generateMainCheckbox(&$captions){
138
		$ck=new HtmlCheckbox("main-ck-".$this->identifier,"");
139
		$checkedMessageCall="";
140
		if($this->_hasCheckedMessage)
141
			$checkedMessageCall="updateChecked();";
142
143
		$ck->setOnChecked($this->_setAllChecked("true").$checkedMessageCall);
144
		$ck->setOnUnchecked($this->_setAllChecked("false").$checkedMessageCall);
145
		\array_unshift($captions, $ck);
146
	}
147
148
	private function _setAllChecked($checked){
149
		$result="$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',".$checked.");";
150
		if(isset($this->_checkedClass)){
151
			$result.="$('#".$this->identifier." tr').toggleClass('".$this->_checkedClass."',".$checked.");";
152
		}
153
		return $result;
154
	}
155
156
	protected function _generateContent($table){
157
		$objects=$this->_modelInstance;
158
		if(isset($this->_pagination)){
159
			$objects=$this->_pagination->getObjects($this->_modelInstance);
160
		}
161
		InstanceViewer::setIndex(0);
162
		$table->fromDatabaseObjects($objects, function($instance) use($table){
163
			$this->_instanceViewer->setInstance($instance);
164
			InstanceViewer::$index++;
165
			$values= $this->_instanceViewer->getValues();
166
			if($this->_hasCheckboxes){
167
				$ck=new HtmlCheckbox("ck-".$this->identifier,"");
168
				$field=$ck->getField();
169
				$field->setProperty("value",$this->_instanceViewer->getIdentifier());
170
				$field->setProperty("name", "selection[]");
171
				\array_unshift($values, $ck);
172
			}
173
			$result=$table->newRow();
174
			$result->setIdentifier($this->identifier."-tr-".$this->_instanceViewer->getIdentifier());
175
			$result->setValues($values);
176
			return $result;
177
		});
178
	}
179
180
	private function _generatePagination($table){
181
		$footer=$table->getFooter();
182
		$footer->mergeCol();
183
		$menu=new HtmlPaginationMenu("pagination-".$this->identifier,$this->_pagination->getPagesNumbers());
184
		$menu->floatRight();
185
		$menu->setActiveItem($this->_pagination->getPage()-1);
186
		$footer->setValues($menu);
187
		if(isset($this->_urls["refresh"]))
188
			$menu->postOnClick($this->_urls["refresh"],"{'p':$(this).attr('data-page')}","#".$this->identifier." tbody",["preventDefault"=>false,"jqueryDone"=>"replaceWith"]);
189
	}
190
191
	protected function _getFieldName($index){
192
		return parent::_getFieldName($index)."[]";
193
	}
194
195
	protected function _getFieldCaption($index){
196
		return null;
197
	}
198
199
	protected function _setToolbarPosition($table,$captions=NULL){
200
		switch ($this->_toolbarPosition){
201
			case PositionInTable::BEFORETABLE:
202
			case PositionInTable::AFTERTABLE:
203
				if(isset($this->_compileParts)===false){
204
					$this->content[$this->_toolbarPosition]=$this->_toolbar;
205
				}
206
				break;
207
			case PositionInTable::HEADER:
208
			case PositionInTable::FOOTER:
209
			case PositionInTable::BODY:
210
				$this->addToolbarRow($this->_toolbarPosition,$table, $captions);
211
				break;
212
		}
213
	}
214
215
	/**
216
	 * Associates a $callback function after the compilation of the field at $index position
217
	 * The $callback function can take the following arguments : $field=>the compiled field, $instance : the active instance of the object, $index: the field position
218
	 * @param int $index postion of the compiled field
219
	 * @param callable $callback function called after the field compilation
220
	 * @return \Ajax\semantic\widgets\datatable\DataTable
221
	 */
222
	public function afterCompile($index,$callback){
223
		$this->_instanceViewer->afterCompile($index,$callback);
224
		return $this;
225
	}
226
227
	private function addToolbarRow($part,$table,$captions){
228
		$hasPart=$table->hasPart($part);
229
		if($hasPart){
230
			$row=$table->getPart($part)->addRow(\sizeof($captions));
231
		}else{
232
			$row=$table->getPart($part)->getRow(0);
233
		}
234
		$row->mergeCol();
235
		$row->setValues([$this->_toolbar]);
236
	}
237
238
	public function getHtmlComponent(){
239
		return $this->content["table"];
240
	}
241
242
	public function getUrls() {
243
		return $this->_urls;
244
	}
245
246
	/**
247
	 * Sets the associative array of urls for refreshing, updating or deleting
248
	 * @param string|array $urls associative array with keys refresh: for refreshing with search field or pagination, edit : for updating a row, delete: for deleting a row
249
	 * @return \Ajax\semantic\widgets\datatable\DataTable
250
	 */
251
	public function setUrls($urls) {
252
		if(\is_array($urls)){
253
			$this->_urls["refresh"]=JArray::getValue($urls, "refresh",0);
254
			$this->_urls["edit"]=JArray::getValue($urls, "edit",1);
255
			$this->_urls["delete"]=JArray::getValue($urls, "delete",2);
256
		}else{
257
			$this->_urls=["refresh"=>$urls,"edit"=>$urls,"delete"=>$urls];
258
		}
259
		return $this;
260
	}
261
262
	public function paginate($items_per_page=10,$page=1){
263
		$this->_pagination=new Pagination($items_per_page,4,$page);
264
	}
265
266
	public function getHasCheckboxes() {
267
		return $this->_hasCheckboxes;
268
	}
269
270
	public function setHasCheckboxes($_hasCheckboxes) {
271
		$this->_hasCheckboxes=$_hasCheckboxes;
272
		return $this;
273
	}
274
275
	public function refresh($compileParts=["tbody"]){
276
		$this->_compileParts=$compileParts;
277
		return $this;
278
	}
279
280
281
	public function addSearchInToolbar($position=Direction::RIGHT){
282
		return $this->addInToolbar($this->getSearchField())->setPosition($position);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Ajax\common\html\HtmlDoubleElement as the method setPosition() does only exist in the following sub-classes of Ajax\common\html\HtmlDoubleElement: Ajax\bootstrap\html\content\HtmlGridCol, Ajax\semantic\html\colle...menus\HtmlAccordionMenu, Ajax\semantic\html\collections\menus\HtmlIconMenu, Ajax\semantic\html\colle...nus\HtmlLabeledIconMenu, Ajax\semantic\html\collections\menus\HtmlMenu, Ajax\semantic\html\colle...enus\HtmlPaginationMenu, Ajax\semantic\html\content\HtmlAccordionMenuItem, Ajax\semantic\html\content\HtmlDropdownItem, Ajax\semantic\html\content\HtmlMenuItem, Ajax\semantic\html\modules\HtmlPopup. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
283
	}
284
285
	public function getSearchField(){
286
		if(isset($this->_searchField)===false){
287
			$this->_searchField=new HtmlInput("search-".$this->identifier,"search","","Search...");
288
			$this->_searchField->addIcon("search",Direction::RIGHT);
289
		}
290
		return $this->_searchField;
291
	}
292
293
	/**
294
	 * The callback function called after the insertion of each row when fromDatabaseObjects is called
295
	 * callback function takes the parameters $row : the row inserted and $object: the instance of model used
296
	 * @param callable $callback
297
	 * @return DataTable
298
	 */
299
	public function onNewRow($callback) {
300
		$this->content["table"]->onNewRow($callback);
301
		return $this;
302
	}
303
304
	public function asForm(){
305
		return $this->getForm();
306
	}
307
308
309
310
	protected function getTargetSelector() {
311
		$result=$this->_targetSelector;
312
		if(!isset($result))
313
			$result="#".$this->identifier;
314
		return $result;
315
	}
316
317
	/**
318
	 * Sets the response element selector for Edit and Delete request with ajax
319
	 * @param string $_targetSelector
320
	 * @return \Ajax\semantic\widgets\datatable\DataTable
321
	 */
322
	public function setTargetSelector($_targetSelector) {
323
		$this->_targetSelector=$_targetSelector;
324
		return $this;
325
	}
326
327
	protected function getCheckedMessage() {
328
		$result= $this->_checkedMessage;
329
		if(!isset($result)){
330
			$result=[0=>"none selected",1=>"one item selected","other"=>"{count} items selected"];
331
		}
332
		return $result;
333
	}
334
335
	/**
336
	 * Defines the message displayed when checkboxes are checked or unchecked
337
	 * with an associative array 0=>no selection,1=>one item selected, other=>{count} items selected
338
	 * @param array $_checkedMessage
339
	 * @return \Ajax\semantic\widgets\datatable\DataTable
340
	 */
341
	public function setCheckedMessage(array $_checkedMessage) {
342
		$this->_checkedMessage=$_checkedMessage;
343
		return $this;
344
	}
345
346
	/**
347
	 * @param array $checkedMessage
348
	 * @param callable $callback
349
	 */
350
	public function addCountCheckedInToolbar(array $checkedMessage=null,$callback=null){
351
		if(isset($checkedMessage))
352
			$this->_checkedMessage=$checkedMessage;
353
		$checkedMessage=$this->getCheckedMessage();
354
		$this->_hasCheckboxes=true;
355
		$this->_hasCheckedMessage=true;
356
		$element=new HtmlLabel("checked-count-".$this->identifier,$checkedMessage[0]);
357
		$this->addInToolbar($element,$callback);
358
	}
359
360
	public function setCheckedClass($_checkedClass) {
361
		$this->_checkedClass=$_checkedClass;
362
		return $this;
363
	}
364
}