Completed
Push — master ( 0b3773...ca057c )
by Jean-Christophe
03:07
created

DataTable::addFieldButton()   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 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\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
use Ajax\semantic\html\elements\HtmlLabel;
18
19
/**
20
 * DataTable widget for displaying list of objects
21
 * @version 1.0
22
 * @author jc
23
 * @since 2.2
24
 *
25
 */
26
class DataTable extends Widget {
27
	use TableTrait;
28
	protected $_searchField;
29
	protected $_urls;
30
	protected $_pagination;
31
	protected $_hasCheckboxes;
32
	protected $_compileParts;
33
	protected $_hasDelete=false;
34
	protected $_hasEdit=false;
35
	protected $_visibleHover=false;
36
	protected $_hasCheckedMessage=false;
37
	protected $_targetSelector;
38
	protected $_checkedMessage;
39
40
	public function __construct($identifier,$model,$modelInstance=NULL) {
41
		parent::__construct($identifier, $model,$modelInstance);
42
		$this->_init(new InstanceViewer($identifier), "table", new HtmlTable($identifier, 0,0), false);
43
		$this->_urls=[];
44
	}
45
46
	public function run(JsUtils $js){
47
		if($this->_hasCheckboxes && isset($js)){
48
			$this->_runCheckboxes($js);
49
		}
50
		if($this->_visibleHover){
51
			$js->execOn("mouseover", "#".$this->identifier." tr", "$(event.target).closest('tr').find('.visibleover').css('visibility', 'visible');",["preventDefault"=>false,"stopPropagation"=>true]);
52
			$js->execOn("mouseout", "#".$this->identifier." tr", "$(event.target).closest('tr').find('.visibleover').css('visibility', 'hidden');",["preventDefault"=>false,"stopPropagation"=>true]);
53
		}
54
		if($this->_hasDelete)
55
			$this->_generateBehavior("delete", $js);
56
		if($this->_hasEdit)
57
			$this->_generateBehavior("edit", $js);
58
		return parent::run($js);
59
	}
60
61
	protected function _runCheckboxes(JsUtils $js){
62
		$checkedMessageCall="";
63
		if($this->_hasCheckedMessage){
64
			$msg=$this->getCheckedMessage();
65
			$checkedMessageFunction="function updateChecked(){var msg='".$msg[0]."',count=\$('#{$this->identifier} [name=\"selection[]\"]:checked').length,all=\$('#{$this->identifier} [name=\"selection[]\"]').length;
66
			if(count==1) msg='".$msg[1]."';
67
						else if(count>1) msg='".$msg["other"]."';
68
						\$('#checked-count-".$this->identifier."').contents().filter(function() {return this.nodeType == 3;}).each(function(){this.textContent = msg.replace('{count}',count);});
69
								\$('#toolbar-{$this->identifier} .visibleOnChecked').toggle(count>0);}\$('#toolbar-".$this->identifier." .visibleOnChecked').hide();";
70
			$checkedMessageCall="updateChecked();";
71
			$js->exec($checkedMessageFunction,true);
72
		}
73
		$js->execOn("change", "#".$this->identifier." [name='selection[]']", "
74
				var \$parentCheckbox=\$('#ck-main-ck-{$this->identifier}'),\$checkbox=\$('#{$this->identifier} [name=\"selection[]\"]'),allChecked=true,allUnchecked=true;
75
				\$checkbox.each(function() {if($(this).prop('checked')){allUnchecked = false;}else{allChecked = false;}});
76
				if(allChecked) {\$parentCheckbox.checkbox('set checked');}else if(allUnchecked){\$parentCheckbox.checkbox('set unchecked');}else{\$parentCheckbox.checkbox('set indeterminate');};".$checkedMessageCall);
77
	}
78
79
	protected function _generateBehavior($op,JsUtils $js){
80
		if(isset($this->_urls[$op]))
81
			$js->getOnClick("#".$this->identifier." .".$op, $this->_urls[$op],$this->getTargetSelector(),["preventDefault"=>false,"attr"=>"data-ajax"]);
82
	}
83
84
	/**
85
	 * {@inheritDoc}
86
	 * @see \Ajax\semantic\html\collections\table\TableTrait::getTable()
87
	 */
88
	protected function getTable() {
89
		return $this->content["table"];
90
	}
91
92
93
	public function compile(JsUtils $js=NULL,&$view=NULL){
94
		if(!$this->_generated){
95
			$this->_instanceViewer->setInstance($this->_model);
96
			$captions=$this->_instanceViewer->getCaptions();
97
98
			$table=$this->content["table"];
99
100
			if($this->_hasCheckboxes){
101
				$this->_generateMainCheckbox($captions);
102
			}
103
104
			$table->setRowCount(0, \sizeof($captions));
105
			$table->setHeaderValues($captions);
106
			if(isset($this->_compileParts))
107
				$table->setCompileParts($this->_compileParts);
108
109
			if(isset($this->_searchField) && isset($js)){
110
				if(isset($this->_urls["refresh"]))
111
					$this->_searchField->postOn("change", $this->_urls["refresh"],"{'s':$(this).val()}","#".$this->identifier." tbody",["preventDefault"=>false,"jqueryDone"=>"replaceWith"]);
112
			}
113
114
			$this->_generateContent($table);
115
116
			if($this->_hasCheckboxes && $table->hasPart("thead")){
117
					$table->getHeader()->getCell(0, 0)->addToProperty("class","no-sort");
118
			}
119
120
			if(isset($this->_pagination) && $this->_pagination->getVisible()){
121
				$this->_generatePagination($table);
122
			}
123
			if(isset($this->_toolbar)){
124
				$this->_setToolbarPosition($table, $captions);
125
			}
126
			$this->content=JArray::sortAssociative($this->content, [PositionInTable::BEFORETABLE,"table",PositionInTable::AFTERTABLE]);
127
			$this->_compileForm();
128
			$this->_generated=true;
129
		}
130
		return parent::compile($js,$view);
131
	}
132
133
	private function _generateMainCheckbox(&$captions){
134
		$ck=new HtmlCheckbox("main-ck-".$this->identifier,"");
135
		$checkedMessageCall="";
136
		if($this->_hasCheckedMessage)
137
			$checkedMessageCall="updateChecked();";
138
		$ck->setOnChecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',true);".$checkedMessageCall);
139
		$ck->setOnUnchecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',false);".$checkedMessageCall);
140
		\array_unshift($captions, $ck);
141
	}
142
143
	protected function _generateContent($table){
144
		$objects=$this->_modelInstance;
145
		if(isset($this->_pagination)){
146
			$objects=$this->_pagination->getObjects($this->_modelInstance);
147
		}
148
		InstanceViewer::setIndex(0);
149
		$table->fromDatabaseObjects($objects, function($instance) use($table){
150
			$this->_instanceViewer->setInstance($instance);
151
			InstanceViewer::$index++;
152
			$values= $this->_instanceViewer->getValues();
153
			if($this->_hasCheckboxes){
154
				$ck=new HtmlCheckbox("ck-".$this->identifier,"");
155
				$field=$ck->getField();
156
				$field->setProperty("value",$this->_instanceViewer->getIdentifier());
157
				$field->setProperty("name", "selection[]");
158
				\array_unshift($values, $ck);
159
			}
160
			$result=$table->newRow();
161
			$result->setIdentifier($this->identifier."-tr-".$this->_instanceViewer->getIdentifier());
162
			$result->setValues($values);
163
			return $result;
164
		});
165
	}
166
167
	private function _generatePagination($table){
168
		$footer=$table->getFooter();
169
		$footer->mergeCol();
170
		$menu=new HtmlPaginationMenu("pagination-".$this->identifier,$this->_pagination->getPagesNumbers());
171
		$menu->floatRight();
172
		$menu->setActiveItem($this->_pagination->getPage()-1);
173
		$footer->setValues($menu);
174
		if(isset($this->_urls["refresh"]))
175
			$menu->postOnClick($this->_urls["refresh"],"{'p':$(this).attr('data-page')}","#".$this->identifier." tbody",["preventDefault"=>false,"jqueryDone"=>"replaceWith"]);
176
	}
177
178
	protected function _getFieldName($index){
179
		return parent::_getFieldName($index)."[]";
180
	}
181
182
	protected function _getFieldCaption($index){
183
		return null;
184
	}
185
186
	protected function _setToolbarPosition($table,$captions=NULL){
187
		switch ($this->_toolbarPosition){
188
			case PositionInTable::BEFORETABLE:
189
			case PositionInTable::AFTERTABLE:
190
				if(isset($this->_compileParts)===false){
191
					$this->content[$this->_toolbarPosition]=$this->_toolbar;
192
				}
193
				break;
194
			case PositionInTable::HEADER:
195
			case PositionInTable::FOOTER:
196
			case PositionInTable::BODY:
197
				$this->addToolbarRow($this->_toolbarPosition,$table, $captions);
198
				break;
199
		}
200
	}
201
202
	/**
203
	 * Associates a $callback function after the compilation of the field at $index position
204
	 * The $callback function can take the following arguments : $field=>the compiled field, $instance : the active instance of the object, $index: the field position
205
	 * @param int $index postion of the compiled field
206
	 * @param callable $callback function called after the field compilation
207
	 * @return \Ajax\semantic\widgets\datatable\DataTable
208
	 */
209
	public function afterCompile($index,$callback){
210
		$this->_instanceViewer->afterCompile($index,$callback);
211
		return $this;
212
	}
213
214
	private function addToolbarRow($part,$table,$captions){
215
		$hasPart=$table->hasPart($part);
216
		if($hasPart){
217
			$row=$table->getPart($part)->addRow(\sizeof($captions));
218
		}else{
219
			$row=$table->getPart($part)->getRow(0);
220
		}
221
		$row->mergeCol();
222
		$row->setValues([$this->_toolbar]);
223
	}
224
225
	public function getHtmlComponent(){
226
		return $this->content["table"];
227
	}
228
229
	public function getUrls() {
230
		return $this->_urls;
231
	}
232
233
	/**
234
	 * Sets the associative array of urls for refreshing, updating or deleting
235
	 * @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
236
	 * @return \Ajax\semantic\widgets\datatable\DataTable
237
	 */
238
	public function setUrls($urls) {
239
		if(\is_array($urls)){
240
			$this->_urls["refresh"]=JArray::getValue($urls, "refresh",0);
241
			$this->_urls["edit"]=JArray::getValue($urls, "edit",1);
242
			$this->_urls["delete"]=JArray::getValue($urls, "delete",2);
243
		}else{
244
			$this->_urls=["refresh"=>$urls,"edit"=>$urls,"delete"=>$urls];
245
		}
246
		return $this;
247
	}
248
249
	public function paginate($items_per_page=10,$page=1){
250
		$this->_pagination=new Pagination($items_per_page,4,$page);
251
	}
252
253
	public function getHasCheckboxes() {
254
		return $this->_hasCheckboxes;
255
	}
256
257
	public function setHasCheckboxes($_hasCheckboxes) {
258
		$this->_hasCheckboxes=$_hasCheckboxes;
259
		return $this;
260
	}
261
262
	public function refresh($compileParts=["tbody"]){
263
		$this->_compileParts=$compileParts;
264
		return $this;
265
	}
266
	/**
267
	 * @param string $caption
268
	 * @param callable $callback
269
	 * @param boolean $visibleHover
270
	 * @return callable
271
	 */
272
	private function getFieldButtonCallable($caption,$visibleHover=true,$callback=null){
273
		return $this->getCallable("getFieldButton",[$caption,$visibleHover],$callback);
274
	}
275
276
	/**
277
	 * @param callable $thisCallback
278
	 * @param array $parameters
279
	 * @param callable $callback
280
	 * @return callable
281
	 */
282
	private function getCallable($thisCallback,$parameters,$callback=null){
283
		$result=function($instance) use($thisCallback,$parameters,$callback){
284
			$object=call_user_func_array(array($this,$thisCallback), $parameters);
285
			if(isset($callback)){
286
				if(\is_callable($callback)){
287
					$callback($object,$instance);
288
				}
289
			}
290
			if($object instanceof HtmlSemDoubleElement){
291
				$object->setProperty("data-ajax",$this->_instanceViewer->getIdentifier());
292
				if($object->propertyContains("class","visibleover")){
293
					$this->_visibleHover=true;
294
					$object->setProperty("style","visibility:hidden;");
295
				}
296
			}
297
			return $object;
298
		};
299
		return $result;
300
	}
301
302
	/**
303
	 * @param string $caption
304
	 * @return HtmlButton
305
	 */
306
	private function getFieldButton($caption,$visibleHover=true){
307
		$bt= new HtmlButton("",$caption);
308
		if($visibleHover)
309
			$this->_visibleOver($bt);
310
		return $bt;
311
	}
312
313
	/**
314
	 * Inserts a new Button for each row
315
	 * @param string $caption
316
	 * @param callable $callback
317
	 * @param boolean $visibleHover
318
	 * @return \Ajax\semantic\widgets\datatable\DataTable
319
	 */
320
	public function addFieldButton($caption,$visibleHover=true,$callback=null){
321
		$this->addField($this->getCallable("getFieldButton",[$caption,$visibleHover],$callback));
322
		return $this;
323
	}
324
325
	/**
326
	 * Inserts a new Button for each row at col $index
327
	 * @param int $index
328
	 * @param string $caption
329
	 * @param callable $callback
330
	 * @return \Ajax\semantic\widgets\datatable\DataTable
331
	 */
332
	public function insertFieldButton($index,$caption,$visibleHover=true,$callback=null){
333
		$this->insertField($index, $this->getFieldButtonCallable($caption,$visibleHover,$callback));
334
		return $this;
335
	}
336
337
	/**
338
	 * Inserts a new Button for each row in col at $index
339
	 * @param int $index
340
	 * @param string $caption
341
	 * @param callable $callback
342
	 * @return \Ajax\semantic\widgets\datatable\DataTable
343
	 */
344
	public function insertInFieldButton($index,$caption,$visibleHover=true,$callback=null){
345
		$this->insertInField($index, $this->getFieldButtonCallable($caption,$visibleHover,$callback));
346
		return $this;
347
	}
348
349
	private function addDefaultButton($icon,$class=null,$visibleHover=true,$callback=null){
350
		$this->addField($this->getCallable("getDefaultButton",[$icon,$class,$visibleHover],$callback));
351
		return $this;
352
	}
353
354
	private function insertDefaultButtonIn($index,$icon,$class=null,$visibleHover=true,$callback=null){
355
		$this->insertInField($index,$this->getCallable("getDefaultButton",[$icon,$class,$visibleHover],$callback));
356
		return $this;
357
	}
358
359
	private function getDefaultButton($icon,$class=null,$visibleHover=true){
360
		$bt=$this->getFieldButton("",$visibleHover);
361
		$bt->asIcon($icon);
362
		if(isset($class))
363
			$bt->addToProperty("class", $class);
364
		return $bt;
365
	}
366
367
	public function addDeleteButton($visibleHover=true,$generateBehavior=true,$callback=null){
368
		$this->_hasDelete=$generateBehavior;
369
		return $this->addDefaultButton("remove","delete red basic",$visibleHover,$callback);
370
	}
371
372
	public function addEditButton($visibleHover=true,$generateBehavior=true,$callback=null){
373
		$this->_hasEdit=$generateBehavior;
374
		return $this->addDefaultButton("edit","edit basic",$visibleHover,$callback);
375
	}
376
377
	public function addEditDeleteButtons($visibleHover=true,$generateBehavior=true,$callbackEdit=null,$callbackDelete=null){
378
		$this->addEditButton($visibleHover,$generateBehavior,$callbackEdit);
379
		$index=$this->_instanceViewer->visiblePropertiesCount()-1;
380
		$this->insertDeleteButtonIn($index,$visibleHover,$generateBehavior,$callbackDelete);
381
		return $this;
382
	}
383
384
	public function insertDeleteButtonIn($index,$visibleHover=true,$generateBehavior=true,$callback=null){
385
		$this->_hasDelete=$generateBehavior;
386
		return $this->insertDefaultButtonIn($index,"remove","delete red basic",$visibleHover,$callback);
387
	}
388
389
	public function insertEditButtonIn($index,$visibleHover=true,$generateBehavior=true,$callback=null){
390
		$this->_hasEdit=$generateBehavior;
391
		return $this->insertDefaultButtonIn($index,"edit","edit basic",$visibleHover,$callback);
392
	}
393
394
	public function addSearchInToolbar($position=Direction::RIGHT){
395
		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...
396
	}
397
398
	public function getSearchField(){
399
		if(isset($this->_searchField)===false){
400
			$this->_searchField=new HtmlInput("search-".$this->identifier,"search","","Search...");
401
			$this->_searchField->addIcon("search",Direction::RIGHT);
402
		}
403
		return $this->_searchField;
404
	}
405
406
	/**
407
	 * The callback function called after the insertion of each row when fromDatabaseObjects is called
408
	 * callback function takes the parameters $row : the row inserted and $object: the instance of model used
409
	 * @param callable $callback
410
	 * @return DataTable
411
	 */
412
	public function onNewRow($callback) {
413
		$this->content["table"]->onNewRow($callback);
414
		return $this;
415
	}
416
417
	public function asForm(){
418
		return $this->getForm();
419
	}
420
421
	/**
422
	 * Creates a submit button at $index position
423
	 * @param int $index
424
	 * @param string $cssStyle
425
	 * @param string $url
426
	 * @param string $responseElement
427
	 * @param array $attributes
428
	 * @return \Ajax\semantic\widgets\datatable\DataTable
429
	 */
430
	public function fieldAsSubmit($index,$cssStyle=NULL,$url=NULL,$responseElement=NULL,$attributes=NULL){
431
		return $this->_fieldAs(function($id,$name,$value,$caption) use ($url,$responseElement,$cssStyle,$index,$attributes){
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->_fieldAs(f..., $index, $attributes); (Ajax\semantic\widgets\datatable\DataTable) is incompatible with the return type of the parent method Ajax\common\Widget::fieldAsSubmit of type Ajax\semantic\widgets\base\FieldAsTrait.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
Unused Code introduced by
The parameter $caption is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
432
			$button=new HtmlButton($id,$value,$cssStyle);
433
			$button->postOnClick($url,"$(event.target).closest('tr').find(':input').serialize()",$responseElement,$attributes["ajax"]);
434
			if(!isset($attributes["visibleHover"]) || $attributes["visibleHover"])
435
				$this->_visibleOver($button);
436
			return $button;
437
		}, $index,$attributes);
438
	}
439
440
	protected function _visibleOver($element){
441
		$this->_visibleHover=true;
442
		return $element->addToProperty("class", "visibleover")->setProperty("style","visibility:hidden;");
443
	}
444
445
	protected function getTargetSelector() {
446
		$result=$this->_targetSelector;
447
		if(!isset($result))
448
			$result="#".$this->identifier;
449
		return $result;
450
	}
451
452
	/**
453
	 * Sets the response element selector for Edit and Delete request with ajax
454
	 * @param string $_targetSelector
455
	 * @return \Ajax\semantic\widgets\datatable\DataTable
456
	 */
457
	public function setTargetSelector($_targetSelector) {
458
		$this->_targetSelector=$_targetSelector;
459
		return $this;
460
	}
461
462
	protected function getCheckedMessage() {
463
		$result= $this->_checkedMessage;
464
		if(!isset($result)){
465
			$result=[0=>"none selected",1=>"one item selected","other"=>"{count} items selected"];
466
		}
467
		return $result;
468
	}
469
470
	/**
471
	 * Defines the message displayed when checkboxes are checked or unchecked
472
	 * with an associative array 0=>no selection,1=>one item selected, other=>{count} items selected
473
	 * @param array $_checkedMessage
474
	 * @return \Ajax\semantic\widgets\datatable\DataTable
475
	 */
476
	public function setCheckedMessage(array $_checkedMessage) {
477
		$this->_checkedMessage=$_checkedMessage;
478
		return $this;
479
	}
480
481
	/**
482
	 * @param array $checkedMessage
483
	 * @param callable $callback
484
	 */
485
	public function addCountCheckedInToolbar(array $checkedMessage=null,$callback=null){
486
		if(isset($checkedMessage))
487
			$this->_checkedMessage=$checkedMessage;
488
		$checkedMessage=$this->getCheckedMessage();
489
		$this->_hasCheckboxes=true;
490
		$this->_hasCheckedMessage=true;
491
		$element=new HtmlLabel("checked-count-".$this->identifier,$checkedMessage[0]);
492
		$this->addInToolbar($element,$callback);
493
	}
494
495
496
}