├── examples ├── test.latte ├── sources │ ├── nette_source.php │ ├── doctrine_source.php │ └── array_source.php ├── basic.php ├── editable.php └── index.php ├── package.json ├── src └── Mesour │ ├── DataGrid │ ├── Column │ │ ├── Template │ │ │ └── Template.latte │ │ ├── core │ │ │ ├── IPrependedColumn.php │ │ │ ├── IExportable.php │ │ │ ├── IContainer.php │ │ │ ├── IOrdering.php │ │ │ ├── IFiltering.php │ │ │ ├── IColumn.php │ │ │ ├── IInlineEdit.php │ │ │ ├── BaseColumn.php │ │ │ ├── Ordering.php │ │ │ ├── Filtering.php │ │ │ └── InlineEdit.php │ │ ├── SubItem.php │ │ ├── Status │ │ │ ├── IStatusItem.php │ │ │ ├── StatusButton.php │ │ │ └── StatusDropDown.php │ │ ├── EmptyData.php │ │ ├── Selection.php │ │ ├── Text.php │ │ ├── Sortable.php │ │ ├── SubItemButton.php │ │ ├── Date.php │ │ ├── Number.php │ │ ├── Template.php │ │ ├── Image.php │ │ ├── Status.php │ │ └── Container.php │ ├── Extensions │ │ ├── SubItem │ │ │ ├── Items │ │ │ │ ├── Template.latte │ │ │ │ ├── CallbackItem.php │ │ │ │ ├── TemplateItem.php │ │ │ │ ├── ComponentItem.php │ │ │ │ ├── GridItem.php │ │ │ │ └── Item.php │ │ │ ├── ISubItem.php │ │ │ └── SubItemExtension.php │ │ ├── Editable │ │ │ ├── IEditable.php │ │ │ └── EditableExtension.php │ │ ├── SimpleFilter │ │ │ ├── ISimpleFilter.php │ │ │ └── SimpleFilterExtension.php │ │ ├── Ordering │ │ │ ├── IOrdering.php │ │ │ └── OrderingExtension.php │ │ ├── Filter │ │ │ ├── IFilter.php │ │ │ └── FilterExtension.php │ │ ├── Pager │ │ │ ├── IPager.php │ │ │ └── PagerExtension.php │ │ ├── Selection │ │ │ ├── ISelection.php │ │ │ ├── Link.php │ │ │ ├── Links.php │ │ │ └── SelectionExtension.php │ │ ├── IHasColumn.php │ │ ├── Sortable │ │ │ ├── ISortable.php │ │ │ └── SortableExtension.php │ │ ├── Export │ │ │ ├── IExport.php │ │ │ └── ExportExtension.php │ │ ├── IExtension.php │ │ └── Base.php │ ├── Exceptions.php │ ├── Sources │ │ ├── IGridSource.php │ │ ├── NetteDbGridSource.php │ │ ├── DoctrineGridSource.php │ │ └── ArrayGridSource.php │ ├── Renderer │ │ ├── IGridRenderer.php │ │ ├── GridRenderer.php │ │ └── GridListRenderer.php │ ├── TemplateFileTrait.php │ ├── TemplateFile.php │ ├── ExtendedGrid.php │ ├── SubItemGrid.php │ └── ExtensionStorage.php │ └── UI │ └── DataGrid.php ├── README.md ├── composer.json ├── license.md └── ruleset.xml /examples/test.latte: -------------------------------------------------------------------------------- 1 | {define test} 2 | test {$name} 3 | {/define} 4 | 5 | {define test2} 6 | test2 {$name} 7 | {/define} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "mesour-datagrid": "^0.1.0" 4 | }, 5 | "devDependencies": { 6 | "eonasdan-bootstrap-datetimepicker": "^4.17.42" 7 | } 8 | } -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/Template/Template.latte: -------------------------------------------------------------------------------- 1 | {if $_block !== FALSE} 2 | {includeblock $_template_path} 3 | {include #$_block} 4 | {else} 5 | {include $_template_path} 6 | {/if} -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/SubItem/Items/Template.latte: -------------------------------------------------------------------------------- 1 | {if $_block !== FALSE} 2 | {includeblock $_template_path} 3 | {include #$_block} 4 | {else} 5 | {include $_template_path} 6 | {/if} 7 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Exceptions.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class NoDataSourceException extends \Exception 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/core/IPrependedColumn.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | interface IPrependedColumn 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/core/IExportable.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | interface IExportable extends IColumn 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/core/IContainer.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IContainer extends Mesour\Components\ComponentModel\IComponent, IColumn 18 | { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Editable/IEditable.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IEditable extends Mesour\DataGrid\Extensions\IExtension 18 | { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Sources/IGridSource.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IGridSource extends Mesour\Filter\Sources\IFilterSource 18 | { 19 | 20 | public function fetchForExport(); 21 | 22 | public function getColumnNames(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/SimpleFilter/ISimpleFilter.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface ISimpleFilter extends Mesour\Filter\ISimpleFilter, Mesour\DataGrid\Extensions\IExtension 18 | { 19 | 20 | public function beforeCreate(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/core/IOrdering.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IOrdering extends Mesour\Components\ComponentModel\IComponent, IColumn 18 | { 19 | 20 | public function setOrdering($ordering = true); 21 | 22 | public function hasOrdering(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Ordering/IOrdering.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IOrdering extends Mesour\DataGrid\Extensions\IExtension 18 | { 19 | 20 | public function setDefaultOrder($key, $sorting = 'ASC'); 21 | 22 | public function enableMulti(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Filter/IFilter.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IFilter extends Mesour\Filter\IFilter, Mesour\DataGrid\Extensions\IExtension 18 | { 19 | 20 | public function setInline($inline = true); 21 | 22 | public function isInline(); 23 | 24 | public function beforeCreate(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Pager/IPager.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IPager extends Mesour\Pager\IPager, Mesour\DataGrid\Extensions\IExtension 18 | { 19 | 20 | public function beforeRender(); 21 | 22 | public function reset($hard = false); 23 | 24 | public function handleSetPage($page = null); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mesour DataGrid 2 | 3 | [![Latest Stable Version](https://img.shields.io/github/release/mesour/datagrid.svg)](https://github.com/mesour/DataGrid/releases "Latest Stable Version") 4 | 5 | Mesour DataGrid is DataGrid for PHP >= 5.5 with options like to inline edit, export to csv, create sub items, sort data using jQuery.ui.nestedSortable and much more. 6 | 7 | - [Documentation/Demo](http://grid.mesour.com/version3/) 8 | - [API](http://apis.mesour.com/api/DataGrid3.0.0/) 9 | - [Author](http://mesour.com) 10 | 11 | # Install 12 | 13 | - With [Composer](https://getcomposer.org) 14 | 15 | composer require mesour/datagrid 16 | 17 | - Or download source from [GitHub](https://github.com/mesour/DataGrid/releases) -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Selection/ISelection.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | interface ISelection extends Selection\ISelection, Mesour\DataGrid\Extensions\IHasColumn, Mesour\DataGrid\Extensions\IExtension 19 | { 20 | 21 | /** 22 | * @return Links 23 | */ 24 | public function getLinks(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/IHasColumn.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IHasColumn 18 | { 19 | 20 | /** 21 | * @return Mesour\DataGrid\Column\IColumn 22 | */ 23 | public function getSpecialColumn(); 24 | 25 | /** 26 | * @return string 27 | */ 28 | public function getSpecialColumnName(); 29 | 30 | /** 31 | * @return bool 32 | */ 33 | public function isGetSpecialColumnUsed(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/SubItem/ISubItem.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface ISubItem extends Mesour\DataGrid\Extensions\IExtension 18 | { 19 | 20 | public function getPageLimit(); 21 | 22 | public function getItems(); 23 | 24 | public function getOpened(); 25 | 26 | public function hasSubItems(); 27 | 28 | public function getItem($name); 29 | 30 | public function setPermission($resource, $privilege); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/core/IFiltering.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IFiltering extends Mesour\Components\ComponentModel\IComponent, IColumn 18 | { 19 | 20 | public function setFiltering($filtering = true); 21 | 22 | public function hasFiltering(); 23 | 24 | public function setInline($inline = true); 25 | 26 | public function isInline(); 27 | 28 | public function attachToFilter(Mesour\DataGrid\Extensions\Filter\IFilter $filter, $hasCheckers); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/SubItem.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class SubItem extends EmptyData 16 | { 17 | 18 | public function setText($text) 19 | { 20 | $this->text = $text; 21 | return $this; 22 | } 23 | 24 | public function getBodyAttributes($data, $need = true, $rawData = []) 25 | { 26 | return parent::mergeAttributes([], ['colspan' => $data]); 27 | } 28 | 29 | public function getBodyContent($data, $rawData) 30 | { 31 | $this->tryInvokeCallback([$rawData, $this, $this->text]); 32 | return $this->text; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Sortable/ISortable.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface ISortable extends Mesour\DataGrid\Extensions\IExtension, Mesour\DataGrid\Extensions\IHasColumn 18 | { 19 | 20 | /** 21 | * @param string $columnName 22 | * @return mixed 23 | */ 24 | public function setColumnName($columnName); 25 | 26 | public function setPermission($resource, $privilege); 27 | 28 | public function isGetSpecialColumnUsed(); 29 | 30 | public function handleSortData($data, $item); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/core/IColumn.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | interface IColumn extends Render\IColumn, Mesour\Components\Control\IControl 19 | { 20 | 21 | /** 22 | * @param Mesour\DataGrid\Extensions\Filter\IFilter $filter 23 | * @internal 24 | */ 25 | public function setFilterReset(Mesour\DataGrid\Extensions\Filter\IFilter $filter); 26 | 27 | public function setDisabled($disabled = true); 28 | 29 | public function isDisabled(); 30 | 31 | public function validate(array $rowData, $data = []); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/Status/IStatusItem.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IStatusItem extends Mesour\Components\Control\IAttributesControl 18 | { 19 | 20 | public function isActive($columnName, $data); 21 | 22 | public function setStatus($status, $statusName, $selectionTitle = null); 23 | 24 | /** 25 | * @return array|null [$this->status => $this->statusName] 26 | */ 27 | public function getStatusOptions(); 28 | 29 | public function getStatus(); 30 | 31 | public function setPermission($resource = null, $privilege = null); 32 | 33 | public function getStatusName(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Renderer/IGridRenderer.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IGridRenderer extends Mesour\Components\Utils\IString 18 | { 19 | 20 | public function getComponent($type); 21 | 22 | public function setComponent($type, $component); 23 | 24 | /** 25 | * @return Mesour\Components\Utils\Html 26 | */ 27 | public function getWrapper(); 28 | 29 | public function renderGrid(); 30 | 31 | public function renderPager(); 32 | 33 | public function renderEditable(); 34 | 35 | public function renderSelection(); 36 | 37 | public function renderFilter(); 38 | 39 | public function renderExport(); 40 | 41 | public function render(); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/SubItem/Items/CallbackItem.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class CallbackItem extends Item 18 | { 19 | 20 | public function __construct(Mesour\DataGrid\Extensions\SubItem\ISubItem $parent, $name, $description = null) 21 | { 22 | parent::__construct($parent, $name, $description); 23 | } 24 | 25 | public function render($key = null, $rowData = null, $rawData = null) 26 | { 27 | if (is_null($key) || is_null($rowData)) { 28 | return ''; 29 | } 30 | return parent::invoke([$rawData], null, null); 31 | } 32 | 33 | public function reset() 34 | { 35 | 36 | } 37 | 38 | public function invoke(array $args = [], $name, $key) 39 | { 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Export/IExport.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IExport extends Mesour\DataGrid\Extensions\IExtension 18 | { 19 | 20 | public function setFileName($fileName); 21 | 22 | /** 23 | * @param string $delimiter 24 | * @return mixed 25 | */ 26 | public function setDelimiter($delimiter = ','); 27 | 28 | public function setCacheDir($dir); 29 | 30 | public function setColumns(array $columns = []); 31 | 32 | /** 33 | * @return Mesour\UI\Button|Mesour\UI\DropDown 34 | */ 35 | public function getExportButton(); 36 | 37 | public function hasExport(Mesour\Components\ComponentModel\IContainer $column); 38 | 39 | public function handleExport($type = 'all', array $selectedIds = []); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/SubItem/Items/TemplateItem.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class TemplateItem extends Item 18 | { 19 | 20 | use Mesour\Template\TemplateTrait; 21 | 22 | public function __construct(Mesour\DataGrid\Extensions\SubItem\ISubItem $parent, $name, $description = null) 23 | { 24 | parent::__construct($parent, $name, $description); 25 | } 26 | 27 | public function render() 28 | { 29 | return $this->getTemplateFile(); 30 | } 31 | 32 | public function reset() 33 | { 34 | 35 | } 36 | 37 | public function invoke(array $args = [], $name, $key) 38 | { 39 | $arguments = [$this->render()]; 40 | $arguments = array_merge($arguments, $args); 41 | return parent::invoke($arguments, $name, $key); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/EmptyData.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class EmptyData extends BaseColumn 18 | { 19 | 20 | protected $text; 21 | 22 | public function setText($text) 23 | { 24 | $this->text = $this->getTranslator()->translate($text); 25 | return $this; 26 | } 27 | 28 | public function getHeaderAttributes() 29 | { 30 | return []; 31 | } 32 | 33 | public function getHeaderContent() 34 | { 35 | return null; 36 | } 37 | 38 | public function getBodyAttributes($data, $need = true, $rawData = []) 39 | { 40 | return ['colspan' => $data]; 41 | } 42 | 43 | public function getBodyContent($data, $rawData) 44 | { 45 | $text = Mesour\Components\Utils\Html::el('p', ['class' => 'empty-data']); 46 | $text->setText($this->text); 47 | return $text; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Sources/NetteDbGridSource.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class NetteDbGridSource extends Mesour\Filter\Sources\NetteDbFilterSource implements IGridSource 19 | { 20 | 21 | private $columnNames = []; 22 | 23 | public function fetchForExport() 24 | { 25 | $selection = $this->getSelection(false); 26 | $this->lastFetchAllResult = []; 27 | $out = []; 28 | foreach ($selection->fetchAll() as $row) { 29 | /** @var Nette\Database\Table\ActiveRow $row */ 30 | $this->lastFetchAllResult[] = $row; 31 | $out[] = $this->makeArrayHash($row->toArray()); 32 | } 33 | return $out; 34 | } 35 | 36 | public function getColumnNames() 37 | { 38 | if (!count($this->columnNames)) { 39 | $this->columnNames = array_keys($this->getTableColumns($this->getTableName())); 40 | } 41 | return $this->columnNames; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/IExtension.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IExtension extends Mesour\Components\Control\IControl 18 | { 19 | 20 | /** 21 | * @return bool 22 | */ 23 | public function isDisabled(); 24 | 25 | public function setDisabled($disabled = true); 26 | 27 | /** 28 | * @param IExtension $extension 29 | * @param null $name 30 | * @return mixed 31 | */ 32 | public function createInstance(IExtension $extension, $name = null); 33 | 34 | public function gridCreate($data = []); 35 | 36 | public function afterGetCount($count); 37 | 38 | public function beforeFetchData($data = []); 39 | 40 | public function afterFetchData($currentData, $data = [], $rawData = []); 41 | 42 | public function attachToRenderer(Mesour\DataGrid\Renderer\IGridRenderer $renderer, $data = [], $rawData = []); 43 | 44 | public function reset($hard = false); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Sources/DoctrineGridSource.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class DoctrineGridSource extends Mesour\Filter\Sources\DoctrineFilterSource implements IGridSource 20 | { 21 | 22 | private $columnNames = []; 23 | 24 | public function fetchForExport() 25 | { 26 | try { 27 | $this->lastFetchAllResult = $this->cloneQueryBuilder() 28 | ->setMaxResults(null) 29 | ->setFirstResult(null) 30 | ->getQuery() 31 | ->getResult(); 32 | 33 | return $this->fixResult( 34 | $this->getEntityArrayAsArrays($this->lastFetchAllResult) 35 | ); 36 | } catch (Doctrine\ORM\NoResultException $e) { 37 | return []; 38 | } 39 | } 40 | 41 | public function getColumnNames() 42 | { 43 | if (!count($this->columnNames)) { 44 | $this->columnNames = array_keys($this->getTableColumns($this->getTableName())); 45 | } 46 | return $this->columnNames; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /examples/sources/nette_source.php: -------------------------------------------------------------------------------- 1 | addPanel(new \Nette\Bridges\DatabaseTracy\ConnectionPanel($connection)); 13 | 14 | $selection = $context->table('users') 15 | ->select('users.*') 16 | ->select('group.name group_name'); 17 | 18 | $source = new \Mesour\DataGrid\Sources\NetteDbGridSource( 19 | 'users', 20 | 'id', 21 | $selection, 22 | $context, 23 | [ 24 | 'group_name' => 'group.name', 25 | 'group' => 'group.name', 26 | 'wallet' => 'wallet.amount', 27 | 'companies' => ':user_companies.company.name', 28 | 'addresses' => ':user_addresses.city', 29 | 'id' => 'users.id', 30 | 'amount' => 'users.amount', 31 | 'name' => 'users.name', 32 | 'wallet_amount' => 'wallet.amount', 33 | 'company_name' => ':user_companies.company.name', 34 | 'address_city' => ':addresses.city', 35 | ] 36 | ); 37 | 38 | return $source; -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/Selection.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class Selection extends BaseColumn implements IPrependedColumn 19 | { 20 | 21 | use Authorised; 22 | 23 | /** 24 | * @var ISelection 25 | */ 26 | protected $selection; 27 | 28 | public function getHeaderAttributes() 29 | { 30 | $this->selection = $this->getGrid()->getExtension('ISelection'); 31 | return ['class' => 'act act-select']; 32 | } 33 | 34 | public function getHeaderContent() 35 | { 36 | return $this->selection->create()->create(); 37 | } 38 | 39 | public function getBodyAttributes($data, $need = true, $rawData = []) 40 | { 41 | return parent::mergeAttributes($data, ['class' => 'grid-checkbox']); 42 | } 43 | 44 | public function getBodyContent($data, $rawData) 45 | { 46 | return $this->selection->createItem($data[$this->getGrid()->getSource()->getPrimaryKey()]); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/core/IInlineEdit.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface IInlineEdit extends Mesour\Components\ComponentModel\IComponent, IColumn 18 | { 19 | 20 | /** 21 | * @param bool $editable 22 | * @return mixed 23 | */ 24 | public function setEditable($editable = true); 25 | 26 | /** 27 | * @return bool 28 | */ 29 | public function hasEditable(); 30 | 31 | public function setReference($table); 32 | 33 | public function getReference(); 34 | 35 | /** 36 | * @return Mesour\Editable\Structures\Fields\IStructureElementField|Mesour\Editable\Structures\Fields\IStructureField 37 | */ 38 | public function getEditableField(); 39 | 40 | /** 41 | * @return Mesour\UI\Button 42 | */ 43 | public function createEditButton(); 44 | 45 | /** 46 | * @param mixed $data 47 | * @param array $attributes 48 | * @param mixed $itemData 49 | * @return array 50 | */ 51 | public function getEditableAttributes($data, array $attributes = [], $itemData = []); 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Base.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | abstract class Base extends Mesour\UI\Control implements IExtension 18 | { 19 | 20 | private $disabled = false; 21 | 22 | /** 23 | * @return Mesour\DataGrid\ExtendedGrid 24 | */ 25 | public function getGrid() 26 | { 27 | return $this->getParent(); 28 | } 29 | 30 | /** 31 | * @return bool 32 | */ 33 | public function isDisabled() 34 | { 35 | return $this->disabled; 36 | } 37 | 38 | public function setDisabled($disabled = true) 39 | { 40 | $this->disabled = (bool) $disabled; 41 | return $this; 42 | } 43 | 44 | public function gridCreate($data = []) 45 | { 46 | } 47 | 48 | public function createInstance(IExtension $extension, $name = null) 49 | { 50 | } 51 | 52 | public function afterGetCount($count) 53 | { 54 | } 55 | 56 | public function beforeFetchData($data = []) 57 | { 58 | } 59 | 60 | public function afterFetchData($currentData, $data = [], $rawData = []) 61 | { 62 | } 63 | 64 | public function attachToRenderer(Mesour\DataGrid\Renderer\IGridRenderer $renderer, $data = [], $rawData = []) 65 | { 66 | } 67 | 68 | public function reset($hard = false) 69 | { 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mesour/datagrid", 3 | "description": "Mesour DataGrid is DataGrid for PHP >= 5.5 with options like to inline edit, export to csv, create sub items, sort data using jQuery.ui.sortable and much more.", 4 | "license": ["BSD-3-Clause","GPL-2.0","GPL-3.0"], 5 | "keywords": ["grid","datagrid","data grid","component"], 6 | "homepage": "http://grid.mesour.com/", 7 | "authors": [{ 8 | "name": "Matouš Němec", 9 | "homepage": "http://mesour.com" 10 | }], 11 | "require": { 12 | "php": ">=5.6.0", 13 | "mesour/components": "~3.2.0", 14 | "mesour/button": "~3.2.0", 15 | "mesour/dropdown": "~3.2.0", 16 | "mesour/editable": "~3.2.0", 17 | "mesour/filter": "~3.2.0", 18 | "mesour/icon": "~3.2.0", 19 | "mesour/pager": "~3.2.0", 20 | "mesour/selection": "~3.2.0", 21 | "mesour/sources": "~3.0.0", 22 | "mesour/template": "~3.2.0", 23 | "mesour/table": "~3.2.0" 24 | }, 25 | "require-dev": { 26 | "tracy/tracy": "~2.4.0", 27 | "nette/tester": "^1.6", 28 | "latte/latte": "~2.4.0", 29 | "nette/robot-loader": "~2.4.0", 30 | "nette/database": "~2.4.0", 31 | "doctrine/orm": "^2.5", 32 | "mesour/array-manager": "3.0.*@beta", 33 | "slevomat/coding-standard": "^1.0", 34 | "consistence/coding-standard": "^0.10" 35 | }, 36 | "minimum-stability": "dev", 37 | "prefer-stable": true, 38 | "suggest": { 39 | "latte/latte": "to use column Template or Template SubItem", 40 | "ext-gd": "to use column Image" 41 | }, 42 | "autoload": { 43 | "classmap": ["src/"] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/Text.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Text extends InlineEdit implements IExportable 18 | { 19 | 20 | use Mesour\Components\Security\Authorised; 21 | use Mesour\Icon\HasIcon; 22 | 23 | public function getHeaderAttributes() 24 | { 25 | return array_merge([ 26 | 'class' => 'grid-column-' . $this->getName(), 27 | ], parent::getHeaderAttributes()); 28 | } 29 | 30 | public function getBodyAttributes($data, $need = true, $rawData = []) 31 | { 32 | $attributes = parent::getBodyAttributes($data); 33 | $attributes['class'] = 'type-text'; 34 | return parent::mergeAttributes($data, $attributes); 35 | } 36 | 37 | public function getBodyContent($data, $rawData) 38 | { 39 | $fromCallback = $this->tryInvokeCallback([$this, $rawData]); 40 | if ($fromCallback !== self::NO_CALLBACK) { 41 | return $fromCallback; 42 | } 43 | return parent::getBodyContent($data, $rawData); 44 | } 45 | 46 | public function attachToFilter(Mesour\DataGrid\Extensions\Filter\IFilter $filter, $hasCheckers) 47 | { 48 | parent::attachToFilter($filter, $hasCheckers); 49 | $item = $filter->addTextFilter($this->getName(), $this->getHeader()); 50 | $this->setUpFilterItem($item, $hasCheckers); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/Sortable.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Sortable extends BaseColumn implements IPrependedColumn 18 | { 19 | 20 | use Mesour\Icon\HasIcon; 21 | 22 | protected $arrowsIcon = 'arrows'; 23 | 24 | /** 25 | * @return Mesour\UI\Button 26 | */ 27 | public function getButton() 28 | { 29 | if (!isset($this['button'])) { 30 | $this['button'] = new Mesour\UI\Button(); 31 | $this['button']->setSize('btn-sm') 32 | ->setType('default') 33 | ->setAttribute('class', 'move handler', true) 34 | ->setAttribute('href', '#') 35 | ->setIcon($this->arrowsIcon); 36 | } 37 | return $this['button']; 38 | } 39 | 40 | public function getHeaderAttributes() 41 | { 42 | return ['class' => 'sortable-column']; 43 | } 44 | 45 | public function getHeaderContent() 46 | { 47 | $icon = $this->createNewIcon($this->arrowsIcon . ' grid-move'); 48 | return $icon; 49 | } 50 | 51 | public function getBodyAttributes($data, $need = true, $rawData = []) 52 | { 53 | return parent::mergeAttributes($data, ['class' => 'grid-sortable']); 54 | } 55 | 56 | public function getBodyContent($data, $rawData) 57 | { 58 | $this->getButton()->setOption('data', $data); 59 | return $this->getButton()->create(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/SubItem/Items/ComponentItem.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class ComponentItem extends Item 18 | { 19 | 20 | public function __construct(Mesour\DataGrid\Extensions\SubItem\ISubItem $parent, $name, $description = null, $component = null) 21 | { 22 | parent::__construct($parent, $name, $description); 23 | $i = 0; 24 | while ($i < (is_null($this->pageLimit) ? self::DEFAULT_COUNT : $this->pageLimit)) { 25 | if (!$component instanceof Mesour\Components\Control\IControl) { 26 | Mesour\Components\Utils\Helpers::invokeArgs($component, [$this->getParent()->getParent(), $name . $i]); 27 | } else { 28 | $this->getGrid()->addComponent($component, $name . $i); 29 | } 30 | $this->keys[] = $i; 31 | $i++; 32 | } 33 | } 34 | 35 | public function render($key = null) 36 | { 37 | if (is_null($key)) { 38 | return ''; 39 | } 40 | /** @var Mesour\Components\Control\IControl $component */ 41 | $component = $this->getGrid()->getComponent($this->name . $this->getTranslatedKey($key)); 42 | return $component->create(); 43 | } 44 | 45 | public function invoke(array $args = [], $name, $key) 46 | { 47 | $arguments = [$this->getGrid()->getComponent($name . $key)]; 48 | $arguments = array_merge($arguments, $args); 49 | return parent::invoke($arguments, $name, $key); 50 | } 51 | 52 | public function reset() 53 | { 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/SubItem/Items/GridItem.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class GridItem extends Item 18 | { 19 | 20 | public function __construct( 21 | Mesour\DataGrid\Extensions\SubItem\ISubItem $parent, 22 | $name, 23 | $description = null, 24 | Mesour\UI\DataGrid $grid = null 25 | ) 26 | { 27 | parent::__construct($parent, $name, $description); 28 | $i = 0; 29 | while ($i < (is_null($this->pageLimit) ? self::DEFAULT_COUNT : $this->pageLimit)) { 30 | $currentGrid = clone $grid; 31 | $currentGrid->setName($name . $i); 32 | $this->parent->addComponent($currentGrid, $name . $i); 33 | $this->keys[] = $i; 34 | $i++; 35 | } 36 | } 37 | 38 | public function render($key = null) 39 | { 40 | if (is_null($key)) { 41 | return ''; 42 | } 43 | /** @var Mesour\UI\DataGrid $grid */ 44 | $grid = $this->parent->getComponent($this->name . $this->getTranslatedKey($key)); 45 | return $grid->create(); 46 | } 47 | 48 | public function reset() 49 | { 50 | $i = 0; 51 | while ($i <= (is_null($this->pageLimit) ? self::DEFAULT_COUNT : $this->pageLimit)) { 52 | if (isset($this->parent[$this->name . $i])) { 53 | $this->parent[$this->name . $i]->reset(true); 54 | } 55 | $i++; 56 | } 57 | } 58 | 59 | public function invoke(array $args = [], $name, $key) 60 | { 61 | $arguments = [$this->parent->getComponent($name . $key)]; 62 | $arguments = array_merge($arguments, $args); 63 | return parent::invoke($arguments, $name, $key); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Sources/ArrayGridSource.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class ArrayGridSource extends Mesour\Filter\Sources\ArrayFilterSource implements IGridSource 18 | { 19 | 20 | private $columnNames = []; 21 | 22 | /** 23 | * @var Mesour\ArrayManage\Searcher\Select 24 | */ 25 | private $exportSelect; 26 | 27 | private function getExportSelect() 28 | { 29 | if (!$this->exportSelect) { 30 | $this->exportSelect = clone $this->getSelect(); 31 | } 32 | return $this->exportSelect; 33 | } 34 | 35 | public function where($column, $value = null, $condition = null, $operator = 'and') 36 | { 37 | parent::where($column, $value, $condition, $operator); 38 | $this->getExportSelect()->where($column, $value, $condition, $operator); 39 | return $this; 40 | } 41 | 42 | public function fetchForExport() 43 | { 44 | $out = $this->getExportSelect()->fetchAll(); 45 | foreach ($out as $key => $val) { 46 | $this->removeStructureDate($out[$key]); 47 | } 48 | foreach ($out as $key => $val) { 49 | $out[$key] = $this->makeArrayHash($val); 50 | } 51 | $this->lastFetchAllResult = $out; 52 | return $out; 53 | } 54 | 55 | public function getColumnNames() 56 | { 57 | if (count($this->columnNames) === 0) { 58 | $columns = $this->getTableColumns($this->getTableName()); 59 | 60 | $data = $this->fetch(); 61 | if (!$data) { 62 | return []; 63 | } 64 | $item = (array) $data; 65 | $this->columnNames = array_unique(array_merge(array_keys($item), array_keys($columns))); 66 | } 67 | return $this->columnNames; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/SubItemButton.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class SubItemButton extends BaseColumn 18 | { 19 | 20 | private $columnName; 21 | 22 | private $key = -1; 23 | 24 | private $opened = false; 25 | 26 | private $twoRows = false; 27 | 28 | public function setColumnName($name) 29 | { 30 | $this->columnName = $name; 31 | return $this; 32 | } 33 | 34 | public function setKey($key) 35 | { 36 | $this->key = $key; 37 | return $this; 38 | } 39 | 40 | public function setOpened($opened = true) 41 | { 42 | $this->opened = (bool) $opened; 43 | return $this; 44 | } 45 | 46 | public function setTwoRows($twoRows = true) 47 | { 48 | $this->twoRows = (bool) $twoRows; 49 | return $this; 50 | } 51 | 52 | public function getHeaderAttributes() 53 | { 54 | return []; 55 | } 56 | 57 | public function getHeaderContent() 58 | { 59 | return null; 60 | } 61 | 62 | public function getBodyAttributes($data, $need = true, $rawData = []) 63 | { 64 | $attributes = ['colspan' => $data, 'class' => 'subgrid-button']; 65 | if ($this->twoRows) { 66 | $attributes['rowspan'] = 2; 67 | } 68 | return parent::mergeAttributes([], $attributes); 69 | } 70 | 71 | public function getBodyContent($data, $rawData) 72 | { 73 | $button = $this['currentButton'] = new Button; 74 | 75 | $button->setType('info') 76 | ->setSize('btn-sm btn-sm-grid') 77 | ->setAttribute('data-mesour', 'ajax') 78 | ->setAttribute('href', $this->getGrid()->getExtension('ISubItem')->createLink('toggleItem', ['key' => $this->key, 'name' => $this->columnName])); 79 | if ($this->opened) { 80 | $button->setIcon('minus'); 81 | } else { 82 | $button->setIcon('plus'); 83 | } 84 | 85 | $this->tryInvokeCallback([$rawData, $this, $button]); 86 | 87 | return $button->create(); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/TemplateFileTrait.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | trait TemplateTrait 18 | { 19 | 20 | private $file; 21 | 22 | private $block; 23 | 24 | /** 25 | * @var Mesour\Template\ITemplate 26 | */ 27 | private $templateEngine; 28 | 29 | /** 30 | * @var Mesour\UI\TemplateFile 31 | */ 32 | private $templateFile; 33 | 34 | public function setTempDir($tempDir) 35 | { 36 | $this->templateFile = new Mesour\UI\TemplateFile($this->getEngine(), $tempDir); 37 | return $this; 38 | } 39 | 40 | public function setBlock($block) 41 | { 42 | $this->block = $block; 43 | if ($this->templateFile) { 44 | $this->templateFile->setBlock($block); 45 | } 46 | return $this; 47 | } 48 | 49 | public function setFile($file) 50 | { 51 | $this->file = $file; 52 | if ($this->templateFile) { 53 | $this->templateFile->setFile($file); 54 | } 55 | return $this; 56 | } 57 | 58 | public function setTemplateEngine(Mesour\Template\ITemplate $template) 59 | { 60 | $this->templateEngine = $template; 61 | } 62 | 63 | public function getEngine() 64 | { 65 | if (!$this->templateEngine) { 66 | $this->templateEngine = new Mesour\Template\Latte\LatteTemplate(); 67 | } 68 | return $this->templateEngine; 69 | } 70 | 71 | public function renderTemplate() 72 | { 73 | $template = $this->getTemplateFile(); 74 | return $template->render(true); 75 | } 76 | 77 | protected function getTemplateFile() 78 | { 79 | if (!$this->templateFile) { 80 | throw new Mesour\InvalidStateException('Temp dir is required. User setTempDir.'); 81 | } 82 | if (!$this->file) { 83 | throw new Mesour\InvalidStateException('Template file is required. User setFile.'); 84 | } else { 85 | $this->templateFile->setFile($this->file); 86 | } 87 | if ($this->block) { 88 | $this->templateFile->setBlock($this->block); 89 | } 90 | return $this->templateFile; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Sortable/SortableExtension.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class SortableExtension extends Mesour\DataGrid\Extensions\Base implements ISortable 18 | { 19 | 20 | use Mesour\Components\Security\Authorised; 21 | 22 | private $sortableUsed = false; 23 | 24 | private $columnName = false; 25 | 26 | public function getSpecialColumn() 27 | { 28 | $this->sortableUsed = true; 29 | $this->getGrid()->setAttribute('data-mesour-sortable', $this->createLinkName()); 30 | return new Mesour\DataGrid\Column\Sortable; 31 | } 32 | 33 | public function setColumnName($columnName) 34 | { 35 | $this->columnName = $columnName; 36 | return $this; 37 | } 38 | 39 | public function getSpecialColumnName() 40 | { 41 | return $this->columnName; 42 | } 43 | 44 | public function setPermission($resource, $privilege) 45 | { 46 | $this->setPermissionCheck($resource, $privilege); 47 | return $this; 48 | } 49 | 50 | public function isGetSpecialColumnUsed() 51 | { 52 | return $this->sortableUsed; 53 | } 54 | 55 | public function handleSortData($data, $item) 56 | { 57 | if ($this->isDisabled()) { 58 | throw new Mesour\InvalidStateException('Cannot sort data if extension is disabled.'); 59 | } 60 | if (!$this->isAllowed()) { 61 | throw new Mesour\InvalidStateException('Invalid permissions.'); 62 | } 63 | $params = []; 64 | $itemId = $item; 65 | parse_str($data, $params); 66 | $data = $params[$this->getGrid()->createLinkName()]; 67 | foreach ($data as $key => $val) { 68 | if ($val === 'null') { 69 | $data[$key] = null; 70 | } 71 | } 72 | if (!is_array($data)) { 73 | throw new Mesour\InvalidStateException('Empty post data from column sorting.'); 74 | } 75 | $this->getGrid()->reset(true); 76 | $this->getGrid()->onSort($data, $itemId); 77 | } 78 | 79 | public function gridCreate($data = []) 80 | { 81 | $this->create(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/Date.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Date extends InlineEdit implements IExportable 18 | { 19 | 20 | use Mesour\Components\Security\Authorised; 21 | use Mesour\Icon\HasIcon; 22 | 23 | private $format = 'Y-m-d'; 24 | 25 | public function setFormat($format) 26 | { 27 | $this->format = $format; 28 | return $this; 29 | } 30 | 31 | /** 32 | * @return string 33 | */ 34 | public function getFormat() 35 | { 36 | return $this->format; 37 | } 38 | 39 | public function getHeaderAttributes() 40 | { 41 | return [ 42 | 'class' => 'grid-column-' . $this->getName(), 43 | ]; 44 | } 45 | 46 | public function getBodyAttributes($data, $need = true, $rawData = []) 47 | { 48 | $attributes = parent::getBodyAttributes($data, $need, $rawData); 49 | $attributes['class'] = 'type-text'; 50 | return parent::mergeAttributes(parent::getBodyAttributes($data), $attributes); 51 | } 52 | 53 | public function getBodyContent($data, $rawData) 54 | { 55 | if (!$data[$this->getName()]) { 56 | return '-'; 57 | } 58 | if (is_numeric($data[$this->getName()])) { 59 | $date = new \DateTime(); 60 | $date->setTimestamp($data[$this->getName()]); 61 | } elseif ($data[$this->getName()] instanceof \DateTime) { 62 | $date = $data[$this->getName()]; 63 | } else { 64 | $date = new \DateTime($data[$this->getName()]); 65 | } 66 | $formattedDate = $date->format($this->format); 67 | 68 | $fromCallback = $this->tryInvokeCallback([$this, $rawData, $date, $formattedDate]); 69 | if ($fromCallback !== self::NO_CALLBACK) { 70 | return $fromCallback; 71 | } 72 | 73 | return $formattedDate; 74 | } 75 | 76 | public function attachToFilter(Mesour\DataGrid\Extensions\Filter\IFilter $filter, $hasCheckers) 77 | { 78 | parent::attachToFilter($filter, $hasCheckers); 79 | $item = $filter->addDateFilter($this->getName(), $this->getHeader()); 80 | $this->setUpFilterItem($item, $hasCheckers); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Licenses 2 | ======== 3 | 4 | Good news! You may use Mesour DataGrid under the terms of either 5 | the New BSD License or the GNU General Public License (GPL) version 2 or 3. 6 | 7 | You don't have to notify anyone which license you are using. You can freely 8 | use Mesour DataGrid in commercial projects as long as the copyright header 9 | remains intact. 10 | 11 | 12 | New BSD License 13 | --------------- 14 | 15 | Copyright (c) 2015 Matouš Němec (http://mesour.com) 16 | All rights reserved. 17 | 18 | Redistribution and use in source and binary forms, with or without modification, 19 | are permitted provided that the following conditions are met: 20 | 21 | * Redistributions of source code must retain the above copyright notice, 22 | this list of conditions and the following disclaimer. 23 | 24 | * Redistributions in binary form must reproduce the above copyright notice, 25 | this list of conditions and the following disclaimer in the documentation 26 | and/or other materials provided with the distribution. 27 | 28 | * Neither the name of "Mesour DataGrid" nor the names of its contributors 29 | may be used to endorse or promote products derived from this software 30 | without specific prior written permission. 31 | 32 | This software is provided by the copyright holders and contributors "as is" and 33 | any express or implied warranties, including, but not limited to, the implied 34 | warranties of merchantability and fitness for a particular purpose are 35 | disclaimed. In no event shall the copyright owner or contributors be liable for 36 | any direct, indirect, incidental, special, exemplary, or consequential damages 37 | (including, but not limited to, procurement of substitute goods or services; 38 | loss of use, data, or profits; or business interruption) however caused and on 39 | any theory of liability, whether in contract, strict liability, or tort 40 | (including negligence or otherwise) arising in any way out of the use of this 41 | software, even if advised of the possibility of such damage. 42 | 43 | 44 | GNU General Public License 45 | -------------------------- 46 | 47 | GPL licenses are very very long, so instead of including them here we offer 48 | you URLs with full text: 49 | 50 | - [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html) 51 | - [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html) 52 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/core/BaseColumn.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | abstract class BaseColumn extends Table\Column implements IColumn 19 | { 20 | 21 | use Mesour\Components\Security\Authorised; 22 | 23 | /** @var Mesour\Components\Utils\Html */ 24 | private $filterResetButton; 25 | 26 | private $disabled = false; 27 | 28 | public function setDisabled($disabled = true) 29 | { 30 | $this->disabled = (bool) $disabled; 31 | return $this; 32 | } 33 | 34 | public function isDisabled() 35 | { 36 | return $this->disabled; 37 | } 38 | 39 | public function setPermission($resource = null, $privilege = null) 40 | { 41 | $this->setPermissionCheck($resource, $privilege); 42 | return $this; 43 | } 44 | 45 | /** 46 | * @param Mesour\DataGrid\Extensions\Filter\IFilter $filter 47 | * @internal 48 | */ 49 | public function setFilterReset(Mesour\DataGrid\Extensions\Filter\IFilter $filter) 50 | { 51 | if ($filter->isInline() && !$filter->isDisabled()) { 52 | $this->filterResetButton = $filter->createResetButton(); 53 | $this->filterResetButton->setText($this->getTranslator()->translate('Reset filter')); 54 | 55 | $this->filterResetButton->class('btn-xs', true); 56 | } 57 | } 58 | 59 | protected function getFilterResetButton() 60 | { 61 | if ($this->filterResetButton) { 62 | return $this->filterResetButton; 63 | } 64 | return ''; 65 | } 66 | 67 | /** 68 | * @param null $subControl 69 | * @return Mesour\UI\DataGrid|Table\ITable 70 | */ 71 | final public function getGrid($subControl = null) 72 | { 73 | return $this->getTable($subControl); 74 | } 75 | 76 | public function validate(array $rowData, $data = []) 77 | { 78 | } 79 | 80 | protected function mergeAttributes($data, array $current) 81 | { 82 | $base = self::getBodyAttributes($data, false); 83 | if (isset($base['class']) && isset($current['class'])) { 84 | $base['class'] = $base['class'] . ' ' . $current['class']; 85 | } 86 | return array_merge($current, $base); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/TemplateFile.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class TemplateFile extends \stdClass implements Mesour\Components\Utils\IString 19 | { 20 | 21 | private $file; 22 | 23 | private $parameters = []; 24 | 25 | public function __construct($tempDir) 26 | { 27 | if (!class_exists('Latte\Engine')) { 28 | throw new Mesour\InvalidStateException('TemplateFile required composer package "latte/latte".'); 29 | } 30 | if (!self::$engine) { 31 | self::$engine = new Engine; 32 | } 33 | self::$engine->setTempDirectory($tempDir); 34 | } 35 | 36 | public function setTemplateEngine(Mesour\Template\ITemplate $template) 37 | { 38 | $this->templateEngine = $template; 39 | } 40 | 41 | public function getEngine() 42 | { 43 | if (!$this->templateEngine) { 44 | $this->templateEngine = new Mesour\Template\Latte\LatteTemplate(); 45 | } 46 | return $this->templateEngine; 47 | } 48 | 49 | public function setFile($file) 50 | { 51 | $this->file = $file; 52 | } 53 | 54 | public function render($toString = false) 55 | { 56 | if (!$toString) { 57 | self::$engine->render($this->file, $this->parameters); 58 | } else { 59 | return self::$engine->renderToString($this->file, $this->parameters); 60 | } 61 | return ''; 62 | } 63 | 64 | public function __toString() 65 | { 66 | try { 67 | return $this->render(true); 68 | } catch (\Exception $e) { 69 | trigger_error($e->getMessage(), E_USER_WARNING); 70 | return ''; 71 | } 72 | } 73 | 74 | public function __set($name, $value) 75 | { 76 | $this->parameters[$name] = $value; 77 | } 78 | 79 | public function __get($name) 80 | { 81 | if (!isset($this->parameters[$name])) { 82 | throw new Mesour\OutOfRangeException('Parameter with name ' . $name . ' does not exist.'); 83 | } 84 | return $this->parameters[$name]; 85 | } 86 | 87 | public function __isset($name) 88 | { 89 | return isset($this->parameters[$name]); 90 | } 91 | 92 | public function __unset($name) 93 | { 94 | unset($this->parameters[$name]); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/Status/StatusButton.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class StatusButton extends Mesour\UI\Button implements IStatusItem 18 | { 19 | 20 | private $status; 21 | 22 | private $selectionTitle = null; 23 | 24 | private $statusName; 25 | 26 | private $callback; 27 | 28 | private $callbackArgs = []; 29 | 30 | public function __construct($name = null, Mesour\Components\ComponentModel\IContainer $parent = null) 31 | { 32 | parent::__construct($name, $parent); 33 | 34 | $this->setSize('btn-sm'); 35 | } 36 | 37 | public function setStatus($status, $statusName, $selectionTitle = null) 38 | { 39 | $this->status = $status; 40 | $this->statusName = $this->getTranslator()->translate($statusName); 41 | $this->setTooltip($this->statusName, 'right'); 42 | $this->selectionTitle = !is_null($selectionTitle) ? $this->getTranslator()->translate($selectionTitle) : null; 43 | return $this; 44 | } 45 | 46 | /** 47 | * @return array [$this->status => $this->statusName] 48 | */ 49 | public function getStatusOptions() 50 | { 51 | return is_null($this->selectionTitle) ? null : [$this->status => $this->selectionTitle]; 52 | } 53 | 54 | public function getStatusName() 55 | { 56 | return $this->statusName; 57 | } 58 | 59 | public function setCallback($callback) 60 | { 61 | Mesour\Components\Utils\Helpers::checkCallback($callback); 62 | $this->callback = $callback; 63 | return $this; 64 | } 65 | 66 | public function setCallbackArguments(array $arguments) 67 | { 68 | $this->callbackArgs = $arguments; 69 | return $this; 70 | } 71 | 72 | public function getStatus() 73 | { 74 | return $this->status; 75 | } 76 | 77 | public function isActive($columnName, $data) 78 | { 79 | if (!$this->callback) { 80 | return $data[$columnName] == $this->status ? true : false; 81 | } else { 82 | $args = [$data]; 83 | if (count($this->callbackArgs) > 0) { 84 | $args = array_merge($args, $this->callbackArgs); 85 | } 86 | return Mesour\Components\Utils\Helpers::invokeArgs($this->callback, $args); 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Pager/PagerExtension.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class PagerExtension extends Mesour\UI\AdvancedPager implements IPager 18 | { 19 | 20 | use Mesour\Components\Security\Authorised; 21 | 22 | /** @var Mesour\Components\Utils\Html|string */ 23 | private $createdPager; 24 | 25 | private $disabled = false; 26 | 27 | public function handleSetPage($page = null) 28 | { 29 | if ($this->isDisabled()) { 30 | throw new Mesour\InvalidStateException('Cannot change page if extension is disabled.'); 31 | } 32 | $this->getGrid()->reset(); 33 | parent::handleSetPage($page); 34 | } 35 | 36 | /** @return Mesour\DataGrid\ExtendedGrid */ 37 | public function getGrid() 38 | { 39 | return $this->getParent(); 40 | } 41 | 42 | /** 43 | * @return bool 44 | */ 45 | public function isDisabled() 46 | { 47 | return $this->disabled; 48 | } 49 | 50 | public function setDisabled($disabled = true) 51 | { 52 | $this->disabled = $disabled; 53 | return $this; 54 | } 55 | 56 | public function gridCreate($data = []) 57 | { 58 | } 59 | 60 | public function createInstance(Mesour\DataGrid\Extensions\IExtension $extension, $name = null) 61 | { 62 | } 63 | 64 | public function reset($hard = false) 65 | { 66 | parent::reset(); 67 | } 68 | 69 | public function afterGetCount($count) 70 | { 71 | $this->setCount($count); 72 | $this->beforeRender(); 73 | } 74 | 75 | public function beforeFetchData($data = []) 76 | { 77 | $this->createdPager = $this->getForCreate(); 78 | $itemsPerPage = $this->getPaginator()->getItemsPerPage(); 79 | $this->getGrid()->getSource()->applyLimit($itemsPerPage, ($this->getPaginator()->getPage() - 1) * $itemsPerPage); 80 | } 81 | 82 | public function afterFetchData($currentData, $data = [], $rawData = []) 83 | { 84 | } 85 | 86 | public function attachToRenderer(Mesour\DataGrid\Renderer\IGridRenderer $renderer, $data = [], $rawData = []) 87 | { 88 | $pagerWrapper = $this->getGrid()->getPagerPrototype(); 89 | $pagerWrapper->add($this->createdPager); 90 | $renderer->setComponent('pager', $pagerWrapper); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/core/Ordering.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | abstract class Ordering extends BaseColumn implements IOrdering 19 | { 20 | 21 | private $ordering = true; 22 | 23 | public function setOrdering($ordering = true) 24 | { 25 | $this->ordering = (bool) $ordering; 26 | return $this; 27 | } 28 | 29 | public function hasOrdering() 30 | { 31 | return $this->ordering; 32 | } 33 | 34 | public function validate(array $rowData, $data = []) 35 | { 36 | parent::validate($rowData, $data); 37 | 38 | if ($this->hasOrdering() && count($rowData) > 0) { 39 | $item = reset($rowData); 40 | if (!array_key_exists($this->getName(), $item)) { 41 | throw new Mesour\InvalidStateException( 42 | sprintf('If use ordering, column key "%s" must exists in data.', $this->getName()) 43 | ); 44 | } 45 | } 46 | } 47 | 48 | public function getHeaderContent() 49 | { 50 | if ($this->ordering) { 51 | /** @var \Mesour\DataGrid\Extensions\Ordering\OrderingExtension $component */ 52 | $component = $this->getGrid('ordering'); 53 | $ordering = $component->getOrdering($this->getName()); 54 | 55 | $link = Mesour\Components\Utils\Html::el('a', [ 56 | 'href' => $this->getGrid('ordering')->createLink('ordering', ['key' => $this->getName()]), 57 | 'class' => 'ordering' . (!is_null($ordering) ? (' ' . strtolower($ordering)) : ''), 58 | 'data-mesour' => 'ajax', 59 | ]); 60 | 61 | $icon = $this->createNewIcon('cog'); 62 | $link->setText(parent::getHeaderContent()); 63 | if ($this instanceof Column\Number || $this instanceof Column\Date) { 64 | $iconName = 'sort-numeric'; 65 | } elseif ($this instanceof Column\Status) { 66 | $iconName = 'sort-amount'; 67 | } else { 68 | $iconName = 'sort-alpha'; 69 | } 70 | 71 | foreach (['-asc no-sort', '-asc order-asc', '-desc order-desc'] as $suffix) { 72 | $icon->setType($iconName . $suffix); 73 | $link->add($icon->render()); 74 | } 75 | 76 | return $link . $this->getFilterResetButton(); 77 | } else { 78 | return parent::getHeaderContent() . $this->getFilterResetButton(); 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/Status/StatusDropDown.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class StatusDropDown extends Mesour\UI\DropDown implements IStatusItem 18 | { 19 | 20 | private $status; 21 | 22 | private $selectionTitle = null; 23 | 24 | private $statusName; 25 | 26 | private $callback; 27 | 28 | private $callbackArgs = []; 29 | 30 | public function __construct($name = null, Mesour\Components\ComponentModel\IContainer $parent = null) 31 | { 32 | parent::__construct($name, $parent); 33 | 34 | $this->getMainButton() 35 | ->setSize('btn-sm'); 36 | } 37 | 38 | public function setStatus($status, $statusName, $selectionTitle = null) 39 | { 40 | $this->status = $status; 41 | $this->statusName = $this->getTranslator()->translate($statusName); 42 | $this->selectionTitle = !is_null($selectionTitle) ? $this->getTranslator()->translate($selectionTitle) : null; 43 | return $this; 44 | } 45 | 46 | public function setPermission($resource = null, $privilege = null) 47 | { 48 | throw new Mesour\NotImplementedException('Use setPermission on links for this dropdown.'); 49 | } 50 | 51 | /** 52 | * @return array [$this->status => $this->statusName] 53 | */ 54 | public function getStatusOptions() 55 | { 56 | return is_null($this->selectionTitle) ? null : [$this->status => $this->selectionTitle]; 57 | } 58 | 59 | public function getStatusName() 60 | { 61 | return $this->statusName; 62 | } 63 | 64 | public function setCallback($callback) 65 | { 66 | Mesour\Components\Utils\Helpers::checkCallback($callback); 67 | $this->callback = $callback; 68 | return $this; 69 | } 70 | 71 | public function setCallbackArguments(array $arguments) 72 | { 73 | $this->callbackArgs = $arguments; 74 | return $this; 75 | } 76 | 77 | public function getStatus() 78 | { 79 | return $this->status; 80 | } 81 | 82 | public function isActive($columnName, $data) 83 | { 84 | if (!$this->callback) { 85 | return $data[$columnName] == $this->status ? true : false; 86 | } else { 87 | $args = [$data]; 88 | if (count($this->callbackArgs) > 0) { 89 | $args = array_merge($args, $this->callbackArgs); 90 | } 91 | return Mesour\Components\Utils\Helpers::invokeArgs($this->callback, $args); 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /examples/sources/doctrine_source.php: -------------------------------------------------------------------------------- 1 | setMetadataDriverImpl($driver); 17 | 18 | // or if you prefer yaml or XML 19 | //$config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode); 20 | //$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode); 21 | 22 | // database configuration parameters 23 | if (!isset($conn)) { 24 | $conn = [ 25 | 'driver' => 'pdo_mysql', 26 | 'user' => 'root', 27 | 'password' => 'root', 28 | 'dbname' => 'mesour_editable', 29 | ]; 30 | } 31 | 32 | // obtaining the entity manager 33 | $entityManager = EntityManager::create($conn, $config); 34 | 35 | $entityManager->getConfiguration() 36 | ->addCustomDatetimeFunction('DATE', \Mesour\Filter\Sources\DateFunction::class); 37 | 38 | $entityManager->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string'); 39 | \Doctrine\DBAL\Types\Type::addType('enum', \Doctrine\DBAL\Types\StringType::class); 40 | 41 | require_once __DIR__ . '/../../vendor/mesour/sources/tests/Entity/User.php'; 42 | require_once __DIR__ . '/../../vendor/mesour/sources/tests/Entity/Group.php'; 43 | require_once __DIR__ . '/../../vendor/mesour/sources/tests/Entity/UserAddress.php'; 44 | require_once __DIR__ . '/../../vendor/mesour/sources/tests/Entity/Company.php'; 45 | require_once __DIR__ . '/../../vendor/mesour/sources/tests/Entity/Wallet.php'; 46 | 47 | $helperSet = new \Symfony\Component\Console\Helper\HelperSet( 48 | [ 49 | 'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($entityManager->getConnection()), 50 | 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager), 51 | ] 52 | ); 53 | 54 | $qb = $entityManager->createQueryBuilder(); 55 | $qb = $entityManager->createQueryBuilder() 56 | ->select('u') 57 | ->from(Mesour\Sources\Tests\Entity\User::class, 'u') 58 | ->join(\Mesour\Sources\Tests\Entity\Group::class, 'g', \Doctrine\ORM\Query\Expr\Join::WITH, 'u.group = g.id'); 59 | 60 | $source = new \Mesour\DataGrid\Sources\DoctrineGridSource( 61 | Mesour\Sources\Tests\Entity\User::class, 62 | 'id', 63 | $qb, 64 | [ 65 | 'id' => 'u.id', 66 | 'group_id' => 'u.groups', 67 | 'last_login' => 'u.lastLogin', 68 | 'group' => 'g', 69 | ] 70 | ); 71 | 72 | return $source; -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Selection/Link.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @method null onCall($items) 18 | */ 19 | class Link 20 | { 21 | 22 | use Mesour\SmartObject; 23 | 24 | private $name; 25 | 26 | private $fixed_name; 27 | 28 | private $confirm = false; 29 | 30 | private $ajax = true; 31 | 32 | /** @var Mesour\Components\Localization\ITranslator */ 33 | private $translator; 34 | 35 | public $onCall = []; 36 | 37 | /** @var Mesour\UI\Button */ 38 | private $button; 39 | 40 | public function __construct($name, Mesour\UI\Button $button, Mesour\Components\Localization\ITranslator $translator) 41 | { 42 | $this->translator = $translator; 43 | 44 | $name = $this->translator->translate($name); 45 | $this->fixed_name = Mesour\Components\Utils\Helpers::webalize($name); 46 | $this->button = $button; 47 | $this->button->setAttribute('data-mesour-selection', 'ajax'); 48 | $this->button->setAttribute('href', '#'); 49 | $this->button->setAttribute('data-name', $this->getFixedName()); 50 | $this->name = $name; 51 | return $this; 52 | } 53 | 54 | public function setAjax($ajax) 55 | { 56 | $this->ajax = (bool) $ajax; 57 | $this->button->setAttribute('data-mesour-selection', $this->ajax ? 'ajax' : 'none'); 58 | return $this; 59 | } 60 | 61 | public function setGridSelection($dataMesourGridSelection) 62 | { 63 | $this->button->setAttribute('data-mesour-gridselection', $dataMesourGridSelection); 64 | return $this; 65 | } 66 | 67 | public function setConfirm($confirm) 68 | { 69 | $this->confirm = $this->translator->translate($confirm); 70 | $this->button->setAttribute('data-confirm', $this->confirm); 71 | return $this; 72 | } 73 | 74 | public function getName() 75 | { 76 | return $this->name; 77 | } 78 | 79 | public function setPermission($resource, $privilege) 80 | { 81 | $this->button->setPermission($resource, $privilege); 82 | return $this; 83 | } 84 | 85 | public function isAllowed() 86 | { 87 | return $this->button->isAllowed(); 88 | } 89 | 90 | /** 91 | * @return Mesour\UI\Button 92 | */ 93 | public function getButton() 94 | { 95 | return $this->button; 96 | } 97 | 98 | public function getFixedName() 99 | { 100 | return $this->fixed_name; 101 | } 102 | 103 | public function __clone() 104 | { 105 | 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/SimpleFilter/SimpleFilterExtension.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class SimpleFilterExtension extends Mesour\UI\SimpleFilter implements ISimpleFilter 18 | { 19 | 20 | use Mesour\Components\Security\Authorised; 21 | 22 | private $disabled = false; 23 | 24 | /** @var Mesour\Components\Utils\Html|string */ 25 | private $createdFilter; 26 | 27 | /** 28 | * @return bool 29 | */ 30 | public function isDisabled() 31 | { 32 | return $this->disabled; 33 | } 34 | 35 | public function setDisabled($disabled = true) 36 | { 37 | $this->disabled = (bool) $disabled; 38 | return $this; 39 | } 40 | 41 | public function gridCreate($data = []) 42 | { 43 | $this->onFilter[] = function (ISimpleFilter $currentFilter) { 44 | $this->updateFilter($currentFilter); 45 | $this->getGrid()->onFilter($currentFilter); 46 | }; 47 | $this->setSource($this->getGrid()->getSource()); 48 | 49 | $this->setOption('data', $data); 50 | $this->createdFilter = $this->create(); 51 | $this->updateFilter($this); 52 | } 53 | 54 | /** 55 | * @return Mesour\DataGrid\ExtendedGrid|Mesour\Components\Control\IControl 56 | */ 57 | public function getGrid() 58 | { 59 | return $this->getParent(); 60 | } 61 | 62 | public function createInstance(Mesour\DataGrid\Extensions\IExtension $extension, $name = null) 63 | { 64 | 65 | } 66 | 67 | public function afterGetCount($count) 68 | { 69 | 70 | } 71 | 72 | public function beforeFetchData($data = []) 73 | { 74 | 75 | } 76 | 77 | public function afterFetchData($currentData, $data = [], $rawData = []) 78 | { 79 | 80 | } 81 | 82 | public function attachToRenderer(Mesour\DataGrid\Renderer\IGridRenderer $renderer, $data = [], $rawData = []) 83 | { 84 | $filterPrototype = $this->getGrid()->getFilterPrototype(); 85 | $filterPrototype->add($this->createdFilter); 86 | $renderer->setComponent('filter', $filterPrototype); 87 | } 88 | 89 | public function reset($hard = false) 90 | { 91 | 92 | } 93 | 94 | /** 95 | * @param ISimpleFilter $filter 96 | * @throws 97 | */ 98 | private function updateFilter(ISimpleFilter $filter) 99 | { 100 | if (!$this->isDisabled()) { 101 | $source = $this->getGrid()->getSource(); 102 | $source->applySimple($filter->getQuery(), $filter->getAllowedColumns()); 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/Mesour/UI/DataGrid.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class DataGrid extends Mesour\DataGrid\SubItemGrid 19 | { 20 | 21 | /** 22 | * @param string $name 23 | * @param string|null $header 24 | * @return Column\Text 25 | */ 26 | public function addText($name, $header = null) 27 | { 28 | $column = new Column\Text; 29 | $this->setColumn($column, $name, $header); 30 | return $column; 31 | } 32 | 33 | /** 34 | * @param string $name 35 | * @param string|null $header 36 | * @return Column\Number 37 | */ 38 | public function addNumber($name, $header = null) 39 | { 40 | $column = new Column\Number; 41 | $this->setColumn($column, $name, $header); 42 | return $column; 43 | } 44 | 45 | /** 46 | * @param string $name 47 | * @param string|null $header 48 | * @return Column\Date 49 | */ 50 | public function addDate($name, $header = null) 51 | { 52 | $column = new Column\Date; 53 | $this->setColumn($column, $name, $header); 54 | return $column; 55 | } 56 | 57 | /** 58 | * @param string $name 59 | * @param string|null $header 60 | * @return Column\Container 61 | */ 62 | public function addContainer($name, $header = null) 63 | { 64 | $column = new Column\Container; 65 | $this->setColumn($column, $name, $header); 66 | $column->setFiltering(false) 67 | ->setOrdering(false); 68 | return $column; 69 | } 70 | 71 | /** 72 | * @param string $name 73 | * @param string|null $header 74 | * @return Column\Image 75 | */ 76 | public function addImage($name, $header = null) 77 | { 78 | $column = new Column\Image; 79 | $this->setColumn($column, $name, $header); 80 | return $column; 81 | } 82 | 83 | /** 84 | * @param string $name 85 | * @param string|null $header 86 | * @return Column\Status 87 | */ 88 | public function addStatus($name, $header = null) 89 | { 90 | $column = new Column\Status; 91 | $this->setColumn($column, $name, $header); 92 | return $column; 93 | } 94 | 95 | /** 96 | * @param string $name 97 | * @param string|null $header 98 | * @return Column\Template 99 | */ 100 | public function addTemplate($name, $header = null) 101 | { 102 | $column = new Column\Template; 103 | $this->setColumn($column, $name, $header); 104 | return $column; 105 | } 106 | 107 | /** 108 | * @param string $name 109 | * @param null $header 110 | * @return Column\Text 111 | */ 112 | public function addColumn($name, $header = null) 113 | { 114 | return $this->addText($name, $header); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | app/config/environment.php 61 | tests/bootstrap.php 62 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/Number.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Number extends InlineEdit implements IExportable 18 | { 19 | 20 | use Mesour\Components\Security\Authorised; 21 | use Mesour\Icon\HasIcon; 22 | 23 | private $decimals = 0; 24 | 25 | private $unit = null; 26 | 27 | private $decimalPoint = '.'; 28 | 29 | private $thousandSeparator = ','; 30 | 31 | public function setDecimals($decimals) 32 | { 33 | $this->decimals = (int) $decimals; 34 | return $this; 35 | } 36 | 37 | public function setDecimalPoint($decimalPoint = '.') 38 | { 39 | $this->decimalPoint = $decimalPoint; 40 | return $this; 41 | } 42 | 43 | public function setUnit($unit) 44 | { 45 | $this->unit = $unit; 46 | return $this; 47 | } 48 | 49 | public function setThousandsSeparator($thousandSeparator = ',') 50 | { 51 | $this->thousandSeparator = $thousandSeparator; 52 | return $this; 53 | } 54 | 55 | /** 56 | * @return int 57 | */ 58 | public function getDecimals() 59 | { 60 | return $this->decimals; 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function getDecimalPoint() 67 | { 68 | return $this->decimalPoint; 69 | } 70 | 71 | /** 72 | * @return null 73 | */ 74 | public function getUnit() 75 | { 76 | return $this->unit; 77 | } 78 | 79 | /** 80 | * @return string 81 | */ 82 | public function getThousandSeparator() 83 | { 84 | return $this->thousandSeparator; 85 | } 86 | 87 | public function getHeaderAttributes() 88 | { 89 | return parent::mergeAttributes(parent::getHeaderAttributes(), [ 90 | 'class' => 'grid-column-' . $this->getName(), 91 | ]); 92 | } 93 | 94 | public function getBodyAttributes($data, $need = true, $rawData = []) 95 | { 96 | $attributes = parent::getBodyAttributes($data); 97 | $attributes['class'] = 'type-text'; 98 | return parent::mergeAttributes(parent::getBodyAttributes($data), $attributes); 99 | } 100 | 101 | public function getBodyContent($data, $rawData) 102 | { 103 | $formatted = number_format($data[$this->getName()], $this->decimals, $this->decimalPoint, $this->thousandSeparator) 104 | . ($this->unit ? (' ' . $this->unit) : ''); 105 | 106 | $fromCallback = $this->tryInvokeCallback([$this, $rawData, $formatted]); 107 | if ($fromCallback !== self::NO_CALLBACK) { 108 | return $fromCallback; 109 | } 110 | return $formatted; 111 | } 112 | 113 | public function attachToFilter(Mesour\DataGrid\Extensions\Filter\IFilter $filter, $hasCheckers) 114 | { 115 | parent::attachToFilter($filter, $hasCheckers); 116 | $item = $filter->addNumberFilter($this->getName(), $this->getHeader()); 117 | $this->setUpFilterItem($item, $hasCheckers); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/Template.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Template extends Filtering implements IExportable 18 | { 19 | 20 | /** @var Mesour\DataGrid\TemplateFile */ 21 | private $template; 22 | 23 | private $templateFile; 24 | 25 | private $block; 26 | 27 | public function setBlock($block) 28 | { 29 | $this->block = $block; 30 | return $this; 31 | } 32 | 33 | public function setTempDirectory($path) 34 | { 35 | if (!is_dir($path) && !is_writable($path)) { 36 | throw new Mesour\InvalidStateException('Temp directory must be directory and must be writable.'); 37 | } 38 | if ($this->template) { 39 | unset($this->template); 40 | } 41 | $this->template = new Mesour\DataGrid\TemplateFile($path); 42 | return $this; 43 | } 44 | 45 | public function setTemplateFile($file) 46 | { 47 | if (file_exists($file)) { 48 | $this->templateFile = $file; 49 | } else { 50 | throw new Mesour\FileNotFoundException('Template file "' . $file . '" does not exist.'); 51 | } 52 | return $this; 53 | } 54 | 55 | public function getHeaderAttributes() 56 | { 57 | return array_merge( 58 | [ 59 | 'class' => 'grid-column-' . $this->getName(), 60 | ], 61 | parent::getHeaderAttributes() 62 | ); 63 | } 64 | 65 | public function getBodyAttributes($data, $need = true, $rawData = []) 66 | { 67 | $attributes = parent::getBodyAttributes($data); 68 | $attributes['class'] = 'type-template'; 69 | return parent::mergeAttributes($data, $attributes); 70 | } 71 | 72 | public function getBodyContent($data, $rawData) 73 | { 74 | $template = $this->getTemplate(); 75 | 76 | $this->tryInvokeCallback([$this, $rawData, $template]); 77 | 78 | return trim($template); 79 | } 80 | 81 | private function getTemplate() 82 | { 83 | if (!$this->template) { 84 | throw new Mesour\InvalidStateException( 85 | 'Temp directory is required, use setTempDirectory() on this column.' 86 | ); 87 | } 88 | if (!$this->templateFile) { 89 | throw new Mesour\InvalidStateException('Template file is required, use setTemplateFile() on this column.'); 90 | } 91 | $this->template->setFile(__DIR__ . '/Template/Template.latte'); 92 | $this->template->_template_path = $this->templateFile; 93 | $this->template->_block = false; 94 | if (is_string($this->block)) { 95 | $this->template->_block = $this->block; 96 | } 97 | return $this->template; 98 | } 99 | 100 | public function attachToFilter(Mesour\DataGrid\Extensions\Filter\IFilter $filter, $hasCheckers) 101 | { 102 | parent::attachToFilter($filter, $hasCheckers); 103 | $item = $filter->addTextFilter($this->getName(), $this->getHeader()); 104 | $this->setUpFilterItem($item, $hasCheckers); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Renderer/GridRenderer.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class GridRenderer implements IGridRenderer 18 | { 19 | 20 | private $components = []; 21 | 22 | public function setComponent($type, $component) 23 | { 24 | if (!$component instanceof Mesour\Components\Utils\IString && !is_string($component) && !is_int($component)) { 25 | throw new Mesour\InvalidArgumentException('Component must be string or int or instanceof Mesour\Components\IString'); 26 | } 27 | $this->components[$type] = $component; 28 | } 29 | 30 | public function getComponent($type) 31 | { 32 | if (!$this->components[$type]) { 33 | throw new Mesour\InvalidStateException('Component with type ' . $type . ' does not exists. Can use only ' . implode('|', array_keys($this->components)) . '.'); 34 | } 35 | return isset($this->components[$type]) ? $this->components[$type] : null; 36 | } 37 | 38 | /** 39 | * @return Mesour\Components\Utils\Html 40 | */ 41 | public function getWrapper() 42 | { 43 | return $this->getComponent('wrapper'); 44 | } 45 | 46 | public function getSnippetId() 47 | { 48 | return $this->getComponent('snippet'); 49 | } 50 | 51 | public function renderGrid() 52 | { 53 | echo $this->getComponent('grid'); 54 | } 55 | 56 | public function renderPager() 57 | { 58 | echo $this->getComponent('pager'); 59 | } 60 | 61 | public function renderEditable() 62 | { 63 | echo $this->getComponent('editable'); 64 | } 65 | 66 | public function renderFilter() 67 | { 68 | echo $this->getComponent('filter'); 69 | } 70 | 71 | public function renderExport() 72 | { 73 | echo $this->getComponent('export'); 74 | } 75 | 76 | public function renderSelection() 77 | { 78 | echo $this->getComponent('selection'); 79 | } 80 | 81 | public function render() 82 | { 83 | echo $this->__toString(); 84 | } 85 | 86 | public function __toString() 87 | { 88 | try { 89 | $wrapper = $this->getWrapper(); 90 | $wrapper->id($this->getSnippetId()); 91 | 92 | if (isset($this->components['filter'])) { 93 | $wrapper->insert(0, $this->getComponent('filter')); 94 | } 95 | if (isset($this->components['editable'])) { 96 | $wrapper->insert(1, $this->getComponent('editable')); 97 | } 98 | 99 | $wrapper->insert(2, $this->getComponent('grid')); 100 | 101 | if (isset($this->components['pager'])) { 102 | $wrapper->insert(3, $this->getComponent('pager')); 103 | } 104 | if (isset($this->components['selection'])) { 105 | $wrapper->insert(4, $this->getComponent('selection')); 106 | } 107 | if (isset($this->components['export'])) { 108 | $wrapper->insert(5, $this->getComponent('export')); 109 | } 110 | $wrapper->insert(6, '
'); 111 | return (string) $wrapper; 112 | } catch (\Exception $e) { 113 | trigger_error($e->getMessage(), E_USER_WARNING); 114 | return ''; 115 | } 116 | } 117 | 118 | public function __clone() 119 | { 120 | $this->components = []; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Selection/Links.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Links implements Mesour\Components\Utils\IString 18 | { 19 | 20 | use Mesour\SmartObject; 21 | 22 | /** 23 | * @var Mesour\Components\Control\IControl|Mesour\Components\Localization\Translatable 24 | */ 25 | private $parent; 26 | 27 | /** 28 | * @var Mesour\UI\DropDown 29 | */ 30 | private $dropDown; 31 | 32 | /** 33 | * @var Link[] 34 | */ 35 | private $links = []; 36 | 37 | public function __construct(ISelection $parent) 38 | { 39 | $this->parent = $parent; 40 | $this->dropDown = new Mesour\UI\DropDown('links', $parent); 41 | $this->dropDown 42 | ->setAttribute('class', 'dropdown selection-dropdown'); 43 | 44 | $this->dropDown 45 | ->getMainButton() 46 | ->setText('Selected') 47 | ->setDisabled(true); 48 | } 49 | 50 | public function setParent(ISelection $parent) 51 | { 52 | $this->parent = $parent; 53 | $this->dropDown = clone $this->dropDown; 54 | $parent->removeComponent('links'); 55 | $parent->addComponent($this->dropDown, 'links'); 56 | } 57 | 58 | /** 59 | * @param string $name 60 | * @return Link 61 | */ 62 | public function addLink($name) 63 | { 64 | $button = $this->dropDown->addButton($this->parent->getTranslator()->translate($name)); 65 | $link = new Link($name, $button, $this->parent->getTranslator()); 66 | $this->links[$link->getFixedName()] = $link; 67 | 68 | return $link; 69 | } 70 | 71 | /** 72 | * @param array $attributes 73 | * @return $this 74 | */ 75 | public function addDivider(array $attributes = []) 76 | { 77 | $this->dropDown->addDivider($attributes); 78 | return $this; 79 | } 80 | 81 | /** 82 | * @param string $text 83 | * @param array $attributes 84 | * @return $this 85 | */ 86 | public function addHeader($text, array $attributes = []) 87 | { 88 | $this->dropDown->addHeader($text, $attributes); 89 | return $this; 90 | } 91 | 92 | /** 93 | * @param string $fixedName 94 | * @return Link 95 | */ 96 | public function getLink($fixedName) 97 | { 98 | return $this->links[$fixedName]; 99 | } 100 | 101 | /** 102 | * @return Mesour\UI\DropDown 103 | */ 104 | public function getDropDown() 105 | { 106 | return $this->dropDown; 107 | } 108 | 109 | public function create($data = []) 110 | { 111 | $this->dropDown->getControlPrototype() 112 | ->addAttributes([ 113 | 'data-mesour-selectiondropdown' => $this->parent->createLinkName(), 114 | ]); 115 | foreach ($this->getLinks() as $link) { 116 | $link->setGridSelection($this->parent->createLinkName()); 117 | } 118 | $this->dropDown->setOption('data', $data); 119 | return $this->dropDown->create(); 120 | } 121 | 122 | public function __toString() 123 | { 124 | return (string) $this->create(); 125 | } 126 | 127 | /** 128 | * @return Link[] 129 | */ 130 | public function getLinks() 131 | { 132 | $out = []; 133 | foreach ($this->links as $link) { 134 | if ($link->isAllowed()) { 135 | $out[] = $link; 136 | } 137 | } 138 | return $out; 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Renderer/GridListRenderer.php: -------------------------------------------------------------------------------- 1 | getWrapperPrototype() 39 | ->class('mesour-datagrid-list', true); 40 | } 41 | 42 | private function getCreateButton() 43 | { 44 | if (!$this->createButton) { 45 | $this->createButton = new Mesour\UI\Button('_create_button'); 46 | $this->createButton->setIcon('plus') 47 | ->setClassName(''); 48 | $this->createButton->setAttribute('data-grid-is-add', 'true'); 49 | } 50 | return $this->createButton; 51 | } 52 | 53 | private function createRemoveButton() 54 | { 55 | $deleteButton = new Mesour\UI\Button('_delete_button'); 56 | $deleteButton->setIcon('times') 57 | ->setClassName(''); 58 | 59 | $deleteButton->setAttribute('data-grid-is-remove', 'true'); 60 | 61 | return $deleteButton; 62 | } 63 | 64 | public function render() 65 | { 66 | $wrapper = $this->getWrapperPrototype(); 67 | $column = $this->getColumn(); 68 | $hasEditable = $column instanceof Mesour\DataGrid\Column\IInlineEdit && $column->hasEditable(); 69 | if ($hasEditable) { 70 | $editableField = $column->getEditableField(); 71 | } 72 | 73 | $this->onRender($this, $wrapper); 74 | 75 | foreach ($this->getItems() as $item) { 76 | $li = $this->createLiPrototype(); 77 | if ($this->getCallback()) { 78 | Mesour\Components\Utils\Helpers::invokeArgs($this->getCallback(), [$this, $li, $item[0], $item[1], $item[2]]); 79 | } else { 80 | $li->add($item[1]); 81 | 82 | $attributes = $column->getEditableAttributes($item[0], $this->liAttributes, $item[2]); 83 | $li->addAttributes($attributes); 84 | 85 | if ($hasEditable) { 86 | $li->add(' '); 87 | $li->add($column->createEditButton()); 88 | } 89 | 90 | if (isset($editableField) && $editableField->hasRemoveRowEnabled()) { 91 | $li->add(' '); 92 | $li->add($this->createRemoveButton()); 93 | } 94 | } 95 | $this->onRenderRow($this, $li, $item[0], $item[1]); 96 | $wrapper->add($li); 97 | } 98 | 99 | if (isset($editableField) && $editableField->hasCreateNewRowEnabled()) { 100 | $createLi = $this->createLiPrototype(); 101 | $createLi->add($this->getCreateButton()); 102 | $wrapper->add($createLi); 103 | } 104 | 105 | return $wrapper; 106 | } 107 | 108 | public function __clone() 109 | { 110 | parent::__clone(); 111 | 112 | $this->liAttributes = []; 113 | 114 | if ($this->createButton) { 115 | $this->createButton = clone $this->createButton; 116 | $this->createButton->setAttributes([]); 117 | } 118 | if ($this->removeButton) { 119 | $this->removeButton = clone $this->removeButton; 120 | $this->removeButton->setAttributes([]); 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/SubItem/Items/Item.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | abstract class Item 18 | { 19 | 20 | use Mesour\SmartObject; 21 | 22 | const DEFAULT_COUNT = 20; 23 | 24 | /** @var Mesour\DataGrid\Extensions\SubItem\ISubItem */ 25 | protected $parent; 26 | 27 | protected $callback; 28 | 29 | protected $checkCallback; 30 | 31 | protected $type; 32 | 33 | protected $name; 34 | 35 | protected $disabled = false; 36 | 37 | protected $description; 38 | 39 | protected $pageLimit; 40 | 41 | protected $keys = []; 42 | 43 | protected $permission = false; 44 | 45 | protected $aliases = []; 46 | 47 | public function __construct(Mesour\DataGrid\Extensions\SubItem\ISubItem $parent, $name, $description = null) 48 | { 49 | $this->parent = $parent; 50 | $this->name = $name; 51 | $this->pageLimit = $this->parent->getPageLimit(); 52 | $this->description = $description; 53 | } 54 | 55 | /** 56 | * @return Mesour\DataGrid\SubItemGrid 57 | */ 58 | protected function getGrid() 59 | { 60 | return $this->getParent()->getParent(); 61 | } 62 | 63 | public function setCallback($callback) 64 | { 65 | Mesour\Components\Utils\Helpers::checkCallback($callback); 66 | $this->callback = $callback; 67 | return $this; 68 | } 69 | 70 | public function setCheckCallback($callback) 71 | { 72 | Mesour\Components\Utils\Helpers::checkCallback($callback); 73 | $this->checkCallback = $callback; 74 | return $this; 75 | } 76 | 77 | public function check($rowData) 78 | { 79 | if ($this->checkCallback) { 80 | Mesour\Components\Utils\Helpers::invokeArgs($this->checkCallback, [$rowData, $this]); 81 | } 82 | } 83 | 84 | public function isDisabled() 85 | { 86 | return $this->disabled; 87 | } 88 | 89 | public function setDisabled($disabled = true) 90 | { 91 | $this->disabled = $disabled; 92 | return $this; 93 | } 94 | 95 | public function setPermission($resource, $privilege) 96 | { 97 | $this->permission = [$this->parent->getUserRole(), $resource, $privilege]; 98 | return $this; 99 | } 100 | 101 | public function isAllowed() 102 | { 103 | return !$this->permission 104 | || Mesour\Components\Utils\Helpers::invokeArgs([$this->parent->getAuthorizator(), 'isAllowed'], $this->permission); 105 | } 106 | 107 | public function getName() 108 | { 109 | return $this->name; 110 | } 111 | 112 | /** 113 | * @return Mesour\DataGrid\Column\SubItem 114 | */ 115 | protected function getParent() 116 | { 117 | return $this->parent; 118 | } 119 | 120 | public function setDescription($description) 121 | { 122 | return $this->description = $description; 123 | } 124 | 125 | public function getDescription() 126 | { 127 | return $this->description ? $this->description : $this->name; 128 | } 129 | 130 | public function addAlias($forKey, $alias) 131 | { 132 | $this->aliases[$forKey] = $alias; 133 | } 134 | 135 | public function getTranslatedKey($key) 136 | { 137 | return isset($this->aliases[$key]) ? $this->aliases[$key] : $key; 138 | } 139 | 140 | public function invoke(array $args = [], $name, $key) 141 | { 142 | return $this->callback ? Mesour\Components\Utils\Helpers::invokeArgs($this->callback, $args) : null; 143 | } 144 | 145 | public function hasKey($key) 146 | { 147 | return isset($this->parent[$this->name . $key]); 148 | } 149 | 150 | public function getKeys() 151 | { 152 | return $this->keys; 153 | } 154 | 155 | abstract public function render(); 156 | 157 | abstract public function reset(); 158 | 159 | } 160 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/core/Filtering.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | abstract class Filtering extends Ordering implements IFiltering 19 | { 20 | 21 | private $filtering = true; 22 | 23 | private $inline = false; 24 | 25 | /** @var Mesour\DataGrid\Extensions\Filter\IFilter */ 26 | private $filter; 27 | 28 | protected $filterItemSizeClass = 'btn-xs'; 29 | 30 | /** 31 | * @param bool $filtering 32 | * @return $this 33 | */ 34 | public function setFiltering($filtering = true) 35 | { 36 | $this->filtering = (bool) $filtering; 37 | return $this; 38 | } 39 | 40 | /** 41 | * @return bool 42 | */ 43 | public function hasFiltering() 44 | { 45 | return $this->filtering; 46 | } 47 | 48 | /** 49 | * @param bool $inline 50 | * @return $this 51 | */ 52 | public function setInline($inline = true) 53 | { 54 | $this->inline = (bool) $inline; 55 | return $this; 56 | } 57 | 58 | /** 59 | * @return bool 60 | */ 61 | public function isInline() 62 | { 63 | return $this->inline; 64 | } 65 | 66 | protected function setFilter(Mesour\DataGrid\Extensions\Filter\IFilter $filter) 67 | { 68 | $this->filter = $filter; 69 | } 70 | 71 | public function attachToFilter(Mesour\DataGrid\Extensions\Filter\IFilter $filter, $hasCheckers) 72 | { 73 | $this->setFilter($filter); 74 | } 75 | 76 | public function validate(array $rowData, $data = []) 77 | { 78 | parent::validate($rowData, $data); 79 | 80 | if ($this->hasFiltering()) { 81 | $dataStructure = $this->getGrid()->getSource()->getDataStructure(); 82 | if ($dataStructure->hasColumn($this->getName())) { 83 | $structureColumn = $dataStructure->getColumn($this->getName()); 84 | if ($structureColumn instanceof Mesour\Sources\Structures\Columns\BaseTableColumnStructure) { 85 | $this->setFiltering(false); 86 | return; 87 | } 88 | } 89 | 90 | if (count($rowData) > 0) { 91 | $item = reset($rowData); 92 | if (!array_key_exists($this->getName(), $item)) { 93 | throw new Mesour\InvalidStateException( 94 | sprintf('If use filtering, column key "%s" must exists in data.', $this->getName()) 95 | ); 96 | } 97 | } 98 | } 99 | } 100 | 101 | public function getHeaderAttributes() 102 | { 103 | if (isset($this->filter[$this->getName()]) && $this->inline && $this->filtering) { 104 | return array_merge( 105 | [ 106 | 'data-with-filter' => '1', 107 | ], 108 | parent::getHeaderAttributes() 109 | ); 110 | } 111 | return parent::getHeaderAttributes(); 112 | } 113 | 114 | public function getHeaderContent() 115 | { 116 | $parentContent = parent::getHeaderContent(); 117 | if ($this->inline && !$this->isReferencedColumn()) { 118 | $filterItem = $this->filter->getItem($this->getName()); 119 | $filterItem->getButtonPrototype() 120 | ->class($this->filterItemSizeClass, true); 121 | $parentContent .= ' ' . $filterItem->render(); 122 | 123 | return $parentContent; 124 | } else { 125 | return $parentContent; 126 | } 127 | } 128 | 129 | /** 130 | * @param Mesour\Filter\IFilterItem $item 131 | * @param bool $hasCheckers 132 | */ 133 | protected function setUpFilterItem(Mesour\Filter\IFilterItem $item, $hasCheckers) 134 | { 135 | $dataStructure = $this->getGrid()->getSource()->getDataStructure(); 136 | if ($dataStructure->hasColumn($this->getName())) { 137 | $column = $dataStructure->getColumn($this->getName()); 138 | if ($column instanceof Mesour\Sources\Structures\Columns\BaseTableColumnStructure) { 139 | $item->setMainFilter(false); 140 | } 141 | } 142 | 143 | $item->setCheckers($hasCheckers); 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Editable/EditableExtension.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class EditableExtension extends Mesour\UI\Editable implements IEditable 19 | { 20 | 21 | /** 22 | * @var Mesour\Components\Utils\Html 23 | */ 24 | private $createdEditable; 25 | 26 | /** 27 | * @return Mesour\DataGrid\ExtendedGrid 28 | */ 29 | public function getGrid() 30 | { 31 | return $this->getParent(); 32 | } 33 | 34 | public function getDataStructure() 35 | { 36 | try { 37 | return parent::getDataStructure(); 38 | } catch (Mesour\InvalidStateException $e) { 39 | $this->setDataStructure( 40 | Mesour\Editable\Structures\DataStructure::fromSource($this->getGrid()->getSource()) 41 | ); 42 | } 43 | return parent::getDataStructure(); 44 | } 45 | 46 | public function gridCreate($data = []) 47 | { 48 | 49 | } 50 | 51 | public function createInstance(Extensions\IExtension $extension, $name = null) 52 | { 53 | } 54 | 55 | public function afterGetCount($count) 56 | { 57 | $editableDataStructure = $this->getDataStructure(); 58 | $dataStructure = $this->getGrid()->getSource()->getDataStructure(); 59 | foreach ($this->getGrid()->getColumns() as $column) { 60 | if ( 61 | $column instanceof Mesour\DataGrid\Column\IInlineEdit 62 | && $column->hasEditable() 63 | && !$editableDataStructure->hasField($this->getName()) 64 | ) { 65 | if ($dataStructure->hasColumn($column->getName())) { 66 | $this->determineAndAttachField( 67 | $editableDataStructure, 68 | $column, 69 | $dataStructure->getColumn($column->getName()) 70 | ); 71 | } else { 72 | $this->determineAndAttachField($editableDataStructure, $column); 73 | } 74 | } 75 | } 76 | } 77 | 78 | private function determineAndAttachField( 79 | Mesour\Editable\Structures\IDataStructure $structure, 80 | Mesour\DataGrid\Column\IColumn $column, 81 | Mesour\Sources\Structures\Columns\IColumnStructure $columnStructure = null 82 | ) 83 | { 84 | if ($column instanceof Mesour\DataGrid\Column\Text) { 85 | $structure->addText($column->getName(), $column->getHeader()); 86 | } elseif ($column instanceof Mesour\DataGrid\Column\Number) { 87 | $structure->addNumber($column->getName(), $column->getHeader()) 88 | ->setDecimalPoint($column->getDecimalPoint()) 89 | ->setDecimals($column->getDecimals()) 90 | ->setThousandSeparator($column->getThousandSeparator()) 91 | ->setUnit($column->getUnit()); 92 | } elseif ($column instanceof Mesour\DataGrid\Column\Date) { 93 | $structure->addDate($column->getName(), $column->getHeader()) 94 | ->setFormat($column->getFormat()); 95 | } 96 | } 97 | 98 | public function beforeFetchData($data = []) 99 | { 100 | $this->createdEditable = $this->create(); 101 | $this->getGrid()->setAttribute('data-mesour-editable', $this->createLinkName()); 102 | 103 | $editableDataStructure = $this->getDataStructure(); 104 | foreach ($this->getGrid()->getColumns() as $column) { 105 | if ( 106 | $column instanceof Mesour\DataGrid\Column\IInlineEdit 107 | && !$column->hasEditable() 108 | && $editableDataStructure->hasField($column->getName()) 109 | ) { 110 | $editableDataStructure->getField($column->getName())->setDisabled(true); 111 | } 112 | } 113 | } 114 | 115 | public function afterFetchData($currentData, $data = [], $rawData = []) 116 | { 117 | } 118 | 119 | public function attachToRenderer(Mesour\DataGrid\Renderer\IGridRenderer $renderer, $data = [], $rawData = []) 120 | { 121 | $renderer->setComponent('editable', $this->createdEditable); 122 | } 123 | 124 | public function reset($hard = false) 125 | { 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Selection/SelectionExtension.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class SelectionExtension extends Mesour\UI\Selection implements ISelection 18 | { 19 | 20 | use Mesour\Components\Localization\Translatable; 21 | use Mesour\Components\Security\Authorised; 22 | 23 | private $selection_used = false; 24 | 25 | private $disabled = false; 26 | 27 | /** @var Mesour\UI\DropDown */ 28 | private $links; 29 | 30 | public function __construct($name = null, Mesour\Components\ComponentModel\IContainer $parent = null) 31 | { 32 | parent::__construct($name, $parent); 33 | $this->links = new Links($this); 34 | } 35 | 36 | /** 37 | * @return Links 38 | */ 39 | public function getLinks() 40 | { 41 | return $this->links; 42 | } 43 | 44 | public function getSpecialColumn() 45 | { 46 | $this->selection_used = true; 47 | return new Mesour\DataGrid\Column\Selection; 48 | } 49 | 50 | public function getSpecialColumnName() 51 | { 52 | return '_grid_selection'; 53 | } 54 | 55 | public function isGetSpecialColumnUsed() 56 | { 57 | return $this->selection_used; 58 | } 59 | 60 | /** 61 | * @return bool 62 | */ 63 | public function isDisabled() 64 | { 65 | return $this->disabled; 66 | } 67 | 68 | public function setDisabled($disabled = true) 69 | { 70 | $this->disabled = (bool) $disabled; 71 | return $this; 72 | } 73 | 74 | public function handleOnSelect($name, array $items) 75 | { 76 | $link = $this->getLinks()->getLink($name); 77 | if (!$link->isAllowed()) { 78 | throw new Mesour\InvalidStateException('Invalid permissions.'); 79 | } 80 | $link->onCall($items); 81 | } 82 | 83 | public function gridCreate($data = []) 84 | { 85 | } 86 | 87 | public function createInstance(Mesour\DataGrid\Extensions\IExtension $extension, $name = null) 88 | { 89 | } 90 | 91 | public function afterGetCount($count) 92 | { 93 | } 94 | 95 | public function beforeFetchData($data = []) 96 | { 97 | } 98 | 99 | /** 100 | * @return Mesour\DataGrid\ExtendedGrid 101 | */ 102 | public function getGrid() 103 | { 104 | return $this->getParent(); 105 | } 106 | 107 | public function afterFetchData($currentData, $data = [], $rawData = []) 108 | { 109 | $items = []; 110 | $statuses = []; 111 | foreach ($this->getGrid()->getColumns() as $column) { 112 | if ($column instanceof Mesour\DataGrid\Column\Status) { 113 | foreach ($currentData as $item) { 114 | $currentItem = null; 115 | foreach ($column as $statusItem) { 116 | /** @var Mesour\DataGrid\Column\Status\IStatusItem $statusItem */ 117 | $options = $statusItem->getStatusOptions(); 118 | if ($options) { 119 | if ($item[$column->getName()] == $statusItem->getStatus()) { 120 | $currentItem = $options; 121 | break; 122 | } 123 | } 124 | } 125 | if (!is_null($currentItem)) { 126 | $statusName = reset($currentItem); 127 | $currentKey = key($currentItem); 128 | $statuses[$currentKey] = $statusName; 129 | $items[$item[$this->getGrid()->getPrimaryKey()]] = (string) $currentKey; 130 | } 131 | } 132 | } 133 | } 134 | $this->setItems($items); 135 | foreach ($statuses as $status => $statusName) { 136 | $this->addStatus($status, $statusName); 137 | } 138 | } 139 | 140 | public function attachToRenderer(Mesour\DataGrid\Renderer\IGridRenderer $renderer, $data = [], $rawData = []) 141 | { 142 | $renderer->setComponent('selection', $this->getLinks()->create($data)); 143 | } 144 | 145 | public function reset($hard = false) 146 | { 147 | } 148 | 149 | public function __clone() 150 | { 151 | parent::__clone(); 152 | $this->links = clone $this->links; 153 | $this->links->setParent($this); 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/core/InlineEdit.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | abstract class InlineEdit extends Filtering implements IInlineEdit 18 | { 19 | 20 | private $editable = true; 21 | 22 | private $reference; 23 | 24 | public function getBodyAttributes($data, $need = true, $rawData = []) 25 | { 26 | $attributes = parent::getBodyAttributes($data, $need, $rawData); 27 | 28 | return $this->getEditableAttributes($data, $attributes); 29 | } 30 | 31 | /** 32 | * @return Mesour\UI\Button 33 | */ 34 | public function createEditButton() 35 | { 36 | $editButton = new Mesour\UI\Button('_edit_button'); 37 | $editButton->setIcon('pencil') 38 | ->setClassName(''); 39 | 40 | $editButton->setAttribute('data-grid-is-edit', 'true'); 41 | 42 | return $editButton; 43 | } 44 | 45 | public function setEditable($editable = true) 46 | { 47 | $this->editable = (bool) $editable; 48 | return $this; 49 | } 50 | 51 | public function hasEditable() 52 | { 53 | if (!$this->editable) { 54 | return false; 55 | } 56 | $editable = $this->getGrid()->getExtension('IEditable', false); 57 | return $editable instanceof Mesour\DataGrid\Extensions\Editable\IEditable 58 | && $editable->isAllowed() 59 | && !$editable->isDisabled(); 60 | } 61 | 62 | /** 63 | * @return Mesour\Editable\Structures\Fields\IStructureElementField|Mesour\Editable\Structures\Fields\IStructureField 64 | */ 65 | public function getEditableField() 66 | { 67 | /** @var Mesour\DataGrid\Extensions\Editable\EditableExtension $editable */ 68 | $editable = $this->getGrid()->getExtension('IEditable'); 69 | return $editable->getDataStructure()->getField($this->getName()); 70 | } 71 | 72 | public function getEditableAttributes($data, array $attributes = [], $itemData = []) 73 | { 74 | if ($this->hasEditable()) { 75 | $value = null; 76 | $identifier = null; 77 | 78 | $dataStructure = $this->getGrid()->getSource()->getDataStructure(); 79 | if ($dataStructure->hasColumn($this->getName())) { 80 | $column = $dataStructure->getColumn($this->getName()); 81 | if ( 82 | $column instanceof Mesour\Sources\Structures\Columns\ManyToOneColumnStructure 83 | || $column instanceof Mesour\Sources\Structures\Columns\OneToOneColumnStructure 84 | ) { 85 | $value = $data[$column->getReferencedColumn()]; 86 | $identifier = $data[$this->getGrid()->getSource()->getPrimaryKey()]; 87 | } elseif ( 88 | $itemData && ( 89 | $column instanceof Mesour\Sources\Structures\Columns\OneToManyColumnStructure 90 | || $column instanceof Mesour\Sources\Structures\Columns\ManyToManyColumnStructure 91 | ) 92 | ) { 93 | $value = $itemData[$column->getTableStructure()->getPrimaryKey()]; 94 | } 95 | 96 | $value = $value ?: $data[$this->getName()]; 97 | 98 | if ( 99 | !$column instanceof Mesour\Sources\Structures\Columns\OneToManyColumnStructure 100 | && !$column instanceof Mesour\Sources\Structures\Columns\ManyToManyColumnStructure 101 | && ((!is_array($value) && $value !== null) || (is_array($value) && count($value) > 0)) 102 | ) { 103 | $attributes['data-grid-edit'] = 'true'; 104 | } 105 | } 106 | 107 | $valueAttributes = $attributes + [ 108 | 'data-grid-value' => $value ?: $data[$this->getName()], 109 | ]; 110 | if ($itemData) { 111 | return $valueAttributes; 112 | } 113 | 114 | return $valueAttributes + [ 115 | 'data-grid-editable' => $this->getName(), 116 | 'data-grid-id' => $identifier ?: $identifier = $data[$this->getGrid()->getSource()->getPrimaryKey()], 117 | ]; 118 | } 119 | return []; 120 | } 121 | 122 | public function setReference($table) 123 | { 124 | $this->reference = (string) $table; 125 | return $this; 126 | } 127 | 128 | public function getReference() 129 | { 130 | return $this->reference; 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Ordering/OrderingExtension.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class OrderingExtension extends Mesour\DataGrid\Extensions\Base implements IOrdering 18 | { 19 | 20 | use Mesour\Components\Security\Authorised; 21 | 22 | private $defaultOrder = []; 23 | 24 | private $disabled = false; 25 | 26 | private $multi = false; 27 | 28 | private $ordering = []; 29 | 30 | /** @var Mesour\Components\Session\ISessionSection */ 31 | private $privateSession; 32 | 33 | public function attached(Mesour\Components\ComponentModel\IContainer $parent) 34 | { 35 | parent::attached($parent); 36 | $this->privateSession = $this->getSession()->getSection($this->createLinkName()); 37 | $this->ordering = $this->privateSession->get('ordering', []); 38 | return $this; 39 | } 40 | 41 | public function setDefaultOrder($key, $sorting = 'ASC') 42 | { 43 | $this->defaultOrder = [$key, $sorting]; 44 | } 45 | 46 | public function setDisabled($disabled = true) 47 | { 48 | $this->disabled = $disabled; 49 | } 50 | 51 | public function isDisabled() 52 | { 53 | return $this->disabled; 54 | } 55 | 56 | public function enableMulti() 57 | { 58 | $this->multi = true; 59 | } 60 | 61 | /** 62 | * Get ordering for column by column ID 63 | * @param int $columnId 64 | * @return NULL|string(ASC|DESC) 65 | */ 66 | public function getOrdering($columnId) 67 | { 68 | if (count($this->defaultOrder) > 0 69 | && $this->ordering === 0 70 | && $this->defaultOrder[0] === $columnId 71 | ) { 72 | return $this->defaultOrder[1]; 73 | } 74 | if (!isset($this->ordering[$columnId])) { 75 | return null; 76 | } else { 77 | return $this->ordering[$columnId]; 78 | } 79 | } 80 | 81 | /** 82 | * @return Mesour\DataGrid\ExtendedGrid 83 | */ 84 | public function getGrid() 85 | { 86 | return $this->getParent(); 87 | } 88 | 89 | public function applyOrder() 90 | { 91 | $c = count($this->ordering); 92 | if ($c > 0) { 93 | foreach ($this->ordering as $key => $howToOrder) { 94 | if (!in_array($key, $this->getGrid()->getRealColumnNames())) { 95 | unset($this->ordering[$key]); 96 | } else { 97 | $this->getGrid()->getSource()->orderBy($key, $howToOrder); 98 | } 99 | } 100 | } 101 | if ($c === 0 && count($this->defaultOrder) > 0) { 102 | $this->getGrid()->getSource() 103 | ->orderBy($this->defaultOrder[0], $this->defaultOrder[1]); 104 | } 105 | } 106 | 107 | public function reset($hard = false) 108 | { 109 | if ($hard) { 110 | $this->ordering = []; 111 | $this->privateSession->get('set', $this->ordering); 112 | } 113 | } 114 | 115 | public function handleOrdering($key) 116 | { 117 | $pager = $this->getGrid()->getComponent('pager', false); 118 | if ($pager instanceof Mesour\DataGrid\Extensions\Pager\IPager) { 119 | $pager->reset(); 120 | } 121 | 122 | if (!isset($this->ordering[$key])) { 123 | $this->ordering[$key] = 'ASC'; 124 | } elseif ($this->ordering[$key] === 'ASC') { 125 | $this->ordering[$key] = 'DESC'; 126 | } else { 127 | unset($this->ordering[$key]); 128 | } 129 | if (!$this->multi) { 130 | $current = isset($this->ordering[$key]) ? $this->ordering[$key] : null; 131 | if (!is_null($current)) { 132 | $this->ordering = []; 133 | $this->ordering[$key] = $current; 134 | } 135 | } 136 | $this->privateSession->set('ordering', $this->ordering); 137 | } 138 | 139 | public function beforeFetchData($data = []) 140 | { 141 | $this->applyOrder(); 142 | } 143 | 144 | public function afterGetCount($count) 145 | { 146 | $this->create(); 147 | foreach ($this->getGrid()->getColumns() as $column) { 148 | if ($column instanceof Mesour\DataGrid\Column\IOrdering 149 | && $this->getGrid()->getExtension('IOrdering')->isDisabled() 150 | ) { 151 | $column->setOrdering(false); 152 | } 153 | } 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/Image.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Image extends BaseColumn 18 | { 19 | 20 | private $maxWidth = 0; 21 | 22 | private $maxHeight = 0; 23 | 24 | private $previewPath; 25 | 26 | private $previewRootPath; 27 | 28 | private $rootPath; 29 | 30 | /** 31 | * Set max width in pixels 32 | * @param mixed $maxWidth 0 = unlimited 33 | * @return $this 34 | */ 35 | public function setMaxWidth($maxWidth) 36 | { 37 | $this->maxWidth = (int) $maxWidth; 38 | return $this; 39 | } 40 | 41 | /** 42 | * Set max height in pixels 43 | * @param mixed $maxHeight 0 = unlimited 44 | * @return $this 45 | */ 46 | public function setMaxHeight($maxHeight) 47 | { 48 | $this->maxHeight = (int) $maxHeight; 49 | return $this; 50 | } 51 | 52 | /** 53 | * @param string $previewWebPath 54 | * @param null $previewRootPath default $_SERVER['DOCUMENT_ROOT'] 55 | * @param null $imageRootPath default $_SERVER['DOCUMENT_ROOT'] 56 | * @return $this 57 | * @throws Mesour\InvalidArgumentException 58 | */ 59 | public function setPreviewPath($previewWebPath, $previewRootPath = null, $imageRootPath = null) 60 | { 61 | if (!is_null($imageRootPath) && !is_string($imageRootPath)) { 62 | throw new Mesour\InvalidArgumentException('Image root path must be string. ' . gettype($imageRootPath) . ' given.'); 63 | } elseif (is_null($imageRootPath)) { 64 | $this->rootPath = $_SERVER['DOCUMENT_ROOT']; 65 | } else { 66 | $this->rootPath = $imageRootPath; 67 | } 68 | 69 | if (!is_null($previewRootPath) && !is_string($previewRootPath)) { 70 | throw new Mesour\InvalidArgumentException('Preview root path must be string. ' . gettype($previewRootPath) . ' given.'); 71 | } elseif (is_null($previewRootPath)) { 72 | $this->previewRootPath = $_SERVER['DOCUMENT_ROOT']; 73 | } else { 74 | $this->previewRootPath = $previewRootPath; 75 | } 76 | 77 | $this->previewPath = $previewWebPath; 78 | return $this; 79 | } 80 | 81 | public function getHeaderAttributes() 82 | { 83 | return [ 84 | 'class' => 'grid-column-' . $this->getName(), 85 | ]; 86 | } 87 | 88 | public function getBodyContent($data, $rawData) 89 | { 90 | $src = $this->tryInvokeCallback([$this, $rawData]); 91 | if ($src === self::NO_CALLBACK) { 92 | $src = $data[$this->getName()]; 93 | } 94 | 95 | $img = Mesour\Components\Utils\Html::el('img'); 96 | if (!$this->previewPath) { 97 | if ($this->maxWidth > 0) { 98 | $img->style('max-width:' . $this->fixPixels($this->maxWidth)); 99 | } 100 | if ($this->maxHeight > 0) { 101 | $img->style('max-height:' . $this->fixPixels($this->maxHeight)); 102 | } 103 | } else { 104 | $imageName = str_replace(['/', '\\'], '_', $src); 105 | $imageDir = $this->previewRootPath . '/' . $this->previewPath; 106 | $imagePath = $imageDir . '/' . $imageName; 107 | 108 | @mkdir($imageDir); 109 | 110 | if (!is_dir($imageDir)) { 111 | throw new Mesour\InvalidArgumentException('Image preview dir "' . $imageDir . '" does not exist.'); 112 | } 113 | 114 | if (!is_file($imagePath)) { 115 | $image = Mesour\Components\Utils\Image::fromFile($this->createFullPath($src)); 116 | $newWidth = $width = $image->getWidth(); 117 | $newHeight = $height = $image->getHeight(); 118 | 119 | if ($this->maxWidth > 0) { 120 | if ($width > $this->maxWidth) { 121 | $newWidth = $this->maxWidth; 122 | } 123 | } 124 | if ($this->maxHeight > 0) { 125 | if ($height > $this->maxHeight) { 126 | $newHeight = $this->maxHeight; 127 | } 128 | } 129 | 130 | $image->resize($newWidth, $newHeight, Mesour\Components\Utils\Image::FIT); 131 | $image->save($imagePath); 132 | } 133 | 134 | $src = $this->previewPath . '/' . $imageName; 135 | } 136 | $img->src($src); 137 | return $img; 138 | } 139 | 140 | private function createFullPath($imageFile) 141 | { 142 | return $this->rootPath . $imageFile; 143 | } 144 | 145 | private function fixPixels($value) 146 | { 147 | return is_numeric($value) ? ($value . 'px') : $value; 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/Status.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @method Mesour\DataGrid\Column\Status\IStatusItem[] getComponents() 18 | * @method Mesour\DataGrid\Column\Status\IStatusItem getComponent($name, $need = false) 19 | */ 20 | class Status extends Filtering implements IExportable 21 | { 22 | 23 | use Mesour\Components\Security\Authorised; 24 | use Mesour\Icon\HasIcon; 25 | 26 | static public $no_active_class = 'no-active-button'; 27 | 28 | /** 29 | * @param string $name 30 | * @return Mesour\DataGrid\Column\Status\StatusButton 31 | */ 32 | public function addButton($name) 33 | { 34 | $button = new Mesour\DataGrid\Column\Status\StatusButton($name); 35 | $this->addComponent($button); 36 | return $button; 37 | } 38 | 39 | /** 40 | * @param string $name 41 | * @return Mesour\DataGrid\Column\Status\StatusDropDown 42 | */ 43 | public function addDropDown($name) 44 | { 45 | $dropDown = new Mesour\DataGrid\Column\Status\StatusDropDown($name); 46 | $this->addComponent($dropDown); 47 | return $dropDown; 48 | } 49 | 50 | public function addComponent(Mesour\Components\ComponentModel\IComponent $component, $name = null) 51 | { 52 | if (!$component instanceof Mesour\DataGrid\Column\Status\IStatusItem) { 53 | throw new Mesour\InvalidArgumentException('Can add children for status column only if is instance of Mesour\DataGrid\Column\Status\IStatusItem.'); 54 | } 55 | return parent::addComponent($component, $name); 56 | } 57 | 58 | public function setPermission($resource = null, $privilege = null) 59 | { 60 | foreach ($this->getComponents() as $component) { 61 | $component->setPermission($resource, $privilege); 62 | //todo: na dropdownu udělat také set permission aby to nastavilo práva na všech buttonech 63 | } 64 | return $this; 65 | } 66 | 67 | public function getHeaderAttributes() 68 | { 69 | return array_merge([ 70 | 'class' => 'grid-column-' . $this->getName() . ' column-status', 71 | ], parent::getHeaderAttributes()); 72 | } 73 | 74 | public function getBodyAttributes($data, $need = true, $rawData = []) 75 | { 76 | $class = 'button-component'; 77 | $activeCount = 0; 78 | foreach ($this as $button) { 79 | /** @var Mesour\DataGrid\Column\Status\IStatusItem $button */ 80 | if ($button->isActive($this->getName(), $data)) { 81 | $class .= ' is-' . $button->getStatus(); 82 | $activeCount++; 83 | } 84 | } 85 | if ($activeCount === 0) { 86 | $class .= ' ' . self::$no_active_class; 87 | } 88 | return parent::mergeAttributes($data, ['class' => $class]); 89 | } 90 | 91 | public function getBodyContent($data, $rawData, $export = false) 92 | { 93 | $buttons = $export ? [] : ''; 94 | 95 | $activeCount = 0; 96 | foreach ($this as $button) { 97 | /** @var Mesour\DataGrid\Column\Status\IStatusItem $button */ 98 | $isActive = false; 99 | 100 | if ($button->isActive($this->getName(), $data)) { 101 | $isActive = true; 102 | } 103 | 104 | $this->tryInvokeCallback([$rawData, $this, $isActive]); 105 | 106 | $button->setOption('data', $data); 107 | if ($isActive && !$export) { 108 | $buttons .= $button->create() . ' '; 109 | $activeCount++; 110 | } elseif ($isActive && $export) { 111 | $buttons[] = $button->getStatusName(); 112 | } 113 | } 114 | 115 | $container = Mesour\Components\Utils\Html::el('div', ['class' => 'status-buttons buttons-count-' . $activeCount]); 116 | $container->setHtml($export ? implode('|', $buttons) : $buttons); 117 | 118 | return $export ? trim(strip_tags($container)) : $container; 119 | } 120 | 121 | public function attachToFilter(Mesour\DataGrid\Extensions\Filter\IFilter $filter, $hasCheckers) 122 | { 123 | parent::attachToFilter($filter, $hasCheckers); 124 | 125 | $statuses = []; 126 | foreach ($this->getComponents() as $component) { 127 | $statuses[$component->getStatus()] = $component->getStatusName(); 128 | } 129 | 130 | $filter->setCustomReference($this->getName(), $statuses); 131 | 132 | $item = $filter->addTextFilter($this->getName(), $this->getHeader(), $statuses); 133 | $item->setMainFilter(false); 134 | $item->setCheckers($hasCheckers); 135 | $item->setReferenceSettings(Mesour\DataGrid\Extensions\Filter\FilterExtension::PREDEFINED_KEY); 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Filter/FilterExtension.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class FilterExtension extends Mesour\UI\Filter implements IFilter 19 | { 20 | 21 | use Mesour\Components\Security\Authorised; 22 | 23 | private $isInline = true; 24 | 25 | private $disabled = false; 26 | 27 | /** @var Mesour\Components\Utils\Html|string */ 28 | private $createdFilter; 29 | 30 | protected $filterIcon = 'filter'; 31 | 32 | /** 33 | * @return bool 34 | */ 35 | public function isDisabled() 36 | { 37 | return $this->disabled; 38 | } 39 | 40 | public function setDisabled($disabled = true) 41 | { 42 | $this->disabled = (bool) $disabled; 43 | return $this; 44 | } 45 | 46 | public function setInline($inline = true) 47 | { 48 | $this->isInline = $inline; 49 | $this->setupInlineFilterAttribute(); 50 | return $this; 51 | } 52 | 53 | public function setFilterIcon($filterIcon) 54 | { 55 | $this->filterIcon = $filterIcon; 56 | return $this; 57 | } 58 | 59 | public function isInline() 60 | { 61 | return $this->isInline; 62 | } 63 | 64 | public function gridCreate($data = []) 65 | { 66 | $this->onFilter[] = function (IFilter $currentFilter) { 67 | $this->updateFilter($currentFilter); 68 | $this->getGrid()->onFilter($currentFilter); 69 | }; 70 | $this->setSource($this->getGrid()->getSource()); 71 | $fullData = $this->beforeCreate(); 72 | 73 | foreach ($this->getGrid()->getColumns() as $column) { 74 | if ($column instanceof Mesour\DataGrid\Column\IFiltering) { 75 | if ($column->hasFiltering()) { 76 | $column->attachToFilter($this, count($fullData) > 0); 77 | } 78 | } 79 | } 80 | if (!$this->isInline()) { 81 | $this->setOption('data', $data); 82 | $this->createdFilter = $this->create(); 83 | } else { 84 | $this->beforeRender(); 85 | $this->createdFilter = $this->createHiddenInput($fullData); 86 | } 87 | $this->updateFilter($this); 88 | } 89 | 90 | /** 91 | * @return Mesour\DataGrid\ExtendedGrid 92 | */ 93 | public function getGrid() 94 | { 95 | return $this->getParent(); 96 | } 97 | 98 | public function createInstance(Mesour\DataGrid\Extensions\IExtension $extension, $name = null) 99 | { 100 | $this->setupInlineFilterAttribute(); 101 | } 102 | 103 | private function setupInlineFilterAttribute() 104 | { 105 | $this->getGrid()->setAttribute( 106 | 'data-mesour-enabled-filter', 107 | (int) ($this->isInline() && !$this->isDisabled()) 108 | ); 109 | } 110 | 111 | public function afterGetCount($count) 112 | { 113 | foreach ($this->getGrid()->getColumns() as $column) { 114 | if ( 115 | $column instanceof Mesour\DataGrid\Column\IFiltering 116 | && $this->isInline() 117 | && isset($this[$column->getName()]) 118 | ) { 119 | $column->setInline(); 120 | $this[$column->getName()]->setText($this->createNewIcon($this->filterIcon), false); 121 | } 122 | } 123 | } 124 | 125 | public function beforeFetchData($data = []) 126 | { 127 | } 128 | 129 | public function afterFetchData($currentData, $data = [], $rawData = []) 130 | { 131 | 132 | } 133 | 134 | public function attachToRenderer(Mesour\DataGrid\Renderer\IGridRenderer $renderer, $data = [], $rawData = []) 135 | { 136 | $filterPrototype = $this->getGrid()->getFilterPrototype(); 137 | $filterPrototype->add($this->createdFilter); 138 | if ($this->createdFilter->getName() === 'input') { 139 | $filterPrototype->add($this->getModal()->create()); 140 | } 141 | $renderer->setComponent('filter', $filterPrototype); 142 | } 143 | 144 | public function reset($hard = false) 145 | { 146 | } 147 | 148 | /** 149 | * @param IFilter $filter 150 | * @throws 151 | * @internal 152 | */ 153 | private function updateFilter(IFilter $filter) 154 | { 155 | if (!$this->isDisabled()) { 156 | $source = $this->getGrid()->getSource(); 157 | foreach ($filter->getValues() as $name => $value) { 158 | $type = isset($value['type']) ? $value['type'] : 'text'; 159 | foreach ($value as $key => $val) { 160 | switch ($key) { 161 | case 'checkers': 162 | $source->applyCheckers($name, $val, $type); 163 | break; 164 | case 'custom': 165 | $source->applyCustom($name, $val, $type); 166 | break; 167 | } 168 | } 169 | } 170 | } 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/ExtendedGrid.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @method null onSort($data, $itemId) 18 | * @method null onFilter(Extensions\Filter\IFilter|Extensions\SimpleFilter\ISimpleFilter $filter) 19 | */ 20 | abstract class ExtendedGrid extends BaseGrid 21 | { 22 | 23 | const FILTER_WRAPPER = 'filter-wrapper'; 24 | 25 | const PAGER_WRAPPER = 'pager-wrapper'; 26 | 27 | static public $defaults = [ 28 | self::WRAPPER => [ 29 | 'el' => 'div', 30 | 'attributes' => [ 31 | 'class' => 'mesour-datagrid', 32 | ], 33 | ], 34 | self::PAGER_WRAPPER => [ 35 | 'el' => 'div', 36 | 'attributes' => [ 37 | 'class' => 'mesour-datagrid-pager', 38 | ], 39 | ], 40 | self::FILTER_WRAPPER => [ 41 | 'el' => 'div', 42 | 'attributes' => [ 43 | 'class' => 'mesour-datagrid-filter', 44 | ], 45 | ], 46 | ]; 47 | 48 | public function __construct($name = null, Mesour\Components\ComponentModel\IContainer $parent = null) 49 | { 50 | parent::__construct($name, $parent); 51 | $this->option = self::$defaults; 52 | } 53 | 54 | /** @var Mesour\Components\Utils\Html */ 55 | protected $pagerWrapper; 56 | 57 | /** @var Mesour\Components\Utils\Html */ 58 | protected $filterWrapper; 59 | 60 | public $onSort = []; 61 | 62 | /** 63 | * @var array 64 | * @deprecated not called 65 | */ 66 | public $onEditCell = []; 67 | 68 | public $onFilter = []; 69 | 70 | /** 71 | * @param int $pageLimit 72 | * @return Extensions\Pager\IPager 73 | */ 74 | public function enablePager($pageLimit = 20) 75 | { 76 | $pager = $this->getExtension('IPager'); 77 | /** @var Extensions\Pager\IPager $pager */ 78 | $pager->getPaginator()->setItemsPerPage($pageLimit); 79 | return $pager; 80 | } 81 | 82 | /** 83 | * @param string $cacheDir 84 | * @param string|null $fileName 85 | * @return Extensions\Export\IExport 86 | */ 87 | public function enableExport($cacheDir, $fileName = null) 88 | { 89 | return $this->getExtension('IExport') 90 | ->setCacheDir($cacheDir) 91 | ->setFileName($fileName); 92 | } 93 | 94 | /** 95 | * @param bool $inline 96 | * @return Extensions\Filter\IFilter 97 | */ 98 | public function enableFilter($inline = true) 99 | { 100 | if ($this->getExtension('ISimpleFilter', false)) { 101 | throw new Mesour\InvalidStateException('Simple filter is already used.'); 102 | } 103 | return $this->getExtension('IFilter')->setInline($inline); 104 | } 105 | 106 | /** 107 | * @param array $allowedColumns 108 | * @return Extensions\SimpleFilter\ISimpleFilter 109 | */ 110 | public function enableSimpleFilter(array $allowedColumns = []) 111 | { 112 | if ($this->getExtension('IFilter', false)) { 113 | throw new Mesour\InvalidStateException('Normal filter is already used.'); 114 | } 115 | return $this->getExtension('ISimpleFilter')->setAllowedColumns($allowedColumns); 116 | } 117 | 118 | /** 119 | * @return Extensions\Selection\ISelection 120 | * @throws Mesour\InvalidArgumentException 121 | */ 122 | public function enableRowSelection() 123 | { 124 | return $this->getExtension('ISelection'); 125 | } 126 | 127 | /** 128 | * @param string $columnName 129 | * @return Extensions\Sortable\ISortable 130 | * @throws Mesour\InvalidArgumentException 131 | */ 132 | public function enableSortable($columnName) 133 | { 134 | return $this->getExtension('ISortable') 135 | ->setColumnName($columnName); 136 | } 137 | 138 | /** 139 | * @return Extensions\Editable\EditableExtension 140 | */ 141 | public function enableEditable() 142 | { 143 | return $this->getExtension('IEditable'); 144 | } 145 | 146 | public function getPagerPrototype() 147 | { 148 | return $this->pagerWrapper 149 | ? $this->pagerWrapper 150 | : ($this->pagerWrapper = Mesour\Components\Utils\Html::el( 151 | $this->option[self::PAGER_WRAPPER]['el'], 152 | $this->option[self::PAGER_WRAPPER]['attributes'] 153 | )); 154 | } 155 | 156 | public function getFilterPrototype() 157 | { 158 | return $this->filterWrapper 159 | ? $this->filterWrapper 160 | : ($this->filterWrapper = Mesour\Components\Utils\Html::el( 161 | $this->option[self::FILTER_WRAPPER]['el'], 162 | $this->option[self::FILTER_WRAPPER]['attributes'] 163 | )); 164 | } 165 | 166 | public function create($data = []) 167 | { 168 | $filter = $this->getExtension('IFilter', false); 169 | $this->onRenderColumnHeader[] = function (Column\IColumn $column, $i, $columnCount) use ($filter) { 170 | if ($i + 1 === $columnCount && $filter instanceof Extensions\Filter\IFilter) { 171 | $column->setFilterReset($filter); 172 | } 173 | }; 174 | return parent::create($data); 175 | } 176 | 177 | public function __clone() 178 | { 179 | $this->pagerWrapper = null; 180 | $this->filterWrapper = null; 181 | parent::__clone(); 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /examples/sources/array_source.php: -------------------------------------------------------------------------------- 1 | '1', 'action' => '0', 'group_id' => '1', 'name' => 'Peter', 'surname' => NULL, 'email' => 'john.doe@test.xx', 'last_login' => '2014-09-01 06:27:32', 'amount' => '1561.456542', 'avatar' => 'avatar/01.png', 'sort' => '100', 'timestamp' => '1418255325'], 5 | ['user_id' => '2', 'action' => '1', 'group_id' => '2', 'name' => 'John', 'surname' => 'Doe', 'email' => 'peter.larson@test.xx', 'last_login' => '2014-09-09 13:37:32', 'amount' => '15220.654', 'avatar' => 'avatar/02.png', 'sort' => '160', 'timestamp' => '1418255330'], 6 | ['user_id' => '3', 'action' => '1', 'group_id' => '2', 'name' => 'Claude', 'surname' => 'Graves', 'email' => 'claude.graves@test.xx', 'last_login' => '2014-09-02 14:17:32', 'amount' => '9876.465498', 'avatar' => 'avatar/03.png', 'sort' => '180', 'timestamp' => '1418255311'], 7 | ['user_id' => '4', 'action' => '0', 'group_id' => '3', 'name' => 'Stuart', 'surname' => 'Norman', 'email' => 'stuart.norman@test.xx', 'last_login' => '2014-09-09 18:39:18', 'amount' => '98766.2131', 'avatar' => 'avatar/04.png', 'sort' => '120', 'timestamp' => '1418255328'], 8 | ['user_id' => '5', 'action' => '1', 'group_id' => '1', 'name' => 'Kathy', 'surname' => 'Arnold', 'email' => 'kathy.arnold@test.xx', 'last_login' => '2014-09-07 10:24:07', 'amount' => '456.987', 'avatar' => 'avatar/05.png', 'sort' => '140', 'timestamp' => '1418155313'], 9 | ['user_id' => '6', 'action' => '0', 'group_id' => '3', 'name' => 'Jan', 'surname' => 'Wilson', 'email' => 'jan.wilson@test.xx', 'last_login' => '2014-09-03 13:15:22', 'amount' => '123', 'avatar' => 'avatar/06.png', 'sort' => '150', 'timestamp' => '1418255318'], 10 | ['user_id' => '7', 'action' => '0', 'group_id' => '1', 'name' => 'Alberta', 'surname' => 'Erickson', 'email' => 'alberta.erickson@test.xx', 'last_login' => '2014-08-06 13:37:17', 'amount' => '98753.654', 'avatar' => 'avatar/07.png', 'sort' => '110', 'timestamp' => '1418255327'], 11 | ['user_id' => '8', 'action' => '1', 'group_id' => '3', 'name' => 'Ada', 'surname' => 'Wells', 'email' => 'ada.wells@test.xx', 'last_login' => '2014-08-12 11:25:16', 'amount' => '852.3654', 'avatar' => 'avatar/08.png', 'sort' => '70', 'timestamp' => '1418255332'], 12 | ['user_id' => '9', 'action' => '0', 'group_id' => '2', 'name' => 'Ethel', 'surname' => 'Figueroa', 'email' => 'ethel.figueroa@test.xx', 'last_login' => '2014-09-05 10:23:26', 'amount' => '45695.986', 'avatar' => 'avatar/09.png', 'sort' => '20', 'timestamp' => '1418255305'], 13 | ['user_id' => '10', 'action' => '1', 'group_id' => '3', 'name' => 'Ian', 'surname' => 'Goodwin', 'email' => 'ian.goodwin@test.xx', 'last_login' => '2014-09-04 12:26:19', 'amount' => '1236.9852', 'avatar' => 'avatar/10.png', 'sort' => '130', 'timestamp' => '1418255331'], 14 | ['user_id' => '11', 'action' => '1', 'group_id' => '2', 'name' => 'Francis', 'surname' => 'Hayes', 'email' => 'francis.hayes@test.xx', 'last_login' => '2014-09-03 10:16:17', 'amount' => '5498.345', 'avatar' => 'avatar/11.png', 'sort' => '0', 'timestamp' => '1418255293'], 15 | ['user_id' => '12', 'action' => '0', 'group_id' => '1', 'name' => 'Erma', 'surname' => 'Burns', 'email' => 'erma.burns@test.xx', 'last_login' => '2014-07-02 15:42:15', 'amount' => '63287.9852', 'avatar' => 'avatar/12.png', 'sort' => '60', 'timestamp' => '1418255316'], 16 | ['user_id' => '13', 'action' => '1', 'group_id' => '3', 'name' => 'Kristina', 'surname' => 'Jenkins', 'email' => 'kristina.jenkins@test.xx', 'last_login' => '2014-08-20 14:39:43', 'amount' => '74523.96549', 'avatar' => 'avatar/13.png', 'sort' => '40', 'timestamp' => '1418255334'], 17 | ['user_id' => '14', 'action' => '0', 'group_id' => '3', 'name' => 'Virgil', 'surname' => 'Hunt', 'email' => 'virgil.hunt@test.xx', 'last_login' => '2014-08-12 16:09:38', 'amount' => '65654.6549', 'avatar' => 'avatar/14.png', 'sort' => '30', 'timestamp' => '1418255276'], 18 | ['user_id' => '15', 'action' => '1', 'group_id' => '1', 'name' => 'Max', 'surname' => 'Martin', 'email' => 'max.martin@test.xx', 'last_login' => '2014-09-01 12:14:20', 'amount' => '541236.5495', 'avatar' => 'avatar/15.png', 'sort' => '170', 'timestamp' => '1418255317'], 19 | ['user_id' => '16', 'action' => '1', 'group_id' => '2', 'name' => 'Melody', 'surname' => 'Manning', 'email' => 'melody.manning@test.xx', 'last_login' => '2014-09-02 12:26:20', 'amount' => '9871.216', 'avatar' => 'avatar/16.png', 'sort' => '50', 'timestamp' => '1418255281'], 20 | ['user_id' => '17', 'action' => '1', 'group_id' => '3', 'name' => 'Catherine', 'surname' => 'Todd', 'email' => 'catherine.todd@test.xx', 'last_login' => '2014-06-11 15:14:39', 'amount' => '100.2', 'avatar' => 'avatar/17.png', 'sort' => '10', 'timestamp' => '1418255313'], 21 | ['user_id' => '18', 'action' => '0', 'group_id' => '1', 'name' => 'Douglas', 'surname' => 'Stanley', 'email' => 'douglas.stanley@test.xx', 'last_login' => '2014-04-16 15:22:18', 'amount' => '900', 'avatar' => 'avatar/18.png', 'sort' => '90', 'timestamp' => '1418255332'], 22 | ['user_id' => '19', 'action' => '0', 'group_id' => '3', 'name' => 'Patti', 'surname' => 'Diaz', 'email' => 'patti.diaz@test.xx', 'last_login' => '2014-09-11 12:17:16', 'amount' => '1500', 'avatar' => 'avatar/19.png', 'sort' => '80', 'timestamp' => '1418255275'], 23 | ['user_id' => '20', 'action' => '0', 'group_id' => '3', 'name' => 'John', 'surname' => 'Petterson', 'email' => 'john.petterson@test.xx', 'last_login' => '2014-10-10 10:10:10', 'amount' => '2500', 'avatar' => 'avatar/20.png', 'sort' => '190', 'timestamp' => '1418255275'] 24 | ]; 25 | 26 | $groups = [ 27 | ['id' => '2', 'name' => 'Group 2'], 28 | ['id' => '1', 'name' => 'Group 1'], 29 | ['id' => '3', 'name' => 'Group 3'], 30 | ]; 31 | 32 | $source = new \Mesour\DataGrid\Sources\ArrayGridSource('users', 'id', $data, [ 33 | 'group' => $groups 34 | ]); 35 | 36 | return $source; -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Column/Container.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class Container extends Filtering implements IExportable, IContainer 19 | { 20 | 21 | use Mesour\Icon\HasIcon; 22 | 23 | /** 24 | * @param string $name 25 | * @param string|null $header 26 | * @return Text 27 | */ 28 | public function addText($name, $header = null) 29 | { 30 | $column = new Text; 31 | $this->setColumn($column, $name, $header); 32 | return $column; 33 | } 34 | 35 | /** 36 | * @param string $name 37 | * @param string|null $header 38 | * @return Date 39 | */ 40 | public function addDate($name, $header = null) 41 | { 42 | $column = new Date; 43 | $this->setColumn($column, $name, $header); 44 | return $column; 45 | } 46 | 47 | /** 48 | * @param string $name 49 | * @param string|null $header 50 | * @return self 51 | */ 52 | public function addContainer($name, $header = null) 53 | { 54 | $column = new self; 55 | $column->setFiltering(false) 56 | ->setOrdering(false); 57 | $this->setColumn($column, $name, $header); 58 | return $column; 59 | } 60 | 61 | /** 62 | * @param string $name 63 | * @param string|null $header 64 | * @return Image 65 | */ 66 | public function addImage($name, $header = null) 67 | { 68 | $column = new Image; 69 | $this->setColumn($column, $name, $header); 70 | return $column; 71 | } 72 | 73 | /** 74 | * @param string $name 75 | * @param string|null $header 76 | * @return Status 77 | */ 78 | public function addStatus($name, $header = null) 79 | { 80 | $column = new Status; 81 | $this->setColumn($column, $name, $header); 82 | return $column; 83 | } 84 | 85 | /** 86 | * @param string $name 87 | * @param string|null $header 88 | * @return Template 89 | */ 90 | public function addTemplate($name, $header = null) 91 | { 92 | $column = new Template; 93 | $this->setColumn($column, $name, $header); 94 | return $column; 95 | } 96 | 97 | /** 98 | * @param string $name 99 | * @return Mesour\UI\Button( 100 | * @throws Mesour\InvalidArgumentException 101 | */ 102 | public function addButton($name) 103 | { 104 | $button = new Mesour\UI\Button($name); 105 | $button->setSize('btn-sm'); 106 | $this->addComponent($button); 107 | return $button; 108 | } 109 | 110 | /** 111 | * @param string $name 112 | * @return Mesour\UI\DropDown 113 | * @throws Mesour\InvalidArgumentException 114 | */ 115 | public function addDropDown($name) 116 | { 117 | $dropDown = new Mesour\UI\DropDown($name); 118 | $dropDown->getMainButton() 119 | ->setSize('btn-sm'); 120 | $this->addComponent($dropDown); 121 | return $dropDown; 122 | } 123 | 124 | public function attachToFilter(Mesour\DataGrid\Extensions\Filter\IFilter $filter, $hasCheckers) 125 | { 126 | parent::attachToFilter($filter, $hasCheckers); 127 | $item = $filter->addTextFilter($this->getName(), $this->getHeader()); 128 | $this->setUpFilterItem($item, $hasCheckers); 129 | } 130 | 131 | protected function setColumn(Render\IColumn $column, $name, $header = null) 132 | { 133 | $column->setHeader($header); 134 | return $this[$name] = $column; 135 | } 136 | 137 | public function getHeaderAttributes() 138 | { 139 | return [ 140 | 'class' => 'grid-column-' . $this->getName() . ' column-container', 141 | ]; 142 | } 143 | 144 | public function getBodyAttributes($data, $need = true, $rawData = []) 145 | { 146 | return parent::getBodyAttributes($data, false, $rawData); 147 | } 148 | 149 | public function getBodyContent($data, $rawData, $export = false) 150 | { 151 | if ( 152 | !isset($data->{$this->getName()}) 153 | && (property_exists($data, $this->getName()) && !is_null($data->{$this->getName()})) 154 | && ($this->hasFiltering() || $this->hasOrdering()) 155 | ) { 156 | throw new Mesour\OutOfRangeException('Column with name ' . $this->getName() . ' does not exists in data source.'); 157 | } 158 | 159 | $onlyButtons = true; 160 | $container = Mesour\Components\Utils\Html::el('span', ['class' => 'container-content']); 161 | foreach ($this as $control) { 162 | if (!$control instanceof Mesour\UI\Button && !$control instanceof Mesour\UI\DropDown) { 163 | $onlyButtons = false; 164 | } 165 | $span = Mesour\Components\Utils\Html::el('span'); 166 | 167 | if ($control instanceof Render\IColumn) { 168 | $span->addAttributes($control->getHeaderAttributes()); 169 | $span->addAttributes($control->getBodyAttributes($data)); 170 | } 171 | 172 | $fromCallback = $this->tryInvokeCallback([$this, $rawData, $span, $control]); 173 | 174 | if ($fromCallback === self::NO_CALLBACK) { 175 | if ($control instanceof Render\IColumn) { 176 | $content = $control->getBodyContent($data, $rawData); 177 | if (!is_null($content)) { 178 | $span->add($content); 179 | } 180 | } elseif ($control instanceof Mesour\Components\Control\IOptionsControl) { 181 | $control->setOption('data', $data); 182 | $span->add($control->create()); 183 | } else { 184 | $span->add($control->render()); 185 | } 186 | } else { 187 | $span->add($fromCallback); 188 | } 189 | 190 | $container->add($span); 191 | $container->add(' '); 192 | } 193 | if ($onlyButtons) { 194 | $container->class('only-buttons', true); 195 | } 196 | return $export ? trim(strip_tags($container)) : $container; 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/SubItem/SubItemExtension.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class SubItemExtension extends Extensions\Base implements ISubItem 19 | { 20 | 21 | use Mesour\Components\Localization\Translatable; 22 | use Mesour\Components\Security\Authorised; 23 | 24 | private $items = []; 25 | 26 | /** @var Mesour\Components\Session\ISessionSection */ 27 | private $privateSession; 28 | 29 | public function attached(Mesour\Components\ComponentModel\IContainer $parent) 30 | { 31 | parent::attached($parent); 32 | $this->privateSession = $this->getSession()->getSection($this->createLinkName()); 33 | return $this; 34 | } 35 | 36 | public function getPageLimit() 37 | { 38 | $parent = $this->getGrid(); 39 | $pager = $parent->getExtension('IPager', false); 40 | if ($pager instanceof Extensions\Pager\IPager) { 41 | return $pager->getPaginator()->getItemsPerPage(); 42 | } 43 | return $parent->getSource()->getTotalCount(); 44 | } 45 | 46 | /** 47 | * @param string $name 48 | * @param null|string $description 49 | * @param Mesour\UI\DataGrid $grid 50 | * @return Extensions\SubItem\Items\GridItem 51 | */ 52 | public function addGridItem($name, $description, Mesour\UI\DataGrid $grid) 53 | { 54 | $this->check($name); 55 | $item = new Extensions\SubItem\Items\GridItem($this, $name, $this->getTranslator()->translate($description), $grid); 56 | $this->items[$name] = $item; 57 | return $item; 58 | } 59 | 60 | public function addTemplateItem($name, $description) 61 | { 62 | $this->check($name); 63 | $item = new Extensions\SubItem\Items\TemplateItem($this, $name, $this->getTranslator()->translate($description)); 64 | $this->items[$name] = $item; 65 | return $item; 66 | } 67 | 68 | public function addComponentItem($name, $description, $callback) 69 | { 70 | if (!$callback instanceof Mesour\Components\Control\IControl) { 71 | Mesour\Components\Utils\Helpers::checkCallback($callback); 72 | } 73 | $this->check($name); 74 | $item = new Extensions\SubItem\Items\ComponentItem($this, $name, $this->getTranslator()->translate($description), $callback); 75 | $this->items[$name] = $item; 76 | return $item; 77 | } 78 | 79 | public function addCallbackItem($name, $description) 80 | { 81 | $this->check($name); 82 | $item = new Extensions\SubItem\Items\CallbackItem($this, $name, $this->getTranslator()->translate($description)); 83 | $this->items[$name] = $item; 84 | return $item; 85 | } 86 | 87 | public function setPermission($resource, $privilege) 88 | { 89 | $this->setPermissionCheck($resource, $privilege); 90 | return $this; 91 | } 92 | 93 | private function check($name) 94 | { 95 | if (isset($this->items[$name])) { 96 | throw new Mesour\InvalidStateException('Sub item with name ' . $this->items[$name] . ' is already exist.'); 97 | } 98 | } 99 | 100 | public function reset($hard = false) 101 | { 102 | $this->privateSession->set('settings', []); 103 | foreach ($this->items as $item) { 104 | /** @var Extensions\SubItem\Items\Item $item */ 105 | $item->reset(); 106 | } 107 | } 108 | 109 | public function getOpened() 110 | { 111 | $output = []; 112 | $settings = $this->privateSession->get('settings', []); 113 | foreach ($settings as $name => $value) { 114 | if (!isset($this->items[$name])) { 115 | unset($settings[$name]); 116 | continue; 117 | } 118 | /** @var Extensions\SubItem\Items\Item $item */ 119 | $item = $this->items[$name]; 120 | 121 | $keys = $item->getKeys(); 122 | if (count($keys) > 0 && count($keys) < count($settings[$name])) { 123 | while (count($keys) < count($settings[$name])) { 124 | array_shift($settings[$name]); 125 | } 126 | } 127 | foreach ($settings[$name] as $key => $i) { 128 | if (!isset($settings[$name][$key])) { 129 | unset($settings[$name][$key]); 130 | continue; 131 | } 132 | if (!$item->hasKey($settings[$name][$key])) { 133 | $item->addAlias($i, end($keys)); 134 | } 135 | $output[$name]['keys'][] = $settings[$name][$key]; 136 | if (!isset($output[$name]['item'])) { 137 | $output[$name]['item'] = $this->items[$name]; 138 | } 139 | } 140 | } 141 | $this->privateSession->set('settings', $settings); 142 | return $output; 143 | } 144 | 145 | public function getItem($name) 146 | { 147 | if (!isset($this->items[$name])) { 148 | throw new Mesour\InvalidStateException('Item ' . $name . ' does not exist.'); 149 | } 150 | return $this->items[$name]; 151 | } 152 | 153 | public function getNames() 154 | { 155 | return array_keys($this->items); 156 | } 157 | 158 | public function getItemsCount() 159 | { 160 | return count($this->items); 161 | } 162 | 163 | public function getItems() 164 | { 165 | return $this->items; 166 | } 167 | 168 | public function hasSubItems() 169 | { 170 | return count($this->items) > 0; 171 | } 172 | 173 | public function handleToggleItem($key, $name) 174 | { 175 | $settings = $this->privateSession->get('settings', []); 176 | if (isset($settings[$name])) { 177 | if (in_array($key, $settings[$name])) { 178 | unset($settings[$name][array_search($key, $settings[$name])]); 179 | } else { 180 | $settings[$name][] = $key; 181 | } 182 | } else { 183 | $settings[$name][] = $key; 184 | } 185 | $this->privateSession->set('settings', $settings); 186 | } 187 | 188 | public function gridCreate($data = []) 189 | { 190 | $this->create(); 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/SubItemGrid.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | abstract class SubItemGrid extends ExtendedGrid 18 | { 19 | 20 | private $subItems = []; 21 | 22 | /** 23 | * @return Extensions\SubItem\SubItemExtension 24 | */ 25 | public function enableSubItems() 26 | { 27 | $this->addComponent(new Mesour\UI\Control, 'subCol'); 28 | $this->addComponent(new Mesour\UI\Control, 'subButton'); 29 | return $this->getExtension('ISubItem'); 30 | } 31 | 32 | public function create($data = []) 33 | { 34 | $subItem = $this->getExtension('ISubItem'); 35 | $this->onRender[] = function (Mesour\UI\DataGrid $dataGrid, $rawData, $fullData) use ($subItem) { 36 | if ($subItem instanceof Extensions\SubItem\ISubItem && $subItem->hasSubItems() && $subItem->isAllowed() && !$subItem->isDisabled()) { 37 | $opened = $subItem->getOpened(); 38 | foreach ($fullData as $key => $rowData) { 39 | foreach ($opened as $name => $item) { 40 | if (in_array((string) $key, $item['keys'])) { 41 | /** @var Extensions\SubItem\Items\Item $instance */ 42 | $instance = $item['item']; 43 | $translatedKey = $instance->getTranslatedKey($key); 44 | $instance->invoke([$rawData[$key]], $name, $translatedKey); 45 | if (isset($this[$name . $translatedKey])) { 46 | $this->subItems[$key][$name] = $this[$name . $translatedKey]; 47 | } else { 48 | $this->subItems[$key][$name] = true; 49 | } 50 | } 51 | } 52 | } 53 | } 54 | }; 55 | $this->onAfterRenderRow[] = function ($body, $key, $rawData, $rowData) use ($subItem) { 56 | if ($subItem instanceof Extensions\SubItem\ISubItem && $subItem->isAllowed() && !$subItem->isDisabled()) { 57 | foreach ($subItem->getItems() as $name => $item) { 58 | /** @var Extensions\SubItem\Items\Item $item */ 59 | $item->check($rawData); 60 | if (isset($this->subItems[$key][$name])) { 61 | $this->addOpenedSubItemRow($body, $rowData, $name, $key, $item, $rawData); 62 | } else { 63 | $this->addClosedSubItemRow($body, $rowData, $name, $key, $item, $rawData); 64 | } 65 | } 66 | } 67 | }; 68 | 69 | return parent::create($data); 70 | } 71 | 72 | protected function setSubItemColumn(Column\IColumn $column, $name, $header = null) 73 | { 74 | $column->setHeader($header); 75 | return $this['subCol'][$name] = $column; 76 | } 77 | 78 | protected function setSubItemButton(Column\IColumn $column, $name, $header = null) 79 | { 80 | $column->setHeader($header); 81 | return $this['subButton'][$name] = $column; 82 | } 83 | 84 | protected function addClosedSubItemRow(Mesour\Table\Render\Body &$body, $rowData, $name, $key, Extensions\SubItem\Items\Item $item, $rawData) 85 | { 86 | if ($item->isDisabled() || !$item->isAllowed()) { 87 | return; 88 | } 89 | 90 | $columnsCount = count($this->getColumns()); 91 | $oldName = $name; 92 | 93 | $name = $this->getExtension('ISubItem')->getItem($oldName)->getName() . $key; 94 | $column = new Column\SubItemButton($name); 95 | $column->setColumnName($oldName) 96 | ->setKey($key); 97 | 98 | $this->setSubItemButton($column, $name); 99 | 100 | $rendererFactory = $this->getRendererFactory(); 101 | $cell = $rendererFactory->createCell(1, $column, $rawData); 102 | $row = $rendererFactory->createRow($rowData, $rawData); 103 | $row->addCell($cell); 104 | $columnsCount--; 105 | 106 | $subItemColumn = new Column\SubItem(); 107 | $subItemColumn->setText($this->getExtension('ISubItem')->getItem($oldName)->getDescription()); 108 | 109 | $this->setSubItemColumn($subItemColumn, $name); 110 | $cell = $rendererFactory->createCell($columnsCount, $subItemColumn, $rawData); 111 | 112 | $row->setAttribute('class', 'no-sort'); 113 | $row->addCell($cell); 114 | $body->addRow($row); 115 | } 116 | 117 | protected function addOpenedSubItemRow(Mesour\Table\Render\Body &$body, $rowData, $name, $key, Extensions\SubItem\Items\Item $item, $rawData) 118 | { 119 | if ($item->isDisabled() || !$item->isAllowed()) { 120 | return; 121 | } 122 | 123 | $columnsCount = count($this->getColumns()); 124 | $columnsCount--; 125 | $oldName = $name; 126 | 127 | $name = $this->getExtension('ISubItem')->getItem($oldName)->getName() . $key; 128 | $content = $this->getExtension('ISubItem')->getItem($oldName)->render($key, $rowData, $rawData); 129 | 130 | $column = new Column\SubItemButton($name); 131 | $column->setColumnName($oldName) 132 | ->setKey($key) 133 | ->setTwoRows() 134 | ->setOpened(true); 135 | 136 | $this->setSubItemButton($column, $name); 137 | 138 | $rendererFactory = $this->getRendererFactory(); 139 | 140 | $subItem = new Column\SubItem; 141 | $subItem->setText($content); 142 | 143 | $this->setSubItemColumn($subItem, $name); 144 | $cell = $rendererFactory->createCell($columnsCount, $subItem, $rawData); 145 | 146 | $row = $rendererFactory->createRow($rowData, $rawData); 147 | $row->setAttribute('class', 'no-sort'); 148 | $row->addCell($cell); 149 | 150 | $currentRow = $rendererFactory->createRow($rowData, $rawData); 151 | $description = new Column\SubItem; 152 | $description->setText($this->getExtension('ISubItem')->getItem($oldName)->getDescription()); 153 | 154 | $this->setSubItemColumn($description, $name . '_des'); 155 | $subItemCell = $rendererFactory->createCell($columnsCount, $description, $rawData); 156 | 157 | $currentRow->addCell($rendererFactory->createCell(1, $column, $rawData)); 158 | $currentRow->addCell($subItemCell); 159 | $currentRow->setAttribute('class', 'no-sort'); 160 | $body->addRow($currentRow); 161 | $body->addRow($row); 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/ExtensionStorage.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class ExtensionStorage 18 | { 19 | 20 | /** 21 | * @var Mesour\DataGrid\Extensions\IExtension[]|BaseGrid 22 | */ 23 | private $parent; 24 | 25 | protected $extensions = [ 26 | 'IPager' => [ 27 | 'name' => 'pager', 28 | 'interface' => Mesour\DataGrid\Extensions\Pager\IPager::class, 29 | 'class' => Mesour\DataGrid\Extensions\Pager\PagerExtension::class, 30 | ], 31 | 'IFilter' => [ 32 | 'name' => 'filter', 33 | 'interface' => Mesour\DataGrid\Extensions\Filter\IFilter::class, 34 | 'class' => Mesour\DataGrid\Extensions\Filter\FilterExtension::class, 35 | ], 36 | 'ISimpleFilter' => [ 37 | 'name' => 'filter', 38 | 'interface' => Mesour\DataGrid\Extensions\SimpleFilter\ISimpleFilter::class, 39 | 'class' => Mesour\DataGrid\Extensions\SimpleFilter\SimpleFilterExtension::class, 40 | ], 41 | 'ISortable' => [ 42 | 'name' => 'sortable', 43 | 'interface' => Mesour\DataGrid\Extensions\Sortable\ISortable::class, 44 | 'class' => Mesour\DataGrid\Extensions\Sortable\SortableExtension::class, 45 | ], 46 | 'ISelection' => [ 47 | 'name' => 'selection', 48 | 'interface' => Mesour\DataGrid\Extensions\Selection\ISelection::class, 49 | 'class' => Mesour\DataGrid\Extensions\Selection\SelectionExtension::class, 50 | ], 51 | 'IEditable' => [ 52 | 'name' => 'editable', 53 | 'interface' => Mesour\DataGrid\Extensions\Editable\IEditable::class, 54 | 'class' => Mesour\DataGrid\Extensions\Editable\EditableExtension::class, 55 | ], 56 | 'IOrdering' => [ 57 | 'name' => 'ordering', 58 | 'interface' => Mesour\DataGrid\Extensions\Ordering\IOrdering::class, 59 | 'class' => Mesour\DataGrid\Extensions\Ordering\OrderingExtension::class, 60 | ], 61 | 'IExport' => [ 62 | 'name' => 'export', 63 | 'interface' => Mesour\DataGrid\Extensions\Export\IExport::class, 64 | 'class' => Mesour\DataGrid\Extensions\Export\ExportExtension::class, 65 | ], 66 | 'ISubItem' => [ 67 | 'name' => 'sub_item', 68 | 'interface' => Mesour\DataGrid\Extensions\SubItem\ISubItem::class, 69 | 'class' => Mesour\DataGrid\Extensions\SubItem\SubItemExtension::class, 70 | ], 71 | ]; 72 | 73 | public function __construct(BaseGrid $parent) 74 | { 75 | $this->setParent($parent); 76 | } 77 | 78 | public function setParent(BaseGrid $parent) 79 | { 80 | $this->parent = $parent; 81 | } 82 | 83 | public function addNewExtension($name, $componentName, $interface, $className) 84 | { 85 | if (isset($this->extensions[$name])) { 86 | throw new Mesour\InvalidStateException('Extension with name already exists.'); 87 | } 88 | if (!interface_exists($interface)) { 89 | throw new Mesour\InvalidStateException('Interface "' . $interface . '" does not exists.'); 90 | } 91 | if (!class_exists($className)) { 92 | throw new Mesour\InvalidStateException('Class "' . $className . '" does not exists.'); 93 | } 94 | 95 | $checks = [ 96 | 'name' => [ 97 | 'value' => $componentName, 98 | 'exception_name' => 'component name', 99 | ], 100 | 'interface' => [ 101 | 'value' => $interface, 102 | ], 103 | 'class' => [ 104 | 'value' => $className, 105 | 'exception_name' => 'class name', 106 | ], 107 | ]; 108 | 109 | foreach ($checks as $key => $check) { 110 | foreach ($this->extensions as $extension) { 111 | if ($extension[$key] === $check['value']) { 112 | throw new Mesour\InvalidStateException('Extension with "' 113 | . (isset($check['exception_name']) ? $check['exception_name'] : $key) . '" already exists.'); 114 | } 115 | } 116 | } 117 | 118 | $this->extensions[$name] = [ 119 | 'name' => $componentName, 120 | 'interface' => $interface, 121 | 'class' => $className, 122 | ]; 123 | 124 | return $this; 125 | } 126 | 127 | /** 128 | * @param Extensions\IExtension $extension 129 | * @param string $extensionName 130 | * @return Extensions\IExtension 131 | * @throws Mesour\InvalidStateException 132 | * @throws Mesour\InvalidArgumentException 133 | */ 134 | public function set(Extensions\IExtension $extension, $extensionName) 135 | { 136 | if (!isset($this->extensions[$extensionName])) { 137 | throw new Mesour\InvalidStateException('Extension name "' . $extensionName . '" does not exists.'); 138 | } else { 139 | if (!$extension instanceof $this->extensions[$extensionName]['interface']) { 140 | throw new Mesour\InvalidArgumentException( 141 | 'Extension must implement interface ' . $this->extensions[$extensionName]['interface'] . ' for extension "' . $extensionName . '".' 142 | ); 143 | } 144 | } 145 | $extName = $this->getExtensionName($extension); 146 | if ($extName) { 147 | if (strlen($extension->getName()) === 0) { 148 | throw new Mesour\InvalidStateException('Extension must have a set name.'); 149 | } 150 | 151 | /** @var Extensions\IExtension $extension */ 152 | $extension->createInstance($extension, $extensionName); 153 | 154 | $this->extensions[$extName]['name'] = $extension->getName(); 155 | 156 | return $extension; 157 | } else { 158 | throw new Mesour\OutOfRangeException('Trying to set unknown extension.'); 159 | } 160 | } 161 | 162 | public function get($extensionName, $need = true) 163 | { 164 | if (!is_string($extensionName)) { 165 | throw new Mesour\InvalidArgumentException('Extension must be string.'); 166 | } 167 | if (isset($this->extensions[$extensionName])) { 168 | $name = $this->extensions[$extensionName]['name']; 169 | $class = $this->extensions[$extensionName]['class']; 170 | $extension = $this->parent->getComponent($name, false); 171 | if (is_null($extension) && $need) { 172 | $instance = $this->set(new $class($name, $this->parent), $extensionName); 173 | if (!isset($this->parent[$name])) { 174 | $this->parent->addComponent($instance); 175 | } 176 | } elseif (is_null($extension)) { 177 | return null; 178 | } 179 | return $this->parent[$name]; 180 | } else { 181 | if ($need) { 182 | throw new Mesour\OutOfRangeException('Trying to get unknown extension.'); 183 | } else { 184 | return null; 185 | } 186 | } 187 | } 188 | 189 | /** 190 | * @return Extensions\IExtension[] 191 | */ 192 | public function getActiveExtensions() 193 | { 194 | $output = []; 195 | foreach ($this->extensions as $interface => $option) { 196 | if ( 197 | isset($this->parent[$option['name']]) 198 | && !$this->parent[$option['name']]->isDisabled() 199 | && $this->parent[$option['name']]->isAllowed() 200 | ) { 201 | $output[$option['name']] = $this->parent[$option['name']]; 202 | } 203 | } 204 | return $output; 205 | } 206 | 207 | /** 208 | * @param Extensions\IExtension $extension 209 | * @return string 210 | */ 211 | private function getExtensionName(Extensions\IExtension $extension) 212 | { 213 | foreach ($this->extensions as $name => $ext) { 214 | if (is_subclass_of($extension, $ext['interface'])) { 215 | return $name; 216 | } 217 | } 218 | return false; 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /examples/basic.php: -------------------------------------------------------------------------------- 1 | addDirectory(__DIR__ . '/../src'); 15 | $loader->setCacheStorage(new Nette\Caching\Storages\FileStorage(__DIR__ . '/temp')); 16 | $loader->register(); 17 | 18 | ?> 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 |
36 |

Basic functionality

37 | 38 |
39 | 40 | setRequest($_REQUEST); 50 | 51 | $application->setUserRole('registered'); 52 | 53 | $auth = $application->getAuthorizator(); 54 | 55 | $auth->addRole('guest'); 56 | $auth->addRole('registered', 'guest'); 57 | 58 | $auth->addResource('menu'); 59 | 60 | $auth->allow('guest', 'menu', ['first', 'second']); 61 | $auth->allow('registered', 'menu'); 62 | $auth->deny('registered', 'menu', 'second'); 63 | 64 | $grid = new \Mesour\UI\DataGrid('basicDataGrid', $application); 65 | 66 | $wrapper = $grid->getWrapperPrototype(); 67 | 68 | $wrapper->class('my-class'); 69 | 70 | // TRUE = append 71 | $wrapper->class('my-next-class', true); 72 | 73 | /** @var \Mesour\DataGrid\Sources\IGridSource $source */ 74 | $source = require_once __DIR__ . '/sources/' . $sourceFile . '.php'; 75 | 76 | $dataStructure = $source->getDataStructure(); 77 | 78 | $dataStructure->addManyToOne('group', 'groups', 'group_id', '{name} ({type})'); 79 | 80 | $grid->setSource($source); 81 | 82 | $pager = $grid->enablePager(8); 83 | 84 | $filter = $grid->enableFilter(); 85 | 86 | $selection = $grid->enableRowSelection(); 87 | 88 | $selection = $selection->getLinks(); 89 | 90 | $selection->addHeader('Active'); 91 | 92 | $selection->addLink('Active')// add selection link 93 | ->onCall[] = function () { 94 | dump('ActivateSelected', func_get_args()); 95 | }; 96 | 97 | $selection->addLink('Unactive') 98 | ->setAjax(false)// disable AJAX 99 | ->onCall[] = function () { 100 | dump('InactivateSelected', func_get_args()); 101 | }; 102 | 103 | $selection->addDivider(); 104 | 105 | $selection->addLink('Delete') 106 | ->setConfirm('Really delete all selected users?')// set confirm text 107 | ->onCall[] = function () { 108 | dump('DeleteSelected', func_get_args()); 109 | }; 110 | 111 | $sortable = $grid->enableSortable('sort'); 112 | 113 | $export = $grid->enableExport(__DIR__ . '/temp'); 114 | 115 | $status = $grid->addStatus('action', 'S') 116 | ->setPermission('menu', 'second'); 117 | 118 | $status->addButton('active') 119 | ->setStatus(1, 'Active', 'All active') 120 | ->setIcon('check-circle-o') 121 | ->setType('success') 122 | ->setAttribute('href', '#'); 123 | 124 | $status->addButton('inactive') 125 | ->setStatus(0, 'Inactive', 'All inactive') 126 | ->setIcon('times-circle-o') 127 | ->setType('danger') 128 | ->setAttribute('href', '#'); 129 | 130 | $grid->addText('name', 'Name'); 131 | 132 | $grid->addText('email', 'E-mail'); 133 | 134 | $grid->addText('group_name', 'Group'); 135 | 136 | $grid->addText('group', 'Group'); 137 | 138 | $grid->addNumber('amount', 'Amount') 139 | ->setUnit('CZK'); 140 | 141 | $container = $grid->addContainer('blablablablablabla', 'Actions'); 142 | 143 | //$container->setDisabled(); 144 | 145 | $button = $container->addButton('test_button'); 146 | 147 | $button->setIcon('pencil') 148 | ->setType('primary') 149 | ->setAttribute('href', $button->link('http://mesour.com')) 150 | ->setAttribute('target', '_blank'); 151 | 152 | $dropDown = $container->addDropDown('test_drop_down') 153 | ->setPullRight() 154 | ->setAttribute('class', 'dropdown'); 155 | 156 | $dropDown->addHeader('Test header'); 157 | 158 | $first = $dropDown->addButton(); 159 | 160 | $first->setText('First button') 161 | ->setAttribute('href', $dropDown->link('/first/')); 162 | 163 | $dropDown->addDivider(); 164 | 165 | $dropDown->addHeader('Test header 2'); 166 | 167 | $dropDown->addButton() 168 | ->setText('Second button') 169 | ->setConfirm('Test confirm :-)') 170 | ->setAttribute('href', $dropDown->link('/second/')); 171 | 172 | $dropDown->addButton() 173 | ->setText('Third button') 174 | ->setAttribute('href', $dropDown->link('/third/')); 175 | 176 | $mainButton = $dropDown->getMainButton(); 177 | 178 | $mainButton->setText('Actions') 179 | ->setType('danger'); 180 | 181 | // TEST ------------------------------------------------- 182 | 183 | $grid->onRenderBody[] = function( 184 | \Mesour\Table\Render\Table\Body $body, 185 | \Mesour\Table\Render\Table\RendererFactory $rendererFactory, 186 | $rawData, 187 | $data 188 | ) use ($grid) { 189 | $myCustomRowData = [ 190 | 'some_key' => 'value', 191 | ]; 192 | 193 | $row = $rendererFactory->createRow([], $myCustomRowData); 194 | 195 | $column = new \Mesour\DataGrid\Column\Text('test_column', $grid); 196 | 197 | $column->setCallback(function($column, $myCustomRowData) { 198 | return $myCustomRowData['some_key']; 199 | }); 200 | 201 | $cell = $rendererFactory->createCell([], $column, $myCustomRowData); 202 | 203 | $row->addCell($cell); 204 | 205 | $body->addRow($row); 206 | }; 207 | 208 | // / TEST ----------------------------------------------- 209 | 210 | $time_end = microtime(true); 211 | $time = $time_end - $time_start; 212 | 213 | echo "
Execution time (before render): " . number_format($time, 3, ',', ' ') . " seconds
"; 214 | 215 | echo $grid->render(); 216 | 217 | $time_end = microtime(true); 218 | $time = $time_end - $time_start; 219 | 220 | echo "
Execution time (after render): " . number_format($time, 3, ',', ' ') . " seconds
"; 221 | 222 | ?> 223 |
224 | 225 |
226 | 227 |























228 | 229 | 230 | 231 | 232 | 233 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /src/Mesour/DataGrid/Extensions/Export/ExportExtension.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class ExportExtension extends Mesour\DataGrid\Extensions\Base implements IExport 18 | { 19 | 20 | use Mesour\Components\Security\Authorised; 21 | 22 | /** 23 | * Cache directory 24 | * @var string 25 | */ 26 | private $cache_dir; 27 | 28 | /** 29 | * Restriction for some columns 30 | * @var array 31 | */ 32 | private $exportColumns = []; 33 | 34 | /** @var string|null */ 35 | private $fileName = null; 36 | 37 | private $delimiter = ','; 38 | 39 | private $file_path; 40 | 41 | /** @var Mesour\UI\Button */ 42 | private $createdExport; 43 | 44 | public function setFileName($fileName) 45 | { 46 | if (!is_string($fileName) && !is_null($fileName)) { 47 | throw new Mesour\InvalidArgumentException( 48 | sprintf('Export file name must be string, %s given.', gettype($fileName)) 49 | ); 50 | } 51 | $this->fileName = $fileName; 52 | return $this; 53 | } 54 | 55 | public function setDelimiter($delimiter = ',') 56 | { 57 | $this->delimiter = $delimiter; 58 | return $this; 59 | } 60 | 61 | public function setCacheDir($dir) 62 | { 63 | if (!is_dir($dir)) { 64 | throw new Mesour\DirectoryNotFoundException('Cache dir is not a directory.'); 65 | } 66 | if (!is_writable($dir)) { 67 | throw new Mesour\InvalidStateException('Cache dir is not a writable.'); 68 | } 69 | $this->cache_dir = $dir; 70 | return $this; 71 | } 72 | 73 | public function setColumns(array $columns = []) 74 | { 75 | $this->exportColumns = $columns; 76 | return $this; 77 | } 78 | 79 | /** 80 | * @return Mesour\UI\Button|Mesour\UI\DropDown 81 | */ 82 | public function getExportButton() 83 | { 84 | if (!isset($this['button'])) { 85 | $filter = $this->getGrid()->getExtension('IFilter', false); 86 | if ($filter instanceof Mesour\DataGrid\Extensions\Filter\IFilter) { 87 | $this['button'] = $dropdown = new Mesour\UI\DropDown; 88 | 89 | $dropdown->setAttribute('class', 'show-export', true) 90 | ->setPullRight(); 91 | 92 | if ($filter instanceof Mesour\DataGrid\Extensions\Filter\IFilter) { 93 | $dropdown->addButton('Export filtered') 94 | ->setAttribute('href', $this->createLink('export', ['type' => 'filtered'])); 95 | } 96 | 97 | $dropdown->addDivider(); 98 | $dropdown->addButton('Export all') 99 | ->setAttribute('href', $this->createLink('export')); 100 | 101 | $button = $dropdown->getMainButton(); 102 | } else { 103 | $this['button'] = $button = new Mesour\UI\Button; 104 | $button->setAttribute('class', 'show-export', true) 105 | ->setAttribute('href', $this->createLink('export')); 106 | } 107 | $button->setType('primary') 108 | ->setText('Export'); 109 | 110 | } 111 | return $this['button']; 112 | } 113 | 114 | public function gridCreate($data = []) 115 | { 116 | parent::create(); 117 | 118 | $this->createdExport = $this->getExportButton(); 119 | 120 | $selection = $this->getGrid()->getExtension('ISelection', false); 121 | 122 | if ($selection instanceof Mesour\DataGrid\Extensions\Selection\ISelection) { 123 | if (count($selection->getLinks()->getLinks())) { 124 | $selection->getLinks() 125 | ->addDivider(); 126 | } 127 | $selection->getLinks() 128 | ->addLink('Export to CSV') 129 | ->setAjax(false) 130 | ->onCall[] = function (array $selectedItems) { 131 | $ids = array_keys($selectedItems, 'true'); 132 | $this->handleExport('selected', $ids); 133 | }; 134 | } 135 | } 136 | 137 | public function attachToRenderer(Mesour\DataGrid\Renderer\IGridRenderer $renderer, $data = [], $rawData = []) 138 | { 139 | $this->createdExport->setOption('data', $data); 140 | $renderer->setComponent('export', $this->createdExport->create()); 141 | } 142 | 143 | public function hasExport(Mesour\Components\ComponentModel\IContainer $column) 144 | { 145 | return ($column instanceof Mesour\DataGrid\Column\IExportable); 146 | } 147 | 148 | public function handleExport($type = 'all', array $selectedIds = []) 149 | { 150 | if ($this->isDisabled()) { 151 | throw new Mesour\InvalidStateException('Cannot edit cell if extension is disabled.'); 152 | } 153 | 154 | /** @var Mesour\DataGrid\Column\IColumn|array|Mesour\DataGrid\Column\BaseColumn $column */ 155 | $headerArr = []; 156 | $exportColumns = []; 157 | 158 | if (empty($this->exportColumns)) { 159 | foreach ($this->getGrid()->getColumns() as $column) { 160 | if ($this->hasExport($column) && $column->isAllowed()) { 161 | $exportColumns[] = $column; 162 | } 163 | } 164 | } else { 165 | foreach ($this->exportColumns as $columnVal) { 166 | $usedInColumns = false; 167 | foreach ($this->getGrid()->getColumns() as $column) { 168 | /** @var Mesour\DataGrid\Column\BaseColumn $column */ 169 | if ($this->hasExport($column) && $column->isAllowed()) { 170 | if (is_array($columnVal)) { 171 | $columnName = key($columnVal); 172 | } else { 173 | $columnName = $columnVal; 174 | } 175 | if ($columnName === $column->getName()) { 176 | $exportColumns[] = $column; 177 | $usedInColumns = true; 178 | } 179 | } 180 | } 181 | if ($usedInColumns === false) { 182 | $exportColumns[] = $columnVal; 183 | } 184 | } 185 | } 186 | $this->file_path = $this->createFilePath(); 187 | $file = fopen($this->file_path, 'w'); 188 | fputs($file, chr(0xEF) . chr(0xBB) . chr(0xBF)); // add BOM to fix UTF-8 in Excel 189 | foreach ($exportColumns as $column) { 190 | if ($column instanceof Mesour\DataGrid\Column\IColumn) { 191 | $headerArr[] = $column->getHeader(); 192 | } else { 193 | if (is_array($column)) { 194 | $headerArr[] = reset($column); 195 | } else { 196 | $headerArr[] = $column; 197 | } 198 | } 199 | } 200 | fputcsv($file, $headerArr, $this->delimiter); 201 | $first = true; 202 | $exportData = []; 203 | switch ($type) { 204 | case 'selected': 205 | $source = $this->getGrid()->getSource(); 206 | $allData = $source->fetchAll(); 207 | 208 | foreach ($selectedIds as $selectedId) { 209 | foreach ($allData as $currentData) { 210 | if ($selectedId == $currentData[$source->getPrimaryKey()]) { 211 | $exportData[] = $currentData; 212 | break; 213 | } 214 | } 215 | } 216 | break; 217 | case 'filtered': 218 | $exportData = $this->getGrid()->getSource()->fetchForExport(); 219 | break; 220 | default: 221 | $exportData = $this->getGrid()->getSource()->fetchFullData(); 222 | } 223 | $rawData = $this->getGrid()->getSource()->fetchLastRawRows(); 224 | foreach ($exportData as $key => $data) { 225 | $lineData = []; 226 | foreach ($exportColumns as $column) { 227 | if ($column instanceof Mesour\DataGrid\Column\IColumn) { 228 | $lineData[] = strip_tags($column->getBodyContent($data, $rawData[$key], true)); 229 | } else { 230 | if (is_array($column)) { 231 | $columnName = key($column); 232 | } else { 233 | $columnName = $column; 234 | } 235 | if ($first && !isset($data[$columnName]) && !is_null($data[$columnName])) { 236 | throw new Mesour\OutOfRangeException('Column "' . $columnName . '" does not exist in data.'); 237 | } 238 | $lineData[] = strip_tags($data[$columnName]); 239 | } 240 | } 241 | fputcsv($file, $lineData, $this->delimiter); 242 | $first = false; 243 | } 244 | fclose($file); 245 | 246 | $this->download($this->file_path, (is_null($this->fileName) ? $this->getGrid()->createLinkName() : $this->fileName) . '.csv'); 247 | } 248 | 249 | protected function createFilePath() 250 | { 251 | return sprintf( 252 | $this->cache_dir . '/%s%s.csv', 253 | Mesour\Components\Utils\Helpers::webalize($this->getGrid()->createLinkName()), 254 | time() 255 | ); 256 | } 257 | 258 | private function download($filePath, $fileName) 259 | { 260 | ob_clean(); 261 | header('Content-Type: applications/octet-stream'); 262 | header('Content-Disposition: attachment; filename="' . $fileName . '"'); 263 | header('Cache-Control: private, max-age=0, must-revalidate'); 264 | header('Pragma: public'); 265 | echo file_get_contents($filePath); 266 | exit(1); 267 | } 268 | 269 | public function __destruct() 270 | { 271 | if (is_file($this->file_path)) { 272 | unlink($this->file_path); 273 | } 274 | } 275 | 276 | } 277 | -------------------------------------------------------------------------------- /examples/editable.php: -------------------------------------------------------------------------------- 1 | addDirectory(__DIR__ . '/../src'); 15 | $loader->setCacheStorage(new Nette\Caching\Storages\FileStorage(__DIR__ . '/temp')); 16 | $loader->register(); 17 | 18 | ?> 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 35 |
36 | 37 |
38 |

Basic functionality

39 | 40 |
41 | 42 | getConfiguration() 52 | ->setTempDir(__DIR__ . '/temp'); 53 | 54 | $application->setRequest($_REQUEST); 55 | 56 | $application->getUser()->setRoles('registered'); 57 | 58 | $auth = $application->getAuthorizator(); 59 | 60 | $auth->addRole('guest'); 61 | $auth->addRole('registered', 'guest'); 62 | 63 | $auth->addResource('menu'); 64 | 65 | $auth->allow('guest', 'menu', ['first', 'second']); 66 | $auth->allow('registered', 'menu'); 67 | $auth->deny('registered', 'menu', 'second'); 68 | 69 | $grid = new \Mesour\UI\DataGrid('basicDataGrid', $application); 70 | 71 | $wrapper = $grid->getWrapperPrototype(); 72 | 73 | $wrapper->class('my-class'); 74 | 75 | // TRUE = append 76 | $wrapper->class('my-next-class', true); 77 | 78 | /** @var \Mesour\DataGrid\Sources\IGridSource $source */ 79 | $source = require_once __DIR__ . '/sources/' . $sourceFile . '.php'; 80 | 81 | $dataStructure = $source->getDataStructure(); 82 | 83 | $dataStructure->renameColumn('user_addresses', 'addresses'); 84 | $dataStructure->renameColumn('groups', 'group'); 85 | $dataStructure->renameColumn('wallets', 'wallet'); 86 | 87 | /** @var \Mesour\Sources\Structures\Columns\ManyToManyColumnStructure $companiesColumn */ 88 | $companiesColumn = $dataStructure->getColumn('companies'); 89 | $companiesColumn->setPattern('{name}'); 90 | 91 | /** @var \Mesour\Sources\Structures\Columns\OneToManyColumnStructure $addressesColumn */ 92 | $addressesColumn = $dataStructure->getColumn('addresses'); 93 | $addressesColumn->setPattern('{street}, {zip} {city}, {country}'); 94 | 95 | /** @var \Mesour\Sources\Structures\Columns\ManyToOneColumnStructure $groupColumn */ 96 | $groupColumn = $dataStructure->getColumn('group'); 97 | $groupColumn->setPattern('{name} ({type})'); 98 | 99 | /** @var \Mesour\Sources\Structures\Columns\OneToOneColumnStructure $walletColumn */ 100 | $walletColumn = $dataStructure->getColumn('wallet'); 101 | $walletColumn->setPattern('{amount}'); 102 | 103 | $grid->setSource($source); 104 | 105 | $pager = $grid->enablePager(8); 106 | 107 | //$filter = $grid->enableFilter(); 108 | $filter = $grid->enableSimpleFilter(); 109 | 110 | $selection = $grid->enableRowSelection(); 111 | 112 | $selection = $selection->getLinks(); 113 | 114 | $selection->addHeader('Active'); 115 | 116 | $selection->addLink('Active')// add selection link 117 | ->onCall[] = function () { 118 | dump('ActivateSelected', func_get_args()); 119 | }; 120 | 121 | $selection->addLink('Unactive') 122 | ->setAjax(false)// disable AJAX 123 | ->onCall[] = function () { 124 | dump('InactivateSelected', func_get_args()); 125 | }; 126 | 127 | $selection->addDivider(); 128 | 129 | $selection->addLink('Delete') 130 | ->setConfirm('Really delete all selected users?')// set confirm text 131 | ->onCall[] = function () { 132 | dump('DeleteSelected', func_get_args()); 133 | }; 134 | 135 | // EDITABLE 136 | 137 | $editable = $grid->enableEditable(); 138 | 139 | $editableStructure = $editable->getDataStructure(); 140 | 141 | $editableStructure->addOneToOne('wallet', 'Wallet') 142 | ->enableCreateNewRow(); 143 | 144 | $editableStructure->addManyToOne('group', 'Groups') 145 | ->enableEditCurrentRow() 146 | ->enableCreateNewRow() 147 | ->setNullable(); 148 | 149 | $editableStructure->addOneToMany('addresses', 'Addresses') 150 | ->enableCreateNewRow() 151 | ->enableRemoveRow(); 152 | 153 | $editableStructure->addManyToMany('companies', 'Companies') 154 | ->enableAttachRow() 155 | ->enableCreateNewRow() 156 | ->enableRemoveRow(); 157 | 158 | $companyStructure = $editableStructure->getOrCreateElement('companies', 'id'); 159 | $companyStructure->addText('name', 'Name'); 160 | $companyStructure->addNumber('reg_num', 'Reg. number'); 161 | $companyStructure->addBool('verified', 'Verified'); 162 | 163 | $walletStructure = $editableStructure->getOrCreateElement('wallets', 'id'); 164 | $walletStructure->addNumber('amount', 'Amount') 165 | ->setDecimals(2) 166 | ->setThousandSeparator('.') 167 | ->setDecimalPoint(','); 168 | $walletStructure->addEnum('currency', 'Currency') 169 | ->addValue('CZK', 'CZK') 170 | ->addValue('EUR', 'EUR'); 171 | 172 | $groupsStructure = $editableStructure->getOrCreateElement('groups', 'id'); 173 | $groupsStructure->addText('name', 'Name'); 174 | $groupsStructure->addEnum('type', 'Type') 175 | ->setNullable() 176 | ->addValue('first', 'First') 177 | ->addValue('second', 'Second'); 178 | $groupsStructure->addDate('date', 'Date'); 179 | $groupsStructure->addNumber('members', 'Members'); 180 | 181 | // / EDITABLE 182 | 183 | $grid->enableSortable('sort'); 184 | 185 | $status = $grid->addStatus('action', 'S') 186 | ->setPermission('menu', 'second'); 187 | 188 | $status->addButton('active') 189 | ->setStatus(1, 'Active', 'All active') 190 | ->setIcon('check-circle-o') 191 | ->setType('success') 192 | ->setAttribute('href', '#'); 193 | 194 | $status->addButton('inactive') 195 | ->setStatus(0, 'Inactive', 'All inactive') 196 | ->setIcon('times-circle-o') 197 | ->setType('danger') 198 | ->setAttribute('href', '#'); 199 | 200 | $grid->addText('name', 'Name'); 201 | 202 | $grid->addText('email', 'E-mail'); 203 | 204 | $grid->addText('role', 'Role'); 205 | 206 | $grid->addDate('last_login', 'Last login') 207 | ->setFormat('Y-m-d'); 208 | 209 | $grid->addText('has_pro', 'Has pro') 210 | ->setAttribute('title', 'Has pro') 211 | ->setCallback( 212 | function (\Mesour\DataGrid\Column\Text $column, $data) { 213 | if($data['has_pro']) { 214 | return 'Yes'; 215 | } 216 | return 'No'; 217 | } 218 | ); 219 | 220 | $grid->addText('group', 'Group') 221 | //->setFiltering(false) 222 | ->setAttribute('title', 'Select group'); 223 | 224 | $grid->addText('wallet', 'Wallet') 225 | //->setFiltering(false) 226 | ->setAttribute('title', 'Wallet'); 227 | 228 | $grid->addText('addresses', 'Addresses') 229 | //->setFiltering(false) 230 | ; 231 | 232 | $grid->addText('companies', 'Companies') 233 | //->setFiltering(false) 234 | ; 235 | 236 | $grid->addNumber('amount', 'Amount') 237 | ->setUnit('CZK'); 238 | 239 | $time_end = microtime(true); 240 | $time = $time_end - $time_start; 241 | 242 | echo "
Execution time (before render): " . number_format($time, 3, ',', ' ') . " seconds
"; 243 | 244 | echo $grid->render(); 245 | 246 | $time_end = microtime(true); 247 | $time = $time_end - $time_start; 248 | 249 | echo "
Execution time (after render): " . number_format($time, 3, ',', ' ') . " seconds
"; 250 | 251 | ?> 252 |
253 | 254 |
255 | 256 |























257 | 258 | 259 | 260 | 261 | 262 | 265 | 266 | 267 | 268 | 269 | 270 | -------------------------------------------------------------------------------- /examples/index.php: -------------------------------------------------------------------------------- 1 | addDirectory(__DIR__ . '/../src'); 17 | $loader->setCacheStorage(new Nette\Caching\Storages\FileStorage(__DIR__ . '/temp')); 18 | $loader->register(); 19 | 20 | 21 | ?> 22 | 23 | 24 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 |
35 |

Basic functionality

36 | 37 |
38 | 39 | enablePager(5); 56 | 57 | $_sub_grid->onEditCell[] = function () { 58 | dump(func_get_args()); 59 | }; 60 | 61 | $_sub_grid->enableSortable('sort'); 62 | 63 | $_sub_grid->onSort[] = function () { 64 | dump(func_get_args()); 65 | }; 66 | 67 | $_sub_grid->addText('name'); 68 | 69 | $_sub_grid->addText('surname'); 70 | 71 | $_sub_grid->enableFilter(); 72 | 73 | $selection = $_sub_grid->enableRowSelection() 74 | ->getLinks(); 75 | 76 | $selection->addLink('Active') 77 | ->onCall[] = function () { 78 | dump(func_get_args()); 79 | }; 80 | 81 | $selection->addLink('Unactive') 82 | ->setAjax(false) 83 | ->onCall[] = function () { 84 | dump(func_get_args()); 85 | }; 86 | 87 | $selection->addLink('Delete') 88 | ->setConfirm('Really delete all selected users?') 89 | ->onCall[] = function () { 90 | dump(func_get_args()); 91 | }; 92 | 93 | return $_sub_grid; 94 | } 95 | 96 | function createTestButton(\Mesour\Components\Control\IControl $parent, $name) 97 | { 98 | $button = new \Mesour\UI\Button($name, $parent); 99 | 100 | $button->setText('To mesour.com >>'); 101 | 102 | $button->setAttribute('href', $button->link('http://mesour.com')) 103 | ->setAttribute('target', '_blank'); 104 | 105 | return $button; 106 | } 107 | 108 | $application->getConfiguration() 109 | ->setTempDir(__DIR__ . '/temp'); 110 | 111 | $application->setRequest($_REQUEST); 112 | 113 | $application->getUser()->setRoles('registered'); 114 | 115 | $auth = $application->getAuthorizator(); 116 | 117 | $auth->addRole('guest'); 118 | $auth->addRole('registered', 'guest'); 119 | 120 | $auth->addResource('menu'); 121 | 122 | $auth->allow('guest', 'menu', ['first', 'second']); 123 | $auth->allow('registered', 'menu'); 124 | $auth->deny('registered', 'menu', 'second'); 125 | 126 | Mesour\UI\Filter::$maxCheckboxCount = 10; 127 | 128 | $grid = new \Mesour\UI\DataGrid('extendedDataGrid', $application); 129 | 130 | $wrapper = $grid->getWrapperPrototype(); 131 | 132 | $wrapper->class('my-class'); 133 | 134 | // TRUE = append 135 | $wrapper->class('my-next-class', true); 136 | 137 | /** @var \Mesour\DataGrid\Sources\IGridSource $source */ 138 | $source = require_once __DIR__ . '/sources/' . $sourceFile . '.php'; 139 | 140 | $dataStructure = $source->getDataStructure(); 141 | 142 | /** @var \Mesour\Sources\Structures\Columns\ManyToManyColumnStructure $companiesColumn */ 143 | $companiesColumn = $dataStructure->getColumn('companies'); 144 | $companiesColumn->setPattern('{name}'); 145 | 146 | /** @var \Mesour\Sources\Structures\Columns\OneToManyColumnStructure $addressesColumn */ 147 | $addressesColumn = $dataStructure->getColumn('addresses'); 148 | $addressesColumn->setPattern('{street}, {zip} {city}, {country}'); 149 | 150 | /** @var \Mesour\Sources\Structures\Columns\ManyToOneColumnStructure $groupColumn */ 151 | $groupColumn = $dataStructure->getColumn('group'); 152 | $groupColumn->setPattern('{name} ({type})'); 153 | 154 | /** @var \Mesour\Sources\Structures\Columns\OneToOneColumnStructure $walletColumn */ 155 | $walletColumn = $dataStructure->getColumn('wallet'); 156 | $walletColumn->setPattern('{amount}'); 157 | 158 | for ($x = 0; $x < 8; $x++) { 159 | $sources[] = clone $source; 160 | } 161 | 162 | $grid->setSource($source); 163 | 164 | $pager = $grid->enablePager(8); 165 | 166 | $filter = $grid->enableFilter(); 167 | 168 | $grid->onEditCell[] = function () { 169 | dump(func_get_args()); 170 | }; 171 | 172 | $subItems = $grid->enableSubItems(); 173 | 174 | //$subItems->setPermission('menu', 'second'); 175 | 176 | $subItems->addCallbackItem('test', 'Test callback item') 177 | //->setPermission('menu', 'second') 178 | ->setCallback(function (User $user) { 179 | return $user->getName() . ' ' . $user->getSurname(); 180 | }); 181 | 182 | $i = 0; 183 | $subItems->addGridItem('groups', 'User groups', getSubGrid()) 184 | //->setPermission('menu', 'second') 185 | ->setCheckCallback(function (User $user, \Mesour\DataGrid\Extensions\SubItem\Items\Item $item) { 186 | /** @var \Mesour\Sources\Tests\Entity\User $user */ 187 | if ($user->getId() == 1) { 188 | $item->setDisabled(); 189 | } else { 190 | $item->setDisabled(false); 191 | } 192 | }) 193 | ->setCallback(function (\Mesour\UI\DataGrid $subGrid, $rowData) use ($sources, & $i) { 194 | $_source = $sources[$i]; 195 | $subGrid->setSource($_source); 196 | $i++; 197 | }); 198 | 199 | $subItems->addComponentItem('button', 'Component item', 'createTestButton') 200 | //->setPermission('menu', 'second') 201 | ->setCallback(function (\Mesour\UI\Button $button, User $user) { 202 | 203 | $button->setText('Go to mesour.com from: ' . $user->getName() . ' ' . $user->getSurname() . ' >>'); 204 | $button->setAttribute('href', $button->link('http://mesour.com', [ 205 | 'userId' => $user->getId(), 206 | ])); 207 | }); 208 | 209 | $templateItem = $subItems->addTemplateItem('description', 'Template item') 210 | //->setPermission('menu', 'second') 211 | ->setCallback(function (\Mesour\UI\TemplateFile $template, User $user) { 212 | 213 | $template->name = $user->getName() . ' ' . $user->getSurname(); 214 | }); 215 | $templateItem->setTempDir(__DIR__ . '/temp'); 216 | $templateItem->setFile(__DIR__ . '/test.latte'); 217 | $templateItem->setBlock('test'); 218 | 219 | $selection = $grid->enableRowSelection() 220 | ->getLinks(); 221 | 222 | $selection->addHeader('Active'); 223 | 224 | $selection->addLink('Active')// add selection link 225 | ->onCall[] = function () { 226 | dump('ActivateSelected', func_get_args()); 227 | }; 228 | 229 | $selection->addLink('Unactive') 230 | ->setAjax(false)// disable AJAX 231 | ->onCall[] = function () { 232 | dump('InactivateSelected', func_get_args()); 233 | }; 234 | 235 | $selection->addDivider(); 236 | 237 | $selection->addLink('Delete') 238 | ->setConfirm('Really delete all selected users?')// set confirm text 239 | ->onCall[] = function () { 240 | dump('DeleteSelected', func_get_args()); 241 | }; 242 | 243 | $grid->enableExport(__DIR__ . '/temp'); 244 | 245 | $grid->enableSortable('sort'); 246 | 247 | $grid->onSort[] = function () { 248 | dump(func_get_args()); 249 | }; 250 | 251 | $status = $grid->addStatus('action', 'S'); 252 | 253 | $status->addButton('active') 254 | ->setStatus(1, 'Active', 'All active') 255 | ->setIcon('check-circle-o') 256 | ->setType('success') 257 | ->setAttribute('href', '#'); 258 | 259 | $status->addButton('inactive') 260 | ->setStatus(0, 'Inactive', 'All inactive') 261 | ->setIcon('times-circle-o') 262 | ->setType('danger') 263 | ->setAttribute('href', '#'); 264 | 265 | $grid->addImage('avatar', 'Avatar') 266 | ->setPreviewPath('preview', __DIR__, __DIR__ . '/') 267 | ->setMaxHeight(80)// translated as max-height: 80px; 268 | ->setMaxWidth(80); // can use 80, "80px", "1em"... 269 | 270 | $container = $grid->addContainer('surname', 'Name') 271 | ->setFiltering() 272 | ->setOrdering(); 273 | 274 | $container->addText('surname'); 275 | 276 | $container->addText('name'); 277 | 278 | $grid->addText('group', 'Group'); 279 | 280 | $grid->addText('email', 'E-mail') 281 | ->setOrdering(false); 282 | 283 | $grid->addNumber('amount', 'Amount') 284 | ->setUnit('EUR'); 285 | 286 | $grid->addDate($lastLogin, 'Last login') 287 | ->setFormat('j.n.Y - H:i:s'); 288 | 289 | $container = $grid->addContainer('blablablablablabla', 'Actions'); 290 | 291 | $button = $container->addButton('test_button'); 292 | 293 | $button->setIcon('pencil') 294 | ->setType('primary') 295 | ->setAttribute('href', $button->link('http://mesour.com')) 296 | ->setAttribute('target', '_blank'); 297 | 298 | $dropDown = $container->addDropDown('test_drop_down'); 299 | 300 | $dropDown->addHeader('Test header'); 301 | 302 | $first = $dropDown->addButton(); 303 | 304 | $first->setText('First button') 305 | ->setAttribute('href', $dropDown->link('/first/')); 306 | 307 | $dropDown->addDivider(); 308 | 309 | $dropDown->addHeader('Test header 2'); 310 | 311 | $dropDown->addButton() 312 | ->setText('Second button') 313 | ->setConfirm('Test confirm :-)') 314 | ->setAttribute('href', $dropDown->link('/second/')); 315 | 316 | $dropDown->addButton() 317 | ->setText('Third button') 318 | ->setAttribute('href', $dropDown->link('/third/')); 319 | 320 | $mainButton = $dropDown->getMainButton(); 321 | 322 | $mainButton->setText('Actions') 323 | ->setType('danger'); 324 | 325 | $time_end = microtime(true); 326 | $time = $time_end - $time_start; 327 | 328 | echo "
Execution time (before render): " . number_format($time, 3, ',', ' ') . " seconds
"; 329 | 330 | //dump($_SESSION['Mesour\Components\Session']); 331 | 332 | $grid->render(); 333 | 334 | $time_end = microtime(true); 335 | $time = $time_end - $time_start; 336 | 337 | echo "
Execution time (after render): " . number_format($time, 3, ',', ' ') . " seconds
"; 338 | 339 | ?> 340 |
341 | 342 |
343 | 344 |























345 | 346 | 347 | 348 | 349 | 350 | 353 | 354 | 355 | 356 | 357 | --------------------------------------------------------------------------------