Completed
Push — master ( baf19c...6831db )
by Jean-Christophe
03:30
created

DataTable::getSearchField()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 0
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\widgets\base\InstanceViewer;
15
use Ajax\semantic\html\collections\table\traits\TableTrait;
16
use Ajax\semantic\html\elements\HtmlLabel;
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;
27
	protected $_searchField;
28
	protected $_urls;
29
	protected $_pagination;
30
	protected $_hasCheckboxes;
31
	protected $_compileParts;
32
	protected $_hasDelete=false;
33
	protected $_hasEdit=false;
34
	protected $_visibleHover=false;
35
	protected $_hasCheckedMessage=false;
36
	protected $_targetSelector;
37
	protected $_checkedMessage;
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($this->_hasDelete)
54
			$this->_generateBehavior("delete", $js);
55
		if($this->_hasEdit)
56
			$this->_generateBehavior("edit", $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
			$js->exec($checkedMessageFunction,true);
71
		}
72
		$js->execOn("change", "#".$this->identifier." [name='selection[]']", "
73
				var \$parentCheckbox=\$('#ck-main-ck-{$this->identifier}'),\$checkbox=\$('#{$this->identifier} [name=\"selection[]\"]'),allChecked=true,allUnchecked=true;
74
				\$checkbox.each(function() {if($(this).prop('checked')){allUnchecked = false;}else{allChecked = false;}});
75
				if(allChecked) {\$parentCheckbox.checkbox('set checked');}else if(allUnchecked){\$parentCheckbox.checkbox('set unchecked');}else{\$parentCheckbox.checkbox('set indeterminate');};".$checkedMessageCall);
76
	}
77
78
	protected function _generateBehavior($op,JsUtils $js){
79
		if(isset($this->_urls[$op]))
80
			$js->getOnClick("#".$this->identifier." .".$op, $this->_urls[$op],$this->getTargetSelector(),["preventDefault"=>false,"attr"=>"data-ajax"]);
81
	}
82
83
	/**
84
	 * {@inheritDoc}
85
	 * @see \Ajax\semantic\html\collections\table\TableTrait::getTable()
86
	 */
87
	protected function getTable() {
88
		return $this->content["table"];
89
	}
90
91
92
	public function compile(JsUtils $js=NULL,&$view=NULL){
93
		if(!$this->_generated){
94
			$this->_instanceViewer->setInstance($this->_model);
95
			$captions=$this->_instanceViewer->getCaptions();
96
97
			$table=$this->content["table"];
98
99
			if($this->_hasCheckboxes){
100
				$this->_generateMainCheckbox($captions);
101
			}
102
103
			$table->setRowCount(0, \sizeof($captions));
104
			$table->setHeaderValues($captions);
105
			if(isset($this->_compileParts))
106
				$table->setCompileParts($this->_compileParts);
107
108
			if(isset($this->_searchField) && isset($js)){
109
				if(isset($this->_urls["refresh"]))
110
					$this->_searchField->postOn("change", $this->_urls["refresh"],"{'s':$(this).val()}","#".$this->identifier." tbody",["preventDefault"=>false,"jqueryDone"=>"replaceWith"]);
111
			}
112
113
			$this->_generateContent($table);
114
115
			if($this->_hasCheckboxes && $table->hasPart("thead")){
116
					$table->getHeader()->getCell(0, 0)->addClass("no-sort");
117
			}
118
119
			if(isset($this->_pagination) && $this->_pagination->getVisible()){
120
				$this->_generatePagination($table);
121
			}
122
			if(isset($this->_toolbar)){
123
				$this->_setToolbarPosition($table, $captions);
124
			}
125
			$this->content=JArray::sortAssociative($this->content, [PositionInTable::BEFORETABLE,"table",PositionInTable::AFTERTABLE]);
126
			$this->_compileForm();
127
			$this->_generated=true;
128
		}
129
		return parent::compile($js,$view);
130
	}
