Site-wide form with Symfony2? - forms

I have simple form at all site pages: username, password, [Sign In]
I tried to make with with simple HTML, but I get
The CSRF token is invalid. Please try to resubmit the form
Idea to make form in each action seems bad. What is the good practice of doing site-wide forms?

You have to do it this way:
First, I guess you have some base template file like layout.html.twig and all other pages extend it. Eg:
// Resources/views/layout.html.twig
<doc ... bla blah>
<title>My site</title>
...(js, css)...
<body>
<div id="top">
{% render url("site_wide_form") %}
</div>
{% block content %}
{% endblock content %}
</body>
You need controller that will handle your form:
//Controller/SitewideController.php
/**
* #Route("/some/url/here", name="site_wide_form")
* #Template("yourbudle:folder:site_wide_form.html.twig")
*/
public function someAction()
{
..... your code for form, process submission etc ...
return ["form"=>$form->createView()] ;
}
and template file:
// site_wide_form.html.twig
<form action="{{ path("site_wide_form") }}" method="post">
{{ form_widget(form) }}
</form>
That's it. Read this to understand render tag: http://symfony.com/doc/2.2/book/templating.html#embedding-controllers

The CSRF token is missing. In your template (I assume you use Twig). You can tell Symfony2 to render all the remaining form elements using:
{{ form_rest(form) }}
Or you can render just the CSRF token:
{{ form_row(form._token) }}

Related

How to get a new object after form submittig?

I'm trying flask nad there is a simple task to submit a form.
Page is showing a one picture and a form, if form was submitted correctly, picture should be changed, if not - be the same.
I can't understand the mechanism of how to show only one object on a page and get another after form submitting.
Tried to use iterator over the list of images is folder "static", but my implementation was not working correctly.
Please provide me a feedback how to do it in rigth way?
Now I have the simple view:
#app.route("/", methods=["GET", "POST"])
def start_view():
picture = None
form = InputForm(csrf_enabled=False)
if form.validate_on_submit():
picture = form.picture.data
form.picture.data = ""
return render_template('04-2.html', form=form, picture=picture)
class InputForm(Form):
picture = StringField('What is on a picture?', validators[DataRequired()])
submit = SubmitField('Submit')
And a simple template:
<body>
<form method="POST">
{{ form.picture.label }} {{ form.picture }}
{{ form.submit() }}
</form>
{% if form.errors %}
<span style="color: red">{{ form.error }}</span>
{% endif %}
</body>
Thank you!
Your form doesnt contain any pictures. It has a StringField and a SubmitField. If you want to see any image you need to have an <img> tag in your HTML pointing to the image location in your server
your view should look like:
from Flask import session
# in order to use sessions you have to use a secret key for your app
app.secret_key = 'some secret key'
#app.route("/", methods=["GET", "POST"])
def start_view():
img_list = ['filename1', 'filename2', 'filename3']
# if this is not the first form submission
if session.has_key('current'):
# if we reach the end of the list show the first image again
if int(session['current']) == len(img_list) - 1:
session['current'] = 0
# move to next image
else:
session['current'] = int(session['current']) + 1
else:
session['current'] = 0
picture = 'first_image_filename' # this should be the img on load
form = InputForm(csrf_enabled=False)
if form.validate_on_submit():
picture = img_list[int(session['current'])] # the filename of the next image
form.picture.data = ""
return render_template('04-2.html', form=form, picture=picture)
so template should look like:
<body>
<form method="POST">
{{ form.picture.label }} {{ form.picture }}
<img src="{{url_for('static', filename='img/' + picture)}}"
{{ form.submit() }}
</form>
{% if form.errors %}
<span style="color: red">{{ form.error }}</span>
{% endif %}
</body>

Flask render form values

I have a question about rendering form results. When i submit the form i want to show the term below the form. what did i wrong ?
Now wehen i submit the form i get a 200 Status Code and no error message. But the term dosent show up in the defined place.
# app.py
#app.route('/search')
def search():
return render_template('search/index.html')
#app.route('/search<q>')
def results(q):
return render_template('search/index.html', term=q)
# search/index.html
{% extends 'base.html' %}
{% block content %}
<div>
<form method="get" action="">
<input type="text" name="q" id="">
<input type="submit">
</form>
{{ term }}
</div>
{% endblock %}
You have confused path parameters (which Flask routes parse out and pass to the view) with query parameters (which are available in request.args). Remove your second route and update the first to access the query.
from flask import request
#app.route('/search')
def search():
term = request.args.get('q')
return render_template('search.html', term=term)

