У меня проблема с отображением сетки типа «детали», показанной здесь:
http://demos.telerik.com/php-ui/grid/hierarchy
Чтобы это было как можно короче, я покажу пример набора данных в виде массива:
//$data['kendoList']['companies'] =
[0] => Array
(
[name] => MyCompany
[sub_domain] => myco
[company_logo_180] => myco_180.png
[company_logo_100] => myco_100.png
[company_logo_70] => myco_70.png
[status] => Active
[created] => 2012-08-27 08:42:15
[modified] => 2014-05-14 15:24:03
[client_id] => 1
[Logo] => /media/myco/logos/myco_180.png
[company_id] => 20
[InspCount] => 3017
[ItemCount] => 1864
[UserCount] => 2
[AdvCount] => 2
[ProjectCount] => 0
[client] => MyClient
)
//$data['kendoList']['plants'] =
[0] => Array
(
[company_id] => 20
[name] => MyPlant
[plant_prefix] => myplant
[image] => myplant-map.png
[mappdf] => myplant-mappdf.pdf
[icon_on] => myplant_on.png
[icon_off] => myplant_off.png
[status] => Active
[supports] => onshore
[measurements] => Feet
[created] => 2012-08-27 09:57:57
[modified] => 2012-12-14 13:44:53
[Logo] => /media/myplant/facilities/myplant-map.png
[plant_id] => 37
[InspCount] => 3018
[ItemCount] => 1865
)
Странная вещь в этом — поля InspCount и ItemCount будут различимы, так как их можно принудительно при построении этих данных иметь различное число и фактически правильно отображать в сетке. С другой стороны, поле Logo на заводах, которые должны быть дочерними для компании, будет иметь то же значение, что и поле Logo в компаниях. Здесь идет код для распечатки. Трое из нас, двое других, имеющих гораздо больше опыта работы с Кендо, чем я, царапают головы.
<style>
.k-grid-header th.k-header > .k-link {
text-decoration: none;
}
</style>
<?php
if(isset($data["kendoList"]["companies"])) {
$status = '<div class="tileThis #=status#">#:status#</div>';
$columnsSetCo = array(
array('width' => 80, 'filterable' => false,
'template' => '<img src="#=Logo#?'.time().'" alt="logo" style="max-height: 70px; max-width: 70px;" height="70" />'),
array('field' => 'name', 'title' => ucfirst($data['companies']["name_ui_s"]).' Name'),
array('field' => 'client', 'title' => 'Client', 'hidden'=>true),
array('field' => 'company_id', 'title' => 'company_id', 'hidden'=>true),
array('field' => 'UserCount', 'width' => $colWidths["InspCount"], 'title' => 'Users', 'filterable' => false),
array('field' => 'AdvCount', 'width' => $colWidths["AdvCount"], 'title' => 'Facilities', 'filterable' => false),
array('field' => 'ProjectCount', 'width' => $colWidths["ProjectCount"], 'title' => 'Projects', 'filterable' => false),
array('field' => 'ItemCount', 'width' => $colWidths["ItemCount"], 'title' => 'Items', 'filterable' => false),
array('field' => 'InspCount', 'width' => $colWidths["InspCount"], 'title' => 'Inspections', 'filterable' => false),
array('field' => 'status', 'width' => $colWidths["Status"], 'template' => "$status")
);
if($data["kendoList"]['canEdit']){
$edit = "<a href='".$this->Html->url(route_array('companies', 'edit', 'admin', array('#=company_id#')))."'><div class='tileThis edit'>Edit</div></a>";
$columnsSetCo[] = array('field' => '', 'width' => 70, 'template' => $edit);
}
$grid = $this->Kendo->prepareGrid($data["kendoList"]["companies"], $columnsSetCo, array(
'name' => 'companyGrid',
'default_column_settings' => array(
'attributes' => array('class' => 'center')
),
'data_source' => array(
'addFilterItem' => array('field' => 'status', 'operator' => 'eq', 'value' => 'Active'),
'addGroupItem' => array('field' => 'client', 'dir' => 'asc')
),
'grid_settings'=>array(
'detailTemplateId'=>'details'
),
));
echo $grid->render();
echo "<script id='details' type='text/x-kendo-template'>";
//Build the facility list
$status = '<div class="tileThis #=status#">#:status#</div>';
$columnsSetFac = array(
array('width' => 80, 'filterable' => false,
'template' => '<img src="#=Logo#" alt="logo" style="max-height: 70px; max-width: 70px;" height="70" />'),
array('field' => 'name', 'title' => ucfirst($data['plants']["name_ui_s"]).' Name'),
array('field' => 'company_id', 'title' => 'company_id', 'hidden'=>true),
array('field' => 'ItemCount', 'width' => $colWidths["ItemCount"], 'title' => 'Items', 'filterable' => false),
array('field' => 'InspCount', 'width' => $colWidths["InspCount"], 'title' => 'Inspections', 'filterable' => false),
array('field' => 'status', 'width' => $colWidths["Status"], 'template' => "$status")
);
if($data["kendoList"]['canEdit']){
$edit = "<a href='".$this->Html->url(route_array('plants', 'edit', 'admin', array('#:plant_id#')))."'><div class='tileThis edit'>Edit</div></a>";
$columnsSetFac[] = array('field' => '', 'width' => 70, 'template' => $edit);
}
$detailGrid = $this->Kendo->prepareGrid($data["kendoList"]["plants"], $columnsSetFac, array(
'name' => 'detailGrid#=company_id#',
'default_column_settings' => array(
'attributes' => array('class' => 'center')
),
'data_source' => array(
'addFilterItem' => array('field' => 'company_id', 'operator' => 'eq', 'value' => '#=company_id#')
),
'grid_settings'=>array(
'groupable'=>false
)
));
echo $detailGrid->renderInTemplate();
echo "</script>";
}
Сетка подготовки была построена другим сотрудником в качестве помощника сетки. У нас никогда не было проблем с этим, но для полноты:
<?php
/**
* Kendo Helper
*
* PHP version 5
*
* @category Helper
* @version 1.0
*/
class KendoHelper extends AppHelper
{
private $_utilities = null;
public function __construct($options = null) {
parent::__construct($options);
$this->_utilities = ClassRegistry::init('UtilitiesComponent');
}
/**
* Prepares a Kendo Grid with some common defaults and takes in data and columns from array
* It also knows if there is no title. It makes a title based on the field value.
* By default it sets this settings in the grid:
* * scrollable = false
* * sortable = true
* * groupable = true
* * resizable = true
* * filterable = array('extra' => false)
* * pageable = array('pageSize' => 25, 'pageSizes' => array('25', '50', '100'))
*
* @param array $dataArr Array of arrays. Array with row of data with the key being the field name.
* @param array $columnsArr Array of arrays. The array elements have key-value pair for method-param respectively.
* @param array $options To change the output of the function
* @return \Kendo\UI\Grid The prepared grid to use.
*/
public function prepareGrid(array $dataArr, array $columnsArr = array(), $options = array())
{
$options = array_merge(array(
'name' => 'grid',
'default_column_settings' => array(),
'data_source' => array(),
'extra_field_types' => array(),
'grid_settings' => array()
), $options);
$options['grid_settings'] = array_merge(array(
'scrollable' => false,
'sortable' => true,
'groupable' => true,
'resizable' => true,
'filterable' => array('extra' => false),
'pageable' => array('pageSize' => 25, 'pageSizes' => array('25', '50', '100'))
), $options['grid_settings']);
$options['name'] = empty($options['name'])
? 'grid-'.substr(str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 8) : $options['name'];
$grid = new \Kendo\UI\Grid($options['name']);
$columns = array();
$modelFields = array();
//# Setup extra fields
if(!empty($options['extra_field_types'])) {
foreach($options['extra_field_types'] as $field => $type) {
$modelField = new \Kendo\Data\DataSourceSchemaModelField($field);
$modelField->type($type);
$modelFields[] = $modelField;
}
}
//# Setup the Columns and Models
foreach($columnsArr as $col){
$gridCol = new \Kendo\UI\GridColumn();
//http://docs.telerik.com/kendo-ui/api/framework/model#methods-Model.define
//The available options are "string", "number", "boolean", "date". The default is "string"$type = 'string';
//get the type
if(isset($col['value_type'])) {
$type = $col['value_type'];
unset($col['value_type']);
} elseif (isset($col['field'])) {
$field = Set::extract($dataArr, '0.' . $col['field']);
$val_type = gettype($field);
switch($val_type) {
case 'boolean':
$type = 'boolean';
break;
case 'integer':
case 'double':
case 'float':
$type = 'number';
break;
case 'object':
$type = ($val_type instanceof DateTime) ? 'date' : 'string';
break;
default:
$type = 'string';
break;
}
//if there is not a title use the field name
if(!isset($col['title'])) {
$col['title'] = ucwords(str_replace('_', ' ', join(' ', preg_split('/(?=[A-Z])/', $col['field']))));
}
}
$col = array_merge($options['default_column_settings'], $col);
foreach($col as $method => $value) {
if(in_array($method, get_class_methods($gridCol))) {
$gridCol->{$method}($value);
}
}
if(isset($col['field'])) {
$modelField = new \Kendo\Data\DataSourceSchemaModelField($col['field']);
$modelField->type($type);
$modelFields[] = $modelField;
}
$columns[] = $gridCol;
}
//# Setup datasource
$dataSource = new \Kendo\Data\DataSource();
if(count($modelFields) > 0) {
$model = new \Kendo\Data\DataSourceSchemaModel();
call_user_func_array(array($model, 'addField'), $modelFields);
$schema = new \Kendo\Data\DataSourceSchema();
$schema->model($model);
$dataSource->schema($schema);
}
if (count($dataArr) > 0) {
$dataSource->data($dataArr);
}
if(!empty($options['data_source'])) {
foreach($options['data_source'] as $method => $value) {
if(in_array($method, get_class_methods($dataSource))) {
$dataSource->{$method}($value);
}
}
}
if(count($columns) > 0) {
call_user_func_array(array($grid, 'addColumn'), $columns);
}
$grid->dataSource($dataSource);
if(!empty($options['grid_settings']) && is_array($options['grid_settings'])) {
foreach($options['grid_settings'] as $method => $setting){
if(in_array($method, get_class_methods($grid))) {
$grid->{$method}($setting);
}
}
}
return $grid;
}
/**
* For $items_tree each node (array) can have MenuItem methods names as keys. Except for startContent and endContent.
* An extra key is items. It holds the sub items that are nested in that item.
* Example:
*
* [
* ['text' => 'HOME', 'url' => '/'],
* ['text' => 'ITEMS', 'items' => [
* ['text' => '<b>NEED APPROVAL</b>', 'url' => '/items/approve'],
* ['text' => '<b>DISABLED</b>', 'url' => '/items/?disabled'],
* new \Kendo\UI\MenuItem('text')
* ]]
* ]
*
* @param $name
* @param array $items_tree Tree (array) of menu items (arrays or MenuItems)
* @param array $options
* @return \Kendo\UI\Menu
*/
public function prepareMenu($name, array $items_tree, array $options = array())
{
$menu = new \Kendo\UI\Menu($name);
//configure menu
foreach($options as $method => $value) {
if(method_exists($menu, $method)) {
$menu->{$method}($value);
}
}
if(empty($items_tree)){
return $menu;
}
//Adding items, recursively if they are nested
$menu = $this->recursiveMenuItemInsertion($menu, $items_tree);
return $menu;
}
/**
* It uses the keys has_access to figure out if an item should be filtered out.
* If this key doesn't exist, the item is not filtered out.
* If the has_access exist, it's boolean value tells the function to filter it out (false) or not.
* @param array $items_tree
* @return array
*/
public function filterMenuItemsByAccess(array $items_tree)
{
//by reference so that we can unset it, plus it doesn't create a copy of the array
foreach($items_tree as $i => &$cur_item) {
//recur nested items
if(isset($cur_item['items']) && is_array($cur_item['items']) && !empty($cur_item['items'])){
$cur_item['items'] = $this->filterMenuItemsByAccess($cur_item['items']);
}
if(isset($cur_item['has_access'])) {
//Does it have access
$has_access = (bool)$cur_item['has_access'];
if(!$has_access) {
unset($items_tree[$i]);
} else {
unset($cur_item['has_access']);
}
}
}
return $items_tree;
}
/**
* This tries to process nested menu items (array) in a recursive form to add them into its parent,
* @param \Kendo\UI\Menu|\Kendo\UI\MenuItem $parent_item
* @param array $items_tree
* @return \Kendo\UI\MenuItem|\Kendo\UI\Menu|mixed Whatever $parent_item is
*/
public function recursiveMenuItemInsertion($parent_item, array $items_tree)
{
if(!($parent_item instanceof \Kendo\UI\MenuItem || $parent_item instanceof \Kendo\UI\Menu)){
return $parent_item;
}
foreach($items_tree as $cur_item) {
if($cur_item instanceof \Kendo\UI\MenuItem) {
$menuItem = $cur_item;
} elseif(is_array($cur_item)) {
$menuItem = new \Kendo\UI\MenuItem();
$methods = array_diff_key($cur_item, array('items' => true));
//configure item
foreach($methods as $method => $value) {
if(method_exists($menuItem, $method)) {
$menuItem->{$method}($value);
}
}
//Deal with nested items
if(isset($cur_item['items'])){
$menuItem = $this->recursiveMenuItemInsertion($menuItem, $cur_item['items']);
}
}
if(isset($menuItem)){
//add to parent, either menu or an item
/** @var \Kendo\UI\Menu|\Kendo\UI\MenuItem $parent_item */
$parent_item->addItem($menuItem);
}
}
return $parent_item;
}
}
Задача ещё не решена.
Других решений пока нет …