131
132
	private function _generateMainCheckbox(&$captions){
133
		$ck=new HtmlCheckbox("main-ck-".$this->identifier,"");
134
		$checkedMessageCall="";
135
		if($this->_hasCheckedMessage)
136
			$checkedMessageCall="updateChecked();";
137
		$ck->setOnChecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',true);".$checkedMessageCall);
138
		$ck->setOnUnchecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',false);".$checkedMessageCall);
139
		\array_unshift($captions, $ck);
140
	}
141
142
	protected function _generateContent($table){
143
		$objects=$this->_modelInstance;
144
		if(isset($this->_pagination)){
145
			$objects=$this->_pagination->getObjects($this->_modelInstance);
146
		}
147
		InstanceViewer::setIndex(0);
148
		$table->fromDatabaseObjects($objects, function($instance) use($table){
149
			$this->_instanceViewer->setInstance($instance);
150
			InstanceViewer::$index++;
151
			$values= $this->_instanceViewer->getValues();
152
			if($this->_hasCheckboxes){
153
				$ck=new HtmlCheckbox("ck-".$this->identifier,"");
154
				$field=$ck->getField();
155
				$field->setProperty("value",$this->_instanceViewer->getIdentifier());
156
				$field->setProperty("name", "selection[]");
157
				\array_unshift($values, $ck);
158
			}
159
			$result=$table->newRow();
160
			$result->setIdentifier($this->identifier."-tr-".$this->_instanceViewer->getIdentifier());
161
			$result->setValues($values);
162
			return $result;
163
		});
164
	}
165
166
	private function _generatePagination($table){
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->setValues($menu);
173
		if(isset($this->_urls["refresh"]))
174
			$menu->postOnClick($this->_urls["refresh"],"{'p':$(this).attr('data-page')}","#".$this->identifier." tbody",["preventDefault"=>false,"jqueryDone"=>"replaceWith"]);
175
	}
176
177
	protected function _getFieldName($index){
178
		return parent::_getFieldName($index)."[]";
179
	}
180
181
	protected function _getFieldCaption($index){
182
		return null;
183
	}
184
185
	protected function _setToolbarPosition($table,$captions=NULL){
186
		switch ($this->_toolbarPosition){
187
			case PositionInTable::BEFORETABLE:
188
			case PositionInTable::AFTERTABLE:
189
				if(isset($this->_compileParts)===false){
190
					$this->content[$this->_toolbarPosition]=$this->_toolbar;
191
				}
192
				break;
193
			case PositionInTable::HEADER:
194
			case PositionInTable::FOOTER:
195
			case PositionInTable::BODY:
196
				$this->addToolbarRow($this->_toolbarPosition,$table, $captions);
197
				break;
198
		}
199
	}
200
201
	/**
202
	 * Associates a $callback function after the compilation of the field at $index position
203
	 * The $callback function can take the following arguments : $field=>the compiled field, $instance : the active instance of the object, $index: the field position
204
	 * @param int $index postion of the compiled field
205
	 * @param callable $callback function called after the field compilation
206
	 * @return \Ajax\semantic\widgets\datatable\DataTable
207
	 */
208
	public function afterCompile($index,$callback){
209
		$this->_instanceViewer->afterCompile($index,$callback);
210
		return $this;
211
	}
212
213
	private function addToolbarRow($part,$table,$captions){
214
		$hasPart=$table->hasPart($part);
215
		if($hasPart){
216
			$row=$table->getPart($part)->addRow(\sizeof($captions));
217
		}else{
218
			$row=$table->getPart($part)->getRow(0);
219
		}
220
		$row->mergeCol();
221
		$row->setValues([$this->_toolbar]);
222
	}
223
224
	public function getHtmlComponent(){
225
		return $this->content["table"];
226
	}
227
228
	public function getUrls() {
229
		return $this->_urls;
230
	}
231
232
	/**
233
	 * Sets the associative array of urls for refreshing, updating or deleting
234
	 * @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
235
	 * @return \Ajax\semantic\widgets\datatable\DataTable
236
	 */
