1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* ## TbExtendedGridView class file |
4
|
|
|
* |
5
|
|
|
* @author Antonio Ramirez <[email protected]> |
6
|
|
|
* @copyright Copyright © Clevertech 2012- |
7
|
|
|
* @license [New BSD License](http://www.opensource.org/licenses/bsd-license.php) |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
Yii::import('booster.widgets.TbGridView'); |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
*## TbExtendedGridView is an extended version of TbGridView. |
14
|
|
|
* |
15
|
|
|
* Features are: |
16
|
|
|
* - Display an extended summary of the records shown. The extended summary can be configured to any of the |
17
|
|
|
* {@link TbOperation} type of widgets. |
18
|
|
|
* - Automatic chart display (using TbHighCharts widget), where user can 'switch' between views. |
19
|
|
|
* - Selectable cells |
20
|
|
|
* - Sortable rows |
21
|
|
|
* |
22
|
|
|
* @property CActiveDataProvider $dataProvider the data provider for the view. |
23
|
|
|
* @property TbDataColumn[] $columns |
24
|
|
|
* |
25
|
|
|
* @package booster.widgets.grids |
26
|
|
|
*/ |
27
|
|
|
class TbExtendedGridView extends TbGridView { |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var bool $fixedHeader if set to true will keep the header fixed position |
31
|
|
|
*/ |
32
|
|
|
public $fixedHeader = false; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var integer $headerOffset, when $fixedHeader is set to true, headerOffset will position table header top position |
36
|
|
|
* at $headerOffset. If you are using bootstrap and has navigation top fixed, its height is 40px, so it is recommended |
37
|
|
|
* to use $headerOffset=40; |
38
|
|
|
*/ |
39
|
|
|
public $headerOffset = 0; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @var string the template to be used to control the layout of various sections in the view. |
43
|
|
|
* These tokens are recognized: {extendedSummary}, {summary}, {items} and {pager}. They will be replaced with the |
44
|
|
|
* extended summary, summary text, the items, and the pager. |
45
|
|
|
*/ |
46
|
|
|
public $template = "{summary}\n{items}\n{pager}\n{extendedSummary}"; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var array $extendedSummary displays an extended summary version. |
50
|
|
|
* There are different types of summary types, |
51
|
|
|
* please, see {@link TbSumOperation}, {@link TbSumOfTypeOperation},{@link TbPercentOfTypeGooglePieOperation} |
52
|
|
|
* {@link TbPercentOfTypeOperation} and {@link TbPercentOfTypeEasyPieOperation}. |
53
|
|
|
* |
54
|
|
|
* The following is an example, please review the different types of TbOperation classes to find out more about |
55
|
|
|
* its configuration parameters. |
56
|
|
|
* |
57
|
|
|
* <pre> |
58
|
|
|
* 'extendedSummary' => array( |
59
|
|
|
* 'title' => '', // the extended summary title |
60
|
|
|
* 'columns' => array( // the 'columns' that will be displayed at the extended summary |
61
|
|
|
* 'id' => array( // column name "id" |
62
|
|
|
* 'class' => 'TbSumOperation', // what is the type of TbOperation we are going to display |
63
|
|
|
* 'label' => 'Sum of Ids' // label is name of label of the resulted value (ie Sum of Ids:) |
64
|
|
|
* ), |
65
|
|
|
* 'results' => array( // column name "results" |
66
|
|
|
* 'class' => 'TbPercentOfTypeGooglePieOperation', // the type of TbOperation |
67
|
|
|
* 'label' => 'How Many Of Each? ', // the label of the operation |
68
|
|
|
* 'types' => array( // TbPercentOfTypeGooglePieOperation "types" attributes |
69
|
|
|
* '0' => array('label' => 'zeros'), // a value of "0" will be labelled "zeros" |
70
|
|
|
* '1' => array('label' => 'ones'), // a value of "1" will be labelled "ones" |
71
|
|
|
* '2' => array('label' => 'twos')) // a value of "2" will be labelled "twos" |
72
|
|
|
* ) |
73
|
|
|
* ) |
74
|
|
|
* ), |
75
|
|
|
* </pre> |
76
|
|
|
*/ |
77
|
|
|
public $extendedSummary = array(); |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @var string $extendedSummaryCssClass is the class name of the layer containing the extended summary |
81
|
|
|
*/ |
82
|
|
|
public $extendedSummaryCssClass = 'extended-summary'; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @var array $extendedSummaryOptions the HTML attributes of the layer containing the extended summary |
86
|
|
|
*/ |
87
|
|
|
public $extendedSummaryOptions = array(); |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* @var array $componentsAfterAjaxUpdate has scripts that will be executed after components have updated. |
91
|
|
|
* It is used internally to render scripts required for components to work correctly. You may use it for your own |
92
|
|
|
* scripts, just make sure it is of type array. |
93
|
|
|
*/ |
94
|
|
|
public $componentsAfterAjaxUpdate = array(); |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* @var array $componentsReadyScripts hold scripts that will be executed on document ready. |
98
|
|
|
* It is used internally to render scripts required for components to work correctly. You may use it for your own |
99
|
|
|
* scripts, just make sure it is of type array. |
100
|
|
|
*/ |
101
|
|
|
public $componentsReadyScripts = array(); |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* @var array $chartOptions if configured, the extended view will display a highcharts chart. |
105
|
|
|
*/ |
106
|
|
|
public $chartOptions = array(); |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* @var bool $sortableRows. If true the rows at the table will be sortable. |
110
|
|
|
*/ |
111
|
|
|
public $sortableRows = false; |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* @var string Database field name for row sorting |
115
|
|
|
*/ |
116
|
|
|
public $sortableAttribute = 'sort_order'; |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* @var boolean Save sort order by ajax defaults to false |
120
|
|
|
* @see bootstrap.action.TbSortableAction for an easy way to use with your controller |
121
|
|
|
*/ |
122
|
|
|
public $sortableAjaxSave = false; |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* @var string Name of the action to call and sort values |
126
|
|
|
* @see bootstrap.action.TbSortableAction for an easy way to use with your controller |
127
|
|
|
* |
128
|
|
|
* <pre> |
129
|
|
|
* 'sortableAction' => 'module/controller/sortable' | 'controller/sortable' |
130
|
|
|
* </pre> |
131
|
|
|
* |
132
|
|
|
* The widget will make use of the string to create the URL and then append $sortableAttribute |
133
|
|
|
* @see $sortableAttribute |
134
|
|
|
*/ |
135
|
|
|
public $sortableAction; |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* @var string a javascript function that will be invoked after a successful sorting is done. |
139
|
|
|
* The function signature is <code>function(id, position)</code> where 'id' refers to the ID of the model id key, |
140
|
|
|
* 'position' the new position in the list. |
141
|
|
|
*/ |
142
|
|
|
public $afterSortableUpdate; |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* @var bool whether to allow selecting of cells |
146
|
|
|
*/ |
147
|
|
|
public $selectableCells = false; |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* @var string the filter to use to allow selection. For example, if you set the "htmlOptions" property of a column to have a |
151
|
|
|
* "class" of "tobeselected", you could set this property as: "td.tobeselected" in order to allow selection to |
152
|
|
|
* those columns with that class only. |
153
|
|
|
*/ |
154
|
|
|
public $selectableCellsFilter = 'td'; |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* @var string a javascript function that will be invoked after a selection is done. |
158
|
|
|
* The function signature is <code>function(selected)</code> where 'selected' refers to the selected columns. |
159
|
|
|
*/ |
160
|
|
|
public $afterSelectableCells; |
161
|
|
|
/** |
162
|
|
|
* @var array the configuration options to display a TbBulkActions widget |
163
|
|
|
* @see TbBulkActions widget for its configuration |
164
|
|
|
*/ |
165
|
|
|
public $bulkActions = array(); |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* @var string the aligment of the bulk actions. It can be 'left' or 'right'. |
169
|
|
|
*/ |
170
|
|
|
public $bulkActionAlign = 'right'; |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* @var TbBulkActions component that will display the bulk actions to the grid |
174
|
|
|
*/ |
175
|
|
|
protected $bulk; |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* @var boolean $displayExtendedSummary a helper property that is set to true if we have to render the |
179
|
|
|
* extended summary |
180
|
|
|
*/ |
181
|
|
|
protected $displayExtendedSummary; |
182
|
|
|
/** |
183
|
|
|
* @var boolean $displayChart a helper property that is set to true if we have to render a chart. |
184
|
|
|
*/ |
185
|
|
|
protected $displayChart; |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* @var TbOperation[] $extendedSummaryTypes hold the current configured TbOperation that will process column values. |
189
|
|
|
*/ |
190
|
|
|
protected $extendedSummaryTypes = array(); |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* @var array $extendedSummaryOperations hold the supported operation types |
194
|
|
|
*/ |
195
|
|
|
protected $extendedSummaryOperations = array( |
196
|
|
|
'TbSumOperation', |
197
|
|
|
'TbCountOfTypeOperation', |
198
|
|
|
'TbPercentOfTypeOperation', |
199
|
|
|
'TbPercentOfTypeEasyPieOperation', |
200
|
|
|
'TbPercentOfTypeGooglePieOperation' |
201
|
|
|
); |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
*### .init() |
205
|
|
|
* |
206
|
|
|
* Widget initialization |
207
|
|
|
*/ |
208
|
|
|
public function init(){ |
209
|
|
|
|
210
|
|
|
if ($this->shouldEnableExtendedSummary()) |
211
|
|
|
$this->prepareDisplayingExtendedSummary(); |
212
|
|
|
|
213
|
|
|
if ($this->shouldEnableChart() && $this->hasData()) |
214
|
|
|
$this->enableChart(); |
215
|
|
|
|
216
|
|
|
if ($this->shouldEnableBulkActions()) |
217
|
|
|
$this->enableBulkActions(); |
218
|
|
|
|
219
|
|
|
$this->fillSelectionChangedProperty(); |
220
|
|
|
|
221
|
|
|
parent::init(); |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
*### .renderContent() |
226
|
|
|
* |
227
|
|
|
* Renders grid content |
228
|
|
|
*/ |
229
|
|
|
public function renderContent() |
230
|
|
|
{ |
231
|
|
|
parent::renderContent(); |
232
|
|
|
$this->registerCustomClientScript(); |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
*### .renderKeys() |
237
|
|
|
* |
238
|
|
|
* Renders the key values of the data in a hidden tag. |
239
|
|
|
*/ |
240
|
|
|
public function renderKeys() |
241
|
|
|
{ |
242
|
|
|
$data = $this->dataProvider->getData(); |
243
|
|
|
|
244
|
|
|
if (!$this->sortableRows || (isset($data[0]) && !isset($data[0]->attributes[(string)$this->sortableAttribute]))) { |
245
|
|
|
parent::renderKeys(); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
echo CHtml::openTag( |
249
|
|
|
'div', |
250
|
|
|
array( |
251
|
|
|
'class' => 'keys', |
252
|
|
|
'style' => 'display:none', |
253
|
|
|
'title' => Yii::app()->getRequest()->getUrl(), |
254
|
|
|
) |
255
|
|
|
); |
256
|
|
|
foreach ($data as $d) { |
257
|
|
|
echo CHtml::tag( |
258
|
|
|
'span', |
259
|
|
|
array('data-order' => $this->getAttribute($d, $this->sortableAttribute)), |
260
|
|
|
CHtml::encode($this->getPrimaryKey($d)) |
261
|
|
|
); |
262
|
|
|
} |
263
|
|
|
echo "</div>\n"; |
264
|
|
|
return true; |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
*### .getAttribute() |
269
|
|
|
* |
270
|
|
|
* Helper function to get an attribute from the data |
271
|
|
|
* |
272
|
|
|
* @param CActiveRecord $data |
273
|
|
|
* @param string $attribute the attribute to get |
274
|
|
|
* |
275
|
|
|
* @return mixed the attribute value null if none found |
276
|
|
|
*/ |
277
|
|
|
protected function getAttribute($data, $attribute) |
278
|
|
|
{ |
279
|
|
|
if ($this->dataProvider instanceof CActiveDataProvider && $data->hasAttribute($attribute)) { |
|
|
|
|
280
|
|
|
return $data->{$attribute}; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
if ($this->dataProvider instanceof CArrayDataProvider || $this->dataProvider instanceof CSqlDataProvider) { |
|
|
|
|
284
|
|
|
if (is_object($data) && isset($data->{$attribute})) { |
285
|
|
|
return $data->{$attribute}; |
286
|
|
|
} |
287
|
|
|
if (isset($data[$attribute])) { |
288
|
|
|
return $data[$attribute]; |
289
|
|
|
} |
290
|
|
|
} |
291
|
|
|
return null; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
*### .getPrimaryKey() |
296
|
|
|
* |
297
|
|
|
* Helper function to return the primary key of the $data |
298
|
|
|
* IMPORTANT: composite keys on CActiveDataProviders will return the keys joined by comma |
299
|
|
|
* |
300
|
|
|
* @param CActiveRecord $data |
301
|
|
|
* |
302
|
|
|
* @return null|string |
303
|
|
|
*/ |
304
|
|
|
protected function getPrimaryKey($data) |
305
|
|
|
{ |
306
|
|
|
if ($this->dataProvider instanceof CActiveDataProvider) { |
|
|
|
|
307
|
|
|
$key = $this->dataProvider->keyAttribute === null ? $data->getPrimaryKey() : $data->{$this->dataProvider->keyAttribute}; |
308
|
|
|
return is_array($key) ? implode(',', $key) : $key; |
309
|
|
|
} |
310
|
|
|
if (($this->dataProvider instanceof CArrayDataProvider || $this->dataProvider instanceof CSqlDataProvider) && !empty($this->dataProvider->keyField)) { |
|
|
|
|
311
|
|
|
return is_object($data) ? $data->{$this->dataProvider->keyField} |
312
|
|
|
: $data[$this->dataProvider->keyField]; |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
return null; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
*### .renderTableHeader() |
320
|
|
|
* |
321
|
|
|
* Renders grid header |
322
|
|
|
*/ |
323
|
|
|
public function renderTableHeader() { |
324
|
|
|
|
325
|
|
|
$this->renderChart(); |
326
|
|
|
parent::renderTableHeader(); |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
*### .renderTableFooter() |
331
|
|
|
* |
332
|
|
|
* Renders the table footer. |
333
|
|
|
*/ |
334
|
|
|
public function renderTableFooter() |
335
|
|
|
{ |
336
|
|
|
$hasFilter = $this->filter !== null && $this->filterPosition === self::FILTER_POS_FOOTER; |
337
|
|
|
|
338
|
|
|
$hasFooter = $this->getHasFooter(); |
339
|
|
|
if ($this->bulk !== null || $hasFilter || $hasFooter) { |
340
|
|
|
echo "<tfoot>\n"; |
341
|
|
|
if ($hasFooter) { |
342
|
|
|
echo "<tr>\n"; |
343
|
|
|
/** @var $column CDataColumn */ |
344
|
|
|
foreach ($this->columns as $column) { |
345
|
|
|
$column->renderFooterCell(); |
346
|
|
|
} |
347
|
|
|
echo "</tr>\n"; |
348
|
|
|
} |
349
|
|
|
if ($hasFilter) { |
350
|
|
|
$this->renderFilter(); |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
if ($this->bulk !== null) { |
354
|
|
|
$this->renderBulkActions(); |
355
|
|
|
} |
356
|
|
|
echo "</tfoot>\n"; |
357
|
|
|
} |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
*### .renderBulkActions() |
362
|
|
|
*/ |
363
|
|
|
public function renderBulkActions() { |
364
|
|
|
|
365
|
|
|
Booster::getBooster()->registerAssetJs('jquery.saveselection.gridview.js'); |
366
|
|
|
$this->componentsAfterAjaxUpdate[] = "$.fn.yiiGridView.afterUpdateGrid('".$this->id."');"; |
367
|
|
|
echo '<tr><td colspan="' . count($this->columns) . '">'; |
368
|
|
|
$this->bulk->renderButtons(); |
369
|
|
|
echo '</td></tr>'; |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
|
373
|
|
|
/** |
374
|
|
|
*### .renderChart() |
375
|
|
|
* |
376
|
|
|
* Renders chart |
377
|
|
|
* @throws CException |
378
|
|
|
*/ |
379
|
|
|
public function renderChart() { |
380
|
|
|
|
381
|
|
|
if (!$this->displayChart || $this->dataProvider->getItemCount() <= 0) { |
382
|
|
|
return; |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
if (!isset($this->chartOptions['data']['series'])) { |
386
|
|
|
throw new CException(Yii::t( |
387
|
|
|
'zii', |
388
|
|
|
'You need to set the "series" attribute in order to render a chart' |
389
|
|
|
)); |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
$configSeries = $this->chartOptions['data']['series']; |
393
|
|
|
if (!is_array($configSeries)) { |
394
|
|
|
throw new CException(Yii::t('zii', '"chartOptions.series" is expected to be an array.')); |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
|
398
|
|
|
if (!isset($this->chartOptions['config'])) { |
399
|
|
|
$this->chartOptions['config'] = array(); |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
// **************************************** |
403
|
|
|
// render switch buttons |
404
|
|
|
$buttons = Yii::createComponent( |
405
|
|
|
array( |
406
|
|
|
'class' => 'booster.widgets.TbButtonGroup', |
407
|
|
|
'toggle' => 'radio', |
408
|
|
|
'buttons' => array( |
409
|
|
|
array( |
410
|
|
|
'label' => Yii::t('zii', 'Grid'), |
411
|
|
|
'url' => '#', |
412
|
|
|
'htmlOptions' => array('class' => 'active ' . $this->getId() . '-grid-control grid') |
413
|
|
|
), |
414
|
|
|
array( |
415
|
|
|
'label' => Yii::t('zii', 'Chart'), |
416
|
|
|
'url' => '#', |
417
|
|
|
'htmlOptions' => array('class' => $this->getId() . '-grid-control chart') |
418
|
|
|
), |
419
|
|
|
), |
420
|
|
|
'htmlOptions' => array('style' => 'margin-bottom:5px') |
421
|
|
|
) |
422
|
|
|
); |
423
|
|
|
echo '<div>'; |
424
|
|
|
$buttons->init(); |
425
|
|
|
$buttons->run(); |
426
|
|
|
echo '</div>'; |
427
|
|
|
|
428
|
|
|
$chartId = preg_replace('[-\\ ?]', '_', 'exgvwChart' . $this->getId()); // cleaning out most possible characters invalid as javascript variable identifiers. |
429
|
|
|
|
430
|
|
|
$this->componentsReadyScripts[] = '$(document).on("click",".' . $this->getId() . '-grid-control", function() { |
431
|
|
|
$(this).parent().find("input[type=\"radio\"]").parent().toggleClass("active"); |
432
|
|
|
if ($(this).hasClass("grid") && $("#' . $this->getId() . ' #' . $chartId . '").is(":visible")) |
433
|
|
|
{ |
434
|
|
|
$("#' . $this->getId() . ' #' . $chartId . '").hide(); |
435
|
|
|
$("#' . $this->getId() . ' table.items").show(); |
436
|
|
|
} |
437
|
|
|
if ($(this).hasClass("chart") && $("#' . $this->getId() . ' table.items").is(":visible")) |
438
|
|
|
{ |
439
|
|
|
$("#' . $this->getId() . ' table.items").hide(); |
440
|
|
|
$("#' . $this->getId() . ' #' . $chartId . '").show(); |
441
|
|
|
} |
442
|
|
|
return false; |
443
|
|
|
});'; |
444
|
|
|
|
445
|
|
|
$this->componentsAfterAjaxUpdate[] = ' |
446
|
|
|
if($("label.grid.'.$this->getId().'-grid-control").hasClass("active")) { |
447
|
|
|
$("#' . $this->getId() . ' #' . $chartId . '").hide(); |
448
|
|
|
$("#' . $this->getId() . ' table.items").show(); |
449
|
|
|
} else { |
450
|
|
|
$("#' . $this->getId() . ' table.items").hide(); |
451
|
|
|
$("#' . $this->getId() . ' #' . $chartId . '").show(); |
452
|
|
|
} |
453
|
|
|
'; |
454
|
|
|
// end switch buttons |
455
|
|
|
// **************************************** |
456
|
|
|
|
457
|
|
|
// render Chart |
458
|
|
|
// chart options |
459
|
|
|
$data = $this->dataProvider->getData(); |
460
|
|
|
$count = count($data); |
461
|
|
|
$seriesData = array(); |
462
|
|
|
$cnt = 0; |
463
|
|
|
foreach ($configSeries as $set) { |
464
|
|
|
$seriesData[$cnt] = array('name' => isset($set['name']) ? $set['name'] : null, 'data' => array()); |
465
|
|
|
|
466
|
|
|
for ($row = 0; $row < $count; ++$row) { |
467
|
|
|
$column = $this->getColumnByName($set['attribute']); |
468
|
|
|
if (!is_null($column) && $column->value !== null) { |
469
|
|
|
$seriesData[$cnt]['data'][] = $this->evaluateExpression( |
470
|
|
|
$column->value, |
471
|
|
|
array('data' => $data[$row], 'row' => $row) |
472
|
|
|
); |
473
|
|
|
} else { |
474
|
|
|
$value = CHtml::value($data[$row], $set['attribute']); |
475
|
|
|
$seriesData[$cnt]['data'][] = is_numeric($value) ? (float)$value : $value; |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
} |
479
|
|
|
++$cnt; |
480
|
|
|
} |
481
|
|
|
|
482
|
|
|
$xAxisData = array(); |
483
|
|
|
|
484
|
|
|
$xAxisData[] = array('categories'=>array()); |
485
|
|
|
if(!empty($this->chartOptions['data']['xAxis'])){ |
486
|
|
|
$xAxis = $this->chartOptions['data']['xAxis']; |
487
|
|
|
$categories = $xAxis['categories']; |
488
|
|
|
if(is_array($categories)) { |
489
|
|
|
$xAxisData['categories'] = $categories; |
490
|
|
|
} else { // field name |
491
|
|
|
for ($row = 0; $row < $count; ++$row) { |
492
|
|
|
$column = $this->getColumnByName($categories); |
493
|
|
|
if (!is_null($column) && $column->value !== null) { |
494
|
|
|
$xAxisData['categories'][] = $this->evaluateExpression( |
495
|
|
|
$column->value, |
496
|
|
|
array('data' => $data[$row], 'row' => $row) |
497
|
|
|
); |
498
|
|
|
} else { |
499
|
|
|
$value = CHtml::value($data[$row], $categories); |
500
|
|
|
$xAxisData['categories'][] = $value; |
501
|
|
|
} |
502
|
|
|
} |
503
|
|
|
} |
504
|
|
|
} |
505
|
|
|
|
506
|
|
|
// **************************************** |
507
|
|
|
// render chart |
508
|
|
|
$options = CMap::mergeArray( |
509
|
|
|
$this->chartOptions['config'], |
510
|
|
|
array('series' => $seriesData, 'xAxis' => $xAxisData) |
511
|
|
|
); |
512
|
|
|
$this->chartOptions['htmlOptions'] = isset($this->chartOptions['htmlOptions']) |
513
|
|
|
? $this->chartOptions['htmlOptions'] : array(); |
514
|
|
|
|
515
|
|
|
// sorry but use a class to provide styles, we need this |
516
|
|
|
if(empty($this->chartOptions['htmlOptions']['style'])) |
517
|
|
|
$this->chartOptions['htmlOptions']['style'] = 'width: 100%; height: 100%;'; |
518
|
|
|
else |
519
|
|
|
$this->chartOptions['htmlOptions']['style'] = $this->chartOptions['htmlOptions']['style'].'; width: 100%; height: 100%;'; |
520
|
|
|
|
521
|
|
|
// build unique ID |
522
|
|
|
// important! |
523
|
|
|
echo '<div>'; |
524
|
|
|
if ($this->ajaxUpdate !== false) { |
525
|
|
|
if (isset($options['chart']) && is_array($options['chart'])) { |
526
|
|
|
$options['chart']['renderTo'] = $chartId; |
527
|
|
|
} else { |
528
|
|
|
$options['chart'] = array('renderTo' => $chartId); |
529
|
|
|
} |
530
|
|
|
$jsOptions = CJSON::encode($options); |
531
|
|
|
|
532
|
|
|
if (isset($this->chartOptions['htmlOptions']['data-config'])) { |
533
|
|
|
unset($this->chartOptions['htmlOptions']['data-config']); |
534
|
|
|
} |
535
|
|
|
|
536
|
|
|
echo "<div id='{$chartId}' " . CHtml::renderAttributes( |
537
|
|
|
$this->chartOptions['htmlOptions'] |
538
|
|
|
) . " data-config='{$jsOptions}'></div>"; |
539
|
|
|
|
540
|
|
|
/* fix for chart dimensions changing after ajax */ |
541
|
|
|
$this->componentsAfterAjaxUpdate[] = " |
542
|
|
|
$('#".$chartId."').width($('#".$this->id." table').width()); |
543
|
|
|
$('#".$chartId."').height($('#".$this->id." table').height() + 150); |
544
|
|
|
highchart{$chartId} = new Highcharts.Chart($('#{$chartId}').data('config')); |
545
|
|
|
"; |
546
|
|
|
} |
547
|
|
|
$configChart = array( |
548
|
|
|
'class' => 'booster.widgets.TbHighCharts', |
549
|
|
|
'id' => $chartId, |
550
|
|
|
'options' => $options, |
551
|
|
|
'htmlOptions' => $this->chartOptions['htmlOptions'] |
552
|
|
|
); |
553
|
|
|
$chart = Yii::createComponent($configChart); |
554
|
|
|
$chart->init(); |
555
|
|
|
$chart->run(); |
556
|
|
|
echo '</div>'; |
557
|
|
|
// end chart display |
558
|
|
|
// **************************************** |
559
|
|
|
|
560
|
|
|
// check if the chart should appear by default |
561
|
|
|
if(isset($this->chartOptions['defaultView']) && $this->chartOptions['defaultView'] === true) { |
562
|
|
|
$this->componentsReadyScripts[] = ' |
563
|
|
|
$(".' . $this->getId() . '-grid-control.grid").removeClass("active"); |
564
|
|
|
$(".' . $this->getId() . '-grid-control.chart").addClass("active"); |
565
|
|
|
$("#' . $this->getId() . ' table.items").hide(); |
566
|
|
|
$("#' . $this->getId() . ' #' . $chartId . '").show(); |
567
|
|
|
'; |
568
|
|
|
} else { |
569
|
|
|
$this->componentsReadyScripts[] = ' |
570
|
|
|
$(".' . $this->getId() . '-grid-control.grid").addClass("active"); |
571
|
|
|
$(".' . $this->getId() . '-grid-control.chart").removeClass("active"); |
572
|
|
|
$("#' . $this->getId() . ' table.items").show(); |
573
|
|
|
$("#' . $this->getId() . ' #' . $chartId . '").hide(); |
574
|
|
|
'; |
575
|
|
|
} |
576
|
|
|
} |
577
|
|
|
|
578
|
|
|
/** |
579
|
|
|
*### .renderTableRow() |
580
|
|
|
* |
581
|
|
|
* Renders a table body row. |
582
|
|
|
* |
583
|
|
|
* This method is a copy-paste from CGridView.renderTableRow(), |
584
|
|
|
* because we cannot override *just* the `renderDataCell` routine (it's polymorphic) |
585
|
|
|
* and Yii doesn't have a seam in place for us to do this. |
586
|
|
|
* @see https://github.com/yiisoft/yii/pull/3571 |
587
|
|
|
* |
588
|
|
|
* Only meaningful change in here is the replacement of the `$column->renderDataCell($row)` |
589
|
|
|
* with our own method. |
590
|
|
|
* |
591
|
|
|
* @param integer $row the row number (zero-based). |
592
|
|
|
* |
593
|
|
|
* @deprecated This method will be removed after Yii 1.1.17 release. |
594
|
|
|
*/ |
595
|
|
|
public function renderTableRow($row) |
596
|
|
|
{ |
597
|
|
|
$htmlOptions = array(); |
598
|
|
|
if ($this->rowHtmlOptionsExpression !== null) { |
599
|
|
|
$data = $this->dataProvider->data[$row]; |
600
|
|
|
$options = $this->evaluateExpression( |
601
|
|
|
$this->rowHtmlOptionsExpression, |
602
|
|
|
array('row' => $row, 'data' => $data) |
603
|
|
|
); |
604
|
|
|
if (is_array($options)) { |
605
|
|
|
$htmlOptions = $options; |
606
|
|
|
} |
607
|
|
|
} |
608
|
|
|
|
609
|
|
|
if ($this->rowCssClassExpression !== null) { |
610
|
|
|
$data = $this->dataProvider->data[$row]; |
611
|
|
|
$class = $this->evaluateExpression($this->rowCssClassExpression, array('row' => $row, 'data' => $data)); |
612
|
|
|
} elseif (is_array($this->rowCssClass) && ($n = count($this->rowCssClass)) > 0) { |
613
|
|
|
$class = $this->rowCssClass[$row % $n]; |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
if (!empty($class)) { |
617
|
|
|
if (isset($htmlOptions['class'])) { |
618
|
|
|
$htmlOptions['class'] .= ' ' . $class; |
619
|
|
|
} else { |
620
|
|
|
$htmlOptions['class'] = $class; |
621
|
|
|
} |
622
|
|
|
} |
623
|
|
|
|
624
|
|
|
echo CHtml::openTag('tr', $htmlOptions); |
625
|
|
|
foreach ($this->columns as $column) |
626
|
|
|
$this->renderDataCellProcessingSummariesIfNeeded($column, $row); |
627
|
|
|
echo CHtml::closeTag('tr'); |
628
|
|
|
} |
629
|
|
|
|
630
|
|
|
/** |
631
|
|
|
*### .renderExtendedSummary() |
632
|
|
|
* |
633
|
|
|
* Renders summary |
634
|
|
|
*/ |
635
|
|
|
public function renderExtendedSummary() |
636
|
|
|
{ |
637
|
|
|
if (!isset($this->extendedSummaryOptions['class'])) { |
638
|
|
|
$this->extendedSummaryOptions['class'] = $this->extendedSummaryCssClass; |
639
|
|
|
} else { |
640
|
|
|
$this->extendedSummaryOptions['class'] .= ' ' . $this->extendedSummaryCssClass; |
641
|
|
|
} |
642
|
|
|
|
643
|
|
|
echo '<div ' . CHtml::renderAttributes($this->extendedSummaryOptions) . '></div>'; |
644
|
|
|
} |
645
|
|
|
|
646
|
|
|
/** |
647
|
|
|
*### .renderExtendedSummaryContent() |
648
|
|
|
* |
649
|
|
|
* Renders summary content. Will be appended to |
650
|
|
|
*/ |
651
|
|
|
public function renderExtendedSummaryContent() |
652
|
|
|
{ |
653
|
|
|
if ($this->dataProvider->getItemCount() <= 0) |
654
|
|
|
return; |
655
|
|
|
|
656
|
|
|
if (empty($this->extendedSummaryTypes)) |
657
|
|
|
return; |
658
|
|
|
|
659
|
|
|
echo '<div id="' . $this->id . '-extended-summary" style="display:none">'; |
660
|
|
|
if (isset($this->extendedSummary['title'])) { |
661
|
|
|
echo '<h3>' . $this->extendedSummary['title'] . '</h3>'; |
662
|
|
|
} |
663
|
|
|
foreach ($this->extendedSummaryTypes as $summaryType) { |
664
|
|
|
/** @var $summaryType TbOperation */ |
665
|
|
|
$summaryType->run(); |
666
|
|
|
echo '<br/>'; |
667
|
|
|
} |
668
|
|
|
echo '</div>'; |
669
|
|
|
} |
670
|
|
|
|
671
|
|
|
/** |
672
|
|
|
*### .registerCustomClientScript() |
673
|
|
|
* |
674
|
|
|
* This script must be run at the end of content rendering not at the beginning as it is common with normal CGridViews |
675
|
|
|
*/ |
676
|
|
|
public function registerCustomClientScript() |
677
|
|
|
{ |
678
|
|
|
/** @var $cs CClientScript */ |
679
|
|
|
$cs = Yii::app()->getClientScript(); |
680
|
|
|
|
681
|
|
|
$fixedHeaderJs = ''; |
682
|
|
|
if ($this->fixedHeader) { |
683
|
|
|
Booster::getBooster()->registerAssetJs('jquery.stickytableheaders' . (!YII_DEBUG ? '.min' : '') . '.js'); |
684
|
|
|
$fixedHeaderJs = "$('#{$this->id} table.items').stickyTableHeaders({fixedOffset:{$this->headerOffset}});"; |
685
|
|
|
$this->componentsAfterAjaxUpdate[] = $fixedHeaderJs; |
686
|
|
|
} |
687
|
|
|
|
688
|
|
|
if ($this->sortableRows) { |
689
|
|
|
$afterSortableUpdate = ''; |
690
|
|
|
if ($this->afterSortableUpdate !== null) { |
691
|
|
|
if (!($this->afterSortableUpdate instanceof CJavaScriptExpression) && strpos( |
|
|
|
|
692
|
|
|
$this->afterSortableUpdate, |
693
|
|
|
'js:' |
694
|
|
|
) !== 0 |
695
|
|
|
) { |
696
|
|
|
$afterSortableUpdate = new CJavaScriptExpression($this->afterSortableUpdate); |
697
|
|
|
} else { |
698
|
|
|
$afterSortableUpdate = $this->afterSortableUpdate; |
699
|
|
|
} |
700
|
|
|
} |
701
|
|
|
|
702
|
|
|
$this->selectableRows = 1; |
703
|
|
|
$cs->registerCoreScript('jquery.ui'); |
704
|
|
|
Booster::getBooster()->registerAssetJs('jquery.sortable.gridview.js'); |
705
|
|
|
|
706
|
|
|
if ($this->sortableAjaxSave && $this->sortableAction !== null) { |
707
|
|
|
$sortableAction = Yii::app()->createUrl( |
708
|
|
|
$this->sortableAction, |
709
|
|
|
array('sortableAttribute' => $this->sortableAttribute) |
710
|
|
|
); |
711
|
|
|
} else { |
712
|
|
|
$sortableAction = ''; |
713
|
|
|
} |
714
|
|
|
|
715
|
|
|
$afterSortableUpdate = CJavaScript::encode($afterSortableUpdate); |
716
|
|
|
if (Yii::app()->request->enableCsrfValidation) |
717
|
|
|
{ |
718
|
|
|
$csrfTokenName = Yii::app()->request->csrfTokenName; |
719
|
|
|
$csrfToken = Yii::app()->request->csrfToken; |
720
|
|
|
$csrf = "{'$csrfTokenName':'$csrfToken' }"; |
721
|
|
|
} else |
722
|
|
|
$csrf = '{}'; |
723
|
|
|
|
724
|
|
|
$this->componentsReadyScripts[] = "$.fn.yiiGridView.sortable('{$this->id}', '{$sortableAction}', {$afterSortableUpdate}, $csrf);"; |
725
|
|
|
$this->componentsAfterAjaxUpdate[] = "$.fn.yiiGridView.sortable('{$this->id}', '{$sortableAction}', {$afterSortableUpdate}, $csrf);"; |
726
|
|
|
} |
727
|
|
|
|
728
|
|
|
if ($this->selectableCells) { |
729
|
|
|
$afterSelectableCells = ''; |
730
|
|
|
if ($this->afterSelectableCells !== null) { |
731
|
|
|
if (!($this->afterSelectableCells instanceof CJavaScriptExpression) && strpos($this->afterSelectableCells,'js:') !== 0) { |
|
|
|
|
732
|
|
|
$afterSelectableCells = new CJavaScriptExpression($this->afterSelectableCells); |
733
|
|
|
} else { |
734
|
|
|
$afterSelectableCells = $this->afterSelectableCells; |
735
|
|
|
} |
736
|
|
|
} |
737
|
|
|
$cs->registerCoreScript('jquery.ui'); |
738
|
|
|
Booster::getBooster()->registerAssetJs('jquery.selectable.gridview.js'); |
739
|
|
|
$afterSelectableCells = CJavaScript::encode($afterSelectableCells); |
740
|
|
|
$this->componentsReadyScripts[] = "$.fn.yiiGridView.selectable('{$this->id}','{$this->selectableCellsFilter}',{$afterSelectableCells});"; |
741
|
|
|
$this->componentsAfterAjaxUpdate[] = "$.fn.yiiGridView.selectable('{$this->id}','{$this->selectableCellsFilter}', {$afterSelectableCells});"; |
742
|
|
|
} |
743
|
|
|
|
744
|
|
|
$cs->registerScript( |
745
|
|
|
__CLASS__ . '#' . $this->id . 'Ex', |
746
|
|
|
' |
747
|
|
|
var $grid = $("#' . $this->id . '"); |
748
|
|
|
' . $fixedHeaderJs . ' |
749
|
|
|
if ($(".' . $this->extendedSummaryCssClass . '", $grid).length) |
750
|
|
|
{ |
751
|
|
|
$(".' . $this->extendedSummaryCssClass . '", $grid).html($("#' . $this->id . '-extended-summary", $grid).html()); |
752
|
|
|
} |
753
|
|
|
' . (count($this->componentsReadyScripts) ? implode(PHP_EOL, $this->componentsReadyScripts) : '') . ' |
754
|
|
|
$.ajaxPrefilter(function (options, originalOptions, jqXHR) { |
755
|
|
|
var qs = $.deparam.querystring(options.url); |
756
|
|
|
if (qs.hasOwnProperty("ajax") && qs.ajax == "' . $this->id . '") |
757
|
|
|
{ |
758
|
|
|
if (typeof (options.realsuccess) == "undefined" || options.realsuccess !== options.success) |
759
|
|
|
{ |
760
|
|
|
options.realsuccess = options.success; |
761
|
|
|
options.success = function(data) |
762
|
|
|
{ |
763
|
|
|
if (options.realsuccess) { |
764
|
|
|
options.realsuccess(data); |
765
|
|
|
var $data = $("<div>" + data + "</div>"); |
766
|
|
|
// we need to get the grid again... as it has been updated |
767
|
|
|
if ($(".' . $this->extendedSummaryCssClass . '", $("#' . $this->id . '"))) |
768
|
|
|
{ |
769
|
|
|
$(".' . $this->extendedSummaryCssClass . '", $("#' . $this->id . '")).html($("#' . $this->id . '-extended-summary", $data).html()); |
770
|
|
|
} |
771
|
|
|
' . (count($this->componentsAfterAjaxUpdate) ? implode( |
772
|
|
|
PHP_EOL, |
773
|
|
|
$this->componentsAfterAjaxUpdate |
774
|
|
|
) : '') . ' |
775
|
|
|
} |
776
|
|
|
} |
777
|
|
|
} |
778
|
|
|
} |
779
|
|
|
});' |
780
|
|
|
); |
781
|
|
|
} |
782
|
|
|
|
783
|
|
|
/** |
784
|
|
|
*### .parseColumnValue() |
785
|
|
|
* |
786
|
|
|
* @param CGridColumn $column |
787
|
|
|
* @param string $value Value of the $column rendered by $column->renderDataCell($row). |
788
|
|
|
*/ |
789
|
|
|
protected function processColumnValue($column, $value) |
790
|
|
|
{ |
791
|
|
|
if (!($column instanceof CDataColumn)) |
|
|
|
|
792
|
|
|
return; |
793
|
|
|
|
794
|
|
|
if (!array_key_exists($column->name, $this->extendedSummary['columns'])) |
795
|
|
|
return; |
796
|
|
|
|
797
|
|
|
$config = $this->extendedSummary['columns'][$column->name]; |
798
|
|
|
$config['column'] = $column; |
799
|
|
|
|
800
|
|
|
$this->getSummaryOperationInstance($column->name, $config) |
801
|
|
|
->processValue($value); |
802
|
|
|
} |
803
|
|
|
|
804
|
|
|
/** |
805
|
|
|
*### .getSummaryOperationInstance() |
806
|
|
|
* |
807
|
|
|
* Each type of 'extended' summary |
808
|
|
|
* |
809
|
|
|
* @param string $name the name of the column |
810
|
|
|
* @param array $config the configuration of the column at the extendedSummary |
811
|
|
|
* |
812
|
|
|
* @return mixed |
813
|
|
|
* @throws CException |
814
|
|
|
*/ |
815
|
|
|
protected function getSummaryOperationInstance($name, $config) |
816
|
|
|
{ |
817
|
|
|
if (!isset($config['class'])) { |
818
|
|
|
throw new CException(Yii::t( |
819
|
|
|
'zii', |
820
|
|
|
'Column summary configuration must be an array containing a "type" element.' |
821
|
|
|
)); |
822
|
|
|
} |
823
|
|
|
|
824
|
|
|
if (!in_array($config['class'], $this->extendedSummaryOperations)) { |
825
|
|
|
throw new CException(Yii::t( |
826
|
|
|
'zii', |
827
|
|
|
'"{operation}" is an unsupported class operation.', |
828
|
|
|
array('{operation}' => $config['class']) |
829
|
|
|
)); |
830
|
|
|
} |
831
|
|
|
|
832
|
|
|
// name of the column should be unique |
833
|
|
|
if (!isset($this->extendedSummaryTypes[$name])) { |
834
|
|
|
$this->extendedSummaryTypes[$name] = Yii::createComponent($config); |
835
|
|
|
$this->extendedSummaryTypes[$name]->init(); |
836
|
|
|
} |
837
|
|
|
return $this->extendedSummaryTypes[$name]; |
838
|
|
|
} |
839
|
|
|
|
840
|
|
|
/** |
841
|
|
|
*### .getColumnByName() |
842
|
|
|
* |
843
|
|
|
* Helper function to get a column by its name |
844
|
|
|
* |
845
|
|
|
* @param string $name |
846
|
|
|
* |
847
|
|
|
* @return TbDataColumn|null |
848
|
|
|
*/ |
849
|
|
|
protected function getColumnByName($name) |
850
|
|
|
{ |
851
|
|
|
foreach ($this->columns as $column) { |
852
|
|
|
if (strcmp($column->name, $name) === 0) { |
853
|
|
|
return $column; |
854
|
|
|
} |
855
|
|
|
} |
856
|
|
|
return null; |
857
|
|
|
} |
858
|
|
|
|
859
|
|
|
/** |
860
|
|
|
* @return bool |
861
|
|
|
*/ |
862
|
|
|
private function shouldEnableExtendedSummary() |
863
|
|
|
{ |
864
|
|
|
return preg_match( |
865
|
|
|
'/extendedsummary/i', |
866
|
|
|
$this->template |
867
|
|
|
) && !empty($this->extendedSummary) && isset($this->extendedSummary['columns']); |
868
|
|
|
} |
869
|
|
|
|
870
|
|
|
private function prepareDisplayingExtendedSummary() |
871
|
|
|
{ |
872
|
|
|
$this->template .= "\n{extendedSummaryContent}"; |
873
|
|
|
$this->displayExtendedSummary = true; |
874
|
|
|
} |
875
|
|
|
|
876
|
|
|
private function shouldEnableChart() |
877
|
|
|
{ |
878
|
|
|
return !empty($this->chartOptions) && (bool)@$this->chartOptions['data']; |
879
|
|
|
} |
880
|
|
|
|
881
|
|
|
/** |
882
|
|
|
* @return int |
883
|
|
|
*/ |
884
|
|
|
private function hasData() |
885
|
|
|
{ |
886
|
|
|
return $this->dataProvider->getItemCount(); |
887
|
|
|
} |
888
|
|
|
|
889
|
|
|
private function enableChart() |
890
|
|
|
{ |
891
|
|
|
$this->displayChart = true; |
892
|
|
|
} |
893
|
|
|
|
894
|
|
|
/** |
895
|
|
|
* @return bool |
896
|
|
|
*/ |
897
|
|
|
private function shouldEnableBulkActions() |
898
|
|
|
{ |
899
|
|
|
return $this->bulkActions !== array() && isset($this->bulkActions['actionButtons']); |
900
|
|
|
} |
901
|
|
|
|
902
|
|
|
private function enableBulkActions() |
903
|
|
|
{ |
904
|
|
|
if (!isset($this->bulkActions['class'])) { |
905
|
|
|
$this->bulkActions['class'] = 'booster.widgets.TbBulkActions'; |
906
|
|
|
} |
907
|
|
|
|
908
|
|
|
$this->bulk = Yii::createComponent($this->bulkActions, $this); |
909
|
|
|
$this->bulk->init(); |
910
|
|
|
} |
911
|
|
|
|
912
|
|
|
private function fillSelectionChangedProperty() |
913
|
|
|
{ |
914
|
|
|
$this->selectionChanged = $this->makeDefaultSelectionChangedJavascript(); |
915
|
|
|
} |
916
|
|
|
|
917
|
|
|
/** |
918
|
|
|
* @return string |
919
|
|
|
*/ |
920
|
|
|
private function makeDefaultSelectionChangedJavascript() |
921
|
|
|
{ |
922
|
|
|
return 'js:function(id) { |
923
|
|
|
$("#"+id+" input[type=checkbox]").change(); |
924
|
|
|
}'; |
925
|
|
|
} |
926
|
|
|
|
927
|
|
|
/** |
928
|
|
|
* This method will become `renderDataCell` after Yii 1.1.17 will be released |
929
|
|
|
* @see https://github.com/yiisoft/yii/pull/3571 |
930
|
|
|
* |
931
|
|
|
* @param CGridColumn $column |
932
|
|
|
* @param integer $row |
933
|
|
|
*/ |
934
|
|
|
private function renderDataCellProcessingSummariesIfNeeded($column, $row) |
935
|
|
|
{ |
936
|
|
|
$value = $this->getRenderedDataCellValue($column, $row); |
937
|
|
|
|
938
|
|
|
if ($this->isExtendedSummaryEnabled()) |
939
|
|
|
$this->processColumnValue($column, $value); |
940
|
|
|
|
941
|
|
|
echo $value; |
942
|
|
|
} |
943
|
|
|
|
944
|
|
|
/** |
945
|
|
|
* @param CGridColumn $column |
946
|
|
|
* @param integer $row |
947
|
|
|
* |
948
|
|
|
* @return string |
949
|
|
|
*/ |
950
|
|
|
private function getRenderedDataCellValue($column, $row) |
951
|
|
|
{ |
952
|
|
|
ob_start(); |
953
|
|
|
$column->renderDataCell($row); |
954
|
|
|
return ob_get_clean(); |
955
|
|
|
} |
956
|
|
|
|
957
|
|
|
/** |
958
|
|
|
* @return bool |
959
|
|
|
*/ |
960
|
|
|
private function isExtendedSummaryEnabled() |
961
|
|
|
{ |
962
|
|
|
return $this->displayExtendedSummary && !empty($this->extendedSummary['columns']); |
963
|
|
|
} |
964
|
|
|
|
965
|
|
|
} |
966
|
|
|
|
967
|
|
|
/** |
968
|
|
|
*## TbOperation class |
969
|
|
|
* |
970
|
|
|
* Abstract class where all types of operations extend from |
971
|
|
|
* |
972
|
|
|
* @package booster.widgets.grids.operations |
973
|
|
|
*/ |
974
|
|
|
abstract class TbOperation extends CWidget |
975
|
|
|
{ |
976
|
|
|
/** |
977
|
|
|
* @var string $template the template to display label and value of the operation at the summary |
978
|
|
|
*/ |
979
|
|
|
public $template = '{label}: {value}'; |
980
|
|
|
|
981
|
|
|
/** |
982
|
|
|
* @var int $value the resulted value of operation |
983
|
|
|
*/ |
984
|
|
|
public $value = 0; |
985
|
|
|
|
986
|
|
|
/** |
987
|
|
|
* @var string $label the label of the calculated value |
988
|
|
|
*/ |
989
|
|
|
public $label; |
990
|
|
|
|
991
|
|
|
/** |
992
|
|
|
* @var TbDataColumn $column |
993
|
|
|
*/ |
994
|
|
|
public $column; |
995
|
|
|
|
996
|
|
|
/** |
997
|
|
|
* Widget initialization |
998
|
|
|
* @throws CException |
999
|
|
|
*/ |
1000
|
|
|
public function init() |
1001
|
|
|
{ |
1002
|
|
|
if (null == $this->column) { |
1003
|
|
|
throw new CException(Yii::t( |
1004
|
|
|
'zii', |
1005
|
|
|
'"{attribute}" attribute must be defined', |
1006
|
|
|
array('{attribute}' => 'column') |
1007
|
|
|
)); |
1008
|
|
|
} |
1009
|
|
|
} |
1010
|
|
|
|
1011
|
|
|
/** |
1012
|
|
|
* Widget's run method |
1013
|
|
|
*/ |
1014
|
|
|
public function run() |
1015
|
|
|
{ |
1016
|
|
|
$this->displaySummary(); |
1017
|
|
|
} |
1018
|
|
|
|
1019
|
|
|
/** |
1020
|
|
|
* Process the row data value |
1021
|
|
|
* |
1022
|
|
|
* @param $value |
1023
|
|
|
* |
1024
|
|
|
* @return mixed |
1025
|
|
|
*/ |
1026
|
|
|
abstract public function processValue($value); |
1027
|
|
|
|
1028
|
|
|
/** |
1029
|
|
|
* Displays the resulting summary |
1030
|
|
|
* @return mixed |
1031
|
|
|
*/ |
1032
|
|
|
abstract public function displaySummary(); |
1033
|
|
|
|
1034
|
|
|
} |
1035
|
|
|
|
1036
|
|
|
/** |
1037
|
|
|
* TbSumOperation class |
1038
|
|
|
* |
1039
|
|
|
* Displays a total of specified column name. |
1040
|
|
|
* |
1041
|
|
|
* @package booster.widgets.grids.operations |
1042
|
|
|
*/ |
1043
|
|
|
class TbSumOperation extends TbOperation |
1044
|
|
|
{ |
1045
|
|
|
/** |
1046
|
|
|
* @var float $total the total sum |
1047
|
|
|
*/ |
1048
|
|
|
protected $total; |
1049
|
|
|
|
1050
|
|
|
/** |
1051
|
|
|
* @var array $supportedTypes the supported type of values |
1052
|
|
|
*/ |
1053
|
|
|
protected $supportedTypes = array('raw', 'text', 'ntext', 'number'); |
1054
|
|
|
|
1055
|
|
|
/** |
1056
|
|
|
* Widget's initialization method |
1057
|
|
|
* @throws CException |
1058
|
|
|
*/ |
1059
|
|
|
public function init() |
1060
|
|
|
{ |
1061
|
|
|
parent::init(); |
1062
|
|
|
|
1063
|
|
|
if (!in_array($this->column->type, $this->supportedTypes)) { |
1064
|
|
|
throw new CException(Yii::t( |
1065
|
|
|
'zii', |
1066
|
|
|
'Unsupported column type. Supported column types are: "{types}"', |
1067
|
|
|
array( |
1068
|
|
|
'{types}' => implode(', ', $this->supportedTypes) |
1069
|
|
|
) |
1070
|
|
|
)); |
1071
|
|
|
} |
1072
|
|
|
} |
1073
|
|
|
|
1074
|
|
|
/** |
1075
|
|
|
* Extracts the digital part of the calculated value. |
1076
|
|
|
* |
1077
|
|
|
* @param int $value |
1078
|
|
|
* |
1079
|
|
|
* @return bool |
1080
|
|
|
*/ |
1081
|
|
|
protected function extractNumber($value) |
1082
|
|
|
{ |
1083
|
|
|
preg_match_all('/([+-]?[0-9]+[,\.]?)+/', $value, $matches); |
1084
|
|
|
return !empty($matches[0]) && @$matches[0][0] ? $matches[0][0] : 0; |
1085
|
|
|
} |
1086
|
|
|
|
1087
|
|
|
/** |
1088
|
|
|
* Process the value to calculate |
1089
|
|
|
* |
1090
|
|
|
* @param $value |
1091
|
|
|
* |
1092
|
|
|
* @return mixed|void |
1093
|
|
|
*/ |
1094
|
|
|
public function processValue($value) |
1095
|
|
|
{ |
1096
|
|
|
// remove html tags as we cannot access renderDataCellContent from the column |
1097
|
|
|
$clean = strip_tags($value); |
1098
|
|
|
$this->total += ((float)$this->extractNumber($clean)); |
1099
|
|
|
} |
1100
|
|
|
|
1101
|
|
|
/** |
1102
|
|
|
* Displays the summary |
1103
|
|
|
* @return mixed|void |
1104
|
|
|
*/ |
1105
|
|
|
public function displaySummary() |
1106
|
|
|
{ |
1107
|
|
|
echo strtr( |
1108
|
|
|
$this->template, |
1109
|
|
|
array( |
1110
|
|
|
'{label}' => $this->label, |
1111
|
|
|
'{value}' => $this->total === null ? '' : Yii::app()->format->format($this->total, $this->column->type) |
1112
|
|
|
) |
1113
|
|
|
); |
1114
|
|
|
} |
1115
|
|
|
} |
1116
|
|
|
|
1117
|
|
|
/** |
1118
|
|
|
* TbCountOfTypeOperation class |
1119
|
|
|
* |
1120
|
|
|
* Renders a summary based on the count of specified types. For example, if a value has a type 'blue', this class will |
1121
|
|
|
* count the number of times the value 'blue' has on that column. |
1122
|
|
|
* |
1123
|
|
|
* @package booster.widgets.grids.operations |
1124
|
|
|
*/ |
1125
|
|
|
class TbCountOfTypeOperation extends TbOperation |
1126
|
|
|
{ |
1127
|
|
|
/** |
1128
|
|
|
* @var string $template |
1129
|
|
|
* @see parent class |
1130
|
|
|
*/ |
1131
|
|
|
public $template = '{label}: {types}'; |
1132
|
|
|
|
1133
|
|
|
/** |
1134
|
|
|
* @var string $typeTemplate holds the template of each calculated type |
1135
|
|
|
*/ |
1136
|
|
|
public $typeTemplate = '{label}({value})'; |
1137
|
|
|
|
1138
|
|
|
/** |
1139
|
|
|
* @var array $types hold the configuration of types to calculate. The configuration is set by an array which keys |
1140
|
|
|
* are the value types to count. You can set their 'label' independently. |
1141
|
|
|
* |
1142
|
|
|
* <pre> |
1143
|
|
|
* 'types' => array( |
1144
|
|
|
* '0' => array('label' => 'zeros'), |
1145
|
|
|
* '1' => array('label' => 'ones'), |
1146
|
|
|
* '2' => array('label' => 'twos') |
1147
|
|
|
* </pre> |
1148
|
|
|
*/ |
1149
|
|
|
public $types = array(); |
1150
|
|
|
|
1151
|
|
|
/** |
1152
|
|
|
* Widget's initialization |
1153
|
|
|
* @throws CException |
1154
|
|
|
*/ |
1155
|
|
|
public function init() |
1156
|
|
|
{ |
1157
|
|
|
if (empty($this->types)) { |
1158
|
|
|
throw new CException(Yii::t( |
1159
|
|
|
'zii', |
1160
|
|
|
'"{attribute}" attribute must be defined', |
1161
|
|
|
array('{attribute}' => 'types') |
1162
|
|
|
)); |
1163
|
|
|
} |
1164
|
|
|
foreach ($this->types as $type) { |
1165
|
|
|
if (!isset($type['label'])) { |
1166
|
|
|
throw new CException(Yii::t('zii', 'The "label" of a type must be defined.')); |
1167
|
|
|
} |
1168
|
|
|
} |
1169
|
|
|
parent::init(); |
1170
|
|
|
} |
1171
|
|
|
|
1172
|
|
|
/** |
1173
|
|
|
* (no phpDoc) |
1174
|
|
|
* @see TbOperation |
1175
|
|
|
* |
1176
|
|
|
* @param $value |
1177
|
|
|
* |
1178
|
|
|
* @return mixed|void |
1179
|
|
|
*/ |
1180
|
|
|
public function processValue($value) |
1181
|
|
|
{ |
1182
|
|
|
$clean = strip_tags($value); |
1183
|
|
|
|
1184
|
|
|
if (array_key_exists($clean, $this->types)) { |
1185
|
|
|
if (!isset($this->types[$clean]['value'])) { |
1186
|
|
|
$this->types[$clean]['value'] = 0; |
1187
|
|
|
} |
1188
|
|
|
$this->types[$clean]['value'] += 1; |
1189
|
|
|
} |
1190
|
|
|
} |
1191
|
|
|
|
1192
|
|
|
/** |
1193
|
|
|
* (no phpDoc) |
1194
|
|
|
* @see TbOperation |
1195
|
|
|
* @return mixed|void |
1196
|
|
|
*/ |
1197
|
|
|
public function displaySummary() |
1198
|
|
|
{ |
1199
|
|
|
$typesResults = array(); |
1200
|
|
|
foreach ($this->types as $type) { |
1201
|
|
|
if (!isset($type['value'])) { |
1202
|
|
|
$type['value'] = 0; |
1203
|
|
|
} |
1204
|
|
|
|
1205
|
|
|
$typesResults[] = strtr( |
1206
|
|
|
$this->typeTemplate, |
1207
|
|
|
array('{label}' => $type['label'], '{value}' => $type['value']) |
1208
|
|
|
); |
1209
|
|
|
} |
1210
|
|
|
echo strtr($this->template, array('{label}' => $this->label, '{types}' => implode(' ', $typesResults))); |
1211
|
|
|
} |
1212
|
|
|
} |
1213
|
|
|
|
1214
|
|
|
/** |
1215
|
|
|
*## TbPercentOfTypeOperation class |
1216
|
|
|
* |
1217
|
|
|
* Renders a summary based on the percent count of specified types. For example, if a value has a type 'blue', this class will |
1218
|
|
|
* count the percentage number of times the value 'blue' has on that column. |
1219
|
|
|
* |
1220
|
|
|
* @package booster.widgets.grids.operations |
1221
|
|
|
*/ |
1222
|
|
|
class TbPercentOfTypeOperation extends TbCountOfTypeOperation |
1223
|
|
|
{ |
1224
|
|
|
/** |
1225
|
|
|
* @var string $typeTemplate |
1226
|
|
|
* @see TbCountOfTypeOperation |
1227
|
|
|
*/ |
1228
|
|
|
public $typeTemplate = '{label}({value}%)'; |
1229
|
|
|
|
1230
|
|
|
/** |
1231
|
|
|
* @var integer $_total holds the total sum of the values. Required to get the percentage. |
1232
|
|
|
*/ |
1233
|
|
|
protected $_total; |
1234
|
|
|
|
1235
|
|
|
/** |
1236
|
|
|
* @return mixed|void |
1237
|
|
|
* @see TbOperation |
1238
|
|
|
*/ |
1239
|
|
|
public function displaySummary() |
1240
|
|
|
{ |
1241
|
|
|
$typesResults = array(); |
1242
|
|
|
|
1243
|
|
|
foreach ($this->types as $type) { |
1244
|
|
|
if (!isset($type['value'])) { |
1245
|
|
|
$type['value'] = 0; |
1246
|
|
|
} |
1247
|
|
|
|
1248
|
|
|
$type['value'] = $this->getTotal() ? number_format((float)($type['value'] / $this->getTotal()) * 100, 1) |
1249
|
|
|
: 0; |
1250
|
|
|
$typesResults[] = strtr( |
1251
|
|
|
$this->typeTemplate, |
1252
|
|
|
array('{label}' => $type['label'], '{value}' => $type['value']) |
1253
|
|
|
); |
1254
|
|
|
} |
1255
|
|
|
|
1256
|
|
|
echo strtr($this->template, array('{label}' => $this->label, '{types}' => implode(' ', $typesResults))); |
1257
|
|
|
} |
1258
|
|
|
|
1259
|
|
|
/** |
1260
|
|
|
* Returns the total of types |
1261
|
|
|
* @return int holds |
1262
|
|
|
*/ |
1263
|
|
|
protected function getTotal() |
1264
|
|
|
{ |
1265
|
|
|
if (null == $this->_total) { |
1266
|
|
|
$this->_total = 0; |
1267
|
|
|
foreach ($this->types as $type) { |
1268
|
|
|
if (isset($type['value'])) { |
1269
|
|
|
$this->_total += $type['value']; |
1270
|
|
|
} |
1271
|
|
|
} |
1272
|
|
|
} |
1273
|
|
|
return $this->_total; |
1274
|
|
|
} |
1275
|
|
|
} |
1276
|
|
|
|
1277
|
|
|
/** |
1278
|
|
|
*## TbPercentOfTypeGooglePieOperation class |
1279
|
|
|
* |
1280
|
|
|
* Displays a Google visualization pie chart based on the percentage count of type. |
1281
|
|
|
* |
1282
|
|
|
* @package booster.widgets.grids.operations |
1283
|
|
|
*/ |
1284
|
|
|
class TbPercentOfTypeGooglePieOperation extends TbPercentOfTypeOperation |
1285
|
|
|
{ |
1286
|
|
|
/** |
1287
|
|
|
* @var string $chartCssClass the class name of the layer holding the chart |
1288
|
|
|
*/ |
1289
|
|
|
public $chartCssClass = 'bootstrap-operation-google-pie-chart'; |
1290
|
|
|
|
1291
|
|
|
/** |
1292
|
|
|
* The options |
1293
|
|
|
* @var array $chartOptions |
1294
|
|
|
* @see https://google-developers.appspot.com/chart/interactive/docs/gallery/piechart |
1295
|
|
|
*/ |
1296
|
|
|
public $chartOptions = array( |
1297
|
|
|
'title' => 'Google Pie Chart' |
1298
|
|
|
); |
1299
|
|
|
|
1300
|
|
|
/** |
1301
|
|
|
* @var array $data the configuration data of the chart |
1302
|
|
|
*/ |
1303
|
|
|
protected $data = array(); |
1304
|
|
|
|
1305
|
|
|
/** |
1306
|
|
|
* @see TbOperation |
1307
|
|
|
* @return mixed|void |
1308
|
|
|
*/ |
1309
|
|
|
public function displaySummary() { |
1310
|
|
|
|
1311
|
|
|
$this->data[] = array('Label', 'Percent'); |
1312
|
|
|
|
1313
|
|
|
foreach ($this->types as $type) { |
1314
|
|
|
if (!isset($type['value'])) { |
1315
|
|
|
$type['value'] = 0; |
1316
|
|
|
} |
1317
|
|
|
|
1318
|
|
|
$this->data[] = $this->getTotal() ? array( |
1319
|
|
|
$type['label'], |
1320
|
|
|
(float)number_format(($type['value'] / $this->getTotal()) * 100, 1) |
1321
|
|
|
) : 0; |
1322
|
|
|
} |
1323
|
|
|
$data = CJavaScript::jsonEncode($this->data); |
1324
|
|
|
$options = CJavaScript::jsonEncode($this->chartOptions); |
1325
|
|
|
echo "<div id='{$this->id}' class='{$this->chartCssClass}' data-data='{$data}' data-options='{$options}'></div>"; |
1326
|
|
|
|
1327
|
|
|
$this->registerClientScript(); |
1328
|
|
|
} |
1329
|
|
|
|
1330
|
|
|
/** |
1331
|
|
|
* Registers required scripts |
1332
|
|
|
*/ |
1333
|
|
|
public function registerClientScript() |
1334
|
|
|
{ |
1335
|
|
|
$chart = Yii::createComponent( |
1336
|
|
|
array( |
1337
|
|
|
'class' => 'booster.widgets.TbGoogleVisualizationChart', |
1338
|
|
|
'visualization' => 'PieChart', |
1339
|
|
|
'containerId' => $this->getId(), |
1340
|
|
|
'data' => $this->data, |
1341
|
|
|
'options' => $this->chartOptions |
1342
|
|
|
) |
1343
|
|
|
); |
1344
|
|
|
$chart->init(); |
1345
|
|
|
$chart->run(); |
1346
|
|
|
|
1347
|
|
|
/** |
1348
|
|
|
* create custom chart update by using the global chart variable |
1349
|
|
|
* @see TbGoogleVisualizationChart |
1350
|
|
|
*/ |
1351
|
|
|
$this->column->grid->componentsAfterAjaxUpdate[__CLASS__] = |
1352
|
|
|
'var $el = $("#' . $this->getId() . '");var data = $el.data("data");var opts = $el.data("options"); |
1353
|
|
|
data = google.visualization.arrayToDataTable(data); |
1354
|
|
|
' . $chart->getId() . '=new google.visualization.PieChart(document.getElementById("' . $this->getId() . '")); |
1355
|
|
|
' . $chart->getId() . '.draw(data,opts);'; |
1356
|
|
|
} |
1357
|
|
|
|
1358
|
|
|
} |
1359
|
|
|
|
1360
|
|
|
/** |
1361
|
|
|
*## TbPercentOfTypeEasyPieOperation class |
1362
|
|
|
* |
1363
|
|
|
* Displays an chart based on jquery.easy.pie plugin |
1364
|
|
|
* |
1365
|
|
|
* @package booster.widgets.grids.operations |
1366
|
|
|
*/ |
1367
|
|
|
class TbPercentOfTypeEasyPieOperation extends TbPercentOfTypeOperation |
1368
|
|
|
{ |
1369
|
|
|
/** |
1370
|
|
|
* @var string $chartCssClass the class of the layer containing the class |
1371
|
|
|
*/ |
1372
|
|
|
public $chartCssClass = 'bootstrap-operation-easy-pie-chart'; |
1373
|
|
|
|
1374
|
|
|
/** |
1375
|
|
|
* @var string $template |
1376
|
|
|
* @see TbOperation |
1377
|
|
|
*/ |
1378
|
|
|
public $template = '<div style="clear:both">{label}: </div>{types}'; |
1379
|
|
|
|
1380
|
|
|
/** |
1381
|
|
|
* @var string $typeTemplate |
1382
|
|
|
* @see parent class |
1383
|
|
|
*/ |
1384
|
|
|
public $typeTemplate = '<div style="float:left;text-align:center;margin:2px"><div class="{class}" data-percent="{value}">{value}%</div><div>{label}</div></div>'; |
1385
|
|
|
|
1386
|
|
|
// easy-pie-chart plugin options |
1387
|
|
|
// @see https://github.com/rendro/easy-pie-chart#configuration-parameter |
1388
|
|
|
public $chartOptions = array( |
1389
|
|
|
'barColor' => '#ef1e25', |
1390
|
|
|
// The color of the curcular bar. You can pass either a css valid color string like rgb, |
1391
|
|
|
// rgba hex or string colors. But you can also pass a function that accepts the current |
1392
|
|
|
// percentage as a value to return a dynamically generated color. |
1393
|
|
|
'trackColor' => '#f2f2f2', |
1394
|
|
|
// The color of the track for the bar, false to disable rendering. |
1395
|
|
|
'scaleColor' => '#dfe0e0', |
1396
|
|
|
// The color of the scale lines, false to disable rendering. |
1397
|
|
|
'lineCap' => 'round', |
1398
|
|
|
// Defines how the ending of the bar line looks like. Possible values are: butt, round and square. |
1399
|
|
|
'lineWidth' => 5, |
1400
|
|
|
// Width of the bar line in px. |
1401
|
|
|
'size' => 80, |
1402
|
|
|
// Size of the pie chart in px. It will always be a square. |
1403
|
|
|
'animate' => false, |
1404
|
|
|
// Time in milliseconds for a eased animation of the bar growing, or false to deactivate. |
1405
|
|
|
'onStart' => 'js:$.noop', |
1406
|
|
|
// Callback function that is called at the start of any animation (only if animate is not false). |
1407
|
|
|
'onStop' => 'js:$.noop' |
1408
|
|
|
// Callback function that is called at the end of any animation (only if animate is not false). |
1409
|
|
|
); |
1410
|
|
|
|
1411
|
|
|
/** |
1412
|
|
|
* @see TbOperation |
1413
|
|
|
* @return mixed|void |
1414
|
|
|
*/ |
1415
|
|
|
public function displaySummary() |
1416
|
|
|
{ |
1417
|
|
|
$this->typeTemplate = strtr($this->typeTemplate, array('{class}' => $this->chartCssClass)); |
1418
|
|
|
|
1419
|
|
|
parent::displaySummary(); |
1420
|
|
|
$this->registerClientScripts(); |
1421
|
|
|
} |
1422
|
|
|
|
1423
|
|
|
/** |
1424
|
|
|
* Register required scripts |
1425
|
|
|
*/ |
1426
|
|
|
protected function registerClientScripts() |
1427
|
|
|
{ |
1428
|
|
|
$booster = Booster::getBooster(); |
1429
|
|
|
$booster->registerAssetCss('easy-pie-chart.css'); |
1430
|
|
|
$booster->registerAssetJs('jquery.easy.pie.chart.js'); |
1431
|
|
|
|
1432
|
|
|
$options = CJavaScript::encode($this->chartOptions); |
1433
|
|
|
Yii::app()->getClientScript()->registerScript( |
1434
|
|
|
__CLASS__ . '#percent-of-type-operation-simple-pie', |
1435
|
|
|
' |
1436
|
|
|
$("#' . $this->column->grid->id . ' .' . $this->column->grid->extendedSummaryCssClass . ' .' . $this->chartCssClass . '") |
1437
|
|
|
.easyPieChart(' . $options . '); |
1438
|
|
|
' |
1439
|
|
|
); |
1440
|
|
|
$this->column->grid->componentsReadyScripts[__CLASS__] = |
1441
|
|
|
$this->column->grid->componentsAfterAjaxUpdate[__CLASS__] = |
1442
|
|
|
'$("#' . $this->column->grid->id . ' .' . $this->column->grid->extendedSummaryCssClass . ' .' . $this->chartCssClass . '") |
1443
|
|
|
.easyPieChart(' . $options . ');'; |
1444
|
|
|
} |
1445
|
|
|
} |
1446
|
|
|
|
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.