I've created a new form type for a simple one field newsletter signup form. What's the best way to embed this across different pages, multiple times on my site?
I could create a new instance of this form with a different name in every controller to show in my twig templates, but this seems a little messy. Ideally I'd like to be able to add more than one form on a page, and add them easily to a template.
I've tried {% render controller('MyBundle:EmailSubscribe:subscribe') %} but this doesn't work with multiple inserts, and I'm sure there's a better way anyway. Any ideas?
May be with Twig extension? This kind of task is the next one in my current project (e-commerce project where I need to display multiple addToCart forms on category pages), I'm also planning to do this with Twig function, for example (I did'nt test it yet):
private $formFactory;
public function __construct(FormFactoryInterface $formFactory)
{
$this->formFactory = $formFactory;
}
public function getFunctions() {
return [
new \Twig_SimpleFunction('newsletter_form', [$this, 'getNewsletterForm']),
];
}
public function getNewsletterForm()
{
$formFactory = $this->formFactory;
$form = $formFactory->create('my_form_alias', null_OR_data);
return $form->createView();
}
and use it in template as newsletter_form
Im a fan of xurshid29's Answer
What I do is simplay render the controller like your answer suggests. But that causes only one form to get processed, and leaves the unprocessed forms with errors like Cannot extra fields and so forth.
The Solution:
Pass the request from the original controller.
In Controller
function myAction(Request $request){
$this->render('path:to:template.html.twig', array('request'=>$request));
}
And In my Twig Template I simply pass the request to that controller.
{% render(controller('path:to:controller',{request:request} %}
And now you can create a base template for that form and include it in any template all across the site. This was especially useful for me with FOSUserBundle!
There may be some kind of twig function app.request ? I've been too lazy to try look it up. But in that case you don't have to pass request from original controller.
Extra tips on this subject
What I like to do that allows me to insert my forms anywhere for rapid dev is to build a controller/template for each form, like a "Join our Mailing List" or something.
Controller - The goal of this controller is to process the form and output "success" or show the form.
public function emailSubscribeFormAction(Request $request){
$email = new Email() //Presumed Entity for form
$form = /* Create Form Here */->getForm();
$form->handleRequest($request);
if($form->isValid()){
$form = "success"; //Change form into a string
}
$this->render('path:to:email-form.html.twig', array(
'form'=>($form=="success") ? $form : $form->createView();, // Either pass success or the form view
'request'=>$request
}
Twig email-form.html.twig
{% if form == "success" %}
<h4 id="form-success">Thanks for joining!</h4>
<script>
window.location = "#form-success" /*Bring the user back to the success message*/
</script>
{% else %}
<h4>Come on, Join Our Mailing List!</h4>
{{ form(form) }}
{% endif %}
Now you can render that controller anywhere and not have to worry about another thing about it.
Can work with multiple forms but I may have read there are drawbacks to rendering controllers like this?
If I understood your problem correctly you want to embed the same form multiple times in a page and this is to be done for multiple pages. So lets say that for page_1 you want to embed the subscribe_form N times you can obviously create a form and add this form type N times like this:
$form = $this->createFormBuilder()
->add('subscribe1',new SubscribeType())
->add('subscribe2',new SubscribeType())
...
->add('subscribeN',new SubscribeType())
->getForm();
So you can also do this in a for loop like this:
$form = $this->createFormBuilder();
for ($i = 1; $i<N+1; $i++){
$formName = 'subscribe'.$i;
$form->add($formName,new SubscribeType());
}
$form->getForm();
Now you can include this code in a controller as you said and pass the variable N as argument in each of your twig templates. Moreover, you can do this through javascript especially if you want to add these forms(SubscribeType) dynamically using allow_add option in your form. Documentation about this is here: http://symfony.com/doc/current/cookbook/form/form_collections.html#allowing-new-tags-with-the-prototype.
Hope that helps otherwise inform me if I misunderstood your problem.
Related
I want to create a search bar in my navbar, so one can simply search for a user on my page.
I have a base.html.twig, where the navbar, the header, the footer, and more are defined, to make all my pages have the same layout. I have then for every subpage e.g. a profile.html.twig, which defines the actual content of the page.
I wanted to include a search line in the navbar, so I read about embedding controllers, which seemed like a perfect idea for me. So I created a SearchForm class, which builds a form with the FormBuilderInterface like so:
class SearchForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('searched_name')
;
}
}
Then I created a SearchController, which would contain the logic for what should happen when the search form would be submitted and should return a render of the form.
class SearchController extends AbstractController
{
/**
* #param Request $request
*
* #return Response|RedirectResponse
*
* #Route("/search", name="search", methods={"GET","POST"})
*/
public function renderSearch(Request $request) {
$form = $this->createForm(SearchForm::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
return $this->redirectToRoute('ping');
//TODO: Implement search logic
}
return $this->render('searchForm.html.twig', [
'form' => $form->createView()
]);
}
}
I thought, that I now could simply go ahead and let the search form be rendered in the base.html.twig:
<div class="topnav" id="navbar">
Startseite
Übersicht
Über uns
{{ render(url('search')) }}
<a href="/profile" style="float:right">
{% if app.user %}
{{ app.user.username }}
{% else %}
Mein Profil
{% endif %}
</a>
</div>
It actually does get rendered, but the problem is, that as soon as I press Enter to submit the form with the search input, the POST request is not sent to the searchController, but to whichever controller is responsible for handling, let's say, the profile page.
I guess it has to do something with the entire site being rendered there, since I just inherit the base.html.twig in all of my other pages, so the submit requests are sent there. I tested this by calling the search route directly, and that made it work completely fine.
How can I make this formwork on every site without having to reimplement the search logic in every single controller? (That's what this base is made for, after all...)
First of all you could (of course) set the action attribute of the form. Follow this link to find out how: https://symfony.com/doc/4.2/form/action_method.html
But since you show your form on every single page on your website you should ask yourself if you want to redirect the user to an other page after form submit. The answer could be yes or could be no but if it is no then you have the opportunity to use an AJAX submit.
I have created a custom form type - RecaptchaType. I added it into many forms using $builder->add('recaptcha', RecaptchaType::class). For view I have created a macro, so in twig I render it as follows {{ forms.recaptcha(form.recaptcha) }}.
But now I want to display recaptcha only for users who are not logged in. In twig macro I can simple add if condition. But how can I achieve not adding (or removing) recaptcha input in form type if I want to edit only RecaptchaType and don't want to touch form types, which are using this.
I had two ideas, but neither of them did work.
In RecaptchaType use function buildForm and do $builder->remove('recaptcha') when condition is met (user is logged in)
Pass option 'render' to $resolver->setDefaults(), access it in form and depend on value do ->add or not. But I can't access the value.
How would you do that? I can add code example if needed.
TL;DR; To remove a form type conditionally encapsulating this logic within itself, you can do the follows:
RecaptchaType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
if ($this->isAuthenticated()) {
$form = $event->getForm();
$form->getParent()->remove($form->getName());
}
});
}
Now, if you are customizing the form theme make sure to check the existence of the field before render it:
{% if form.recaptcha is defined %}
{{ forms.recaptcha(form.recaptcha) }}
{% endif %}
It should work as long as your RecaptchaType is embedded into another form.
i have a simple form and i want display another form after submitting in the same template twig
mycontroller:
/**
* #Route("/admin/client/modifier", name="modifier")
* #Method({"GET","POST"})
*/
public function modifierAction(Request $request)
{
$form=$this->createFormBuilder()
->add('raisonSocial', TextType::class)
->add('Rechrecher', SubmitType::class)
->getForm();
if($form->isSubmitted() && $form->isValid()){
} ;
return $this->render('myTemplate/Clients/modifierClient.html.twig');
}
You have to adapt your answer to the correct way. I do not remember exactly the correct $request->request->get syntax so check the documentation.
Anyway... I guess you can achieve this by using a render controller inside your twig based on some parameters that come from the post/get of your form submission or via ajax (but you already said you don't know how to do this via ajax).
So let's say you search "test" you should have inside your $request->request->get your parameter test.
you should to something like this in your twig file:
your div where you want to have the form displayed
<div>
{% if app.request.get('your_key') == 'test' %}
{{ render(controller(''AppBundle:Article:recentArticles'')) }}
{% endif %}
</div>
This means that you will need to create yourself a new action in ArticleController called recentArticles where you will have your new form.
You can find more details about this on the symfony documentation here:
https://symfony.com/doc/current/templating/embedding_controllers.html
I have 10 forms in a single page (they are in tabbed pages).
My controller function is massive, in an effort to make the forms modular, I was planning to just show the forms in my controller function and do the POST to their correspondent controller.
Some of these 10 forms are used in other pages, so by changing the action url, it makes it easier to manage them all in separate controllers.
But then, how am I going to show the form errors and prevent the form to reset if it was invalid?
I could achieve this if embedded controllers could redirect. Am I missing another option?
Symfony2 has to have a reason not to allow redirects in embedded controllers, but I wonder why.
Third edit:
Images of the actual forms: http://imgur.com/sIx3sgs,tnZkvqM,ZAP950s,hk45oTW#0
Image 1: Step1 (can only be created once)
Image 2: Step5 (can be created multiple times) You can see the create form and the edit form
Image 3: Step5 ui forms are slided up.
Image 4: Step6 same as Step1
These forms are not required to go in order, the user can create and save step6 without ever needing to create step1.
Second edit:
This isn't a multiple step form. I named them Step1, Step2, etc. for convenience.
Edit:
This is what I have:
class DefaultController extends Controller{
public function processAction(){
$request = $this->getRequest();
/*** FORM 1 ****/
$entity = new Step1();
$form1 = $this->createForm(new Step1Type(), $entity);
if ($request->getMethod() == 'POST'){
$form1->bindRequest($request);
if($form1->isValid()){
return $this->redirect($this->generateUrl('some_link'));
}
}
/***/
//Do the same for other forms
return array(
'form1' => $form1->createView()
//[...to form10]
);
}
}
This is what I would like to have: (I would do it with embedded controllers but you can't redirect)
class DefaultController extends Controller{
public function processAction(){
$request = $this->getRequest();
/*** FORM 1 ****/
$entity = new Step1();
$form1 = $this->createForm(new Step1Type(), $entity);
//change $form1 action url to point to Step1Controller->createAction()
/***/
//Do the same for other forms
return array(
'form1' => $form1->createView()
//[...to form10]
);
}
}
class Step1Controller extends Controller{
public function createAction(){
$request = $this->getRequest();
$entity = new Step1();
$form = $this->createForm(new Step1Type(), $entity);
$form->bindRequest($request);
if($form->isValid()){
//save entity
return $this->redirect($this->generateUrl($getRedirectLinkFromForm));
}
return $this->redirect($this->generateUrl('some_other_link'));
}
}
I came up with 4 solutions that I could think of, in order from best to worst:
AJAX
No redirect in embedded controllers
Save in session posted data / form errors in case form is not valid
Modify Symfony2 internals to allow redirect from an embedded controller
AJAX
This is actually the best as it reduces whole page requests and it's highly modular.
You would render all 10 embedded controllers but each submit button will instead do an ajax post pointed to the correspondant controller, which will only process the correspondent forms and it will only return the view of that specific form.
And you don't need to get fancy on your javascript, as you only need to make the AJAX call when submit is clicked and load the response to the div where the form is.
No redirect in embedded controllers
The problem with this is that if the user presses F5, it will resend the POST data.
Save in session posted data / form errors in case form is not valid
and Modify Symfony2 internals to allow redirect from an embedded controller
These 2 options are less desirable as it creates complexity.
My site I have some content can be voted (+/-). It is working fine now, when all content has its own voter.
Now I'm looking for a way to create a single voting bundle with a entity(votedModel,votedId, user, vote).
Basically the bundle is ready. My problem is how to use it. I'd like to be able to do something like:
class ... extends Controller {
function showAction(Request $request,$id) {
...
$voter=new Voter('myCOntentType',$id,$userid);
...
return $this->render('...', array('voter'=>$voter->getVoter(),...))
}
}
getVoter() would create the voter view.
but I'm stacked how exactly start. I tried to call for the other controller in this manner, but can't create the voter form.
It worked with $voter=$this->forward('VoterbundleNewAction', array('id=>$id,'user'=>$user)->getContent();But this wasn't I had in mind.
I think my approach is all wrong and I may need to do this as service. I cant find my way around.
You can use include or render in your twig template to get other templates' output. So you could create a template (say, voter.html.twig) that contains the HTML for your voting system, and in the Twig, in any place where you need a voter, you could use:
{% include "AcmeVoterBundle:Voter:voter.html.twig" %}
or
{% render "AcmeVoterBundle:Voter:voter" with {"item": item} %}
In the first example, you simply include another template (see also: http://symfony.com/doc/current/book/templating.html#including-other-templates), in the latter situation you actually execute another action method of a controller and the output of that is put into your current template (see also: http://symfony.com/doc/current/book/templating.html#embedding-controllers)