Completed
Push — master ( 2c7b4a...2bc7fe )
by Jean-Christophe
03:22
created

DataTable::_getFieldCaption()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
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\elements\HtmlButton;
12
use Ajax\semantic\html\base\constants\Direction;
13
use Ajax\service\JArray;
14
use Ajax\semantic\html\base\HtmlSemDoubleElement;
15
use Ajax\semantic\widgets\base\InstanceViewer;
16
use Ajax\semantic\html\collections\table\traits\TableTrait;
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;
27
	protected $_searchField;
28
	protected $_urls;
29
	protected $_pagination;
30
	protected $_hasCheckboxes;
31
	protected $_compileParts;
32
33
	public function run(JsUtils $js){
34
		if($this->_hasCheckboxes && isset($js)){
35
			$js->execOn("change", "#".$this->identifier." [name='selection[]']", "
36
		var \$parentCheckbox=\$('#ck-main-ck-{$this->identifier}'),\$checkbox=\$('#{$this->identifier} [name=\"selection[]\"]'),allChecked=true,allUnchecked=true;
37
		\$checkbox.each(function() {if($(this).prop('checked')){allUnchecked = false;}else{allChecked = false;}});
38
		if(allChecked) {\$parentCheckbox.checkbox('set checked');}else if(allUnchecked){\$parentCheckbox.checkbox('set unchecked');}else{\$parentCheckbox.checkbox('set indeterminate');}");
39
		}
40
		parent::run($js);
41
	}
42
43
	public function __construct($identifier,$model,$modelInstance=NULL) {
44
		parent::__construct($identifier, $model,$modelInstance);
45
		$this->_init(new InstanceViewer($identifier), "table", new HtmlTable($identifier, 0,0), false);
46
	}
47
48
	/**
49
	 * {@inheritDoc}
50
	 * @see \Ajax\semantic\html\collections\table\TableTrait::getTable()
51
	 */
52
	protected function getTable() {
53
		return $this->content["table"];
54
	}
55
56
57
	public function compile(JsUtils $js=NULL,&$view=NULL){
58
		$this->_instanceViewer->setInstance($this->_model);
59
		$captions=$this->_instanceViewer->getCaptions();
60
61
		$table=$this->content["table"];
62
63
		if($this->_hasCheckboxes){
64
			$this->_generateMainCheckbox($captions);
65
		}
66
67
		$table->setRowCount(0, \sizeof($captions));
68
		$table->setHeaderValues($captions);
69
		if(isset($this->_compileParts))
70
			$table->setCompileParts($this->_compileParts);
71
		if(isset($this->_searchField) && isset($js)){
72
			$this->_searchField->postOn("change", $this->_urls,"{'s':$(this).val()}","#".$this->identifier." tbody",["preventDefault"=>false,"jqueryDone"=>"replaceWith"]);
73
		}
74
75
		$this->_generateContent($table);
76
77
		if($this->_hasCheckboxes && $table->hasPart("thead")){
78
				$table->getHeader()->getCell(0, 0)->addToProperty("class","no-sort");
79
		}
80
81
		if(isset($this->_pagination) && $this->_pagination->getVisible()){
82
			$this->_generatePagination($table);
83
		}
84
		if(isset($this->_toolbar)){
85
			$this->_setToolbarPosition($table, $captions);
86
		}
87
		$this->content=JArray::sortAssociative($this->content, [PositionInTable::BEFORETABLE,"table",PositionInTable::AFTERTABLE]);
88
		return parent::compile($js,$view);
89
	}
90
91
	private function _generateMainCheckbox(&$captions){
92
		$ck=new HtmlCheckbox("main-ck-".$this->identifier,"");
93
		$ck->setOnChecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',true);");
94
		$ck->setOnUnchecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',false);");
95
		\array_unshift($captions, $ck);
96
	}
97
98
	protected function _generateContent($table){
99
		$objects=$this->_modelInstance;
100
		if(isset($this->_pagination)){
101
			$objects=$this->_pagination->getObjects($this->_modelInstance);
102
		}
103
		InstanceViewer::setIndex(0);
104
		$table->fromDatabaseObjects($objects, function($instance){
105
			$this->_instanceViewer->setInstance($instance);
106
			InstanceViewer::$index++;
107
			$result= $this->_instanceViewer->getValues();
108
			if($this->_hasCheckboxes){
109
				$ck=new HtmlCheckbox("ck-".$this->identifier,"");
110
				$field=$ck->getField();
111
				$field->setProperty("value",$this->_instanceViewer->getIdentifier());
112
				$field->setProperty("name", "selection[]");
113
				\array_unshift($result, $ck);
114
			}
115
			return $result;
116
		});
117
	}
118
119
	private function _generatePagination($table){
120
		$footer=$table->getFooter();
121
		$footer->mergeCol();
122
		$menu=new HtmlPaginationMenu("pagination-".$this->identifier,$this->_pagination->getPagesNumbers());
123
		$menu->floatRight();
124
		$menu->setActiveItem($this->_pagination->getPage()-1);
125
		$footer->setValues($menu);
126
		$menu->postOnClick($this->_urls,"{'p':$(this).attr('data-page')}","#".$this->identifier." tbody",["preventDefault"=>false,"jqueryDone"=>"replaceWith"]);
127
	}
128
129
	protected function _getFieldName($index){
130
		return parent::_getFieldName($index)."[]";
131
	}
132
133
	protected function _getFieldCaption($index){
134
		return null;
135
	}
136
137
	protected function _setToolbarPosition($table,$captions=NULL){
138
		switch ($this->_toolbarPosition){
139
			case PositionInTable::BEFORETABLE:
140
			case PositionInTable::AFTERTABLE:
141
				if(isset($this->_compileParts)===false){
142
					$this->content[$this->_toolbarPosition]=$this->_toolbar;
143
				}
144
				break;
145
			case PositionInTable::HEADER:
146
			case PositionInTable::FOOTER:
147
			case PositionInTable::BODY:
148
				$this->addToolbarRow($this->_toolbarPosition,$table, $captions);
149
				break;
150
		}
151
	}
152
153
	/**
154
	 * Associates a $callback function after the compilation of the field at $index position
155
	 * The $callback function can take the following arguments : $field=>the compiled field, $instance : the active instance of the object, $index: the field position
156
	 * @param int $index postion of the compiled field
157
	 * @param callable $callback function called after the field compilation
158
	 * @return \Ajax\semantic\widgets\datatable\DataTable
159
	 */
160
	public function afterCompile($index,$callback){
161
		$this->_instanceViewer->afterCompile($index,$callback);
162
		return $this;
163
	}
164
165
	private function addToolbarRow($part,$table,$captions){
166
		$hasPart=$table->hasPart($part);
167
		if($hasPart){
168
			$row=$table->getPart($part)->addRow(\sizeof($captions));
169
		}else{
170
			$row=$table->getPart($part)->getRow(0);
171
		}
172
		$row->mergeCol();
173
		$row->setValues([$this->_toolbar]);
174
	}
175
176
	public function getHtmlComponent(){
177
		return $this->content["table"];
178
	}
179
180
	public function getUrls() {
181
		return $this->_urls;
182
	}
183
184
	public function setUrls($urls) {
185
		$this->_urls=$urls;
186
		return $this;
187
	}
188
189
	public function paginate($items_per_page=10,$page=1){
190
		$this->_pagination=new Pagination($items_per_page,4,$page);
191
	}
192
193
	public function getHasCheckboxes() {
194
		return $this->_hasCheckboxes;
195
	}
196
197
	public function setHasCheckboxes($_hasCheckboxes) {
198
		$this->_hasCheckboxes=$_hasCheckboxes;
199
		return $this;
200
	}
201
202
	public function refresh($compileParts=["tbody"]){
203
		$this->_compileParts=$compileParts;
204
		return $this;
205
	}
206
	/**
207
	 * @param string $caption
208
	 * @param callable $callback
209
	 * @return callable
210
	 */
211
	private function getFieldButtonCallable($caption,$callback=null){
212
		return $this->getCallable("getFieldButton",[$caption],$callback);
213
	}
214
215
	/**
216
	 * @param callable $thisCallback
217
	 * @param array $parameters
218
	 * @param callable $callback
219
	 * @return callable
220
	 */
221
	private function getCallable($thisCallback,$parameters,$callback=null){
222
		$result=function($instance) use($thisCallback,$parameters,$callback){
223
			$object=call_user_func_array(array($this,$thisCallback), $parameters);
224
			if(isset($callback)){
225
				if(\is_callable($callback)){
226
					$callback($object,$instance);
227
				}
228
			}
229
			if($object instanceof HtmlSemDoubleElement){
230
				$object->setProperty("data-ajax",$this->_instanceViewer->getIdentifier());
231
			}
232
			return $object;
233
		};
234
		return $result;
235
	}
236
237
	/**
238
	 * @param string $caption
239
	 * @return HtmlButton
240
	 */
241
	private function getFieldButton($caption){
242
		return new HtmlButton("",$caption);
243
	}
244
245
	/**
246
	 * Inserts a new Button for each row
247
	 * @param string $caption
248
	 * @param callable $callback
249
	 * @return \Ajax\semantic\widgets\datatable\DataTable
250
	 */
251
	public function addFieldButton($caption,$callback=null){
252
		$this->addField($this->getCallable("getFieldButton",[$caption],$callback));
253
		return $this;
254
	}
255
256
	/**
257
	 * Inserts a new Button for each row at col $index
258
	 * @param int $index
259
	 * @param string $caption
260
	 * @param callable $callback
261
	 * @return \Ajax\semantic\widgets\datatable\DataTable
262
	 */
263
	public function insertFieldButton($index,$caption,$callback=null){
264
		$this->insertField($index, $this->getFieldButtonCallable($caption,$callback));
265
		return $this;
266
	}
267
268
	/**
269
	 * Inserts a new Button for each row in col at $index
270
	 * @param int $index
271
	 * @param string $caption
272
	 * @param callable $callback
273
	 * @return \Ajax\semantic\widgets\datatable\DataTable
274
	 */
275
	public function insertInFieldButton($index,$caption,$callback=null){
276
		$this->insertInField($index, $this->getFieldButtonCallable($caption,$callback));
277
		return $this;
278
	}
279
280
	private function addDefaultButton($icon,$class=null,$callback=null){
281
		$this->addField($this->getCallable("getDefaultButton",[$icon,$class],$callback));
282
		return $this;
283
	}
284
285
	private function insertDefaultButtonIn($index,$icon,$class=null,$callback=null){
286
		$this->insertInField($index,$this->getCallable("getDefaultButton",[$icon,$class],$callback));
287
		return $this;
288
	}
289
290
	private function getDefaultButton($icon,$class=null){
291
		$bt=$this->getFieldButton("");
292
		$bt->asIcon($icon);
293
		if(isset($class))
294
			$bt->addToProperty("class", $class);
295
		return $bt;
296
	}
297
298
	public function addDeleteButton($callback=null){
299
		return $this->addDefaultButton("remove","delete red basic",$callback);
300
	}
301
302
	public function addEditButton($callback=null){
303
		return $this->addDefaultButton("edit","edit basic",$callback);
304
	}
305
306
	public function addEditDeleteButtons($callbackEdit=null,$callbackDelete=null){
307
		$this->addEditButton($callbackEdit);
308
		$index=$this->_instanceViewer->visiblePropertiesCount()-1;
309
		$this->insertDeleteButtonIn($index,$callbackDelete);
310
		return $this;
311
	}
312
313
	public function insertDeleteButtonIn($index,$callback=null){
314
		return $this->insertDefaultButtonIn($index,"remove","delete red basic",$callback);
315
	}
316
317
	public function insertEditButtonIn($index,$callback=null){
318
		return $this->insertDefaultButtonIn($index,"edit","edit basic",$callback);
319
	}
320
321
	public function addSearchInToolbar($position=Direction::RIGHT){
322
		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...
323
	}
324
325
	public function getSearchField(){
326
		if(isset($this->_searchField)===false){
327
			$this->_searchField=new HtmlInput("search-".$this->identifier,"search","","Search...");
328
			$this->_searchField->addIcon("search",Direction::RIGHT);
329
		}
330
		return $this->_searchField;
331
	}
332
333
	/**
334
	 * The callback function called after the insertion of each row when fromDatabaseObjects is called
335
	 * callback function takes the parameters $row : the row inserted and $object: the instance of model used
336
	 * @param callable $callback
337
	 * @return DataTable
338
	 */
339
	public function onNewRow($callback) {
340
		$this->content["table"]->onNewRow($callback);
341
		return $this;
342
	}
343
}