Symfony2 forms are rendered empty

I've just started trying to build forms with Symfony2.4.2 and having a VERY frustrating time. I have successfully managed to build the example found in the book chapter 12. I've also tried to build essentially the same form in another project, in another bundle and always seems to end up with an empty form:
<html>
<head></head>
<body>
<form action="" method="post" name="form"> </form>
</body>
</html>
The code to create this:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use DMV\form3Bundle\Entity\Task;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
public function newAction(Request $request)
{
// create a task and give it some dummy data for this example
$task = new Task();
$task->setTask('Write a blog post');
$task->setDueDate(new \DateTime('tomorrow'));
$form = $this->createFormBuilder($task)
->add('task', 'text')
->add('dueDate', 'date')
->add('save', 'submit')
->getForm();
return $this->render('DMVform3Bundle:Default:new.html.twig', array('form' => $form->createView(),));
}
}
new.html.twig:
{{ form(form) }}
I've tried tried delving into the "$form" at a breakpoint at the "return $this->render..." line that is suppose to render the form and I DO see the form elements texts in the structure but it is a very large and complex structure and I'm not sure what I should see exactly.
Any suggestions would be appreciated.
You can simply render form in your new.html.twig template like:
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
{{ form(form) }}
But if you use Symfony version < 2.3, so try to use something that:
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<input type="submit" />
</form>

Symfony2 - adding a CSS class to an expanded choice field (radio)

I need to be able to add a CSS class to each individual radio button of my choice field(s). Unfortunately, Symfony2 stuffs expanded choices in a div, which gets any passed in CSS class rather than the buttons themselves.
Here's the default widget theming (in PHP):
<div <?php echo $view['form']->block($form, 'widget_container_attributes') ?>>
<?php foreach ($form as $child): ?>
<?php echo $view['form']->widget($child) ?>
<?php echo $view['form']->label($child) ?>
<?php endforeach ?>
</div>
Here's my div-less version (in twig):
{% block choice_widget_expanded %}
{% for child in form %}
{{ form_widget(child) }}
{{ form_label(child) }}
{% endfor %}
{% endblock %}
With that, how can I pass the CSS class to the actual widget? I'm passing it in like:
{% form_widget(form.blah, { 'attr' : { 'class': 'css-class' } }) %}
In my view, but I'm not sure how to grab it in my widget theme.
I can't not use radio buttons for this, so telling me to switch to a select or checkboxes isn't an option. And I really, really don't want to hard code the radios into my form view if I can help it.
EDIT: I've tried:
{% block choice_widget_expanded %}
{% for child in form %}
<input type="radio" value="{{ child.vars.value }}" {{ block('widget_attributes') }} />
{% endfor %}
{% endblock %}
Yet it still renders a containing div in the source, and passes my CSS class to that div instead of the radio inputs. I know that the theme is 'working' because I had a few exceptions thrown during my fiddling.
EDIT 2: With the suggestion below, I've created my own radio_widget theme:
{% block radio_widget %}
<input type="radio" class="star {split:2}" {{ block('widget_attributes') }} value="{{ value }}" {% if checked == true %}checked="checked"{% endif %} />
{% endblock %}
But, unfortunately, it's not generating the radios with the class I added above. I'm not sure if I need to do some inheritance work.
Interesting, why you want every radio option to have a class attribute. But...
Because choice is a single field (no matter how many options it has) you can add class to the wrapper div only. For the theming you should use CSS.
Speaking about form theming if you want to override radio option you should override block radio_widget (you can find original one in form_div_layout.html.twig)
And of course don't forget to tell you template to use form theme:
{% form_theme form _self %}

Symfony 2 custom form field type: how to add javascript and css only once?

