├── .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 | [](https://travis-ci.org/xiaohuilam/php-x-editable)
8 | [](https://packagist.org/packages/diana/php-x-editable)
9 | [](https://github.com/xiaohuilam/php-x-editable/blob/master/LICENSE)
10 | [](https://packagist.org/packages/diana/php-x-editable)
11 | [](https://github.com/xiaohuilam/php-x-editable/issues)
12 | [](https://github.com/xiaohuilam/php-x-editable/pulls)
13 | [](https://github.com/xiaohuilam/php-x-editable/commits)
14 | [](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 | 
94 |
95 | #### Typeahead
96 | 
97 |
98 | #### Tag
99 | 
100 |
101 | #### Checklist
102 | 
103 |
104 | #### Select
105 | 
106 |
107 | #### Wysiwyg(所见即所得)
108 | 
109 |
110 | #### Datetime(日期时间)
111 | 
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 | 
150 |
--------------------------------------------------------------------------------
/README_cn.md:
--------------------------------------------------------------------------------
1 | # php-x-editable
2 |
3 | 可能是史上最好用的X-Editable PHP插件了
4 |
5 |
6 | ---
7 | [](https://travis-ci.org/xiaohuilam/php-x-editable)
8 | [](https://packagist.org/packages/diana/php-x-editable)
9 | [](https://github.com/xiaohuilam/php-x-editable/blob/master/LICENSE)
10 | [](https://packagist.org/packages/diana/php-x-editable)
11 | [](https://github.com/xiaohuilam/php-x-editable/issues)
12 | [](https://github.com/xiaohuilam/php-x-editable/pulls)
13 | [](https://github.com/xiaohuilam/php-x-editable/commits)
14 | [](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 | 
91 |
92 | #### Typeahead
93 | 
94 |
95 | #### Tag
96 | 
97 |
98 | #### Checklist
99 | 
100 |
101 | #### Select
102 | 
103 |
104 | #### Wysiwyg(所见即所得)
105 | 
106 |
107 | #### Datetime(日期时间)
108 | 
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 | 
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 | }
--------------------------------------------------------------------------------