Completed
Push — master ( 2bc7fe...701cf4 )
by Jean-Christophe
03:09
created

DataTable::afterCompile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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