Compare commits
54
Commits
8842ae4eaa
...
v1.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11756bdfd2 | ||
|
|
00e5a0380a | ||
|
|
e093495909 | ||
|
|
9ddb93b847 | ||
|
|
363da77669 | ||
|
|
94903d6f05 | ||
|
|
3d412d6a46 | ||
|
|
ca2d71e3ed | ||
|
|
7e2d4c7dc2 | ||
|
|
2e3788a295 | ||
|
|
6e7a8f12fb | ||
|
|
a1c51bed10 | ||
|
|
1044e35946 | ||
|
|
6ccf07825a | ||
|
|
23f24fef50 | ||
|
|
33e77b5129 | ||
|
|
a34ebaf0df | ||
|
|
388b2a70f8 | ||
|
|
195586a078 | ||
|
|
5e9ad29f3c | ||
|
|
1991122c32 | ||
|
|
0a7b1cdc12 | ||
|
|
804deb69a2 | ||
|
|
328a4d282c | ||
|
|
fd4e30bd42 | ||
|
|
ea780c697e | ||
|
|
6aaa05b37d | ||
|
|
72940b3615 | ||
|
|
aba66479be | ||
|
|
77d05ae051 | ||
|
|
d984ddc540 | ||
|
|
5397ed4fae | ||
|
|
30ddd4c98b | ||
|
|
f00c98b7ff | ||
|
|
3986437800 | ||
|
|
9190987b53 | ||
|
|
cd3cb18665 | ||
|
|
3a930c13b1 | ||
|
|
bd501bce86 | ||
|
|
1f5d38a8b9 | ||
|
|
164a27d267 | ||
|
|
ceebc6783a | ||
|
|
83fee32ac0 | ||
|
|
97f1f67af1 | ||
|
|
362184f81f | ||
|
|
0d9db0470e | ||
|
|
e75d997b02 | ||
|
|
34828f642c | ||
|
|
6020fa300d | ||
|
|
7437bbfc73 | ||
|
|
95c30be9f8 | ||
|
|
d05a750b39 | ||
|
|
512af58411 | ||
|
|
fd73d8e614 |
No files matched your search
+13
-4
@@ -6,21 +6,30 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "Dave Mc Nicoll",
|
||||
"email": "mcndave@gmail.com"
|
||||
"email": "info@mcnd.ca"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"mcnd/picea": "dev-master"
|
||||
"mcnd/picea": "^1.4.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/mcNdave/picea.git"
|
||||
"type": "composer",
|
||||
"url": "https://git.mcnd.ca/api/packages/mcndave/composer"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Picea\\Ui\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"lean": {
|
||||
"autoload": {
|
||||
"definitions": [
|
||||
"meta/definitions.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
# Picea UI
|
||||
|
||||
Welcome to the `picea-ui` documentation.
|
||||
|
||||
This quick documentation will guide you through the use of the form system, every available
|
||||
UI elements and how to add customs one and individual components.
|
||||
|
||||
Mostly everything you need to build and process form is available in this package.
|
||||
|
||||
The first part is most input callable as a widget :
|
||||
|
||||
```
|
||||
{% ui:text "fullname", "John Doe" %}
|
||||
```
|
||||
|
||||
Current input support is :
|
||||
```php
|
||||
- Input (string $source, array $attributes = [], array $options = [])
|
||||
- Checkbox (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Color (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Date (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Datetime (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Email (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- File (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Hidden (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Image (string $source, array $attributes = [], array $options = [])
|
||||
- Numeric (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Password (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Radio (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Range (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Search (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Select (string $name, array $list, mixed $value = null, array $attributes = [], bool $strictComparison = true)
|
||||
- Tel (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Text (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Textarea (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Time (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Url (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
- Week (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
```
|
||||
|
||||
**[PICEA]** So, using this code:
|
||||
|
||||
```html
|
||||
{% ui:form.post "user.save", current_url() %}
|
||||
<div class="field fullname">
|
||||
<label>Full name</label>
|
||||
{% ui:text "fullname", $user->fullname, [ 'required' => 'required' ] %}
|
||||
</div>
|
||||
|
||||
<div class="field email">
|
||||
<label>Email address</label>
|
||||
{% ui:text "email", $user->email %}
|
||||
</div>
|
||||
|
||||
<div class="field job">
|
||||
<label>Have a job</label>
|
||||
<label>{% ui:radio "job", 'yes', [], [ 'value' => $entity->job ] %} Yes</label>
|
||||
<label>{% ui:radio "job", 'no', [], [ 'value' => $entity->job ] %} No</label>
|
||||
</div>
|
||||
|
||||
<div class="field role">
|
||||
<label>Role</label>
|
||||
{% ui:select "role", [ 'user' => "User", 'moderator' => "Moderator", 'admin' => "Admin" ], $user->role, [ 'required' => 'required' ] %}
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
{% ui:endform %}
|
||||
```
|
||||
|
||||
**[HTML]** Would render as such:
|
||||
|
||||
```html
|
||||
<form class="ui-form" method="post" action="http://127.0.0.1/" enctype="multipart/form-data">
|
||||
<input class="ui-hidden" type="hidden" name="picea-ui-form[user.save]" value="617fb40f1999db63fbd0b4f680a77750">
|
||||
|
||||
<div class="field fullname">
|
||||
<label>Full name</label>
|
||||
<input class="ui-text" type="text" value="" name="fullname" required="required">
|
||||
</div>
|
||||
|
||||
<div class="field email">
|
||||
<label>Email address</label>
|
||||
<input class="ui-text" type="text" value="" name="email">
|
||||
</div>
|
||||
|
||||
<div class="field job">
|
||||
<label>Have a job</label>
|
||||
<label><input class="ui-radio" type="radio" value="yes" name="job"> Yes</label>
|
||||
<label><input class="ui-radio" type="radio" value="no" name="job"> No</label>
|
||||
</div>
|
||||
|
||||
<div class="field role">
|
||||
<label>Role</label>
|
||||
<select class="ui-select" name="role" required="required">
|
||||
<option value="user">User</option>
|
||||
<option value="moderator">Moderator</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
## Use your forms in controller
|
||||
|
||||
Forms can be used to handle how your data is handled, from loading (get) to processing (post).
|
||||
|
||||
The process is as simple as defining a Form, implementing FormInterface :
|
||||
|
||||
```php
|
||||
interface FormInterface
|
||||
{
|
||||
public function initialize(FormContextInterface $context) : void;
|
||||
|
||||
public function validate(FormContextInterface $context) : bool;
|
||||
|
||||
public function execute(FormContextInterface $context);
|
||||
}
|
||||
```
|
||||
|
||||
`initialize` is called directly on `form()` method call.
|
||||
|
||||
`validate` will be called if the request's method is either `post`, `put`, `patch` or `delete`, which implies **adding** / **editing** / **deleting**.
|
||||
|
||||
`execute` will finally launch only if `validate` returns `true`. This is usually where you want to **save** / **destroy** your entities.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:checkbox` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:radio` component, which itself extends `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-checkbox"`
|
||||
- `'type' => "checkbox"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-checkbox" type="checkbox" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:color` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-color"`
|
||||
- `'type' => "color"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-color" type="color" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:date` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-date"`
|
||||
- `'type' => "date"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-date" type="date" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:datetime` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-datetime"`
|
||||
- `'type' => "datetime-local"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-datetime" type="datetime-local" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:email` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-email"`
|
||||
- `'type' => "email"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-email" type="email" value="" name="">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:file` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-file"`
|
||||
- `'type' => "file"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-file" type="file" value="" name="">
|
||||
```
|
||||
@@ -0,0 +1,34 @@
|
||||
# `ui:form` / `ui:form.get` / `ui:form.post` (string $method = "get", string $name = "", string $action = "", array $attributes = [])
|
||||
|
||||
A version exists also for unsupported methods (as of HTML5) on forms `ui:form.put` / `ui:form.patch` / `ui:form.delete`, which
|
||||
could find a use within a Javascript environment.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-form"`
|
||||
- `'enctype' => "multipart/form-data"`
|
||||
- `'method' => "get|post|put|patch|delete"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
|
||||
```html
|
||||
<form class="ui-form" enctype="multipart/form-data" action="$action" method="$method">
|
||||
<input class="ui-hidden" type="hidden" name="picea-ui-form[$name]" value="{ random md5 hash }">
|
||||
</form>
|
||||
```
|
||||
|
||||
### Additional options
|
||||
|
||||
You can now remove the enctype attribute `no-enctype` or the CSRF token `no-csrf` using those option
|
||||
on the form tag :
|
||||
|
||||
```html
|
||||
{% ui:form.post.no-enctype.no-csrf "my.form" %} {% endform %}
|
||||
```
|
||||
|
||||
and would render as such :
|
||||
|
||||
```html
|
||||
<form class="ui-form" action="$action" method="$method">
|
||||
</form>
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:hidden` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-hidden"`
|
||||
- `'type' => "hidden"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-hidden" type="hidden" value="" name="">
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
# `ui:image` (string $source, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-image"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<img class="ui-image" src="$source">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:input` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
This is small
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-input"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
|
||||
```html
|
||||
<input class="ui-input" type="input" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:numeric` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-numeric"`
|
||||
- `'type' => "numeric"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-numeric" type="numeric" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:password` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-password"`
|
||||
- `'type' => "password"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-password" type="password" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:radio` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-radio"`
|
||||
- `'type' => "radio"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-radio" type="radio" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:range` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-range"`
|
||||
- `'type' => "range"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-range" type="range" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:search` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-search"`
|
||||
- `'type' => "search"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-search" type="search" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:select` (string $name, array $list, $value = null, array $attributes = [], bool $strictComparison = true)
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-select"`
|
||||
- `'type' => "select"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-select" type="select" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:tel` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-tel"`
|
||||
- `'type' => "tel"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-tel" type="tel" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:text` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-text"`
|
||||
- `'type' => "text"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-text" type="text" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
# `ui:textarea` / `ui:textarea.raw` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`. This extension also comes with a raw modifier which skips the escaping.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-textarea"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<textarea class="ui-textarea" name="$name">$value</textarea>
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:time` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-time"`
|
||||
- `'type' => "time"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-time" type="time" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:url` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-url"`
|
||||
- `'type' => "url"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-url" type="url" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# `ui:week` (string $name, mixed $value = null, array $attributes = [], array $options = [])
|
||||
|
||||
Built on top of `ui:input`.
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
- `'class' => "ui-week"`
|
||||
- `'type' => "week"`
|
||||
|
||||
and will typically be rendered such as :
|
||||
```html
|
||||
<input class="ui-week" type="week" value="$value" name="$name">
|
||||
```
|
||||
@@ -0,0 +1,107 @@
|
||||
# `form`(FormInterface $form, ? FormContext $formContext = null) : FormHandler
|
||||
|
||||
The `form()` method will process a form in three steps :
|
||||
|
||||
1. `initialize(FormContextInterface $context)` is first called on form processing.
|
||||
2. `validate(FormContextInterface $context) : bool` will execute on POST. If you define a name to the form, it will match the one found in the POST (`picea-ui-form[$context->name]`) ; if the form is not the target, it will be skipped. Also, if an error si found within the message list, the method will return `false`.
|
||||
3. `execute(FormContextInterface $context)` is the last method to run, which is usually where you save your entity and finilize your messages. The return value is accessible from the `formHandler` which `form()` returns.
|
||||
|
||||
**[PHP]** So, using this code:
|
||||
|
||||
in */app/controller/user.php*
|
||||
```php
|
||||
form(new \MyApp\Form\User\Create());
|
||||
```
|
||||
|
||||
*/app/form/create.php*
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace MyProject\Form;
|
||||
|
||||
use MyProject\{ Lib, Entity };
|
||||
use Picea\Ui\Method\{ FormContextInterface, Message\ErrorMessage };
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
use function MyProject\View\{ lang };
|
||||
|
||||
class Create implements \Picea\Ui\Method\FormInterface {
|
||||
|
||||
public function initialize(FormContextInterface $context) : void
|
||||
{
|
||||
$this->user = new Entity\User();
|
||||
|
||||
# $context->{varname} will be populated by $_POST data.
|
||||
$this->user = (string) $context->fullname;
|
||||
}
|
||||
|
||||
public function validate(FormContextInterface $context) : bool
|
||||
{
|
||||
if ( strlen($this->user) < 3 ) {
|
||||
# This won't allow `execute` to run because of the error message.
|
||||
$context->pushMessage(Lib\Message::generateError(
|
||||
lang("form.error.provide_fullName")
|
||||
));
|
||||
}
|
||||
|
||||
return $context->valid();
|
||||
}
|
||||
|
||||
public function execute(FormContextInterface $context) : void
|
||||
{
|
||||
# Saving a new user here
|
||||
Entity\User::repository()->save($this->user);
|
||||
|
||||
$context->pushMessage(Lib\Message::generateSuccess(
|
||||
lang("form.save")
|
||||
));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
which implements this interface :
|
||||
|
||||
```php
|
||||
interface FormInterface
|
||||
{
|
||||
public function initialize(FormContextInterface $context) : void;
|
||||
|
||||
public function validate(FormContextInterface $context) : bool;
|
||||
|
||||
public function execute(FormContextInterface $context);
|
||||
}
|
||||
```
|
||||
|
||||
Would yield the same result such as:
|
||||
```php
|
||||
true
|
||||
true
|
||||
```
|
||||
|
||||
Trying to access either variable before `execute` is called will return `null`.
|
||||
|
||||
## `FormContext` object
|
||||
|
||||
`__construct(ServerRequestInterface $request, ? string $formName = null)`
|
||||
|
||||
Creating a form context manually allows to define the formName, which is essential if you want to use the
|
||||
Cross-Scripting Request Forgery (CSRF).
|
||||
|
||||
It is populated with either $_POST or request's body which is json_decode. A property called `files` is also populated from PSR's `$request->getUploadedFiles()`.
|
||||
|
||||
It's `valid() : bool` method called into a form `validate()` function is first checking every message which were pushed into, using `pushMessage(FormMessage $message)`, and returns false if an error message is found in the list.
|
||||
|
||||
### Retrieve a form execution status / value
|
||||
|
||||
There is two ways to get the execution status (from the `execute` return value):
|
||||
|
||||
```php
|
||||
$formContext = new \Picea\Ui\Method\FormContext($request, 'form.user_create');
|
||||
$form = form(new \MyProject\Form\User\Create(), $formContext);
|
||||
|
||||
if ($formContext->formExecuted) {
|
||||
echo $form->executionStatus;
|
||||
echo $formContext->formExecutionStatus;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
# `ui:popup` (string $name, array $variables = [], array $options = [])
|
||||
|
||||
The following attributes are assigned by this extension :
|
||||
|
||||
and will typically be rendered such as (directly inside an opening tag) :
|
||||
```html
|
||||
ui-popup='{ "name":$name,"vars":{$variables},"options":{$options} }'
|
||||
```
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
use function DI\autowire, DI\create, DI\get;
|
||||
|
||||
use Picea\{ Method\Request };
|
||||
|
||||
use Picea\Extension\{ LanguageExtension, TitleExtension, NumberExtension, UrlExtension, DiExtension };
|
||||
|
||||
use Picea\Ui\{ Method, Ui };
|
||||
|
||||
return [
|
||||
Ui::class => autowire(Ui::class),
|
||||
|
||||
'picea.extensions' => function(\Psr\Container\ContainerInterface $c) {
|
||||
return array_merge([
|
||||
$c->get(LanguageExtension::class),
|
||||
$c->get(TitleExtension::class),
|
||||
$c->get(NumberExtension::class),
|
||||
$c->get(UrlExtension::class),
|
||||
$c->get(Method\Form::class),
|
||||
$c->get(Method\Pagination::class),
|
||||
$c->get(Request::class),
|
||||
$c->get(DiExtension::class),
|
||||
|
||||
], class_exists(\Taxus\Picea\Extension::class) ? [ $c->get(\Taxus\Picea\Extension::class) ] : [],
|
||||
array_map(fn($class) => $c->get($class), $c->get(Lean\Lean::class)->getPiceaExtensions())
|
||||
);
|
||||
},
|
||||
];
|
||||
+71
-121
@@ -2,20 +2,35 @@
|
||||
|
||||
namespace Picea\Ui\Common;
|
||||
|
||||
class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
enum UiElementInsertMode
|
||||
{
|
||||
case append;
|
||||
case prepend;
|
||||
}
|
||||
|
||||
static array $config = [];
|
||||
|
||||
const INSERT_MODE_APPEND = 1;
|
||||
const INSERT_MODE_PREPEND = 2;
|
||||
|
||||
public string $tag = 'div';
|
||||
|
||||
public array $attributes = [
|
||||
'style' => [],
|
||||
'class' => [],
|
||||
class UiElement implements \JsonSerializable {
|
||||
public const TAG_CONFIG_OPTIONS = [
|
||||
"!doctype" => [ "attributes" => [ "html" ], "options" => [ "tag-type" => "single" ] ],
|
||||
"js" => [ "tag" => "script", "attributes" => [ "type" => "text/javascript" ] ],
|
||||
"css" => [ "tag" => "link", "attributes" => [ "rel" => "stylesheet" , "type" => "text/css" ] ],
|
||||
"document" => [ "options" => [ "no-tag" => true ] ],
|
||||
"link" => [ "options" => [ "tag-type" => "single" ] ],
|
||||
"meta" => [ "options" => [ "tag-type" => "single" ] ],
|
||||
"img" => [ "options" => [ "tag-type" => "single" ] ],
|
||||
"input" => [ "options" => [ "tag-type" => "single" ] ],
|
||||
"br" => [ "options" => [ "tag-type" => "single" ] ],
|
||||
"hr" => [ "options" => [ "tag-type" => "single" ] ],
|
||||
"iframe" => [ "options" => [ "tag-type" => "single" ] ],
|
||||
"area" => [ "options" => [ "tag-type" => "single" ] ],
|
||||
"col" => [ "options" => [ "tag-type" => "single" ] ],
|
||||
"frame" => [ "options" => [ "tag-type" => "single" ] ],
|
||||
"param" => [ "options" => [ "tag-type" => "single" ] ],
|
||||
"video" => [ "options" => [ "tag-type" => "single" ] ],
|
||||
"wbr" => [ "options" => [ "tag-type" => "single" ] ]
|
||||
];
|
||||
|
||||
public array $attributes = [ 'style' => [], 'class' => [], ];
|
||||
|
||||
public array $childs = [];
|
||||
|
||||
/**
|
||||
@@ -26,35 +41,20 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
* tag-type single Only render a single tag. Cannot contains any childrens
|
||||
* force-tag-open tag Control the way the opening tag is rendered
|
||||
* force-tag-close tag-close " " " " closing " " "
|
||||
* no-attr Attributes will not be rendered
|
||||
* no-id ID is not automatically given (* may be removed *)
|
||||
* escape-tag-end If you need to echo the Node inside another string, it may be useful to escape the "/" char using "\/"
|
||||
* no-attr true Attributes will not be rendered
|
||||
* no-id true ID is not automatically given (* may be removed *)
|
||||
* escape-tag-end true If you need to echo the Node inside another string, it may be useful to escape the "/" char using "\/"
|
||||
*/
|
||||
|
||||
public array $options = [];
|
||||
|
||||
public $selected = null;
|
||||
|
||||
public string $content = "";
|
||||
|
||||
protected ?UiQuery $kwery = null;
|
||||
public function __construct(
|
||||
public string $tag = "div",
|
||||
) {}
|
||||
|
||||
public function __construct(? string $tag = null) {
|
||||
if ( ! static::$config ) {
|
||||
static::pushConfigArray( include(dirname(__FILE__) . "/taglist.php") );
|
||||
}
|
||||
|
||||
if ($tag !== null) {
|
||||
$this->tag = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
public static function pushConfigArray(array $array) : void
|
||||
{
|
||||
static::$config = array_replace_recursive(static::$config, $array);
|
||||
}
|
||||
|
||||
public static function createStylesheet(string $href, $attributes = [], $options = []) : self
|
||||
public static function stylesheet(string $href, $attributes = [], $options = []) : self
|
||||
{
|
||||
return static::create('link', $attributes + [
|
||||
'href' => $href,
|
||||
@@ -63,7 +63,7 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
], [ 'tag-type' => 'single' ] + $options);
|
||||
}
|
||||
|
||||
public static function createScript(string $src, array $attributes = [], array $options = []) : self
|
||||
public static function script(string $src, array $attributes = [], array $options = []) : self
|
||||
{
|
||||
return static::create('script', $attributes + [
|
||||
'src' => $src,
|
||||
@@ -71,7 +71,7 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
], $options);
|
||||
}
|
||||
|
||||
public static function createPhp(array $attributes = [], array $options = []) : self
|
||||
public static function php(array $attributes = [], array $options = []) : self
|
||||
{
|
||||
return static::create('', $attributes, [
|
||||
'force-tag-open' => '<?php ',
|
||||
@@ -108,7 +108,7 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
$obj->attributes($attributes);
|
||||
}
|
||||
|
||||
if ( false !== ( $custom = static::$config["tags"][$tag] ?? false ) ) {
|
||||
if ( false !== ( $custom = static::TAG_CONFIG_OPTIONS[strtolower($tag)] ?? false ) ) {
|
||||
if ( $custom['tag'] ?? false ) {
|
||||
$obj->tag = $custom['tag'];
|
||||
}
|
||||
@@ -148,7 +148,7 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function option(string $var, $value = 1) : self
|
||||
public function option(string $var, mixed $value = true) : self
|
||||
{
|
||||
$this->options[$var] = $value;
|
||||
|
||||
@@ -177,8 +177,10 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
}
|
||||
}
|
||||
else if ( !is_numeric($key) ) {
|
||||
# will output something like <tag $key=$value></tag>
|
||||
$attributesList[] = strpos($value, '"') !== false ? "$key='$value'" : "$key=\"$value\"";
|
||||
$value = htmlspecialchars($value);
|
||||
|
||||
# will output something like <tag $key="$value"></tag>
|
||||
$attributesList[] = "$key=\"$value\"";
|
||||
}
|
||||
else {
|
||||
# will output something like <tag $value></tag>
|
||||
@@ -188,7 +190,7 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
}
|
||||
|
||||
$content = "";
|
||||
|
||||
|
||||
foreach ( $this->childs as $item ) {
|
||||
if ( is_object($item) ) {
|
||||
$content .= $item->render();
|
||||
@@ -198,14 +200,14 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
}
|
||||
}
|
||||
|
||||
if ( in_array('no-tag', $this->options, true) ) {
|
||||
if ( $this->options['no-tag'] ?? false ) {
|
||||
return $this->content . $content;
|
||||
}
|
||||
else {
|
||||
$compiledAttributes = (count($attributesList) && ! in_array('no-attr', $this->options, true) ? " " . implode(" ", $attributesList) : "");
|
||||
$compiledAttributes = $this->options['no-attr'] ?? false ? "" : " " . implode(" ", $attributesList);
|
||||
|
||||
# Force the node to contain a certain opening tag (php is a good example)
|
||||
if ( in_array('force-tag-open', $this->options, true) ) {
|
||||
if ( $this->options['force-tag-open'] ?? false ) {
|
||||
$opentag = $this->options['force-tag-open'] . $compiledAttributes;
|
||||
}
|
||||
else {
|
||||
@@ -217,11 +219,11 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
$closetag = "";
|
||||
}
|
||||
}
|
||||
elseif ( in_array('force-tag-close', $this->options, true) ) {
|
||||
elseif ( $this->options['force-tag-close'] ?? false ) {
|
||||
$closetag = $this->options['force-tag-close'];
|
||||
}
|
||||
else {
|
||||
$closetag = $this->tag ? "<" . (in_array('escape-tag-end', $this->options) ? "\/" : "/" ) . "{$this->tag}>" : "";
|
||||
$closetag = $this->tag ? "<" . ( $this->options['escape-tag-end'] ?? false ? "\/" : "/" ) . "{$this->tag}>" : "";
|
||||
}
|
||||
|
||||
return $opentag . $this->content . $content . $closetag;
|
||||
@@ -231,7 +233,7 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
public function html($set = null) {
|
||||
if ($set !== null) {
|
||||
$this->content = $set;
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -240,7 +242,7 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
|
||||
public function text(?string $set = null) {
|
||||
if ($set !== null) {
|
||||
$this->content = htmlspecialchars( $set, ENT_NOQUOTES );
|
||||
$this->content = htmlspecialchars( $set, \ENT_NOQUOTES );
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -250,29 +252,28 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
|
||||
public function append( ...$arguments ) : self
|
||||
{
|
||||
return $this->insert( static::INSERT_MODE_APPEND, ...$arguments);
|
||||
return $this->insert( UiElementInsertMode::append, ...$arguments);
|
||||
}
|
||||
|
||||
public function prepend( ...$arguments ) : self
|
||||
{
|
||||
return $this->insert( static::INSERT_MODE_PREPEND, ...( is_array($arguments) ? array_reverse($arguments) : $arguments) );
|
||||
return $this->insert( UiElementInsertMode::prepend, ...( is_array($arguments) ? array_reverse($arguments) : $arguments) );
|
||||
}
|
||||
|
||||
|
||||
protected function insert(int $mode, ...$elements) : self
|
||||
protected function insert(UiElementInsertMode $mode, ...$elements) : self
|
||||
{
|
||||
foreach($elements as $item) {
|
||||
switch($mode) {
|
||||
case static::INSERT_MODE_APPEND:
|
||||
case UiElementInsertMode::append:
|
||||
array_push($this->childs, $item);
|
||||
break;
|
||||
|
||||
case static::INSERT_MODE_PREPEND:
|
||||
|
||||
case UiElementInsertMode::prepend:
|
||||
array_unshift($this->childs, $item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -285,14 +286,24 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
break;
|
||||
|
||||
case 'style' :
|
||||
foreach($value as $var => $val) {
|
||||
$this->css($var, $val);
|
||||
if ( is_string($value) )
|
||||
{
|
||||
foreach(array_filter(explode(";", $value)) as $vars) {
|
||||
list($key, $value) = explode(':', $vars);
|
||||
|
||||
$this->css($key, $value);
|
||||
}
|
||||
}
|
||||
elseif ( is_array($value) ) {
|
||||
foreach($value as $var => $val) {
|
||||
$this->css($var, $val);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if ( is_numeric($key) ) {
|
||||
$this->attributes[] = $value;
|
||||
$this->attributes[$key] = $value;
|
||||
}
|
||||
elseif ( is_array($this->attributes[$key] ?? false) ) {
|
||||
$this->attributes[$key] = array_replace($value, $this->attributes[$key]);
|
||||
@@ -316,7 +327,6 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
*/
|
||||
public function css(...$arguments) {
|
||||
foreach($arguments as $item) {
|
||||
|
||||
if ( is_array($item) ) {
|
||||
foreach($item as $key => $value) {
|
||||
$this->css($key, $value);
|
||||
@@ -335,15 +345,12 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
return array_search(strtolower($className), array_map('strtolower', $this->attributes['class']), true);
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
|
||||
}
|
||||
|
||||
public function count() {
|
||||
return count($this->childs);
|
||||
}
|
||||
|
||||
public function jsonSerialize() {
|
||||
public function jsonSerialize() : mixed
|
||||
{
|
||||
return [
|
||||
'tag' => $this->tag,
|
||||
'attr' => $this->attributes,
|
||||
@@ -352,63 +359,6 @@ class UiElement implements \ArrayAccess, \Iterator, \JsonSerializable {
|
||||
];
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value) {
|
||||
if ( is_numeric($offset) ) {
|
||||
return $this->selected[$offset] = $value;
|
||||
}
|
||||
elseif ( is_null($offset) ) {
|
||||
return $this->childs[] = $value;
|
||||
}
|
||||
else {
|
||||
return $this->childs[$offset] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public function offsetExists($query) : bool
|
||||
{
|
||||
return count($this->kwery()->find($query)) > 0;
|
||||
}
|
||||
|
||||
public function offsetUnset($query) : void
|
||||
{
|
||||
$this->kwery()->find($query)->remove();
|
||||
}
|
||||
|
||||
public function offsetGet($query) : self
|
||||
{
|
||||
return $this->kwery()->find($query);
|
||||
}
|
||||
|
||||
public function rewind() : void
|
||||
{
|
||||
reset($this->childs);
|
||||
}
|
||||
|
||||
public function current() : self
|
||||
{
|
||||
return current($this->childs);
|
||||
}
|
||||
|
||||
public function key() : int
|
||||
{
|
||||
return key($this->childs);
|
||||
}
|
||||
|
||||
public function next() : self
|
||||
{
|
||||
return next($this->childs);
|
||||
}
|
||||
|
||||
public function valid() : bool
|
||||
{
|
||||
return ! in_array(key($this->childs), [ NULL, FALSE ], true);
|
||||
}
|
||||
|
||||
public static function isNode($obj) : bool
|
||||
{
|
||||
return $obj instanceof UiElement;
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
return $this->render();
|
||||
|
||||
@@ -9,7 +9,7 @@ use Picea\Extension\ExtensionTrait;
|
||||
class UiMessage extends UiElement implements Extension {
|
||||
use ExtensionTrait;
|
||||
|
||||
public string $token = "ui.message";
|
||||
public string $token = "ui:message";
|
||||
|
||||
public string $tag = "input";
|
||||
|
||||
@@ -17,7 +17,7 @@ class UiMessage extends UiElement implements Extension {
|
||||
'class' => 'ui-message',
|
||||
];
|
||||
|
||||
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string
|
||||
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -9,22 +9,25 @@ use Picea\Extension\ExtensionTrait;
|
||||
class UiPopup extends UiElement implements Extension {
|
||||
use ExtensionTrait;
|
||||
|
||||
public string $token = "ui.popup";
|
||||
|
||||
public string $tag = "div";
|
||||
public string $token = "ui:popup";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => 'ui-popup',
|
||||
'class' => 'ui-popup',
|
||||
];
|
||||
|
||||
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string
|
||||
public function __construct(
|
||||
public string $tag = "div",
|
||||
) {}
|
||||
|
||||
|
||||
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
|
||||
{
|
||||
return "<?php echo 'ui-popup=\"' . ( new \\" . static::class . "() )->buildAttributes($arguments) . '\"' ?>";
|
||||
}
|
||||
|
||||
public function buildAttributes(string $name, array $variables = []) : string
|
||||
public function buildAttributes(string $name, array $variables = [], array $options = []) : string
|
||||
{
|
||||
return htmlentities(json_encode([ "name" => $name, "vars" => $variables ], JSON_HEX_APOS | JSON_HEX_QUOT), ENT_QUOTES, 'UTF-8');
|
||||
return htmlentities(json_encode([ "name" => $name, "vars" => $variables, "options" => $options ], JSON_HEX_APOS | JSON_HEX_QUOT), \ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace Picea\Ui\Form;
|
||||
|
||||
use Picea\Extension\Extension;
|
||||
use Picea\Extension\ExtensionTrait;
|
||||
use Picea\Ui\Common\UiElement;
|
||||
use Picea\Ui\Component\UiMessage;
|
||||
use Picea\Ui\Component\UiPopup;
|
||||
|
||||
class Ui extends UiElement implements Extension {
|
||||
use ExtensionTrait;
|
||||
|
||||
public string $token = "ui";
|
||||
|
||||
protected /* ? mixed */ $value;
|
||||
|
||||
protected string $name;
|
||||
|
||||
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
|
||||
{
|
||||
switch(true) {
|
||||
case in_array('checkbox', $options):
|
||||
$input = new UiCheckbox();
|
||||
break;
|
||||
|
||||
case in_array('color', $options):
|
||||
$input = new UiColor();
|
||||
break;
|
||||
|
||||
case in_array('date', $options):
|
||||
$input = new UiDate();
|
||||
break;
|
||||
|
||||
case in_array('datetime', $options):
|
||||
$input = new UiDatetime();
|
||||
break;
|
||||
|
||||
case in_array('email', $options):
|
||||
$input = new UiEmail();
|
||||
break;
|
||||
|
||||
case in_array('file', $options):
|
||||
$input = new UiFile();
|
||||
break;
|
||||
|
||||
case in_array('form', $options):
|
||||
case in_array('endform', $options):
|
||||
$input = new UiForm();
|
||||
break;
|
||||
|
||||
case in_array('hidden', $options):
|
||||
$input = new UiHidden();
|
||||
break;
|
||||
|
||||
case in_array('img', $options):
|
||||
case in_array('image', $options):
|
||||
$input = new UiImage();
|
||||
break;
|
||||
|
||||
case in_array('input', $options):
|
||||
$input = new UiInput();
|
||||
break;
|
||||
|
||||
case in_array('numeric', $options):
|
||||
$input = new UiNumeric();
|
||||
break;
|
||||
|
||||
case in_array('password', $options):
|
||||
$input = new UiPassword();
|
||||
break;
|
||||
|
||||
case in_array('radio', $options):
|
||||
$input = new UiRadio();
|
||||
break;
|
||||
|
||||
case in_array('range', $options):
|
||||
$input = new UiRange();
|
||||
break;
|
||||
|
||||
case in_array('search', $options):
|
||||
$input = new UiSearch();
|
||||
break;
|
||||
|
||||
case in_array('select', $options):
|
||||
$input = new UiSelect();
|
||||
break;
|
||||
|
||||
case in_array('tel', $options):
|
||||
$input = new UiTel();
|
||||
break;
|
||||
|
||||
case in_array('text', $options):
|
||||
$input = new UiText();
|
||||
break;
|
||||
|
||||
case in_array('textarea', $options):
|
||||
$input = new UiTextarea();
|
||||
break;
|
||||
|
||||
case in_array('time', $options):
|
||||
$input = new UiTime();
|
||||
break;
|
||||
|
||||
case in_array('url', $options):
|
||||
$input = new UiUrl();
|
||||
break;
|
||||
|
||||
case in_array('week', $options):
|
||||
$input = new UiWeek();
|
||||
break;
|
||||
|
||||
case in_array('popup', $options):
|
||||
$input = new UiPopup();
|
||||
break;
|
||||
|
||||
case in_array('message', $options):
|
||||
$input = new UiMessage();
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($input)) {
|
||||
throw new \Exception("Missing token ? $token $arguments");
|
||||
}
|
||||
|
||||
return $input->parse($context, $arguments, $token, $options);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiCheckbox extends UiRadio {
|
||||
|
||||
public string $token = "ui.checkbox";
|
||||
public string $token = "ui:checkbox";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-checkbox",
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiColor extends UiInput {
|
||||
|
||||
public string $token = "ui.color";
|
||||
public string $token = "ui:color";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-color",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiDate extends UiInput {
|
||||
|
||||
public string $token = "ui.date";
|
||||
public string $token = "ui:date";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-date",
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiDatetime extends UiInput {
|
||||
|
||||
public string $token = "ui.datetime";
|
||||
public string $token = "ui:datetime";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-datetime",
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiEmail extends UiInput {
|
||||
|
||||
public string $token = "ui.email";
|
||||
public string $token = "ui:email";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-email",
|
||||
|
||||
+6
-6
@@ -4,10 +4,10 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiFile extends UiInput {
|
||||
|
||||
public string $token = "ui.file";
|
||||
public string $token = "ui:file";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-file",
|
||||
'type' => "file",
|
||||
];
|
||||
}
|
||||
public array $attributes = [
|
||||
'class' => "ui-file",
|
||||
'type' => "file",
|
||||
];
|
||||
}
|
||||
+73
-34
@@ -11,60 +11,99 @@ class UiForm extends UiElement implements Extension {
|
||||
|
||||
public string $defaultMethod = "get";
|
||||
|
||||
public array $token = [ "ui.form", "ui.endform", "ui.form.get", "ui.form.post", "ui.form.patch", "ui.form.delete", "ui.form.put" ];
|
||||
|
||||
public string $tag = "form";
|
||||
public array $token = [ "ui:form", "ui:endform", "ui:/form" ];
|
||||
|
||||
public array $attributes = [
|
||||
'class' => 'ui-form',
|
||||
];
|
||||
|
||||
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string
|
||||
public function __construct(
|
||||
public string $tag = "form",
|
||||
public bool $enctype = true,
|
||||
public bool $csrf = true,
|
||||
) {}
|
||||
|
||||
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
|
||||
{
|
||||
$constructor = [];
|
||||
|
||||
switch($token) {
|
||||
case 'ui.endform':
|
||||
case 'ui.endform': # bw compat
|
||||
case 'ui:endform':
|
||||
case 'ui:/form':
|
||||
return "</form>";
|
||||
}
|
||||
|
||||
case "ui.form.get":
|
||||
$method = "get";
|
||||
break;
|
||||
if (in_array('no-enctype', $options)) {
|
||||
$constructor[] = "enctype: false";
|
||||
}
|
||||
|
||||
case "ui.form.patch":
|
||||
$method = "patch";
|
||||
break;
|
||||
if (in_array('no-csrf', $options)) {
|
||||
$constructor[] = "csrf: false";
|
||||
}
|
||||
|
||||
case "ui.form.delete":
|
||||
$method = "delete";
|
||||
break;
|
||||
|
||||
case "ui.form.put":
|
||||
$method = "put";
|
||||
break;
|
||||
|
||||
case "ui.form.post":
|
||||
$method = "post";
|
||||
break;
|
||||
if (in_array('get', $options)) {
|
||||
$method = "get";
|
||||
}
|
||||
elseif (in_array('post', $options)) {
|
||||
$method = "post";
|
||||
}
|
||||
elseif (in_array('put', $options)) {
|
||||
$method = "put";
|
||||
}
|
||||
elseif (in_array('delete', $options)) {
|
||||
$method = "delete";
|
||||
}
|
||||
elseif (in_array('patch', $options)) {
|
||||
$method = "patch";
|
||||
}
|
||||
|
||||
$method ??= $this->defaultMethod;
|
||||
|
||||
return "<?php echo ( new \\" . static::class . "() )->buildHtml('$method', $arguments) ?>";
|
||||
|
||||
$opt = var_export($options, true);
|
||||
|
||||
$constructor = implode(',', $constructor);
|
||||
|
||||
return "<?php echo ( new \\" . static::class . "($constructor) )->buildHtml('$method', $arguments) ?>";
|
||||
}
|
||||
|
||||
|
||||
public function buildHtml(string $method = "get", string $name = "", string $action = "", array $attributes = []) : string
|
||||
{
|
||||
$method = strtolower($method);
|
||||
|
||||
# Method passed in arguments take precedents over options
|
||||
$method = strtolower($attributes['method'] ?? $method);
|
||||
|
||||
$this->option('tag-type', 'single');
|
||||
|
||||
$this->attributes([ 'method' => $method, 'action' => $action ] + $attributes);
|
||||
|
||||
if ( $method !== "get" ) {
|
||||
$this->append( ( new UiHidden() )->attributes([ 'name' =>"picea-ui-form[$name]", 'value' => md5( $name . microtime() ) ]) );
|
||||
|
||||
$this->attributes([ 'enctype' => "multipart/form-data" ]);
|
||||
|
||||
if ($attributes['class'] ?? false) {
|
||||
$attributes['class'] .= " {$this->attributes['class']}";
|
||||
unset($this->attributes['class']);
|
||||
}
|
||||
|
||||
$this->attributes([ 'action' => $action, 'method' => $method, ] + $attributes);
|
||||
|
||||
if ( $method !== "get" ) {
|
||||
|
||||
if ($this->csrf) {
|
||||
$token = md5($name . microtime());
|
||||
$key = "picea-ui.form:{$name}";
|
||||
|
||||
if (count($_SESSION[$key] ?? []) > 100) {
|
||||
array_shift($_SESSION[$key]);
|
||||
}
|
||||
|
||||
$_SESSION[$key][] = $token;
|
||||
|
||||
$this->append((new UiHidden())->attributes([
|
||||
'name' => "picea-ui-form[$name]",
|
||||
'value' => $token,
|
||||
]));
|
||||
}
|
||||
|
||||
if ($this->enctype) {
|
||||
$this->attributes([ 'enctype' => "multipart/form-data" ]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render() . PHP_EOL;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiHidden extends UiInput {
|
||||
|
||||
public string $token = "ui.hidden";
|
||||
public string $token = "ui:hidden";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-hidden",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Picea\Ui\Form;
|
||||
|
||||
use Picea\Ui\Common\UiElement;
|
||||
use Picea\Extension\Extension;
|
||||
use Picea\Extension\ExtensionTrait;
|
||||
|
||||
class UiImage extends UiElement implements Extension {
|
||||
use ExtensionTrait;
|
||||
|
||||
public array $tokens = [ "ui:img", "ui:image" ];
|
||||
|
||||
public array $attributes = [
|
||||
'class' => 'ui-image',
|
||||
];
|
||||
|
||||
public array $options = [
|
||||
'tag-type' => "single",
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
public string $tag = "img",
|
||||
) {}
|
||||
|
||||
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
|
||||
{
|
||||
return "<?php echo ( new \\" . static::class . "() )->buildHtml($arguments) ?>";
|
||||
}
|
||||
|
||||
public function buildHtml(string $source, array $attributes = [], array $options = []) : string
|
||||
{
|
||||
if ($options) {
|
||||
$this->options += $options;
|
||||
}
|
||||
|
||||
if ($attributes['class'] ?? false) {
|
||||
$attributes['class'] .= " {$this->attributes['class']}";
|
||||
|
||||
unset($this->attributes['class']);
|
||||
}
|
||||
|
||||
$this->attributes([ 'src' => $source ] + $attributes + $this->attributes + $this->objectAttribute());
|
||||
|
||||
return $this->render();
|
||||
}
|
||||
|
||||
protected function objectAttribute() : array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
+25
-11
@@ -3,15 +3,12 @@
|
||||
namespace Picea\Ui\Form;
|
||||
|
||||
use Picea\Ui\Common\UiElement;
|
||||
use Picea\Extension\Extension;
|
||||
use Picea\Extension\ExtensionTrait;
|
||||
use Picea\Extension\{ Extension, FunctionExtension, ExtensionTrait };
|
||||
|
||||
class UiInput extends UiElement implements Extension {
|
||||
class UiInput extends UiElement implements Extension, FunctionExtension {
|
||||
use ExtensionTrait;
|
||||
|
||||
public string $token = "ui.input";
|
||||
|
||||
public string $tag = "input";
|
||||
public string $token = "ui:input";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => 'ui-input',
|
||||
@@ -21,16 +18,33 @@ class UiInput extends UiElement implements Extension {
|
||||
'tag-type' => "single",
|
||||
];
|
||||
|
||||
protected /* ? mixed */ $value;
|
||||
protected mixed $value;
|
||||
|
||||
protected string $name;
|
||||
|
||||
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string
|
||||
|
||||
public function __construct(
|
||||
public string $tag = "input",
|
||||
) {}
|
||||
|
||||
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
|
||||
{
|
||||
return "<?php echo ( new \\" . static::class . "() )->buildHtml($arguments) ?>";
|
||||
}
|
||||
|
||||
public function buildHtml(string $name, /*? mixed */ $value = null, array $attributes = [], array $options = []) : string
|
||||
public function exportFunctions(): array
|
||||
{
|
||||
if ( null === $type = $this->attributes['type'] ?? null ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$key = str_replace('-', '_', 'ui_'. ( $this->attributes['type'] ?? 'text' ));
|
||||
|
||||
return [
|
||||
$key => fn(string $name, mixed $value = null, array $attributes = [], array $options = []) : string => $this->buildHtml($name, $value, $attributes, $options),
|
||||
];
|
||||
}
|
||||
|
||||
public function buildHtml(string $name, mixed $value = null, array $attributes = [], array $options = []) : string
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
@@ -43,7 +57,7 @@ class UiInput extends UiElement implements Extension {
|
||||
}
|
||||
|
||||
if ($attributes['class'] ?? false) {
|
||||
$attributes['class'] .= " {$this->attributes['class']}";
|
||||
$attributes['class'] = implode(" ", array_merge((array) $attributes['class'], (array) $this->attributes['class']));
|
||||
unset($this->attributes['class']);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiNumeric extends UiInput {
|
||||
|
||||
public string $token = "ui.numeric";
|
||||
public string $token = "ui:numeric";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-numeric",
|
||||
'type' => "numeric",
|
||||
'type' => "number",
|
||||
];
|
||||
}
|
||||
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiPassword extends UiInput {
|
||||
|
||||
public string $token = "ui.password";
|
||||
public string $token = "ui:password";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-password",
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiRadio extends UiInput {
|
||||
|
||||
public string $token = "ui.radio";
|
||||
public string $token = "ui:radio";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-radio",
|
||||
@@ -13,7 +13,7 @@ class UiRadio extends UiInput {
|
||||
|
||||
protected function objectAttribute() : array
|
||||
{
|
||||
if ( ( $this->options['value'] ?? false ) && ( $this->options['value'] === $this->value ) ) {
|
||||
if ( isset($this->options['value']) && ( $this->options['value'] === $this->value ) ) {
|
||||
return [
|
||||
'checked' => "checked"
|
||||
];
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiRange extends UiInput {
|
||||
|
||||
public string $token = "ui.range";
|
||||
public string $token = "ui:range";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-range",
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiSearch extends UiInput {
|
||||
|
||||
public string $token = "ui.search";
|
||||
public string $token = "ui:search";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-search",
|
||||
|
||||
+31
-10
@@ -9,21 +9,28 @@ use Picea\Extension\ExtensionTrait;
|
||||
class UiSelect extends UiElement implements Extension {
|
||||
use ExtensionTrait;
|
||||
|
||||
public string $token = "ui.select";
|
||||
|
||||
public string $tag = "select";
|
||||
public string $token = "ui:select";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => 'ui-select',
|
||||
];
|
||||
|
||||
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string
|
||||
public function __construct(
|
||||
public string $tag = "select",
|
||||
) {}
|
||||
|
||||
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
|
||||
{
|
||||
return "<?php echo ( new \\" . static::class . "() )->buildHtml($arguments) ?>";
|
||||
}
|
||||
|
||||
public function buildHtml(string $name, array $list, $value = null, array $attributes = [], bool $strictComparison = true) : string
|
||||
public function buildHtml(string $name, array $list, mixed $value = null, array $attributes = [], bool $strictComparison = true) : string
|
||||
{
|
||||
if ($attributes['class'] ?? false) {
|
||||
$attributes['class'] = implode(" ", array_merge((array) $attributes['class'], (array) $this->attributes['class']));
|
||||
unset($this->attributes['class']);
|
||||
}
|
||||
|
||||
$this->attributes([ 'name' => $name ] + $attributes + $this->attributes);
|
||||
$this->setList($list, $value, $strictComparison);
|
||||
|
||||
@@ -38,7 +45,6 @@ class UiSelect extends UiElement implements Extension {
|
||||
protected function setList(array $list, $selected, bool $strict = true) : void
|
||||
{
|
||||
foreach($list as $key => $item) {
|
||||
|
||||
if ($item instanceof UiElement) {
|
||||
$this->append($item);
|
||||
|
||||
@@ -49,13 +55,13 @@ class UiSelect extends UiElement implements Extension {
|
||||
$obj->attributes(['label' => $key]);
|
||||
|
||||
foreach($item as $subKey => $subItem) {
|
||||
$obj->append($this->createOption($subItem, $subKey, $this->isSelected($subKey, $selected, $strict)));
|
||||
$obj->append($this->createOption((string) $subItem, $subKey, $this->isSelected($subKey, $selected, $strict)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
$obj = $this->createOption($item, $key, $this->isSelected($key, $selected, $strict));
|
||||
$obj = $this->createOption((string) $item, $key, $this->isSelected($key, $selected, $strict));
|
||||
}
|
||||
|
||||
|
||||
$this->append($obj);
|
||||
}
|
||||
}
|
||||
@@ -73,8 +79,23 @@ class UiSelect extends UiElement implements Extension {
|
||||
return $option;
|
||||
}
|
||||
|
||||
protected function isSelected($check, $value, bool $strict = true) : bool
|
||||
protected function isSelected($check, mixed $value, bool $strict = true) : bool
|
||||
{
|
||||
if ( is_array($value) ) {
|
||||
return in_array($check, array_map(fn($e) => $this->normalizeValue($e), $value), $strict);
|
||||
}
|
||||
|
||||
$value = $this->normalizeValue($value);
|
||||
|
||||
return $strict ? $check === $value : $check == $value;
|
||||
}
|
||||
|
||||
protected function normalizeValue(mixed $value) : mixed
|
||||
{
|
||||
if ($value instanceof \BackedEnum) {
|
||||
return $value->value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiTel extends UiInput {
|
||||
|
||||
public string $token = "ui.tel";
|
||||
public string $token = "ui:tel";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-tel",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiText extends UiInput {
|
||||
|
||||
public string $token = "ui.text";
|
||||
public string $token = "ui:text";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-text",
|
||||
|
||||
+22
-4
@@ -4,9 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiTextarea extends UiInput {
|
||||
|
||||
public string $token = "ui.textarea";
|
||||
|
||||
public string $tag = "textarea";
|
||||
public array $tokens = [ "ui:textarea" ];
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-textarea",
|
||||
@@ -14,8 +12,28 @@ class UiTextarea extends UiInput {
|
||||
|
||||
public array $options = [];
|
||||
|
||||
protected bool $echoRaw = false;
|
||||
|
||||
public function __construct(
|
||||
public string $tag = "textarea",
|
||||
public bool $raw = false,
|
||||
) {}
|
||||
|
||||
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
|
||||
{
|
||||
$raw = in_array('raw', $options) ? 'true' : 'false';
|
||||
|
||||
return "<?php echo ( new \\" . static::class . "(raw: $raw) )->buildHtml($arguments) ?>";
|
||||
}
|
||||
protected function setValue($value) : void
|
||||
{
|
||||
$this->html($value);
|
||||
$this->echoRaw ? $this->html($value) : $this->text($value);
|
||||
}
|
||||
|
||||
protected function echoRaw(bool $value) : self
|
||||
{
|
||||
$this->echoRaw = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiTime extends UiInput {
|
||||
|
||||
public string $token = "ui.time";
|
||||
public string $token = "ui:time";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-time",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiUrl extends UiInput {
|
||||
|
||||
public string $token = "ui.url";
|
||||
public string $token = "ui:url";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-url",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ namespace Picea\Ui\Form;
|
||||
|
||||
class UiWeek extends UiInput {
|
||||
|
||||
public string $token = "ui.week";
|
||||
public string $token = "ui:week";
|
||||
|
||||
public array $attributes = [
|
||||
'class' => "ui-week",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Picea\Ui\Method\Context;
|
||||
|
||||
interface FormContextCastableInterface
|
||||
{
|
||||
public static function instance(mixed $value);
|
||||
}
|
||||
+8
-22
@@ -7,9 +7,10 @@ use Picea\Extension\Extension,
|
||||
|
||||
use Picea\Compiler\Context;
|
||||
|
||||
use Picea\Extension\FunctionExtension;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class Form implements Extension {
|
||||
class Form implements Extension, FunctionExtension {
|
||||
use ExtensionTrait;
|
||||
|
||||
public array $tokens;
|
||||
@@ -22,34 +23,19 @@ class Form implements Extension {
|
||||
|
||||
public function __construct(ServerRequestInterface $request, Context $context) {
|
||||
$this->request = $request;
|
||||
$this->register($context);
|
||||
}
|
||||
|
||||
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string { }
|
||||
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string { }
|
||||
|
||||
public function register(Context $context) : void
|
||||
public function exportFunctions(): array
|
||||
{
|
||||
$context->pushFunction("form", [ $this, 'formClass' ]);
|
||||
}
|
||||
|
||||
public function form_csrf(string $field, string $value) {
|
||||
$values = $this->session("View.form.csrf.$field") ?: [];
|
||||
|
||||
# keeps 20 (from config) latest CSRF key for this form into session,
|
||||
# allowing more than one tab opened and preventing information loss
|
||||
if ( count($values) >= 20 ) {
|
||||
#array_shift($values);
|
||||
}
|
||||
|
||||
$values[] = $value;
|
||||
|
||||
$this->session("View.form.csrf.$field", $values);
|
||||
|
||||
return $value;
|
||||
return [
|
||||
"form" => [ $this, 'formClass' ],
|
||||
];
|
||||
}
|
||||
|
||||
public function formClass(FormInterface $form, ? FormContext $formContext = null) : FormHandler
|
||||
{
|
||||
return new FormHandler($this->request, $form, $formContext);
|
||||
return new FormHandler($formContext ? $formContext->getRequest() : $this->request, $form, $formContext);
|
||||
}
|
||||
}
|
||||
+189
-32
@@ -7,32 +7,67 @@ use Psr\Http\Message\ServerRequestInterface,
|
||||
|
||||
class FormContext implements FormContextInterface
|
||||
{
|
||||
public string $formName;
|
||||
|
||||
public bool $formSent;
|
||||
|
||||
public array $values = [];
|
||||
public string $formContextMethod;
|
||||
|
||||
public array $messages = [];
|
||||
|
||||
public ServerRequestInterface $request;
|
||||
|
||||
public ? ResponseInterface $response = null;
|
||||
public bool $formSent = false;
|
||||
|
||||
public function __construct(ServerRequestInterface $request, ? string $formName = null)
|
||||
{
|
||||
$this->request = $request;
|
||||
|
||||
if ( $formName ) {
|
||||
$this->formName = $formName;
|
||||
public bool $formExecuted = false;
|
||||
|
||||
public mixed $formExecutionStatus = null;
|
||||
|
||||
protected string $formContextValuesRoot;
|
||||
|
||||
public array $formContextValues = [];
|
||||
|
||||
public array $formContextFiles = [];
|
||||
|
||||
public array $formContextMessages = [];
|
||||
|
||||
public bool $formSkipCsrf = false;
|
||||
|
||||
protected array $formContextDefinedProperties = [];
|
||||
|
||||
protected array $formContextCatchedMethods = [ 'POST', 'PUT', 'PATCH', 'DELETE', ];
|
||||
|
||||
protected array $formConstructorProperties = [];
|
||||
|
||||
public ? ResponseInterface $formContextResponse = null;
|
||||
|
||||
public function __construct(
|
||||
public ServerRequestInterface $formContextRequest,
|
||||
public readonly ? string $formName = null
|
||||
) {
|
||||
# From constructor
|
||||
$reflection = new \ReflectionClass($this);
|
||||
|
||||
$this->formConstructorProperties = array_map(fn($e) => strtolower($e->getName()), array_filter($reflection->getConstructor()->getParameters(), fn($e) => $e->isPromoted()));
|
||||
|
||||
$this->formContextValues = $formContextRequest->getParsedBody() ?: [];
|
||||
|
||||
if ( ! $this->formContextValues ) {
|
||||
$content = mb_convert_encoding((string) $formContextRequest->getBody(), 'UTF-8');
|
||||
|
||||
if ( $content && ( $json = json_decode($content, true) ) ) {
|
||||
$this->formContextValues = $json;
|
||||
}
|
||||
}
|
||||
|
||||
$this->values = $request->getParsedBody() ?: [];
|
||||
|
||||
if ($this->formContextValuesRoot ?? false) {
|
||||
$this->formContextValues = $this->formContextValues[$this->formContextValuesRoot] ?? [];
|
||||
}
|
||||
|
||||
$this->fillValues();
|
||||
|
||||
$this->formContextFiles = $formContextRequest->getUploadedFiles() ?: [];
|
||||
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
public function initialize() : void {}
|
||||
|
||||
public function valid() : bool
|
||||
{
|
||||
foreach($this->messages as $message) {
|
||||
foreach($this->formContextMessages as $message) {
|
||||
if ( $message->isError() ) {
|
||||
return false;
|
||||
}
|
||||
@@ -40,12 +75,50 @@ class FormContext implements FormContextInterface
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function executed() : bool
|
||||
{
|
||||
return $this->formExecutionStatus === false ? false : $this->formExecuted;
|
||||
}
|
||||
|
||||
public function formSent() : bool
|
||||
{
|
||||
return $this->formSent;
|
||||
$valid = in_array($this->requestMethod(), $this->formContextCatchedMethods);
|
||||
|
||||
if ( (! $this->formSkipCsrf) && ! empty($this->formName) ) {
|
||||
$token = $this->get('picea-ui-form')[$this->formName] ?? false;
|
||||
|
||||
if ( $token ) {
|
||||
if (! $this->formSkipCsrf) {
|
||||
$valid = in_array($token, $_SESSION["picea-ui.form:{$this->formName}"] ?? []);
|
||||
}
|
||||
else {
|
||||
$valid = (bool) $token;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->formSent = $valid;
|
||||
}
|
||||
|
||||
|
||||
public function getExecutionStatus() : mixed
|
||||
{
|
||||
return $this->formExecutionStatus;
|
||||
}
|
||||
|
||||
public function getRequest() : ServerRequestInterface
|
||||
{
|
||||
return $this->formContextRequest;
|
||||
}
|
||||
|
||||
public function requestMethod() : string
|
||||
{
|
||||
return $this->formContextRequest->getMethod();
|
||||
}
|
||||
|
||||
public function __set($key, $value)
|
||||
{
|
||||
return $this->set($key, $value);
|
||||
@@ -58,7 +131,7 @@ class FormContext implements FormContextInterface
|
||||
|
||||
public function __isset($key)
|
||||
{
|
||||
return array_key_exists($key, $this->values);
|
||||
return array_key_exists($key, $this->formContextValues);
|
||||
}
|
||||
|
||||
public function __unset($key)
|
||||
@@ -66,28 +139,112 @@ class FormContext implements FormContextInterface
|
||||
$this->delete($key);
|
||||
}
|
||||
|
||||
public function get(string $key, $default = null)
|
||||
public function get(string $key, mixed $default = null)
|
||||
{
|
||||
return $this->has($key) ? $this->values[$key] : $default;
|
||||
return $this->has($key) ? $this->formContextValues[$key] : $default;
|
||||
}
|
||||
|
||||
public function set(string $key, $value)
|
||||
public function set(string $key, mixed $value)
|
||||
{
|
||||
return $this->values[$key] = $value;
|
||||
|
||||
if ($this->canWriteProperty($key)) {
|
||||
$this->$key = $this->castValue($key, $value);
|
||||
}
|
||||
|
||||
return $this->formContextValues[$key] = $value;
|
||||
}
|
||||
|
||||
public function sets(array $values) : static
|
||||
{
|
||||
foreach($values as $key =>$value) {
|
||||
$this->set($key, $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function delete(string $key) : void
|
||||
{
|
||||
unset($this->values[$key]);
|
||||
unset($this->formContextValues[$key]);
|
||||
}
|
||||
|
||||
public function has(string $key) : bool
|
||||
{
|
||||
return array_key_exists($key, $this->values);
|
||||
return array_key_exists($key, $this->formContextValues);
|
||||
}
|
||||
|
||||
|
||||
public function pushMessage(FormMessage $message) : void
|
||||
{
|
||||
$this->messages[] = $message;
|
||||
if ($message->isError() /* && $message->isSuccess() @TODO next major v. */ ) {
|
||||
$this->formContextMessages = array_merge([ $message ], $this->formContextMessages);
|
||||
}
|
||||
else {
|
||||
$this->formContextMessages[] = $message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function messages() : array
|
||||
{
|
||||
return $this->formContextMessages;
|
||||
}
|
||||
|
||||
protected function fillValues() : void
|
||||
{
|
||||
foreach($this->formContextValues as $property => $value) {
|
||||
if ($this->canWriteProperty($property)) {
|
||||
$this->$property = $this->castValue($property, $value);
|
||||
$this->formContextDefinedProperties[$property] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function canWriteProperty(string $property) : bool
|
||||
{
|
||||
# Skipping overrides of this particular class vars as a security measure
|
||||
static $skipping = array_keys(array_change_key_case(get_class_vars(FormContext::class), CASE_LOWER));
|
||||
|
||||
return ! in_array(strtolower($property), array_merge($skipping, $this->formConstructorProperties)) && property_exists($this, $property);
|
||||
}
|
||||
|
||||
protected function definedProperty(string $property) : bool
|
||||
{
|
||||
return $this->formContextDefinedProperties[$property] ?? false;
|
||||
}
|
||||
|
||||
protected function castValue(string $property, mixed $value) : mixed
|
||||
{
|
||||
$cast = fn($e) => match(true) {
|
||||
is_a($e, Context\FormContextCastableInterface::class, true) => $e::instance($value),
|
||||
$e === "array" => (function(array|string $value) use ($property) {
|
||||
if (is_string($value)) {
|
||||
return json_validate($value) ? json_decode($value, true) ?? [] : throw new \InvalidArgumentException("Field '$property' is awaiting JSON encoded content.");
|
||||
}
|
||||
|
||||
return $value;
|
||||
})($value),
|
||||
is_null($value) => null,
|
||||
enum_exists($e) => ( $value instanceof \BackedEnum ? $value : $e::tryFrom($value) ) ?? throw new \InvalidArgumentException(sprintf("Field '\$$property' awaiting values : '%s'.", implode("', '", array_map(fn($e) => $e->value, $e::cases())))),
|
||||
function_exists($e) => $e($value),
|
||||
class_exists($e) => new $e(...$value),
|
||||
interface_exists($e) => throw new \InvalidArgumentException("There is no way provided to match an interface object yet."),
|
||||
default => $value
|
||||
};
|
||||
|
||||
$types = ( new \ReflectionProperty($this, $property) )->getType();
|
||||
|
||||
if ($types instanceof \ReflectionNamedType) {
|
||||
$value = $cast($types->getName());
|
||||
}
|
||||
elseif ($types instanceof \ReflectionIntersectionType || $types instanceof \ReflectionUnionType) {
|
||||
foreach($types->getTypes() as $type) {
|
||||
return $cast($type->getName());
|
||||
}
|
||||
}
|
||||
elseif ($types->isBuiltin()) {
|
||||
return $cast($types->getName());
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,16 @@
|
||||
|
||||
namespace Picea\Ui\Method;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
interface FormContextInterface {
|
||||
public function valid() : bool;
|
||||
|
||||
public function formSent() : bool;
|
||||
|
||||
public function requestMethod() : string;
|
||||
|
||||
public function getRequest() : ServerRequestInterface;
|
||||
|
||||
public function getExecutionStatus() : mixed;
|
||||
}
|
||||
+20
-21
@@ -5,15 +5,25 @@ namespace Picea\Ui\Method;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class FormHandler {
|
||||
const DEFAULT_METHODS = [
|
||||
"DELETE", "PATCH", "POST", "PUT",
|
||||
];
|
||||
|
||||
public bool $sent = false;
|
||||
|
||||
public ? bool $executionStatus = null;
|
||||
|
||||
public bool $validateCsrfToken = true;
|
||||
|
||||
public /* mixed */ $executionStatus = null;
|
||||
|
||||
public ? bool $validationStatus = null;
|
||||
|
||||
public FormInterface $form;
|
||||
|
||||
public FormContext $context;
|
||||
|
||||
protected ServerRequestInterface $request;
|
||||
|
||||
protected FormInterface $form;
|
||||
public array $acceptedMethods = self::DEFAULT_METHODS;
|
||||
|
||||
public function __construct(ServerRequestInterface $request, FormInterface $form, ? FormContextInterface $context = null)
|
||||
{
|
||||
@@ -28,37 +38,26 @@ class FormHandler {
|
||||
$this->context = method_exists($form, 'getContext') ? $form->getContext($request) : new FormContext($request);
|
||||
}
|
||||
|
||||
$this->request->context = $this->context;
|
||||
|
||||
$this->formSent();
|
||||
$this->request->withAttribute('picea.context', $this->context);
|
||||
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
protected function formSent() : void
|
||||
{
|
||||
if ( false !== $this->context->formSent = $this->sent ) {
|
||||
if ( $this->context->formName ?? false ) {
|
||||
$this->sent = $this->context->formSent = (bool) ( $this->request->getParsedBody()['picea-ui-form'][$this->context->formName] ?? false );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function initialize() : void
|
||||
{
|
||||
$this->form->initialize($this->context);
|
||||
|
||||
if ( $this->sent ) {
|
||||
if ( $this->form->validate($this->context) ) {
|
||||
$this->executionStatus = $this->form->execute($this->context);
|
||||
if ( $this->sent && $this->context->formSent() ) {
|
||||
if ( $this->validationStatus = $this->form->validate($this->context) ) {
|
||||
$this->context->formExecutionStatus = $this->executionStatus = $this->form->execute($this->context);
|
||||
$this->context->formExecuted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function requestSent() : bool
|
||||
{
|
||||
return in_array($this->request->getMethod(), [
|
||||
"DELETE", "PATCH", "POST", "PUT",
|
||||
]);
|
||||
return in_array(strtoupper($this->request->getMethod()), array_map('strtoupper', $this->acceptedMethods));
|
||||
}
|
||||
|
||||
protected function honeyPot() : bool
|
||||
|
||||
@@ -8,5 +8,5 @@ interface FormInterface
|
||||
|
||||
public function validate(FormContextInterface $context) : bool;
|
||||
|
||||
public function execute(FormContextInterface $context) : void;
|
||||
public function execute(FormContextInterface $context);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Picea\Ui\Method;
|
||||
|
||||
interface FormMessage {
|
||||
interface FormMessage {
|
||||
public function isError() : bool;
|
||||
# public function isSuccess() : bool; # Should be added in next major version
|
||||
}
|
||||
@@ -7,9 +7,10 @@ use Picea\Extension\Extension,
|
||||
|
||||
use Picea\Compiler\Context;
|
||||
|
||||
use Picea\Extension\FunctionExtension;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class Pagination implements Extension {
|
||||
class Pagination implements Extension, FunctionExtension {
|
||||
use ExtensionTrait;
|
||||
|
||||
public array $tokens;
|
||||
@@ -20,14 +21,16 @@ class Pagination implements Extension {
|
||||
|
||||
public function __construct(ServerRequestInterface $request, Context $context) {
|
||||
$this->request = $request;
|
||||
$this->register($context);
|
||||
}
|
||||
|
||||
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string { }
|
||||
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string { }
|
||||
|
||||
public function register(Context $context) : void
|
||||
|
||||
public function exportFunctions(): array
|
||||
{
|
||||
$context->pushFunction("pagination", [ $this, 'paginationClass' ]);
|
||||
return [
|
||||
"pagination" => [ $this, 'paginationClass' ]
|
||||
];
|
||||
}
|
||||
|
||||
public function paginationClass(string $name, ?string $url = null, ?array $params = null) : PaginationHandler
|
||||
|
||||
@@ -41,7 +41,7 @@ class PaginationHandler {
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return "hello world";
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,9 @@ class Ui {
|
||||
|
||||
public function registerFormExtension(Compiler $compiler) : self
|
||||
{
|
||||
# BW compatibility with v1.x syntax
|
||||
$compiler->registerExtension(new Form\Ui());
|
||||
|
||||
$compiler->registerExtension(new Form\UiForm());
|
||||
$compiler->registerExtension(new Form\UiInput());
|
||||
$compiler->registerExtension(new Form\UiCheckbox());
|
||||
@@ -29,6 +32,7 @@ class Ui {
|
||||
$compiler->registerExtension(new Form\UiUrl());
|
||||
$compiler->registerExtension(new Form\UiWeek());
|
||||
$compiler->registerExtension(new Form\UiSelect());
|
||||
$compiler->registerExtension(new Form\UiImage());
|
||||
|
||||
$compiler->registerExtension(new Component\UiPopup());
|
||||
|
||||
|
||||
Reference in new issue
Block a user