├── phpstan.neon ├── src ├── Widget │ ├── DataSource.php │ ├── TreeListDataSource.php │ ├── HierarchicalDataSource.php │ ├── Model.php │ ├── Observable.php │ └── Base.php ├── JavascriptFunction.php └── Kendo.php ├── composer.json ├── LICENSE ├── .github └── workflows │ └── test.yml └── README.md /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: max 3 | paths: 4 | - src -------------------------------------------------------------------------------- /src/Widget/DataSource.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class DataSource extends Base 19 | { 20 | /** 21 | * Widget name. 22 | * 23 | * @var string 24 | */ 25 | protected $_name = 'kendo.data.DataSource'; 26 | } 27 | -------------------------------------------------------------------------------- /src/Widget/TreeListDataSource.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class TreeListDataSource extends DataSource 19 | { 20 | /** 21 | * Widget name. 22 | * 23 | * @var string 24 | */ 25 | protected $_name = 'kendo.data.TreeListDataSource'; 26 | } 27 | -------------------------------------------------------------------------------- /src/Widget/HierarchicalDataSource.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class HierarchicalDataSource extends DataSource 19 | { 20 | /** 21 | * Widget name. 22 | * 23 | * @var string 24 | */ 25 | protected $_name = 'kendo.data.HierarchicalDataSource'; 26 | } 27 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "riesenia/kendo", 3 | "description": "PHP wrapper for Kendo UI widgets", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Tomas Saghy", 8 | "email": "segy@riesenia.com" 9 | } 10 | ], 11 | "require": { 12 | "php": "^7.1 || ^8.0" 13 | }, 14 | "require-dev": { 15 | "friendsofphp/php-cs-fixer": "^2.0", 16 | "phpspec/phpspec": "^5.0 || ^6.0 || ^7.0", 17 | "rshop/php-cs-fixer-config": "^2.0" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "Riesenia\\Kendo\\": "src/" 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Widget/Model.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class Model extends Base 19 | { 20 | /** 21 | * Widget name. 22 | * 23 | * @var string 24 | */ 25 | protected $_name = 'kendo.data.Model'; 26 | 27 | /** 28 | * Add field (alias of addFields). 29 | * 30 | * @param string $key 31 | * @param array $value 32 | * 33 | * @return $this 34 | */ 35 | public function addField(string $key, array $value = []): self 36 | { 37 | return $this->add('fields', $key, $value); 38 | } 39 | 40 | /** 41 | * Return javascript code. 42 | * 43 | * @return string 44 | */ 45 | public function __toString(): string 46 | { 47 | $data = $this->_encode(); 48 | 49 | return $this->_name . '.define(' . $data . ');'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 RIESENIA.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | php-version: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | 17 | - name: Setup PHP 18 | uses: shivammathur/setup-php@v2 19 | with: 20 | php-version: ${{ matrix.php-version }} 21 | coverage: none 22 | 23 | - name: Composer install 24 | run: composer update 25 | 26 | - name: Run phpspec 27 | run: vendor/bin/phpspec run --config=phpspec.yml --no-interaction -vvv 28 | 29 | - name: Run php-cs-fixer 30 | run: vendor/bin/php-cs-fixer fix --config=.php_cs.dist --verbose --diff --dry-run 31 | if: ${{ matrix.php-version == '8.0' }} 32 | 33 | - name: Install phpstan 34 | run: composer require --dev phpstan/phpstan:~0.12 35 | if: ${{ matrix.php-version == '8.0' || matrix.php-version == '8.1' }} 36 | 37 | - name: Run phpstan 38 | run: vendor/bin/phpstan analyse -c phpstan.neon 39 | if: ${{ matrix.php-version == '8.0' || matrix.php-version == '8.1' }} 40 | -------------------------------------------------------------------------------- /src/JavascriptFunction.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class JavascriptFunction implements \JsonSerializable 19 | { 20 | /** 21 | * Value. 22 | * 23 | * @var string 24 | */ 25 | protected $_value; 26 | 27 | /** 28 | * Constructor. 29 | * 30 | * @param string $value 31 | */ 32 | public function __construct(string $value) 33 | { 34 | $this->_value = $value; 35 | } 36 | 37 | /** 38 | * Value getter. 39 | * 40 | * @return string 41 | */ 42 | public function getValue(): string 43 | { 44 | return $this->_value; 45 | } 46 | 47 | /** 48 | * json_encode call. 49 | * 50 | * @return string 51 | */ 52 | public function jsonSerialize(): string 53 | { 54 | return '::FUNCTION::' . $this->_value . '::FUNCTION::'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Widget/Observable.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class Observable extends Base 19 | { 20 | /** 21 | * Widget name. 22 | * 23 | * @var string 24 | */ 25 | protected $_name = 'kendo.observable'; 26 | 27 | /** 28 | * Variable name for observable. 29 | * 30 | * @var string 31 | */ 32 | protected $_var = 'viewModel'; 33 | 34 | /** 35 | * Change variable name. 36 | * 37 | * @param string $name variable name 38 | * 39 | * @return $this 40 | */ 41 | public function variableName(string $name): self 42 | { 43 | $this->_var = $name; 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * Return javascript code. 50 | * 51 | * @return string 52 | */ 53 | public function __toString(): string 54 | { 55 | $data = $this->_encode(); 56 | 57 | $data = $this->_var . ' = ' . $this->_name . '(' . $data . ');'; 58 | 59 | if ($this->_bindTo) { 60 | $data .= ' kendo.bind($("' . $this->_bindTo . '"), ' . $this->_var . ');'; 61 | } 62 | 63 | return $data; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Kendo.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class Kendo 21 | { 22 | /** 23 | * Create and return instance of requested Kendo widget. 24 | * 25 | * @param string $name 26 | * @param string $bindTo 27 | * 28 | * @return Base 29 | */ 30 | public static function create(string $name, string $bindTo = ''): Base 31 | { 32 | $fullName = __NAMESPACE__ . '\\Widget\\' . $name; 33 | $widget = \class_exists($fullName) ? new $fullName() : new Widget\Base('kendo' . $name); 34 | 35 | if ($bindTo) { 36 | $widget->bindTo($bindTo); 37 | } 38 | 39 | return $widget; 40 | } 41 | 42 | /** 43 | * Create javascript function. 44 | * 45 | * @param string $value 46 | * 47 | * @return JavascriptFunction 48 | */ 49 | public static function js(string $value): JavascriptFunction 50 | { 51 | return new JavascriptFunction($value); 52 | } 53 | 54 | /** 55 | * Create javascript date object. 56 | * 57 | * @param string $value 58 | * 59 | * @return JavascriptFunction|null 60 | */ 61 | public static function date(string $value): ?JavascriptFunction 62 | { 63 | $timestamp = \strtotime($value); 64 | 65 | // wrong date format 66 | if (!$timestamp) { 67 | return null; 68 | } 69 | 70 | return new JavascriptFunction('kendo.parseDate("' . \date('Y-m-d H:i:s', $timestamp) . '")'); 71 | } 72 | 73 | /** 74 | * Handle dynamic static method calls. 75 | * 76 | * @param string $method 77 | * @param mixed[] $arguments 78 | * 79 | * @return mixed 80 | */ 81 | public static function __callStatic(string $method, array $arguments) 82 | { 83 | // create method 84 | if (\preg_match('/create([A-Z][a-zA-Z0-9]*)/', $method, $matches)) { 85 | return static::create($matches[1], $arguments[0] ?? ''); 86 | } 87 | 88 | throw new \BadMethodCallException('Unknown method: ' . $method); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP wrapper for Kendo UI widgets 2 | 3 | [![Build Status](https://github.com/riesenia/kendo/workflows/Test/badge.svg)](https://github.com/riesenia/kendo/actions) 4 | [![Latest Version](https://img.shields.io/packagist/v/riesenia/kendo.svg?style=flat-square)](https://packagist.org/packages/riesenia/kendo) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/riesenia/kendo.svg?style=flat-square)](https://packagist.org/packages/riesenia/kendo) 6 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) 7 | 8 | [Kendo UI](http://www.telerik.com/kendo-ui) is a great JavaScript library. It offers both open-source and commercial editions. 9 | 10 | This library provides a wrapper for all Kendo UI widgets. Telerik provides [PHP wrappers](http://www.telerik.com/php-ui) itself, but these are unnecessarily complex and in addition they are paid. Our library is released under the MIT license, so you are free to use it in any project (even commercial projects) as long as the copyright header is left intact. 11 | 12 | ## Installation 13 | 14 | Install the latest version using `composer require riesenia/kendo` 15 | 16 | Or add to your *composer.json* file as a requirement: 17 | 18 | ```json 19 | { 20 | "require": { 21 | "riesenia/kendo": "~3.0" 22 | } 23 | } 24 | ``` 25 | 26 | *Note: if you use PHP 5.4 - 5.6 use 1.\* version of this library.* 27 | 28 | ## Usage 29 | 30 | Any widget can be created calling the *create* method of *Kendo* class. For example creating a grid with selector *"#grid"* (resulting in `$("#grid").kendoGrid({ ... })` can be achieved by calling: 31 | 32 | ```php 33 | use Riesenia\Kendo\Kendo;  34 | 35 | echo Kendo::create('Grid')->bindTo('#grid'); 36 | ``` 37 | 38 | or by: 39 | 40 | ```php 41 | use Riesenia\Kendo\Kendo;  42 | 43 | echo Kendo::createGrid('#grid'); 44 | ``` 45 | 46 | ### Setting properties 47 | 48 | Any property can be set by calling *set* method. For adding to properties that are represented by array (or objects), *add* method can be used. Set method can be also used for batch assignment by passing array as the only parameter. To NOT encode passed data, pass them wrapped by `Kendo::js()` call. All method calls can be chained. Examples: 49 | 50 | ```php 51 | use Riesenia\Kendo\Kendo;  52 | 53 | $grid = Kendo::createGrid('#grid'); 54 | 55 | // set any property 56 | $grid->setHeight(100); 57 | 58 | // set property, that should not be encoded 59 | $grid->setChange(Kendo::js('function(e) { 60 | console.log(this.select()); 61 | }')); 62 | 63 | // set properties by array 64 | $grid->set([ 65 | 'height' => 100, 66 | 'change' => Kendo::js('function(e) { 67 | console.log(this.select()); 68 | }') 69 | ]); 70 | 71 | // add to property 72 | $grid->addColumns(null, 'Name') 73 | ->addColumns(null, ['field' => 'Surname', 'encoded' => false]); 74 | 75 | // pass DataSource object 76 | $grid->setDataSource(Kendo::createDataSource()); 77 | ``` 78 | 79 | ### Complex example 80 | 81 | Creating code for grid as in [this example](https://demos.telerik.com/kendo-ui/grid/local-data-binding "Grid - binding to local data"): 82 | 83 | ```php 84 | use Riesenia\Kendo\Kendo;  85 | 86 | $model = Kendo::createModel() 87 | ->addField('ProductName', ['type' => 'string']) 88 | ->addField('UnitPrice', ['type' => 'number']) 89 | ->addField('UnitsInStock', ['type' => 'number']) 90 | ->addField('Discontinued', ['type' => 'boolean']); 91 | 92 | $dataSource = Kendo::createDataSource() 93 | ->setData(Kendo::js('products')) 94 | ->setSchema(['model' => $model]) 95 | ->setPageSize(20); 96 | 97 | echo Kendo::createGrid('#grid') 98 | ->setDataSource($dataSource) 99 | ->setHeight(550) 100 | ->setScrollable(true) 101 | ->setSortable(true) 102 | ->setFilterable(true) 103 | ->setPageable(['input' => true, 'numeric' => false]) 104 | ->setColumns([ 105 | 'ProductName', 106 | ['field' => 'UnitPrice', 'title' => 'Unit Price', 'format' => '{0:c}', 'width' => '130px'], 107 | ['field' => 'UnitsInStock', 'title' => 'Units In Stock', 'width' => '130px'], 108 | ['field' => 'Discontinued', 'width' => '130px'] 109 | ]); 110 | ``` 111 | 112 | ### Observable (MVVM) 113 | 114 | Rendering for [Kendo observable](https://demos.telerik.com/kendo-ui/mvvm/index "MVVM - basic usage") is slightly different. Predefined variable name is *viewModel*, but this can be overridden by the method *variableName*. Example: 115 | 116 | ```php 117 | use Riesenia\Kendo\Kendo;  118 | 119 | echo Kendo::createObservable('#view') 120 | ->variableName('myMvvm') 121 | ->setFirstName('John') 122 | ->setLastName('Doe') 123 | ->setDisplayGreeting(Kendo::js('function() { 124 | alert("Hello, " + this.get("firstName") + " " + this.get("lastName") + "!!!"); 125 | }')); 126 | ``` 127 | 128 | This will output: 129 | 130 | ```javascript 131 | myMvvm = kendo.observable({ 132 | "firstName": "John", 133 | "lastName": "Doe", 134 | "displayGreeting": function () { 135 | alert("Hello, " + this.get("firstName") + " " + this.get("lastName") + "!!!"); 136 | } 137 | }); 138 | kendo.bind($("#view"), myMvvm); 139 | ``` 140 | -------------------------------------------------------------------------------- /src/Widget/Base.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class Base implements \JsonSerializable 19 | { 20 | /** 21 | * Widget name. 22 | * 23 | * @var string 24 | */ 25 | protected $_name; 26 | 27 | /** 28 | * Selector for jQuery object. 29 | * 30 | * @var string 31 | */ 32 | protected $_bindTo; 33 | 34 | /** 35 | * Data. 36 | * 37 | * @var array 38 | */ 39 | protected $_data = []; 40 | 41 | /** 42 | * Create requested widget. 43 | * 44 | * @param string $name 45 | */ 46 | public function __construct(string $name = '') 47 | { 48 | if ($name) { 49 | $this->_name = $name; 50 | } 51 | } 52 | 53 | /** 54 | * Get widget name. 55 | * 56 | * @return string 57 | */ 58 | public function name(): string 59 | { 60 | return $this->_name; 61 | } 62 | 63 | /** 64 | * Set the jQuery selector for binding. 65 | * 66 | * @param string $selector 67 | * 68 | * @return $this 69 | */ 70 | public function bindTo(string $selector): self 71 | { 72 | $this->_bindTo = $selector; 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * Property setter. 79 | * 80 | * @param array|string $name property name or array of properties 81 | * @param mixed $value property value 82 | * 83 | * @return $this 84 | */ 85 | public function set($name, $value = null): self 86 | { 87 | if (!\is_array($name)) { 88 | $name = [$name => $value]; 89 | } 90 | 91 | foreach ($name as $property => $value) { 92 | $this->_data[$property] = $value; 93 | } 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * Property setter adding to associative arrays. 100 | * 101 | * @param string $name 102 | * @param string|null $key 103 | * @param mixed $value 104 | * 105 | * @return $this 106 | */ 107 | public function add(string $name, ?string $key, $value): self 108 | { 109 | if (!isset($this->_data[$name])) { 110 | $this->_data[$name] = []; 111 | } 112 | 113 | if ($key === null) { 114 | $key = \count($this->_data[$name]); 115 | } 116 | 117 | $this->_data[$name][$key] = $value; 118 | 119 | return $this; 120 | } 121 | 122 | /** 123 | * Property getter. 124 | * 125 | * @param string $name 126 | * 127 | * @return mixed property value 128 | */ 129 | public function get(string $name) 130 | { 131 | if (!isset($this->_data[$name])) { 132 | return null; 133 | } 134 | 135 | return $this->_data[$name]; 136 | } 137 | 138 | /** 139 | * json_encode call. 140 | * 141 | * @return array|null 142 | */ 143 | public function jsonSerialize(): ?array 144 | { 145 | return $this->_data ?: null; 146 | } 147 | 148 | /** 149 | * Handle json_encode. 150 | * 151 | * @return string 152 | */ 153 | protected function _encode(): string 154 | { 155 | // json encode 156 | $data = \json_encode($this, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); 157 | 158 | if ($data === false) { 159 | throw new \Exception('Invalid data'); 160 | } 161 | 162 | // replace markup by JavacriptFunction 163 | $data = \preg_replace_callback('/"::FUNCTION::(.*?)::FUNCTION::"/', function ($matches) { 164 | return \stripcslashes($matches[1]); 165 | }, $data) ?: ''; 166 | 167 | return $data; 168 | } 169 | 170 | /** 171 | * Return javascript code. 172 | * 173 | * @return string 174 | */ 175 | public function __toString(): string 176 | { 177 | $data = $this->_encode(); 178 | 179 | // binding to jquery object 180 | if ($this->_bindTo) { 181 | return '$("' . $this->_bindTo . '").' . $this->_name . '(' . $data . ');'; 182 | } 183 | 184 | return 'new ' . $this->_name . '(' . $data . ');'; 185 | } 186 | 187 | /** 188 | * Handle dynamic method calls. 189 | * 190 | * @param string $method 191 | * @param mixed[] $arguments 192 | * 193 | * @return mixed 194 | */ 195 | public function __call(string $method, array $arguments) 196 | { 197 | // set for setting object attributes 198 | if (\preg_match('/set([A-Z][a-zA-Z0-9]*)/', $method, $matches)) { 199 | return $this->set(\lcfirst($matches[1]), $arguments[0]); 200 | } 201 | 202 | // add for adding to array object attributes 203 | if (\preg_match('/add([A-Z][a-zA-Z0-9]*)/', $method, $matches)) { 204 | return $this->add(\lcfirst($matches[1]), $arguments[0], $arguments[1]); 205 | } 206 | 207 | // get for getting object attributes 208 | if (\preg_match('/get([A-Z][a-zA-Z0-9]*)/', $method, $matches)) { 209 | return $this->get(\lcfirst($matches[1])); 210 | } 211 | 212 | throw new \BadMethodCallException('Unknown method: ' . $method); 213 | } 214 | } 215 | --------------------------------------------------------------------------------