├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── README_cn.md ├── composer.json ├── demo.php ├── example └── Editable.php ├── phpunit.xml ├── src ├── Editable.php ├── Integrates │ ├── EditableException.php │ └── EditableResponse.php └── Interfaces │ └── EditableInterface.php └── tests └── EditableBasicTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - '5.4' 4 | - '5.6' 5 | - '7.0' 6 | - '7.1.9' 7 | install: 8 | - composer install -vvv 9 | script: phpunit tests/EditableBasicTest.php -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Xiaohui Lam 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-x-editable 2 | 3 | Maybe the best X-Editable PHP plugin of the world. 如果你是中文用户,也许看[中文文档](README_cn.md)能更快上手。 4 | 5 | 6 | --- 7 | [![travis-ci.svg](https://img.shields.io/travis/xiaohuilam/php-x-editable/master.svg?style=flat-square)](https://travis-ci.org/xiaohuilam/php-x-editable) 8 | [![packagist-version.svg](https://img.shields.io/packagist/v/diana/php-x-editable.svg?style=flat-square)](https://packagist.org/packages/diana/php-x-editable) 9 | [![license.svg](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/xiaohuilam/php-x-editable/blob/master/LICENSE) 10 | [![download-count.svg](https://img.shields.io/packagist/dt/diana/php-x-editable.svg?style=flat-square)](https://packagist.org/packages/diana/php-x-editable) 11 | [![open-issue.png](https://img.shields.io/github/issues/xiaohuilam/php-x-editable.svg?style=flat-square)](https://github.com/xiaohuilam/php-x-editable/issues) 12 | [![open-pull-request.png](https://img.shields.io/github/issues-pr/xiaohuilam/php-x-editable.svg?style=flat-square)](https://github.com/xiaohuilam/php-x-editable/pulls) 13 | [![last-commit.png](https://img.shields.io/github/last-commit/xiaohuilam/php-x-editable.svg?style=flat-square)](https://github.com/xiaohuilam/php-x-editable/commits) 14 | [![contributors.png](https://img.shields.io/github/contributors/xiaohuilam/php-x-editable.svg?style=flat-square)](https://github.com/xiaohuilam/php-x-editable/graphs/contributors) 15 | 16 | 17 | ## Install 18 | 19 | ``` 20 | $ composer require diana/php-x-editable -vvv 21 | ``` 22 | 23 | After installed, javascript and css is using jsdelivr. 24 | But if, your project is local web using, or orther limitation to hosted assets locally, 25 | please run the command bellow: 26 | 27 | ``` 28 | $ composer require diana/php-x-editable-assets -vvv 29 | $ composer run-script post-autoload-dump -d vendor/diana/php-x-editable-assets 30 | ``` 31 | 32 | The secondary line, is publishing css/js to web directory of your project. it will detect laravel(lumen) 33 | and thinkphp5 framework, to publish to `public/` directory. 34 | ortherwise, defaultly, it will deploy css/js to web root dir itself. 35 | 36 | If indeed, please run 37 | 38 | ``` 39 | $ cp -R ./vendor/diana/php-x-editable-assets/assets/ SPECIFIC_PROJECT_FULLPATH/ 40 | ``` 41 | 42 | 43 | ## Usage 44 | 45 | ```php 46 | 12, 50 | 'name' => '张君宝', 51 | 'home' => '武当山', 52 | 'prefer' => 'php,html', 53 | 'gender' => 1, 54 | 'job' => 2, 55 | 'about' => 'Throne of the seven kingdoms,
Father of the dragon, stormborn, unburn.', 56 | 'created_at' => date('Y-m-d H:i:s'), 57 | ], 58 | 'id', 59 | [], 60 | 'test.php?action=save' 61 | ); 62 | 63 | $editable->typeahead('home', null, [ 64 | '武当山', 65 | '华山', 66 | '峨眉山', 67 | '井冈山', 68 | ], 0); 69 | 70 | $editable->checklist('job', null, [ 71 | ['value' => 1, 'text' => '一代弱鸡'], 72 | ['value' => 2, 'text' => '一代宗师'], 73 | ['value' => 3, 'text' => '一代刺客'] 74 | ], 0); 75 | 76 | $editable->select('gender', null, [ 77 | ['value' => 0, 'text' => '未知'], 78 | ['value' => 1, 'text' => '男'], 79 | ['value' => 2, 'text' => '女'], 80 | ], 0); 81 | 82 | $editable->tag('prefer', null, ['css', 'js', 'google']); 83 | 84 | $editable->wysiwyg('about'); 85 | $editable->datetime('created_at'); 86 | echo $editable->render()->getBody(); 87 | ``` 88 | 89 | To get full demo here: https://github.com/xiaohuilam/php-x-editable/blob/dev/example/Editable.php 90 | 91 | 92 | #### Input[Text] 93 | ![1.png](https://i.loli.net/2017/11/09/5a042ab5a73db.png) 94 | 95 | #### Typeahead 96 | ![2.png](https://i.loli.net/2017/11/09/5a042ab5cc6a1.png) 97 | 98 | #### Tag 99 | ![3.png](https://i.loli.net/2017/11/09/5a042ab5cf328.png) 100 | 101 | #### Checklist 102 | ![5.png](https://i.loli.net/2017/11/09/5a042ab5e86fd.png) 103 | 104 | #### Select 105 | ![4.png](https://i.loli.net/2017/11/09/5a042ab5f2f18.png) 106 | 107 | #### Wysiwyg(所见即所得) 108 | ![6.png](https://i.loli.net/2017/11/09/5a042ab6068d1.png) 109 | 110 | #### Datetime(日期时间) 111 | ![7.png](https://i.loli.net/2017/11/09/5a042ab610250.png) 112 | 113 | 114 | 115 | ## Features and TODO 116 | 117 | |Feature |Description |Status | 118 | |--|--|--| 119 | |text | |Finished| 120 | |select | |Finished| 121 | |tags | |Finished| 122 | |datetime| |Finished| 123 | |wysiwyg| |Finished| 124 | | assets self host | [To see in the introduce](https://github.com/xiaohuilam/php-x-editable#install)|Finished| 125 | |Auto save| re-use your data reading code, avoid code again. | Awaiting | 126 | |Multiple rows|multiple rows data editing | Awaiting| 127 | |Async source |select/typeahead's ajax remote source support| Partly([supportted with select box currently](https://github.com/xiaohuilam/php-x-editable/blob/dev/example/Editable.php#L47))| 128 | |File upload |x-editable is not support file uploading natively, until us| Awaiting| 129 | |Post extra param|other params like csrf_token|Awaiting| 130 | 131 | ## Credits & thanks 132 | 133 | - x-editable: https://github.com/vitalets/x-editable 134 | - bootstrap: https://github.com/twbs/bootstrap/releases/tag/v3.3.7 135 | - php-html-builder: https://github.com/avplab/php-html-builder 136 | 137 | ## License 138 | 139 | ``` 140 | MIT 141 | ``` 142 | 143 | 144 | ## Donation 145 | 146 | There's no donation accepted. 147 | But you can support me when you shopping checkout with `Alipay wallet` APP. 148 | Before checkout, scan the QR code, you can get a small amount disacount, and me, will receive a bonus. 149 | ![zfb.jpg](https://i.loli.net/2017/11/16/5a0d3d6f957bc.jpg) 150 | -------------------------------------------------------------------------------- /README_cn.md: -------------------------------------------------------------------------------- 1 | # php-x-editable 2 | 3 | 可能是史上最好用的X-Editable PHP插件了 4 | 5 | 6 | --- 7 | [![travis-ci.svg](https://img.shields.io/travis/xiaohuilam/php-x-editable/master.svg?style=flat-square)](https://travis-ci.org/xiaohuilam/php-x-editable) 8 | [![packagist-version.svg](https://img.shields.io/packagist/v/diana/php-x-editable.svg?style=flat-square)](https://packagist.org/packages/diana/php-x-editable) 9 | [![license.svg](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/xiaohuilam/php-x-editable/blob/master/LICENSE) 10 | [![download-count.svg](https://img.shields.io/packagist/dt/diana/php-x-editable.svg?style=flat-square)](https://packagist.org/packages/diana/php-x-editable) 11 | [![open-issue.png](https://img.shields.io/github/issues/xiaohuilam/php-x-editable.svg?style=flat-square)](https://github.com/xiaohuilam/php-x-editable/issues) 12 | [![open-pull-request.png](https://img.shields.io/github/issues-pr/xiaohuilam/php-x-editable.svg?style=flat-square)](https://github.com/xiaohuilam/php-x-editable/pulls) 13 | [![last-commit.png](https://img.shields.io/github/last-commit/xiaohuilam/php-x-editable.svg?style=flat-square)](https://github.com/xiaohuilam/php-x-editable/commits) 14 | [![contributors.png](https://img.shields.io/github/contributors/xiaohuilam/php-x-editable.svg?style=flat-square)](https://github.com/xiaohuilam/php-x-editable/graphs/contributors) 15 | 16 | 17 | 18 | ## 安装 19 | 20 | ``` 21 | $ composer require diana/php-x-editable -vvv 22 | ``` 23 | 24 | 在安装完成后,javascript和css是引用jsdelivr的URL来载入的。 25 | 如果您的项目是内网,或者因为其他限制需要本地托管的,请执行下面的命令即可托管在本地。 26 | 27 | ``` 28 | $ composer require diana/php-x-editable-assets -vvv 29 | $ composer run-script post-autoload-dump -d vendor/diana/php-x-editable-assets 30 | ``` 31 | 32 | 其中,第二个命令是发布CSS/JS到项目的WEB目录的。针对laravel(lumen)和thinphp5框架,会发布到public目录。 33 | 其他情况默认发布到根目录。 34 | 如果框架不满足上面情况,需要手工执行 35 | ``` 36 | $ cp -R ./vendor/diana/php-x-editable-assets/assets/ 你项目的WEB根目录/ 37 | ``` 38 | 39 | 40 | ## 使用 41 | 42 | ```php 43 | 12, 47 | 'name' => '张君宝', 48 | 'home' => '武当山', 49 | 'prefer' => 'php,html', 50 | 'gender' => 1, 51 | 'job' => 2, 52 | 'about' => 'Throne of the seven kingdoms,
Father of the dragon, stormborn, unburn.', 53 | 'created_at' => date('Y-m-d H:i:s'), 54 | ], 55 | 'id', 56 | [], 57 | 'test.php?action=save' 58 | ); 59 | 60 | $editable->typeahead('home', null, [ 61 | '武当山', 62 | '华山', 63 | '峨眉山', 64 | '井冈山', 65 | ], 0); 66 | 67 | $editable->checklist('job', null, [ 68 | ['value' => 1, 'text' => '一代弱鸡'], 69 | ['value' => 2, 'text' => '一代宗师'], 70 | ['value' => 3, 'text' => '一代刺客'] 71 | ], 0); 72 | 73 | $editable->select('gender', null, [ 74 | ['value' => 0, 'text' => '未知'], 75 | ['value' => 1, 'text' => '男'], 76 | ['value' => 2, 'text' => '女'], 77 | ], 0); 78 | 79 | $editable->tag('prefer', null, ['css', 'js', 'google']); 80 | 81 | $editable->wysiwyg('about'); 82 | $editable->datetime('created_at'); 83 | echo $editable->render()->getBody(); 84 | ``` 85 | 86 | 完整的DEMO例子请见 https://github.com/xiaohuilam/php-x-editable/blob/dev/example/Editable.php 87 | 88 | 89 | #### Input[Text] 90 | ![1.png](https://i.loli.net/2017/11/09/5a042ab5a73db.png) 91 | 92 | #### Typeahead 93 | ![2.png](https://i.loli.net/2017/11/09/5a042ab5cc6a1.png) 94 | 95 | #### Tag 96 | ![3.png](https://i.loli.net/2017/11/09/5a042ab5cf328.png) 97 | 98 | #### Checklist 99 | ![5.png](https://i.loli.net/2017/11/09/5a042ab5e86fd.png) 100 | 101 | #### Select 102 | ![4.png](https://i.loli.net/2017/11/09/5a042ab5f2f18.png) 103 | 104 | #### Wysiwyg(所见即所得) 105 | ![6.png](https://i.loli.net/2017/11/09/5a042ab6068d1.png) 106 | 107 | #### Datetime(日期时间) 108 | ![7.png](https://i.loli.net/2017/11/09/5a042ab610250.png) 109 | 110 | 111 | ## 功能进度 112 | 113 | |功能 |描述 |状态 | 114 | |--|--|--| 115 | |text | |已完结| 116 | |select | |已完结| 117 | |tags | |已完结| 118 | |datetime| |已完结| 119 | |wysiwyg| |已完结| 120 | |静态资源本地托管 |[安装](https://github.com/xiaohuilam/php-x-editable/blob/master/README_cn.md#安装)|已完结| 121 | |后端自动保存| 就是你不用写保存的代码,因为你在读取时候已经写了所必要的信息 |跳票中| 122 | |多行编辑|一次编辑多行数据,考虑和datatables插件做支持 |跳票中| 123 | |二次异步加载 |select和typeahead的下拉数据,做AJAX加载的支持 |部分支持([单选框select已支持](https://github.com/xiaohuilam/php-x-editable/blob/dev/example/Editable.php#L47))| 124 | |文件上传 |x-editable是不支持上传文件的,我打算支持他|跳票中| 125 | |追加额外的POST参数|如CSRF_TOKEN|跳票中| 126 | 127 | 128 | ## 特别鸣谢 129 | 130 | - x-editable: https://github.com/vitalets/x-editable 131 | - bootstrap: https://github.com/twbs/bootstrap/releases/tag/v3.3.7 132 | - php-html-builder: https://github.com/avplab/php-html-builder 133 | 134 | 135 | ## 授权 136 | 137 | ``` 138 | MIT 139 | ``` 140 | 141 | 142 | ## 捐助 143 | 144 | 您不用捐助我,您只需用 `支付宝钱包` APP扫描下面二维码后,买单时您可获得若干毛钱的减免,而我也可获得奖励。 145 | ![zfb.jpg](https://i.loli.net/2017/11/16/5a0d3d6f957bc.jpg) 146 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diana/php-x-editable", 3 | "description": "Maybe the best x-editable plugin for PHP", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | {"name": "xiaohui lam", "email": "xiaohui.lam@e.hexdata.cn"} 8 | ], 9 | "require": { 10 | "php": ">5.3", 11 | "psr/http-message": "1.*", 12 | "avplab/php-html-builder": ">1.0" 13 | }, 14 | "require-dev": { 15 | "phpunit/phpunit": "*" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "Editable\\": "src/" 20 | } 21 | }, 22 | "autoload-dev": { 23 | "psr-4": { 24 | "Editable\\Example\\": "example/", 25 | "Editable\\Test\\": "test/" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /demo.php: -------------------------------------------------------------------------------- 1 | 0, 'text' => 'Unmarried 未婚'), 9 | array('value' => 1, 'text' => 'Marriaged 已婚'), 10 | array('value' => 2, 'text' => 'Divorced 离异'), 11 | array('value' => 3, 'text' => 'Widowed 丧偶'), 12 | ); 13 | echo json_encode($groups); 14 | exit; 15 | } 16 | ?> 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |

PHP Editable Demo

25 | 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /example/Editable.php: -------------------------------------------------------------------------------- 1 | 12, 9 | 'name' => '张君宝', 10 | 'home' => '武当山', 11 | 'prefer' => 'php,html', 12 | 'gender' => 1, 13 | 'marriage' => 3, 14 | 'job' => 2, 15 | 'about' => 'Throne of the seven kingdoms,
Father of the dragon, stormborn, unburn.', 16 | 'created_at' => date('Y-m-d H:i:s'), 17 | ], 18 | 'id', 19 | [], 20 | 'demo.php?action=save' 21 | ); 22 | 23 | $editable->typeahead('home', null, [ 24 | '武当山', 25 | '华山', 26 | '峨眉山', 27 | '井冈山', 28 | ]); 29 | 30 | $editable->checklist('job', null, [ 31 | ['value' => 1, 'text' => '一代弱鸡'], 32 | ['value' => 2, 'text' => '一代宗师'], 33 | ['value' => 3, 'text' => '一代刺客'] 34 | ]); 35 | 36 | $editable->select('gender', null, [ 37 | ['value' => 0, 'text' => '未知'], 38 | ['value' => 1, 'text' => '男'], 39 | ['value' => 2, 'text' => '女'], 40 | ], 0); 41 | 42 | $editable->tag('prefer', null, ['css', 'js', 'google']); 43 | 44 | $editable->wysiwyg('about'); 45 | $editable->datetime('created_at'); 46 | 47 | $editable->select('marriage', null, '/demo.php?action=marriage', 0); 48 | 49 | return $editable->render()->getBody(); 50 | } 51 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | ./tests 18 | 19 | 20 | 21 | 22 | ./app 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Editable.php: -------------------------------------------------------------------------------- 1 | [ 19 | 'css' => [ 20 | 'https://cdn.jsdelivr.net/gh/twbs/bootstrap@3.3.5/dist/css/bootstrap.min.css', 21 | 'https://cdn.jsdelivr.net/gh/vitalets/x-editable@1.5.1/dist/bootstrap3-editable/css/bootstrap-editable.min.css', 22 | ], 23 | 'js' => [ 24 | 'https://cdn.jsdelivr.net/npm/jquery@1.12.1/dist/jquery.min.js', 25 | 'https://cdn.jsdelivr.net/gh/twbs/bootstrap@3.3.5/dist/js/bootstrap.min.js', 26 | 'https://cdn.jsdelivr.net/gh/vitalets/x-editable@1.5.1/dist/bootstrap3-editable/js/bootstrap-editable.min.js', 27 | ], 28 | ], 29 | 'text' => ['css' => [], 'js' => []], 30 | 'select' => ['css' => [], 'js' => []], 31 | 'checklist' => ['css' => [], 'js' => []], 32 | 'textarea' => ['css' => [], 'js' => []], 33 | 'date' => [ 34 | 'css' => [], 35 | 'js' => [ 36 | 'https://cdn.jsdelivr.net/gh/moment/moment@2.19.1/min/moment.min.js', 37 | ] 38 | ], 39 | 'datetime' => [ 40 | 'css' => [], 41 | 'js' => [ 42 | 'https://cdn.jsdelivr.net/gh/moment/moment@2.19.1/min/moment.min.js', 43 | ] 44 | ], 45 | 'typeaheadjs' => [ 46 | 'css' => [ 47 | 'https://cdn.jsdelivr.net/gh/vitalets/x-editable@1.5.1/dist/inputs-ext/typeaheadjs/lib/typeahead.js-bootstrap.min.css', 48 | ], 49 | 'js' => [ 50 | 'https://cdn.jsdelivr.net/gh/vitalets/x-editable@1.5.1/dist/inputs-ext/typeaheadjs/lib/typeahead.min.js', 51 | 'https://cdn.jsdelivr.net/gh/vitalets/x-editable@1.5.1/dist/inputs-ext/typeaheadjs/typeaheadjs.min.js' 52 | ], 53 | ], 54 | 'tag' => [ 55 | 'css' => [ 56 | 'https://cdn.jsdelivr.net/gh/xiaohuilam/x-editable@9.8.1/assets/select2/select2.min.css', 57 | 'https://cdn.jsdelivr.net/gh/xiaohuilam/x-editable@9.8.1/assets/select2/select2-bootstrap.min.css', 58 | ], 59 | 'js' => [ 60 | 'https://cdn.jsdelivr.net/gh/xiaohuilam/x-editable@9.8.1/assets/select2/select2.min.js', 61 | ], 62 | ], 63 | 'wysiwyg' => [ 64 | 'css' => [ 65 | 'https://cdn.jsdelivr.net/gh/xiaohuilam/x-editable@9.8.1/assets/x-editable/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.3/bootstrap-wysihtml5-0.0.3.min.css', 66 | ], 67 | 'js' => [ 68 | 'https://cdn.jsdelivr.net/gh/xiaohuilam/x-editable@9.8.1/assets/x-editable/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.3/wysihtml5-0.3.0.min.js', 69 | 'https://cdn.jsdelivr.net/gh/xiaohuilam/x-editable@9.8.1/assets/x-editable/inputs-ext/wysihtml5/bootstrap-wysihtml5-0.0.3/bootstrap-wysihtml5-0.0.3.min.js', 70 | 'https://cdn.jsdelivr.net/gh/xiaohuilam/x-editable@9.8.1/assets/x-editable/inputs-ext/wysihtml5/wysihtml5-0.0.3.min.js', 71 | ], 72 | ] 73 | ]; 74 | 75 | /** 76 | * @var array 所有数据 [$type, $key, $value, $options, $index] 77 | */ 78 | protected $data = []; 79 | 80 | protected $row; 81 | protected $pk; 82 | protected $hidden; 83 | protected $ajax; 84 | 85 | /** 86 | * @var PhpHtmlBuilder HTML构造器 87 | */ 88 | protected $builder; 89 | 90 | /** 91 | * @var int 92 | */ 93 | protected $uuid; 94 | 95 | /** 96 | * @param array|ArrayAccess $row 数据库中的一行数据 97 | * @param string $pk 主键名称 98 | * @param array $hidden 保护字段 99 | * @param string $ajax AJAX保存URL 100 | */ 101 | public function __construct($row = null, $pk = "id", $hidden = [], $ajax = '') 102 | { 103 | if (!$row) { 104 | throw new EditableException(null, EditableException::NO_DATA); 105 | } 106 | 107 | $this->row = $row; 108 | $this->pk = isset($this->row[$pk]) ? $this->row[$pk] : null; 109 | $this->hidden = array_flip($hidden); 110 | $this->ajax = $ajax; 111 | 112 | $this->hidden[$pk] = 1; 113 | 114 | if (class_exists('\Editable\Assets\LocalAssets')) { 115 | $this->vendor_assets = \Editable\Assets\LocalAssets::instance()->getVendorAssetsRoutingUrl(); 116 | } 117 | 118 | foreach ($this->row as $key => $value) { 119 | $this->input($key, $value); 120 | } 121 | } 122 | 123 | 124 | /** 125 | * 登记输入框 126 | * 127 | * @param string $type 类型 128 | * @param string $key 129 | * @return self 130 | */ 131 | public function registerComponent($type, $key, $value = null, $options = [], $index = null) 132 | { 133 | $this->data[$key] = func_get_args(); 134 | $this->existed_dom_type[$type] = 1; 135 | return $this; 136 | } 137 | 138 | /** 139 | * 保护一个字段不可编辑 140 | * 141 | * @param string $key 字段 142 | * @return self 143 | */ 144 | public function protect($key) 145 | { 146 | if (is_array($key)) { 147 | foreach ($key as $each) { 148 | $this->protect($each); 149 | } 150 | } else { 151 | $this->hidden[$key] = 1; 152 | } 153 | 154 | return $this; 155 | } 156 | 157 | /** 158 | * 输入框 159 | * 160 | * @param string $key 字段 161 | * @param string $value 值 162 | * @return self 163 | */ 164 | public function input($key, $value) 165 | { 166 | return $this->text($key, $value); 167 | } 168 | public function text($key, $value) 169 | { 170 | return $this->registerComponent(__FUNCTION__, $key, $value); 171 | } 172 | 173 | /** 174 | * 文本框 175 | * 176 | * @param string $key 字段 177 | * @param string $value 值 178 | * @return self 179 | */ 180 | public function textarea($key, $value) 181 | { 182 | return $this->registerComponent(__FUNCTION__, $key, $value); 183 | } 184 | 185 | /** 186 | * 单选框 187 | * 188 | * @param string $key 字段 189 | * @param string $value 值 190 | * @param mixed $options 可供选择的项 191 | * @param int $index 当前值的索引 与$value必填1个 192 | * @return self 193 | */ 194 | public function select($key, $value = null, $options = [], $index = null) 195 | { 196 | return $this->registerComponent(__FUNCTION__, $key, $value, $options, $index); 197 | } 198 | 199 | /** 200 | * 复选框 201 | * 202 | * @param string $key 字段 203 | * @param string|array $value 值 如1,2,3或者[1,2,3] 204 | * @param mixed $options 可供选择的项 205 | * @return self 206 | */ 207 | public function checklist($key, $value = null, $options) 208 | { 209 | return $this->registerComponent(__FUNCTION__, $key, $value, $options); 210 | } 211 | 212 | 213 | /** 214 | * 日期 215 | * 216 | * @param string $key 字段 217 | * @param string $value 值 yyyy/mm/dd dd/mm/yyyy 两种格式 /可用-替代 218 | * @return self 219 | */ 220 | public function date($key, $value = null) 221 | { 222 | return $this->registerComponent(__FUNCTION__, $key, $value); 223 | } 224 | 225 | /** 226 | * 日期时间 227 | * 228 | * @param string $key 字段 229 | * @param string $value 值 "yyyy/mm/dd HH:ii::ss" "dd/mm/yyyy HH:ii:ss" 两种格式 "/"可用"-"替代 230 | * @return self 231 | */ 232 | public function datetime($key, $value = null) 233 | { 234 | return $this->registerComponent(__FUNCTION__, $key, $value); 235 | } 236 | 237 | /** 238 | * 匹配框 239 | * 240 | * @param string $key 字段 241 | * @param string $value 值 242 | * @param mixed $options 可供选择的项 243 | * @return self 244 | */ 245 | public function typeaheadjs($key, $value = null, $options = []) 246 | { 247 | return $this->registerComponent(__FUNCTION__, $key, $value, $options); 248 | } 249 | 250 | public function typeahead($key, $value = null, $options = []) 251 | { 252 | return $this->typeaheadjs($key, $value, $options); 253 | } 254 | 255 | /** 256 | * 标签 257 | * 258 | * @param string $key 字段 259 | * @param array|string $value 已存在的tag ['tag1', 'tag2']或'tag1,tag2' 260 | * @param array $options 可供选择的项 ['tag1', 'tag2'] 261 | * @return self 262 | */ 263 | public function tag($key, $value = null, array $options = []) 264 | { 265 | return $this->registerComponent(__FUNCTION__, $key, $value, $options); 266 | } 267 | 268 | /** 269 | * 所见即所得编辑器 270 | * 271 | * @param string $key 字段 272 | * @param string $value 值 273 | * @return self 274 | */ 275 | public function wysiwyg($key, $value = null) 276 | { 277 | return $this->registerComponent(__FUNCTION__, $key, $value); 278 | } 279 | 280 | /** 281 | * 控件遍历方法 282 | * @param array $each each in $this->data 283 | * @return self 284 | */ 285 | protected function __every_element($each) 286 | { 287 | 288 | $type = $each[0]; 289 | $key = $each[1]; 290 | if ($each[2] === null && isset($this->row[$key]) && $this->row[$key] != null) { 291 | $each[2] = $this->row[$key]; 292 | } 293 | $value = $each[2]; 294 | $show_name = ucfirst(preg_replace_callback('/(_([a-zA-Z0-9]))/', function($a) { 295 | if (isset($a[2])) { 296 | return ' '.strtoupper($a[2]); 297 | } 298 | return $a; 299 | }, $key)); 300 | $title = 'Type the '.$show_name; 301 | $show_type = $type; 302 | 303 | $show_value = $value; 304 | if ($type == 'select') { 305 | $show_value = ''; 306 | if (is_array($each[3])) { 307 | foreach ($each[3] as $option) { 308 | $option_value = isset($option['value']) ? $option['value'] : null; 309 | if ($value == $option_value) { 310 | $show_value = $option['text']; 311 | break; 312 | } 313 | } 314 | } else if (is_string($each[3])) { 315 | 316 | } 317 | } else if ($type == 'typeaheadjs') { 318 | $show_value = ''; 319 | foreach ($each[3] as $option) { 320 | $option_value = isset($option['value']) ? $option['value'] : null; 321 | if ($value == $option_value) { 322 | $show_value = $option['text'].' ('.$value.') '; 323 | break; 324 | } 325 | } 326 | } else if ($type == 'wysiwyg') { 327 | $show_type = 'wysihtml5'; 328 | } else if ($type == 'tag') { 329 | $show_type = 'select2'; 330 | } 331 | 332 | 333 | $this->builder->tr(); 334 | /**/$this->builder->td($show_name)->end(); 335 | /**/$this->builder->td(); 336 | /**//**/$this->builder->a($show_value) 337 | /**//**//**/->setDataName($key) 338 | /**//**//**/->setDataPk($this->pk) 339 | /**//**//**/->setDataUrl($this->ajax) 340 | /**//**//**/->setDataTitle($title) 341 | /**//**//**/->setDataType($show_type) 342 | /**//**//**/->setDataValue($value) 343 | /**//**//**/->setDataPlacement('bottom'); 344 | /**//**//**/if (!isset($this->hidden[$key])) { 345 | /**//**//**//**/$this->builder->setClass('editable-link'); 346 | /**//**//**/} 347 | /**//**//**/if ($type == 'select' || $type == 'tag' || $type == 'checklist') { 348 | /**//**//**//**/$this->builder->setDataSource(is_string($each[3]) ? $each[3] : json_encode($each[3])); 349 | /**//**//**/} else if ($type == 'typeaheadjs') { 350 | /**//**//**//**/$this->builder->setDataTypeahead( # @todo: template 351 | /**//**//**//**//**/json_encode([ 352 | /**//**//**//**//**/'name' => $key, 353 | /**//**//**//**//**/'local' => $each[3], 354 | /**//**//**//**//**/]) 355 | /**//**//**//**/); 356 | /**//**//**/} else if ($type == 'date') { 357 | /**//**//**//**/$this->builder->setDataType('combodate') 358 | /**//**//**//**//**//**/->setDataTemplate('YYYY/MM/DD') 359 | /**//**//**//**//**//**/->setDataFormat('YYYY-MM-DD') 360 | /**//**//**//**//**//**/->setDataViewformat('YYYY-MM-DD'); 361 | /**//**//**/} else if ($type == 'datetime') { 362 | /**//**//**//**/$this->builder->setDataType('combodate') 363 | /**//**//**//**//**//**/->setDataTemplate('YYYY/MM/DD HH:mm:ss') 364 | /**//**//**//**//**//**/->setDataFormat('YYYY-MM-DD HH:mm:ss') 365 | /**//**//**//**//**//**/->setDataViewformat('YYYY-MM-DD HH:mm:ss'); 366 | /**//**//**/} 367 | /**//**/$this->builder->end(); 368 | /**/$this->builder->end(); 369 | $this->builder->end(); 370 | 371 | return $this; 372 | } 373 | 374 | /** 375 | * 尾部JS 376 | * 377 | * @return self 378 | */ 379 | public function __script() 380 | { 381 | $this->builder->script(<<uuid .editable-link").each(function(){ 386 | var self = this; 387 | $(self).editable({ 388 | typeahead: (function(a){ 389 | try{ 390 | if(!$(self).attr('data-typeahead')) return; 391 | opt = $.parseJSON($(self).attr('data-typeahead')) ? $.extend($(self).data('typeahead'), { 392 | template: function(item) { 393 | if('undefined' == typeof item.tokens) return item.value; 394 | return item.tokens + ' (' + item.value + ') '; 395 | } 396 | }) : null; 397 | return opt; 398 | }catch(e){console.error(e)} 399 | })(self), 400 | display: (function(self){ 401 | if($(self).data('type') == 'combodate') { 402 | return null; 403 | } 404 | if($(self).attr('data-typeahead') && $.parseJSON($(self).attr('data-typeahead'))) { 405 | return null; 406 | } 407 | return function(arg_value, options) { 408 | if('object' == typeof arg_value && 'undefined' !== typeof arg_value.length && arg_value.constructor.name == 'Array') { 409 | }else{ 410 | arg_value = [arg_value]; 411 | } 412 | 413 | 414 | var shown = ''; 415 | for(var i = 0; i < arg_value.length; i++){ 416 | var value = arg_value[i]; 417 | if(!options || 'undefined' == typeof options || 'object' !== typeof options || 'undefined' == typeof options.length) { 418 | shown += value; 419 | if(i setType('application/javascript')->end(); 465 | return $this; 466 | } 467 | 468 | 469 | /** 470 | * 渲染模板 471 | * 472 | * @param bool $and_destroy 考虑到常驻进程的框架 保留此选项 默认均销毁 473 | * @return EditableResponse 474 | */ 475 | public function render($and_destroy = true) 476 | { 477 | $this->builder = new PhpHtmlBuilder(); 478 | $this->uuid = mt_rand(1000000, 99999999); 479 | $this->builder->div()->setClass('table-wrapper')->setId('table-wrapper-'.$this->uuid); 480 | /**/$this->builder->link()->setRel('stylesheet')->setHref($this->vendor_assets['xeditable']['css'][0])->end(); 481 | /**/$this->builder->link()->setRel('stylesheet')->setHref($this->vendor_assets['xeditable']['css'][1])->end(); 482 | foreach ($this->existed_dom_type as $dom_type => $one) { 483 | foreach ($this->vendor_assets[$dom_type]['css'] as $css) { 484 | /**/$this->builder->link()->setRel('stylesheet')->setHref($css)->end(); 485 | } 486 | } 487 | /**/$this->builder->table()->setClass('table table-bordered table-striped'); 488 | /**//**/$this->builder->thead(); 489 | /**//**//**/$this->builder->tr(); 490 | /**//**//**//**/$this->builder->th('Name')->setWidth("35%")->end(); 491 | /**//**//**//**/$this->builder->th('Value')->setWidth("65%")->end(); 492 | /**//**//**/$this->builder->end(); 493 | /**//**/$this->builder->end(); 494 | /**//**/$this->builder->tbody(); 495 | 496 | foreach ($this->data as $component) { 497 | $this->__every_element($component); 498 | } 499 | 500 | /**//**/$this->builder->end(); 501 | /**/$this->builder->end(); 502 | 503 | /**/$this->builder->script()->setType('application/javascript')->setSrc($this->vendor_assets['xeditable']['js'][0])->end(); 504 | /**/$this->builder->script()->setType('application/javascript')->setSrc($this->vendor_assets['xeditable']['js'][1])->end(); 505 | /**/$this->builder->script()->setType('application/javascript')->setSrc($this->vendor_assets['xeditable']['js'][2])->end(); 506 | 507 | foreach ($this->existed_dom_type as $dom_type => $one) { 508 | foreach ($this->vendor_assets[$dom_type]['js'] as $js) { 509 | /**/$this->builder->script()->setType('application/javascript')->setSrc($js)->end(); 510 | } 511 | } 512 | $this->__script(); 513 | $this->builder->end(); 514 | 515 | $html = (string) $this->builder; 516 | 517 | $response = new EditableResponse(200, $html); 518 | 519 | if ($and_destroy) { 520 | $this->existed_dom_type = []; 521 | $this->data = []; 522 | $this->builder = null; 523 | } 524 | return $response; 525 | } 526 | } 527 | -------------------------------------------------------------------------------- /src/Integrates/EditableException.php: -------------------------------------------------------------------------------- 1 | '你必须传递一条数据' 9 | ]; 10 | 11 | public function __construct($message = null, $code, $previous = null) 12 | { 13 | if ($message === null) { 14 | $message = self::$messages[$code]; 15 | } 16 | 17 | call_user_func_array([parent::class, __FUNCTION__], func_get_args()); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/Integrates/EditableResponse.php: -------------------------------------------------------------------------------- 1 | status_code = $status_code; 26 | } 27 | if ($body !== null) { 28 | $this->body = $body; 29 | } 30 | } 31 | 32 | 33 | 34 | /** 35 | * Gets the response status code. 36 | * 37 | * The status code is a 3-digit integer result code of the server's attempt 38 | * to understand and satisfy the request. 39 | * 40 | * @return int 41 | */ 42 | public function getStatusCode() 43 | { 44 | return $this->status_code; 45 | } 46 | 47 | 48 | /** 49 | * Gets the body of the message. 50 | * 51 | * @return JSONStringArrayAccess 52 | */ 53 | public function getBody() 54 | { 55 | return $this->body; 56 | } 57 | 58 | public function getProtocolVersion() {} 59 | public function withProtocolVersion($version) {} 60 | public function getHeaders() {} 61 | public function hasHeader($name) {} 62 | public function getHeader($name) {} 63 | public function getHeaderLine($name) {} 64 | public function withHeader($name, $value) {} 65 | public function withAddedHeader($name, $value) {} 66 | public function withoutHeader($name) {} 67 | public function withBody(StreamInterface $body) {} 68 | public function withStatus($code, $reasonPhrase = '') {} 69 | public function getReasonPhrase() {} 70 | } -------------------------------------------------------------------------------- /src/Interfaces/EditableInterface.php: -------------------------------------------------------------------------------- 1 | editable = new Editable( 17 | [ 18 | 'id' => 12, 19 | 'name' => '张君宝', 20 | 'home' => '武当山', 21 | 'prefer' => 'php,html', 22 | 'gender' => 1, 23 | 'job' => 2, 24 | 'about' => 'Throne of the seven kingdoms,
Father of the dragon, stormborn, unburn.', 25 | 'created_at' => date('Y-m-d H:i:s'), 26 | ], 27 | 'id', 28 | [], 29 | 'test.php?action=save' 30 | ); 31 | parent::__construct(); 32 | } 33 | 34 | public function testTypeahead() 35 | { 36 | $this->assertSame( 37 | $this->editable, 38 | $this->editable->typeahead('home', null, [ 39 | '武当山', '华山', '峨眉山', '井冈山', ], 0) 40 | ); 41 | } 42 | 43 | public function testChecklist() 44 | { 45 | $this->assertSame( 46 | $this->editable, 47 | $this->editable->checklist('job', null, [ 48 | ['value' => 1, 'text' => '一代弱鸡'], 49 | ['value' => 2, 'text' => '一代宗师'], 50 | ['value' => 3, 'text' => '一代刺客'] 51 | ], 0) 52 | ); 53 | } 54 | 55 | public function testSelect() 56 | { 57 | $this->assertSame( 58 | $this->editable, 59 | $this->editable->select('gender', null, [ 60 | ['value' => 0, 'text' => '未知'], 61 | ['value' => 1, 'text' => '男'], 62 | ['value' => 2, 'text' => '女'], 63 | ], 0) 64 | ); 65 | } 66 | 67 | public function testTag() 68 | { 69 | $this->assertSame($this->editable, $this->editable->tag('prefer', null, ['css', 'js', 'google']) ); 70 | } 71 | 72 | public function testWysiwyg() 73 | { 74 | $this->assertSame($this->editable, $this->editable->wysiwyg('about') ); 75 | } 76 | 77 | public function testDatetime() 78 | { 79 | $this->assertSame($this->editable, $this->editable->datetime('created_at') ); 80 | } 81 | 82 | public function testRender() 83 | { 84 | $this->assertInstanceOf("\\Editable\\Integrates\\EditableResponse", $this->editable->render()); 85 | } 86 | } --------------------------------------------------------------------------------