Completed
Push — master ( f99fed...57dceb )
by Jean-Christophe
03:29
created

DataTable::_generateRow()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 17
nc 3
nop 3
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\collections\HtmlMessage;
16
use Ajax\semantic\html\collections\menus\HtmlMenu;
17
18
/**
19
 * DataTable widget for displaying list of objects
20
 * @version 1.0
21
 * @author jc
22
 * @since 2.2
23
 *
24
 */
25
class DataTable extends Widget {
26
	use TableTrait,DataTableFieldAsTrait,HasCheckboxesTrait;
27
	protected $_searchField;
28
	protected $_urls;
29
	protected $_pagination;
30
	protected $_compileParts;
31
	protected $_deleteBehavior;
32
	protected $_editBehavior;
33
	protected $_visibleHover=false;
34
	protected $_targetSelector;
35
	protected $_refreshSelector;
36
	protected $_emptyMessage;
37
	protected $_json;
38
	protected $_rowClass="";
39
40
41
	public function __construct($identifier,$model,$modelInstance=NULL) {
42
		parent::__construct($identifier, $model,$modelInstance);
43
		$this->_init(new InstanceViewer($identifier), "table", new HtmlTable($identifier, 0,0), false);
44
		$this->_urls=[];
45
		$this->_emptyMessage=new HtmlMessage("","nothing to display");
46
		$this->_emptyMessage->setIcon("info circle");
47
	}
48
49
	public function run(JsUtils $js){
50
		if($this->_hasCheckboxes && isset($js)){
51
			$this->_runCheckboxes($js);
52
		}
53
		if($this->_visibleHover){
54
			$js->execOn("mouseover", "#".$this->identifier." tr", "$(event.target).closest('tr').find('.visibleover').css('visibility', 'visible');",["preventDefault"=>false,"stopPropagation"=>true]);
55
			$js->execOn("mouseout", "#".$this->identifier." tr", "$(event.target).closest('tr').find('.visibleover').css('visibility', 'hidden');",["preventDefault"=>false,"stopPropagation"=>true]);
56
		}
57
		if(\is_array($this->_deleteBehavior))
58
			$this->_generateBehavior("delete",$this->_deleteBehavior, $js);
59
		if(\is_array($this->_editBehavior))
60
			$this->_generateBehavior("edit",$this->_editBehavior,$js);
61
		return parent::run($js);
62
	}
63
64
65
66
	protected function _generateBehavior($op,$params,JsUtils $js){
67
		if(isset($this->_urls[$op])){
68
			$params=\array_merge($params,["attr"=>"data-ajax"]);
69
			$js->getOnClick("#".$this->identifier." ._".$op, $this->_urls[$op],$this->getTargetSelector(),$params);
70
		}
71
	}
72
73
	/**
74
	 * {@inheritDoc}
75
	 * @see \Ajax\semantic\html\collections\table\TableTrait::getTable()
76
	 */
77
	protected function getTable() {
78
		return $this->content["table"];
79
	}
80
81
82
	public function compile(JsUtils $js=NULL,&$view=NULL){
83
		if(!$this->_generated){
84
			$this->_instanceViewer->setInstance($this->_model);
85
			$captions=$this->_instanceViewer->getCaptions();
86
87
			$table=$this->content["table"];
88
89
			if($this->_hasCheckboxes){
90
				$this->_generateMainCheckbox($captions);
91
			}
92
93
			$table->setRowCount(0, \sizeof($captions));
94
			$table->setHeaderValues($captions);
95
			if(isset($this->_compileParts))
96
				$table->setCompileParts($this->_compileParts);
97
98
			if(isset($this->_searchField) && isset($js)){
99
				if(isset($this->_urls["refresh"]))
100
					$this->_searchField->postOn("change", $this->_urls["refresh"],"{'s':$(this).val()}","#".$this->identifier." tbody",["preventDefault"=>false,"jqueryDone"=>"replaceWith"]);
101
			}
102
103
			$this->_generateContent($table);
104
105
			if($this->_hasCheckboxes && $table->hasPart("thead")){
106
					$table->getHeader()->getCell(0, 0)->addClass("no-sort");
107
			}
108
109
			if(isset($this->_toolbar)){
110
				$this->_setToolbarPosition($table, $captions);
111
			}
112
			if(isset($this->_pagination) && $this->_pagination->getVisible()){
113
				$this->_generatePagination($table,$js);
114
			}
115
116
			$this->content=JArray::sortAssociative($this->content, [PositionInTable::BEFORETABLE,"table",PositionInTable::AFTERTABLE]);
117
			$this->_compileForm();
118
119
			$this->_generated=true;
120
		}
121
		return parent::compile($js,$view);
122
	}
123
124
125
126
	protected function _generateContent($table){
127
		$objects=$this->_modelInstance;
128
		if(isset($this->_pagination)){
129
			$objects=$this->_pagination->getObjects($this->_modelInstance);
130
		}
131
			InstanceViewer::setIndex(0);
132
			$table->fromDatabaseObjects($objects, function($instance) use($table){
133
				return $this->_generateRow($instance, $table);
134
			});
135
		if($table->getRowCount()==0){
136
			$result=$table->addRow();
137
			$result->mergeRow();
138
			$result->setValues([$this->_emptyMessage]);
139
		}
140
	}
141
142
	protected function _generateRow($instance,&$table,$checkedClass=null){
143
		$this->_instanceViewer->setInstance($instance);
144
		InstanceViewer::$index++;
145
		$values= $this->_instanceViewer->getValues();
146
		if($this->_hasCheckboxes){
147
			$ck=new HtmlCheckbox("ck-".$this->identifier,"");
148
			$field=$ck->getField();
149
			$field->setProperty("value",$this->_instanceViewer->getIdentifier());
150
			$field->setProperty("name", "selection[]");
151
			if(isset($checkedClass))
152
				$field->setClass($checkedClass);
153
			\array_unshift($values, $ck);
154
		}
155
		$result=$table->newRow();
156
		$result->setIdentifier($this->identifier."-tr-".$this->_instanceViewer->getIdentifier());
157
		$result->setValues($values);
158
		$result->addToProperty("class",$this->_rowClass);
159
		return $result;
160
	}
161
162
	protected function _generatePagination($table,$js=NULL){
163
		if(isset($this->_toolbar)){
164
			if($this->_toolbarPosition==PositionInTable::FOOTER)
165
				$this->_toolbar->setFloated("left");
166
		}
167
		$footer=$table->getFooter();
168
		$footer->mergeCol();
169
		$menu=new HtmlPaginationMenu("pagination-".$this->identifier,$this->_pagination->getPagesNumbers());
170
		$menu->floatRight();
171
		$menu->setActiveItem($this->_pagination->getPage()-1);
172
		$footer->addValues($menu);
173
		$this->_associatePaginationBehavior($menu,$js);
174
	}
175
176
	protected function _associatePaginationBehavior(HtmlMenu $menu,JsUtils $js=NULL){
177
		if(isset($this->_urls["refresh"])){
178
			$menu->postOnClick($this->_urls["refresh"],"{'p':$(this).attr('data-page')}",$this->getRefreshSelector(),["preventDefault"=>false,"jqueryDone"=>"replaceWith"]);
179
		}
180
	}
181
182
	protected function _getFieldName($index){
183
		return parent::_getFieldName($index)."[]";
184
	}
185
186
	protected function _getFieldCaption($index){
187
		return null;
188
	}
189
190
	protected function _setToolbarPosition($table,$captions=NULL){
191
		switch ($this->_toolbarPosition){
192
			case PositionInTable::BEFORETABLE:
193
			case PositionInTable::AFTERTABLE:
194
				if(isset($this->_compileParts)===false){
195
					$this->content[$this->_toolbarPosition]=$this->_toolbar;
196
				}
197
				break;
198
			case PositionInTable::HEADER:
199
			case PositionInTable::FOOTER:
200
			case PositionInTable::BODY:
201
				$this->addToolbarRow($this->_toolbarPosition,$table, $captions);
202
				break;
203
		}
204
	}
205
206
	/**
207
	 * Associates a $callback function after the compilation of the field at $index position
208
	 * The $callback function can take the following arguments : $field=>the compiled field, $instance : the active instance of the object, $index: the field position
209
	 * @param int $index postion of the compiled field
210
	 * @param callable $callback function called after the field compilation
211
	 * @return DataTable
212
	 */
213
	public function afterCompile($index,$callback){
214
		$this->_instanceViewer->afterCompile($index,$callback);
215
		return $this;
216
	}
217
218
	private function addToolbarRow($part,$table,$captions){
219
		$hasPart=$table->hasPart($part);
220
		if($hasPart){
221
			$row=$table->getPart($part)->addRow(\sizeof($captions));
222
		}else{
223
			$row=$table->getPart($part)->getRow(0);
224
		}
225
		$row->mergeCol();
226
		$row->setValues([$this->_toolbar]);
227
	}
228
229
	public function getHtmlComponent(){
230
		return $this->content["table"];
231
	}
232
233
	public function getUrls() {
234
		return $this->_urls;
235
	}
236
237
	/**
238
	 * Sets the associative array of urls for refreshing, updating or deleting
239
	 * @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
240
	 * @return DataTable
241
	 */
242
	public function setUrls($urls) {
243
		if(\is_array($urls)){
244
			$this->_urls["refresh"]=JArray::getValue($urls, "refresh",0);
245
			$this->_urls["edit"]=JArray::getValue($urls, "edit",1);
246
			$this->_urls["delete"]=JArray::getValue($urls, "delete",2);
247
		}else{
248
			$this->_urls=["refresh"=>$urls,"edit"=>$urls,"delete"=>$urls];
249
		}
250
		return $this;
251
	}
252
253
	/**
254
	 * Paginates the DataTable element with a Semantic HtmlPaginationMenu component
255
	 * @param number $page the active page number
256
	 * @param number $total_rowcount the total number of items
257
	 * @param number $items_per_page The number of items per page
258
	 * @param number $pages_visibles The number of visible pages in the Pagination component
259
	 * @return DataTable
260
	 */
261
	public function paginate($page,$total_rowcount,$items_per_page=10,$pages_visibles=null){
262
		$this->_pagination=new Pagination($items_per_page,$pages_visibles,$page,$total_rowcount);
263
		return $this;
264
	}
265
266
	/**
267
	 * Auto Paginates the DataTable element with a Semantic HtmlPaginationMenu component
268
	 * @param number $page the active page number
269
	 * @param number $items_per_page The number of items per page
270
	 * @param number $pages_visibles The number of visible pages in the Pagination component
271
	 * @return DataTable
272
	 */
273
	public function autoPaginate($page=1,$items_per_page=10,$pages_visibles=4){
274
		$this->_pagination=new Pagination($items_per_page,$pages_visibles,$page);
275
		return $this;
276
	}
277
278
279
280
	/**
281
	 * @param array $compileParts
282
	 * @return DataTable
283
	 */
284
	public function refresh($compileParts=["tbody"]){
285
		$this->_compileParts=$compileParts;
286
		return $this;
287
	}
288
289
290
	public function addSearchInToolbar($position=Direction::RIGHT){
291
		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...
292
	}
293
294
	public function getSearchField(){
295
		if(isset($this->_searchField)===false){
296
			$this->_searchField=new HtmlInput("search-".$this->identifier,"search","","Search...");
297
			$this->_searchField->addIcon("search",Direction::RIGHT);
298
		}
299
		return $this->_searchField;
300
	}
301
302
	/**
303
	 * The callback function called after the insertion of each row when fromDatabaseObjects is called
304
	 * callback function takes the parameters $row : the row inserted and $object: the instance of model used
305
	 * @param callable $callback
306
	 * @return DataTable
307
	 */
308
	public function onNewRow($callback) {
309
		$this->content["table"]->onNewRow($callback);
310
		return $this;
311
	}
312
313
	public function asForm(){
314
		return $this->getForm();
315
	}
316
317
318
319
	protected function getTargetSelector() {
320
		$result=$this->_targetSelector;
321
		if(!isset($result))
322
			$result="#".$this->identifier;
323
		return $result;
324
	}
325
326
	/**
327
	 * Sets the response element selector for Edit and Delete request with ajax
328
	 * @param string $_targetSelector
329
	 * @return \Ajax\semantic\widgets\datatable\DataTable
330
	 */
331
	public function setTargetSelector($_targetSelector) {
332
		$this->_targetSelector=$_targetSelector;
333
		return $this;
334
	}
335
336
	public function getRefreshSelector() {
337
		if(isset($this->_refreshSelector))
338
			return $this->_refreshSelector;
339
		return "#".$this->identifier." tbody";
340
	}
341
342
	public function setRefreshSelector($_refreshSelector) {
343
		$this->_refreshSelector=$_refreshSelector;
344
		return $this;
345
	}
346
347
	public function show($modelInstance){
348
		if(\is_array($modelInstance)){
349
			if(\is_array(array_values($modelInstance)[0]))
350
				$modelInstance=\json_decode(\json_encode($modelInstance), FALSE);
351
		}
352
		$this->_modelInstance=$modelInstance;
353
	}
354
355
	public function getRowClass() {
356
		return $this->_rowClass;
357
	}
358
359
	public function setRowClass($_rowClass) {
360
		$this->_rowClass=$_rowClass;
361
		return $this;
362
	}
363
364
365
}