I am beginner in Phalcon framework, I have to validate form elements in .volt page
I have one form class file where I write hidden filed for record edit purpose, I'm storing record's id in hidden filed when its in edit mode
if ($options["edit"] == 1) {
// $tax_categories_id = new Hidden("tax_categories_id");
$this->add(new Hidden('tax_categories_id'));
//$this->add($tax_categories_id);
}
The problem is when I rendering this hidden filed in add.volt
{{ form.render('tax_categories_id')}}
Its working fine in time of edit mode, but in new record time its give error
Phalcon\Forms\Exception: Element with ID=tax_categories_id is not a part of the form
I know the why error is coming but i am not able to validate this field in .volt file
In the controller can you set your $options variable and then check for it inside of the view?
//controller.php
$this->view->setVar('options', $options);
//view.volt
{% if options['edit'] %}
{{ form.render('tax_categories_id')}}
{% endif %]
Just check if the element is exist
// add.volt
{% if form.has('tax_categories_id') %}
{{ form.render('tax_categories_id') }}
{% endif %}
Assuming you have crated something close to:
<?php
use Phalcon\Forms\Form,
Phalcon\Forms\Element\Text,
Phalcon\Forms\Element\Hidden;
class UsersForm extends Form
{
public function initialize($options = [])
{
if ( isset($options['edit']) && $options['edit'] ) {
$this->add(new Hidden('id'));
}
$this->add(new Text('name'));
}
}
So! Depending on options, you may have one field declared, or two instead. Now when someone sends you this form back, for validation you have to set it up again with proper $options['edit'], depending on if you have $_REQUEST['id'] declared or not:
$form = null;
if( isset($_REQUEST['id']) ) {
$form = new UsersForm();
} else {
$form = new UsersForm(['edit' => true]);
}
$form->bind($_REQUEST);
if($form->isValid()) {
//...
}
Quite an advanced (but with some gaps anyway) manual is here. Bet you were there already, but just in case.
Btw, form are iterators & traversables, so you can loop over them to render only elements, that are declared. Writing this because you have put {{ form.render('tax_categories_id')}} as an example and that makes me feel like you are generating fields by hand.
Related
I'm using Symfony 6.1 with Twig on PHP 8.1. I've already set up my complete layout including fragments/partials/components or whatever we call reusable pieces of the layout at the moment. These fragments include certain kinds of navigations, quotes, tabs and so on which are always supposed to look and work the same and usually only receive a variable with the same name as an array with a few options.
This is how I'm embedding a YouTube-Video for example:
{% set youtube_video = { 'id': 'dQw4w9WgXcQ', 'title': 'Secret Video (unlisted)', 'language': 'en' } %} {% include 'fragment/youtube_video.html.twig' %}
Most of these fragments can be used multiple times on one page (= in the main template of the view or within the base template/s). Some of them however are supposed to be used only once and using them multiple times would create layout issues (e.g. a navigation for mobile devices with a specific CSS id).
For other fragments I would like to have a counter to add a CSS id in addition to a normal class:
<div class="fragment_video" id="fragment_video_{{ counter }}> ... </div>
The question now is how I can count within a fragment template like 'fragment/youtube_video.html.twig' how often this template has been used in that page already. I don't see any Twig functions or anything within the "app" variable for that.
Now I could create a custom Twig function "counter" and call that with with a unique name:
<div class="fragment_video" id="fragment_video_{{ counter('fragment_video') }}> ... </div>
or
{% if counter('fragment_video') == 1 %} ... {% endif %}
BUT how would I store the current count per given name? I don't want to use $GLOBALS in Twig or rather Symfony and storing that information in the session would keep it past the current request. Is there another solution available?
This is how it would look like as a Twig function:
public function getCounter(string $name): int
{
$name = 'twig_counter_'.$name;
if (isset($GLOBALS[$name])) {
++$GLOBALS[$name];
} else {
$GLOBALS[$name] = 1;
}
return $GLOBALS[$name];
}
As suggested by #DarkBee the instance of a TwigExtension object can use properties to keep track of some information:
/**
* #var array<int>
*/
private array $count = [];
...
public function getCounter(string $name): int
{
if (isset($this->count[$name])) {
++$this->count[$name];
} else {
$this->count[$name] = 1;
}
return $this->count[$name];
}
I have two issues when sending a form with the GET method with Symfony 4. This form contains filters and submitting this form updates the list of displayed items according to the selected filters.
The form is built like this:
class MyForm extends AbstractType {
...
public function buildForm(...) {
$builder
->setMethod("GET")
->add(
"first_filter",
ChoiceType::class,
...
)
->add(
"second_filter",
EntityType::class,
...
)
->add(
"button_apply",
SubmitType::class
);
First problem, after sending the form, the URL looks like this:
/action?my_form[first_filter]=...&my_form[second_filter]=...
Is it normal that the form name is included before every field name, and why the URL could not simply be:
/action?first_filter=...&second_filter=...
The second problem is that the submit button is part of the params visible into the URL:
/action?my_form[button_apply]=&...
As far as I know, the submit button itself should not be a parameter ?
Thanks in advance
it's the normal behaviour, and it can be circumvented, but it requires to call createNamed instead of create on a form factory (see ControllerTrait::createForm) ... so Symfony\Component\Form\FormFactory::createNamed())
// at the top: use Symfony\Component\Form\FormFactory
public function yourControllerAction(FormFactory $formFactory, Request $request) {
$form = $formFactory->createNamed(
'', // form name, otherwise form class name camel_cased
YourFormType::class, // your form type
[], // initial data or object or whatever!
[] // form options
);
// rest of the script is identical, it's still a normal form
}
or you don't inject it and do what the trait does:
$form = $this->container->get('form.factory')->createNamed(
// parameters same as before
);
for the button to disappear from the GET/POST, I would advise you to remove the button from your form and instead add it to your template (also increases reusability).
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit">{{ 'your label'|trans }}</button>
{{ form_end(form) }}
which makes the button submit the form, but since it has no name and value it won't add to the data (in contrast to the SubmitType button form type).
So I have a form containing questions of type free fields (text), multiple choice (checkbox), single choice (radio).
I get the values โโof the button checked and I save in database for each user but what I want to do is change this value like this:
data-point "2" = 10 points,
data-point "3" = 7 points,
data-point "4" = 4 points,
data-point "5" = 2 points,
and then with these values โโI have to do a calculation and define a profile according to the result .. how should I do that? SQL request? in the loop? a little help would be welcome ..here my twig to loop on subquestion.
<div class=" col-6 d-flex">
<div class="label-div1">
<label for="subscale{{subQuestion.id}}for{{i}}>
<img src="{{asset('images/survey/humeur/'~ i ~'.jpg')}}">
</label>
<input type="radio" class="radio-pict" name="{{subQuestion.id}}" id="subscale{{i}}" data-point="{{i}}"value="{{i}}">
</div>
here my controller to save de answers
public function saveAnswer(Request $request)
{
/* Repository */
$questionRepo = $this->getDoctrine()->getRepository(Question::class);
$answerRepo = $this->getDoctrine()->getRepository(Answer::class);
$choiceRepo = $this->getDoctrine()->getRepository(Choice::class);
$userSlpRepo = $this->getDoctrine()->getRepository(UserSlp::class);
/* Entity Manager */
$em = $this->getDoctrine()->getManager();
$datas = $request->request->all();
$userSlp = $userSlpRepo->findOneByGaeaUserId($this->getUser()->getId());
foreach ($datas as $data => $value) {
$questionId = explode("_", $data);
$question = $questionRepo->findOneById($questionId[0]);
switch ($question) {
case 'Sub_question_free':
$answer = new Answer_free;
$answer->setFreeAswere($value);
break;
case 'Sub_question_scale':
$answer = new Answer_scale;
$answer->setScale($value);
break;
$answer->setQuestion($question);
$answer->setUserSlp($userSlp);
$em->persist($answer);
$em->flush();
}
exit;
}
}
and here my twig to display on admin the results..
{% if answer.question == "Sub_question_choice" or
answer.question == "Sub_question_scale" or
answer.question == "Sub_question_free" %}
{% if answer == "choice" %}
{% for choice in answer.choices %}
<p>{{choice.name}}</p>
{% endfor %}
{% elseif answer == "free" %}
<p>{{answer.freeAswere}}</p>
{% elseif answer == "scale" %}
<p> {{answer.scale}}</p>
{% endif %}
So first, in Symfony a controller always need to send back a response. Even if it's just a redirect or a JsonResponse. So you shouldn't use exit in your controller.
You should use Symfony form, but if you are new to Symfony, I can understand that it's not easy.
Here to help you, you have many options:
You can change value with some conditions inside your swith statement in your controller
You can separate that in a service, look for service in Symfony on the docs. And use dependency injection inside your controller like this :
Service :
public function conversion(){
//Some conditions
}
controller:
public function saveAnswer(Request $request, YourService $service)
And use it inside as a function.
(More Easy Option) You can overload your setter in your entity (Answer.php for example):
public function yourSetter(...){
//do some conditions and after set your variables depending on result
}
Hope this will help you, feel free to ask if you want !
so I have a selection box that gives a dropdown menu to give messages a manager from the dropdown. It takes the input and then changes to a column in the database called manager for it's respective column. When I try to submit the selection menu it gives me the regular error for Laravel. But then when I put ?debug=1 at the end it submits but gives the row's manager column a value of just blank.
Here is what I have in the routes.php
Route::get('foo/{id}', 'fooController#bar');
Route::post('foo/{id}', 'fooController#bar');
This is the form.
{{ Form::open(array('url' => '/admin/foo' . $message->id)) }}
{{ Form::select('handler[]', array('unassigned', 'foo', 'bar'), null, array('style' => 'width: 127px')); }}
{{ Form::submit('Change manager') }}
{{ Form::close() }}
{{ $message->manager }}
and here is what is in the fooController
public function bar($id = null)
{
$message = Message::find($id);
$handler = Input::get('handler[]');
$message->manager = $handler;
$message->save();
return Redirect::action('AdminController#foo_bar');
}
I had a problem like this the other day, I have zero recollection of what I did. I really appreciate any help, thanks! The database is postgresql if that's any help
Try a dd(Input::all()) at the beginning of your controller and make sure you're seeing what you expect.
Also since you're sending an array perhaps you have to do Input::get('handler.0') -- see here right below the Input::only() and Input::except() code block.
It would seem as though because you are naming your select handler[], PHP is grabbing it as part of an array.
When setting up your message model, try this...
public function bar($id = null)
{
$message = Message::find($id);
$handler = Input::get('handler[]');
$message->manager = $handler[0];
$message->save();
return Redirect::action('AdminController#foo_bar');
}
Usually, you'd only use names in your forms post-fixed with [] when you are accepting multiple values like checkboxes/multi-selects etc... Otherwise, it's probably best to stick with not using it because it may cause confusion.
I managed to fix it in a almost frustratingly simple way by just changing the method to PUT.
like this
Form::open(array('url' => 'foo/bar', 'method' => 'put'))
I have a controller where I am creating a form witg two dropdown list inside.
When I am rendering my view, I would like to have the same form elements on the top and the bottom of the page. The problem is that the form elemetns (dropdownlists) are displayed only on the top of the page, even if I am asking twig to put them also on the bottom.
Here is what I would like to have:
The 1 and 2 are the dropdownlists. And I would like to duplicate this on the top and on the bottom of the page.
Any Idea on how can this be done?
The top content and the bottom content, where the two dropdownlists are inside are in a single sseparate twig file (searchPanel.html.twig) and this file is included in the page
{% include "MyBundle:Search:searchPanel.html.twig" %}
Here is the searchPanel.html.twig
<div class="searchPanel">
<form action="{{ path }}" method="POST" {{ form_enctype(form) }}>
Papers per page
{{ form_widget(form.papers_per_page, { 'class': 'ppp'}) }}
/ Sort by
{{ form_widget(form.sort_by, { 'class': 'sort'}) }}
{{ form_rest(form) }}
/ Papers ({{ papers_number }} results)
<input type="submit" class="updateSearchResults" value="Update"></input>
</form>
A problem in your approach is that Symfony's Form-component will render the form elements with id's which would be duplicated if you rendered the same form twice on your page. You might also run in trouble with the csrf_token. The gist being that forms are not intended to be duplicated.
Here is what I would do. Create a twig-template containing your paginator form without using Symfony\Form, i.e. create all form elements statically and pass it the paginator-object (or array) to get the data instead of using form_widget(). Something like this:
<form action="{{ path(app.request.attributes.get('_route') }}" method="POST">
<select name="paginator[per_page]">
{% for per_page in paginator.papers_per_page %}
<option value=""{{ per_page }}">{{ per_page }}</option>
{% endfor %}
</select>
</form>
The form action will automatically submit the data to your current route, so you can embed it in different actions and it will submit the data to the same action. On POST you can just create a paginator-object with the post-data and then add it as the form's data. After that you just use isValid() as usual.
In your controller you can get the data like this:
use Symfony\Component\HttpFoundation\Request;
// ...
public function PaperController()
{
public function listAction(Request $request)
{
if ($request->getMethod() == 'POST') {
$data = $request->request->get('paginator');
$paginator = new Paginator($data);
$form = new PaginatorFormType();
$form->setData($paginator);
if ($form->isValid()) {
// ...
}
}
}
}
You can easily embed the form in your view like this:
{{ include 'AcmeDemoBundle:Form:paginator.html.twig' with { 'paginator': paginator } }}
Basically you just use the Form-component in your controller for validation purposes. If you want to set some default values or add additional arguments you might want to create a macro from that template, but for your use case this should suffice. Maybe someone else has a better solution but this is how I went with a similar problem in one of my projects.
another option is user the render twig helper. That way is is possible render the same form in the page as many time as you want. A difference is that using this helper, is also necessary to treat the form renderization as an independent controller Action namely:
in every place in your twig template you want to render the form in the helper to invoke the form there's must be something like this:
{{ render(controller('BundleNameBundle:Controller:Action', {
'param': paramId
})) }}
Thereafter is just a matter of creating the controller...
Another option is in the controller to create 2 forms:
//first form
$form = $this->createForm(new MyFormType(), ...);
//second form: must have different form name that default,
//to render in twig the fields with different ids
$formType = new MyFormType();
$formType->setName('second_form_name');
$formSecond = $this->createForm($formType, ...);
Send both when rendering the twig form:
return $this->render( ...
'form' => $form->createView(), 'formSecond'=>$formSecond->createView()));
Then define the second with name as
formSecond
, and it will conflict with the first.