237
	public function setUrls($urls) {
238
		if(\is_array($urls)){
239
			$this->_urls["refresh"]=JArray::getValue($urls, "refresh",0);
240
			$this->_urls["edit"]=JArray::getValue($urls, "edit",1);
241
			$this->_urls["delete"]=JArray::getValue($urls, "delete",2);
242
		}else{
243
			$this->_urls=["refresh"=>$urls,"edit"=>$urls,"delete"=>$urls];
244
		}
245
		return $this;
246
	}
247
248
	public function paginate($items_per_page=10,$page=1){
249
		$this->_pagination=new Pagination($items_per_page,4,$page);
250
	}
251
252
	public function getHasCheckboxes() {
253
		return $this->_hasCheckboxes;
254
	}
255
256
	public function setHasCheckboxes($_hasCheckboxes) {
257
		$this->_hasCheckboxes=$_hasCheckboxes;
258
		return $this;
259
	}
260
261
	public function refresh($compileParts=["tbody"]){
262
		$this->_compileParts=$compileParts;
263
		return $this;
264
	}
265
266
267
	public function addSearchInToolbar($position=Direction::RIGHT){
268
		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...
269
	}
270
271
	public function getSearchField(){
272
		if(isset($this->_searchField)===false){
273
			$this->_searchField=new HtmlInput("search-".$this->identifier,"search","","Search...");
274
			$this->_searchField->addIcon("search",Direction::RIGHT);
275
		}
276
		return $this->_searchField;
277
	}
278
279
	/**
280
	 * The callback function called after the insertion of each row when fromDatabaseObjects is called
281
	 * callback function takes the parameters $row : the row inserted and $object: the instance of model used
282
	 * @param callable $callback
283
	 * @return DataTable
284
	 */
285
	public function onNewRow($callback) {
286
		$this->content["table"]->onNewRow($callback);
287
		return $this;
288
	}
289
290
	public function asForm(){
291
		return $this->getForm();
292
	}
293
294
295
296
	protected function getTargetSelector() {
297
		$result=$this->_targetSelector;
298
		if(!isset($result))
299
			$result="#".$this->identifier;
300
		return $result;
301
	}
302
303
	/**
304
	 * Sets the response element selector for Edit and Delete request with ajax
305
	 * @param string $_targetSelector
306
	 * @return \Ajax\semantic\widgets\datatable\DataTable
307
	 */
308
	public function setTargetSelector($_targetSelector) {
309
		$this->_targetSelector=$_targetSelector;
310
		return $this;
311
	}
312
313
	protected function getCheckedMessage() {
314
		$result= $this->_checkedMessage;
315
		if(!isset($result)){
316
			$result=[0=>"none selected",1=>"one item selected","other"=>"{count} items selected"];
317
		}
318
		return $result;
319
	}
320
321
	/**
322
	 * Defines the message displayed when checkboxes are checked or unchecked
323
	 * with an associative array 0=>no selection,1=>one item selected, other=>{count} items selected
324
	 * @param array $_checkedMessage
325
	 * @return \Ajax\semantic\widgets\datatable\DataTable
326
	 */
327
	public function setCheckedMessage(array $_checkedMessage) {
328
		$this->_checkedMessage=$_checkedMessage;
329
		return $this;
330
	}
331
332
	/**
333
	 * @param array $checkedMessage
334
	 * @param callable $callback
335
	 */
336
	public function addCountCheckedInToolbar(array $checkedMessage=null,$callback=null){
337
		if(isset($checkedMessage))
338
			$this->_checkedMessage=$checkedMessage;
339
		$checkedMessage=$this->getCheckedMessage();
340
		$this->_hasCheckboxes=true;
341
		$this->_hasCheckedMessage=true;
342
		$element=new HtmlLabel("checked-count-".$this->identifier,$checkedMessage[0]);
343
		$this->addInToolbar($element,$callback);
344
	}
345
346
347
}