I want to use javascript in custom Symfony 2 form field type extension. So, I have Twig extension template like this:
{% block some_widget %}
<input ... />
<script src="some.js"></script>
<link href="some.css" />
{% endblock %}
But I want to have these script and link tags only once in my HTML, ideally in head tag, without modifing base template. I tried to extend Twig blocks, but I have no access to action template blocks inside form template. Or maybe something like this:
{# widget tempate #}
{% block some_widget %}
<input ... />
{{ use_javascript('some.js') }}
{{ use_css('some.css') }}
{% endblock %}
{# main action template #}
...
<head>
{{ dump_javascripts() }}
{{ dump_css() }}
</head>
...
How to do this with Symfony 2 Forms + Twig?
P.S. Sorry for my bad english.
I had to write a self contained form widget that requires javascript, I was able to achieve what you are trying to do through the event_dispatcher listening on the kernel.response to append the javascript at the end of the Symfony\Component\HttpFoundation\Response. Here's a snippet of my form type :
<?php
namespace AcmeBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
class AcmeFileType extends AbstractType{
private $twig;
private $dispatcher;
public function __construct(\Twig_Environment $twig, EventDispatcherInterface $dispatcher){
$this->twig = $twig;
$this->dispatcher = $dispatcher;
}
public function buildView(FormView $view, FormInterface $form, array $options){
$javascriptContent = $this->twig->render('AcmeBundle:Form:AcmeFileType.js.twig', array());
$this->dispatcher->addListener('kernel.response', function($event) use ($javascriptContent) {
$response = $event->getResponse();
$content = $response->getContent();
// finding position of </body> tag to add content before the end of the tag
$pos = strripos($content, '</body>');
$content = substr($content, 0, $pos).$javascriptContent.substr($content, $pos);
$response->setContent($content);
$event->setResponse($response);
});
}
...
When you define your form type in your services.yml it looks like this :
acme.form.acme_file_type:
class: AcmeBundle\Form\AcmeFileType
arguments:
- #twig
- #event_dispatcher
tags:
- { name: form.type, alias: acmefile }
So now, everytime you build a form with acmefile the javascript will be appended to the <body>. This solution does not prevent the javascript from being present multiple time though, but you should easily be able to improve this to suit your needs.
You can also play around with the $response object to modify the headers instead if you wish.
The best way is to provide the separate template with css & scripts loading.
With the comments in readme so only thing developer will must do is to
{% block stylesheets %}
{{ parent() }}
include "#MyBestBundle/Resources/view/styles.html.twig"
{% endblock %}
or try to intercept the form rendering with DI and add the assets. But it more difficult to do , if possible to implement.
My way of doing was by creating a custom twig extension where I add JS to a buffer and during form rendering and later dump it at the of my layout.
Something like this:
<?php
namespace AppBundle\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class WysiwygExtension extends AbstractExtension
{
/**
* #var array
*
* A pool of elements IDs for Wysiwyg binding.
*/
private $wysiwygElements = [];
/**
* {#inheritdoc}
*/
public function getFunctions()
{
return array(
new TwigFunction('addWysiwygBinding', [$this, 'addWysiwygBinding']),
new TwigFunction('popWysiwygBindings', [$this, 'popWysiwygBindings']),
);
}
public function addWysiwygBinding(string $id): void
{
$this->wysiwyglements[] = $id;
}
public function popWysiwygBindings(): array
{
$elements = array_unique($this->wysiwygElements);
$this->wysiwygElements = [];
return $elements;
}
}
Then form-fields.html.twig:
...
{% block wysiwyg_widget %}
{% apply spaceless %}
{{ form_widget(form) }}
{% do addWysiwygBinding(id) %}
{% endapply %}
{% endblock %}
...
Then layout.html.twig:
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
...
{% set ids = popWysiwygBindings() %}
{% if ids is not empty %}
{% javascripts
'bundles/admin/plugins/wysiwyg_1.js'
'bundles/admin/plugins/wysiwyg_2.js'
%}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
{% endif %}
{% for id in ids %}
{{ include('_wysiwyg.html.twig', { id: id }) }}
{% endfor %}
</body>
</html>
This is how I use it. Hope it's what you're looking for.
base.html.twig
<head>
{% block stylesheets %}
css...
{% endblock %}
</head>
foo.html.twig
{% extends '::base.html.twig' %}
{% block stylesheets %}
{{ parent() }}
css that you need in foo.html.twig
{% endblock %}
I found some 'dirty' method used by many peoples in other situations.
We check the loading of script on client side. In case we have a zlkladr.js file, that have a global object 'zlkladr'
{% block our_widget %}
{% spaceless %}
...
<script>
// We must load the script only once, even if many widgets on form
if ( !window.zlkladr ) {
document.write('<script src="{{ asset('bundles/kladr/js/zlkladr.js') }}"></sc'+'ript>');
}
</script>
{% endspaceless %}
{% endblock %}