Skip to content

Commit

Permalink
Merge pull request #41 from peckadesign/vlastni-zprava-v-odpovedi
Browse files Browse the repository at this point in the history
Ve validační odpovědi je možné odeslat vlastní zprávu
  • Loading branch information
Kaczmi authored Mar 25, 2021
2 parents e39e539 + 06c63fa commit 82bde33
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 28 deletions.
2 changes: 2 additions & 0 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- **Měkká validace**: live validace, která upozorní uživatele na problém v inputu, ale umožní odeslání formuláře (není kontrolována na straně serveru)
- **Ajax validace**: live validace, která pro výsledek validace volá asynchronně zpracování na backendu
- **Standardní validace**: validace, která je zpracována na straně serveru a *může* být kontrolována i na straně klienta
- **Dynamické validační zprávy**: validační zprávy, které nejsou generované předem, ale jsou součástí odpovědi AJAXové validace

## Pd\Form\Rules + pdForms.js
Knihovna poskytuje nástroje, pomocí kterých je možné zaregistrovat vlastní validační pravidla do `Nette\Forms` a navíc poskytuje podporu pro live, měkkou a ajaxovou validaci, které lze zaregistrovat v PHP kódu. Řešení vychází z nativní podpory Nette pro [custom validační pravidla](https://pla.nette.org/cs/vlastni-validacni-pravidla), ale nespoléhá ani nekopíruje interní quirks Nette frameworku.
Expand All @@ -14,3 +15,4 @@ Knihovna poskytuje nástroje, pomocí kterých je možné zaregistrovat vlastní
- [Je libo AJAX?](ajax.md)
- [AJAX s JS callbackem, závilost na více formulářových polích](ajax_dependent_inputs.md)
- [Měkká validace pomocí dostupných Nette validátorů](nette_optional.md)
- [Dynamické validační zprávy a našeptávání hodnot inputů přes validační zprávy](suggestions.md)
28 changes: 28 additions & 0 deletions doc/suggestions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Dynamické zprávy a našeptávání hodnot inputů přes validační zprávy

`pdForms` je možné použít k našeptávání například překlepových výrazů u domén e-mailů apod. K tomu slouží takzvané _dynamické zprávy_. Tyto validační zprávy jsou vygenerovány až v průběhu validace v PHP a na frontend jsou v případě AJAXové validace vráceny přes response.

```php
$validationResultWithMessage = new \Pd\Forms\Validation\ValidationResult(TRUE);
$validationResultWithMessage->setMessage('My message');
```

Pokud chceme takovou dynamickou zprávu použít k našeptání hodnoty, stačí k tomu, aby našeptaná hodnota byla obalena do libovolného tagu (`<a>`, `<span>`, ...) s `class="pdforms-suggestion"`. Frontend JS se pak postará o to, že při kliknutí na tento element dojde k vyplnění obsahu do validovaného inputu.

```php
$validationResultWithMessage = new \Pd\Forms\Validation\ValidationResult(TRUE);

$suggestion = \Nette\Utils\Html::el('a');
$suggestion->addAttributes([
'href' => '#',
'class' => 'pdforms-suggestion',
]);
$suggestion->setText('Suggested value');

$message = \Nette\Utils\Html::el('span');
$message->setHtml('Did you mean ' . $suggestion . '?');

$validationResultWithMessage->setMessage((string) $suggestion);
```

Narozdíl od validačních zpráv, které na prvku přidáváte přes `addRule`, se tyto zprávy **neescapují**, aby se správně vykreslily HTML elementy. Z toho důvodu je potřeba na to myslet a dát si pozor na to, co z backendu do této validační zprávy posíláte. Proto doporučuji tyto zprávy skládat pomocí `\Nette\Utils\Html::el` prvků a neskládat je jako stringy.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "pd-forms",
"title": "pdForms",
"description": "Customization of netteForms for use in PeckaDesign.",
"version": "3.3.0",
"version": "3.4.2",
"author": "PeckaDesign, s.r.o <support@peckadesign.cz>",
"contributors": [
"Radek Šerý <radek.sery@peckadesign.cz>",
Expand Down
15 changes: 15 additions & 0 deletions src/Validation/ValidationResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ final class ValidationResult implements \JsonSerializable

private string $messageType = '';

private ?string $message = NULL;


public function __construct(bool $valid, ?string $status = NULL)
{
Expand Down Expand Up @@ -58,6 +60,18 @@ public function setMessageType(string $messageType): void
}


public function getMessage(): ?string
{
return $this->message;
}


public function setMessage(?string $message): void
{
$this->message = $message;
}


/**
* @return array<mixed>
*/
Expand All @@ -71,6 +85,7 @@ public function jsonSerialize()
'status' => $this->status,
'messageType' => $this->messageType,
'dependentInputs' => $this->dependentInputs,
'message' => $this->message,
];

return $valid + \array_filter($rest);
Expand Down
107 changes: 80 additions & 27 deletions src/assets/pdForms.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @name pdForms
* @author Radek Šerý <radek.sery@peckadesign.cz>
* @version 3.3.0
* @version 3.4.2
*
* Features:
* - live validation
Expand Down Expand Up @@ -45,7 +45,7 @@

var pdForms = window.pdForms || {};

pdForms.version = '3.3.0';
pdForms.version = '3.4.2';


/**
Expand Down Expand Up @@ -189,7 +189,7 @@

// if ajax validator is used, validate & push into queue of not-yet resolved rules
if (rule.isAjax) {
var key = pdForms.getAjaxQueueKey(elem, op);
var key = pdForms.getAjaxQueueKey(elem, op, rule.arg.ajaxUrl);
pdForms.ajaxQueue[key] = {
msg: rule.msg,
isOptional: rule.isOptional,
Expand Down Expand Up @@ -274,8 +274,8 @@
/**
* Get key to ajax queue for given element and operation
*/
pdForms.getAjaxQueueKey = function(elem, op) {
return elem.getAttribute('id') + '--' + op;
pdForms.getAjaxQueueKey = function(elem, op, url) {
return elem.getAttribute('id') + '--' + op + '--' + url;
};


Expand All @@ -284,7 +284,7 @@
* after response is received.
*/
pdForms.ajaxEvaluate = function(elem, op, status, payload, arg) {
var key = pdForms.getAjaxQueueKey(elem, op);
var key = pdForms.getAjaxQueueKey(elem, op, arg.ajaxUrl);

// found request in queue, otherwise do nothing
if (key in pdForms.ajaxQueue) {
Expand All @@ -298,24 +298,25 @@
// remove old messages, only when onlyCheck is false
pdForms.removeMessages(elem, true);

if (status in msg && msg[status]) {
var msgType = pdForms.constants.MESSAGE_ERROR;
var msgType = pdForms.constants.MESSAGE_ERROR;

if (typeof payload === 'object' && payload.messageType) {
msgType = payload.messageType;
} else if (status === 'timeout') {
msgType = pdForms.constants.MESSAGE_INFO;
} else if (status === 'valid') {
msgType = pdForms.constants.MESSAGE_VALID;
}
if (typeof payload === 'object' && payload.messageType) {
msgType = payload.messageType;
} else if (status === 'timeout') {
msgType = pdForms.constants.MESSAGE_INFO;
} else if (status === 'valid') {
msgType = pdForms.constants.MESSAGE_VALID;
}

if (isOptional && msgType === pdForms.constants.MESSAGE_ERROR) {
msgType = pdForms.constants.MESSAGE_INFO;
}
if (isOptional && msgType === pdForms.constants.MESSAGE_ERROR) {
msgType = pdForms.constants.MESSAGE_INFO;
}

if (typeof payload === 'object' && payload.message) {
pdForms.addMessage(elem, payload.message, msgType, true, false);
} else if (status in msg && msg[status]) {
pdForms.addMessage(elem, msg[status], msgType, true);
}
else if (status === 'valid') {
} else if (status === 'valid') {
// add pdforms-valid class name if the input is valid and no message is specified
pdForms.addMessage(elem, null, pdForms.constants.MESSAGE_VALID, true);
}
Expand Down Expand Up @@ -354,17 +355,22 @@
var input = document.getElementById(inputId);

if (input && ! input.value) {
var ev = document.createEvent('Event');
ev.initEvent('change', true, true);

input.value = payload.dependentInputs[inputId];
input.dispatchEvent(ev);
pdForms.setInputValue(input, payload.dependentInputs[inputId]);
}
}
}
};


pdForms.setInputValue = function (elem, value) {
var ev = document.createEvent('Event');
ev.initEvent('change', true, true);

elem.value = value;
elem.dispatchEvent(ev);
};


/**
* Find the placeholder element for a given input element.
*/
Expand Down Expand Up @@ -409,7 +415,7 @@
* Using data-pdforms-messages-tagname we could change the default span (p in case of global messages) element.
* Using data-pdforms-messages-global on elem we could force the message to be displayed in global message placeholder.
*/
pdForms.addMessage = function(elem, message, type, isAjaxRuleMessage) {
pdForms.addMessage = function(elem, message, type, isAjaxRuleMessage, escapeMessage) {
var placeholder = pdForms.getMessagePlaceholder(elem);

if (! placeholder.elem) {
Expand Down Expand Up @@ -441,7 +447,13 @@
className = (tagName === 'p') ? 'message message--' + type : className;

var msg = document.createElement(tagName);
msg.textContent = message;

if (typeof escapeMessage === 'undefined' || escapeMessage) {
msg.textContent = message;
} else {
msg.innerHTML = message;
}

msg.setAttribute('class', className + ' pdforms-message');
msg.setAttribute('data-elem', elem.name);

Expand All @@ -451,6 +463,8 @@

if (tagName === 'label') {
msg.setAttribute('for', elem.id);
} else {
msg.setAttribute('data-for', elem.id);
}

placeholder.elem.getAttribute('data-pdforms-messages-prepend') ?
Expand Down Expand Up @@ -541,6 +555,42 @@
};


/**
* Fills in the suggestion from clicked e.target into associated input element.
*/
pdForms.useSuggestion = function(e) {
e.preventDefault();

var suggestion = e.target.text;
var elem = pdForms.getSuggestionInput(e.target);

if (! suggestion || ! elem) {
return false;
}

// clear suggestion message and validate again
pdForms.removeMessages(elem, true);
pdForms.setInputValue(elem, suggestion);
pdForms.liveValidation({ target: elem });
};


/**
* For given suggestion element inside validation message finds and returns associated input element (or null).
*/
pdForms.getSuggestionInput = function(suggestion) {
var msgElem = suggestion.closest('.pdforms-message');

if (! msgElem) {
return null;
}

var elemId = msgElem.getAttribute('for') || msgElem.getAttribute('data-for');

return document.getElementById(elemId);
}


/**
* Optional rules are defined using "optional" property in "arg". We have to convert arg into Nette format before
* validating. This means removing all properties but data from arg and storing them elsewhere.
Expand Down Expand Up @@ -637,6 +687,9 @@
addDelegatedEventListener(form, 'validate focusout change', 'select', pdForms.liveValidation);
addDelegatedEventListener(form, 'validate change', 'input[type="checkbox"], input[type="radio"]', pdForms.liveValidation);

// Suggestions from custom messages
addDelegatedEventListener(form, 'click', '.pdforms-suggestion', pdForms.useSuggestion);

// Validation on custom events
var pdformsValidateOnArr = Array.prototype.slice.call(form.elements);
pdformsValidateOnArr = pdformsValidateOnArr.filter(function(elem) {
Expand Down
19 changes: 19 additions & 0 deletions tests/Unit/Forms/ValidationResultTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,25 @@ public function testSerialization(): void
$validationResult->addDependentInput('second', 2);

\Tester\Assert::same(\Nette\Utils\Json::encode($expected), \Nette\Utils\Json::encode($validationResult));

$validationResultWithMessage = new \Pd\Forms\Validation\ValidationResult(TRUE);
$validationResultWithMessage->setMessage('My message');

$expected = [
'valid' => TRUE,
'message' => 'My message',
];

\Tester\Assert::same(\Nette\Utils\Json::encode($expected), \Nette\Utils\Json::encode($validationResultWithMessage));
}


public function testResultDynamicMessage(): void
{
$validation = new \Pd\Forms\Validation\ValidationResult(TRUE);
\Tester\Assert::null($validation->getMessage());
$validation->setMessage('Test');
\Tester\Assert::equal('Test', $validation->getMessage());
}

}
Expand Down

0 comments on commit 82bde33

Please sign in to comment.