Completed
Push — master ( c6edab...f99fed )
by Jean-Christophe
03:30
created

DataTable::_generatePagination()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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