I'm following this tutorial to create a simple OctoberCMS plugin.
Here is content of /acme/demo/components/todo/default.htm page:
{% set tasks = __SELF__.tasks %}
<form data-request="{{ __SELF__ }}::onAddItem" data-request-success="$('#inputItem').val('success')">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Tasks assigned to: {{__SELF__.name}} </h3>
</div>
<div class="panel-body">
<div class="input-group">
<input name="task" type="text" id="inputItem" class="form-control" value=""/>
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">Add</button>
</span>
</div>
</div>
<ul class="list-group" id="result">
{% for task in tasks %}
<li class="list-group-item">
{{ task }}
<button class="close pull-right">×</button>
</li>
{% endfor %}
</ul>
</div>
</form>
And here is the content of /acme/demo/components/Todo.php:
<?php namespace Acme\Demo\Components;
use Cms\Classes\ComponentBase;
use Acme\Demo\Models\Task;
class Todo extends ComponentBase
{
/**
* This is a person's name.
* This variable will be available in the page as a twig variable
* #var string
*/
public $name;
/**
* The collection of tasks.
* #var array
*/
public $tasks;
public function componentDetails()
{
return [
'name' => 'Todo Component',
'description' => 'A database driven TODO list'
];
}
public function defineProperties()
{
return [];
}
public function init()
{
// This will execute when the component is first initialized, including AJAX events.
}
public function onRun()
{
$this->name = 'Meysam';
$this->tasks = Task::lists('title');
}
public function onAddItem()
{
$taskName = post('task');
$task = new Task();
$task->title = $taskName;
$task->save();
}
}
The problem is that onAddItem is never called. It seems that the form is not submitted properly when I add a new item. Does anybody know how I can fix this? I thought maybe the ajax libraries are missing, so I included {% framework %} as well, which again didn't help:
{% set tasks = __SELF__.tasks %}
{% framework %}
<form data-request="{{ __SELF__ }}::onAddItem" data-request-success="$('#inputItem').val('success')">
Please note that my model is working and $this->tasks = Task::lists('title'); returns the list of tasks.
The problem was that I should have included the jquery file as well:
<script src="{{ [
'assets/javascript/jquery.js',
]|theme }}"></script>
{% framework %}
Related
I have a plugin with 2 components. One is for 'posts', and the other is for a 'profile'. The profile belongs to a user and hasMany 'posts'.
However when I access the relationship it loads every post. I just want to load 5 then paginate or lazy load, how can I do this?
Profile.php model
public $hasMany = [
'posts' => [
'Redstone\Membership\Models\Post',
'table' => 'redstone_membership_profiles_posts',
]
];
public $belongsTo = [
'user' => [
'Rainlab\User\Models\User',
]
];
Post.php model
public $belongsTo = [
'profile' => ['Redstone\Membership\Models\Profile',
'table' => 'redstone_membership_profiles_posts',
]
];
Profile.php component
protected function loadProfile()
{
$id = $this->property('profileUsername');
$profile = new UserProfile
$profile = $profile->where(['slug' => $id]);
$profile = $profile->first();
return $profile;
}
profile/default.htm - component view
{% set profile = __SELF__.profile %}
{% set posts = __SELF__.profile.posts %}
{% for post in posts %}
<div class="card">
<div class="card-header">
<div class="ml-2">
<div class="h5 m-0">{{ profile.title }}</div>
</div>
{{ post.published_at|date('M d') }}
</div>
<div class="card-body text-left">
{{ post.content|raw }}
</div>
</div>
{% else %}
<h1>This user has not made any posts.</h1>
{% endfor %}
Well you can either do something with the OctoberCMS pagination service or a php function or you could build a function through twig.
PHP using slice: This is assuming when you call $profile->posts you get a collection of posts. You could also add a query to the Url like example.com/profile?q=15 to change 5 to 10 15 etc I added an if statement to check to make sure the input is numeric and greater than 5.
protected function loadProfile()
{
$id = $this->property('profileUsername');
$profile = UserProfile::where(['slug' => $id])->first();
if (is_numeric(Input::get('q')) == true && Input::get('q') > 5) {
$profile->posts = $profile->posts->slice(0, Input::get('q'));
} else {
$profile->posts = $profile->posts->slice(0, 5);
}
return $profile;
}
Twig using slice: This is done very similar to the PHP way but done in the htm file instead.
PHP -
protected function getQuery()
{
if (is_numeric(Input::get('q')) == true && Input::get('q') > 5) {
return Input::get('q');
} else {
return 5;
}
}
Twig -
{% set query = __SELF__.getQuery %}
{% set profile = __SELF__.profile %}
{% set posts = __SELF__.profile.posts | slice(0, query) %}
{% for post in posts %}
<div class="card">
<div class="card-header">
<div class="ml-2">
<div class="h5 m-0">{{ profile.title }}</div>
</div>
{{ post.published_at|date('M d') }}
</div>
<div class="card-body text-left">
{{ post.content|raw }}
</div>
</div>
{% else %}
<h1>This user has not made any posts.</h1>
{% endfor %}
Privet all!
I'm new to Symfony 4 and generally to PHP and OOP in general. I was helped to make the form using FormCollection the code looks so
AbilityContoller.php
/**
* #Route("/edit", name="ability_edit_all")
*/
public function edit(Request $request)
{
$abilitys = $this->getDoctrine()->getRepository(Ability::class)->findAll();
$form = $this->createForm(AbilityArrayType::class, ['abilitys' => $abilitys]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
foreach ($form->getData()['abilitys'] as $ability){
$this->getDoctrine()->getManager()->persist($ability);
}
$this->getDoctrine()->getManager()->flush();
$this->addFlash('notice', 'Your changes were saved!');
return $this->redirectToRoute('ability_edit_all');
}
return $this->render('ability/edit.all.html.twig', [
'form' => $form->createView()
]);
}
and screenshot
you see that my record with the form has both tags with a choiceType and a simple input
And I also figured out myself with knp_paginator, because if I output more than 15 records with FormCollection, I get an error about the lack of memory in php. I made a static table where I output all my records, sorting and filtering works, pagination is broken as it should.
here is the code
ServiceFile.php
class AbilityService{
protected $em;
protected $container;
public function __construct(EntityManagerInterface $entityManager, ContainerInterface $container)
{
$this->em = $entityManager;
$this->container = $container;
}
public function ReturnData($request){
$em = $this->em;
$container = $this->container;
$query = $em->createQuery(
'
SELECT
t.idItem,
t.code,
t.reloadTime,
t.durationTime,
t.idAbilityType
FROM
App\Entity\Ability t
'
);
dump($query);
//$result = $query->execute();
$pagenator = $container->get('knp_paginator');
$results = $pagenator->paginate(
$query,
$request->query->getInt('page', 1),
$request->query->getInt('limit', 10)
);
return ($results);
}
}
and controller
use App\Service\ServiceFile;
/**
* #Route("/test-edit", name="ability_test_edit_all")
*/
public function edit_test(AbilityService $query, Request $request)
{
$data = $query->ReturnData($request);
return $this->render('ability/edit.alltest.html.twig',[
'form' => $data,
]);
}
here screenshot
Work a perfect!!
I bet I can not figure out how to make the same result, but with FormCollection?
The second question, if it turns out to be pagination, will filtering and sorting work from knp_paginator?
ok,
ok, something happened and it's not right for mine, but Pagination and sorting + filtering work only on <input> tags, inside <select> <option selected> var </ select> nothing works, but the solution is working
controllerFile.php
/**
* #Route("/edit", name="ability_edit_all")
*/
public function edit(AbilityService $abilityService, Request $request)
{
$data = $abilityService->ReturnData($request);
$form = $this->createForm(AbilityArrayType::class, ['abilitys' => $data->getItems()]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
foreach ($form->getData()['abilitys'] as $properties){
dump($properties);
$ability = $this->getDoctrine()->getRepository(Ability::class)->find($properties['id']);
$ability->setIdItem($properties['idItem']);
$ability->setCode($properties['code']);
$ability->setReloadTime($properties['reloadTime']);
$ability->setDurationTime($properties['durationTime']);
$ability->setIdAbilityType($properties['idAbilityType']);
dump($ability);
$this->getDoctrine()->getManager()->persist($ability);
}
$this->getDoctrine()->getManager()->flush();
$this->addFlash('notice', 'Your changes were saved!');//проделать со всеми см edit.html.twig
return $this->redirectToRoute('ability_edit_all');
}
return $this->render('ability/edit.all.html.twig', [
'form' => $form->createView(),
'paginator' => $data
]);
}
twig tempalte
{% extends 'base.html.twig' %}
{% block title %}Ability edit all{% endblock %}
{% block body %}
<style>
label{display:none}
</style>
<h1>Ability edit all</h1>
<a class="btn-add mar-bot-top-20" href="{{ path('ability_new') }}"><i class="fas fa-plus"></i> Create new</a>
<div class="row">
<div class="col-4 text-center">
<div class="navigation">
{{ knp_pagination_render(paginator) }}
</div>
</div>
<div class="col-8 text-left">
<div class="filtration">
{{ knp_pagination_filter(paginator, {
't.idItem': 'IdItem',
't.code': 'Code',
't.reloadTime': 'ReloadTime',
't.durationTime': 'DurationTime',
't.idAbilityType': 'IdAbilityType',
}) }}
</div>
</div>
</div>
<table class="table">
<tr>
<th>{{ knp_pagination_sortable(paginator, 'idItem', 't.idItem') }}</th>
<th>{{ knp_pagination_sortable(paginator, 'Code', 't.code') }}</th>
<th>{{ knp_pagination_sortable(paginator, 'ReloadTime', 't.reloadTime') }}</th>
<th>{{ knp_pagination_sortable(paginator, 'DurationTime', 't.durationTime') }}</th>
<th>{{ knp_pagination_sortable(paginator, 'idAbilityType', 't.idAbilityType') }}</th>
</tr>
{{ form_start(form) }}
{% for ability in form.abilitys %}
<tr>
<td>{{ form_row(ability.idItem) }}</td>
<td>{{ form_row(ability.code) }}</td>
<td>{{ form_row(ability.reloadTime) }}</td>
<td>{{ form_row(ability.durationTime) }}</td>
<td>{{ form_row(ability.idAbilityType) }}</td>
</tr>
{% else %}
<tr>
<td colspan="9">no records found</td>
</tr>
{% endfor %}
{{ form_end(form) }}
</table>
<div class="row">
<div class="col-4 text-center">
<div class="navigation">
{{ knp_pagination_render(paginator) }}
</div>
</div>
<div class="col-8 text-left">
<div class="filtration">
{{ knp_pagination_filter(paginator, {
't.idItem': 'IdItem',
't.code': 'Code',
't.reloadTime': 'ReloadTime',
't.durationTime': 'DurationTime',
't.idAbilityType': 'IdAbilityType',
}) }}
</div>
</div>
</div>
<a class="btn-add" href="{{ path('ability_new') }}"><i class="fas fa-plus"></i> Create new</a>
{% for message in app.flashes('notice') %}
<div class="flash-notice">
{{ message }}
</div>
{% endfor %}
{% endblock %}
and ServiceFile.php
<?php
namespace App\Service;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Ability;
class AbilityService{
protected $em;
protected $container;
public function __construct(EntityManagerInterface $entityManager, ContainerInterface $container)
{
$this->em = $entityManager;
$this->container = $container;
}
public function ReturnData($request){
$em = $this->em;
$container = $this->container;
$query = $em->createQuery(
'
SELECT
t.id,
t.idItem,
t.code,
t.reloadTime,
t.durationTime,
t.idAbilityType,
t.dateCreated,
t.author,
t.dateChange,
t.lastAuthor
FROM
App\Entity\Ability t
'
);
$pagenator = $container->get('knp_paginator');
$results = $pagenator->paginate(
$query,
$request->query->getInt('page', 1),
$request->query->getInt('limit', 6)
);
return ($results);
}
}
if anyone can tell how to implement sorting within <select> <option selected> var </ select>, you can use <th> {{knp_pagination_sortable (paginator, 'idItem', 't.idItem')}} </ th>
write please!
I have the following ajax I call:
$('.checkbox').click(function () {
$.ajax({
type:'POST',
url: '/loadProducts',
data: {},
success: function(response) {
$('.js-products').html(response);
}});
return false;
});
Now when I call my endpoint loadProducts, I wants to get products, render a product template(s) and return all of them.
In my endpoint I could do this:
/**
* #Route("/loadProducts", name="loadProducts")
*/
public function loadProducts() {
/* #var \AppBundle\Service\ProductService $productService */
$productService = $this->get('app.product');
$products = $productService->getProducts();
$productItems = [];
foreach ($products as $product) {
$productItems = $this->render('home/productItem.html.twig', [
'product' => $product
]);
}
$response = new Response();
$response->headers->set('Content-Type', 'application/json');
$response->setContent(json_encode($productItems));
return $response;
}
But that only renders one product. How could I return rendering multiple product.html.twig files? (apart from creating a new template that renders all)
You will get something like this. Your action will start to render products.html.twig which have a loop that renders multiple times product.html.twig
products.html.twig
{% extends base_template %}
{% block body %}
<div class="container">
<div class="row">
{% for product in products %}
<div class="col-md-3">
{% include 'shop/product.html.twig' %}
</div>
{% endfor %}
</div>
</div>
{% endblock %}
product.html.twig
<div class="product">
<img src="somesource.jpg">
<div class="description">
<h3>{{ product.name }}</h3>
<p>{{ product.description }}</p>
</div>
</div>
In just started at symfony and Im trying to do something very simple (seems simple to me) using the form templating system, but I cant find a way to do it.
My goal is to render a "back" button besides the submit button. I know how to set the form template and how to override the submit_widget, but the problem is: the back button URL must be defined at the template which is calling the form, so I need to pass this as a variable to the submit_widget somehow, and I cant find a way to do it.
Ideally, it would work like this:
Template:
{% form_theme form 'TutsAdminBundle:Form:bootstrap-horizontal.html.twig' %}
{% block content %}
<h1>User creation</h1>
{{ form(form, { 'attr': {'role': 'form', 'class': 'form-horizontal'}, 'back': path('list_user') }) }}
{% endblock %}
And then, at the form template
{% block submit_widget %}
<div class="col-sm-6 col-sm-offset-1">
<div class="pull-right">
<a class="btn btn-warning" href="{{ back }}"> Back</a>
<button type="submit" class="btn btn-success">Save</button>
</div>
</div>
{% endblock submit_widget %}
But I just cant find a way to access my "back" variable inside the submit_widget block.
How can I achieve that?
UPDATE:
I managed to do what I wanted by:
1- creating a custom field following Chausser advice bellow:
class SaveButtonType extends SubmitType
{
public function __construct($router)
{
$this->_router = $router;
}//constructor
protected $_router;
/* (non-PHPdoc)
* #see \Symfony\Component\Form\AbstractType::buildForm()
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
}//buildForm
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$href = function(Options $options, $value){
return array('href'=>$this->_router->generate($options['route'], $options['params']), 'class'=>'btm btn-warning');
};
$resolver->setDefaults(array('mapped'=>false))->setRequired(array('route', 'params'));
$resolver->setNormalizers(array('attr'=>$href));
}//setDefaultOptions
public function getName()
{
return 'save_button';
}//getName
}//SaveButtonType
Ive made it extends the SubmitType otherwise it would be rendered inside a form_row.
services.yml
tuts_admin.form.field.type.save_button:
class: Tuts\AdminBundle\Form\Field\SaveButtonType
arguments: ["#router"]
tags:
- {name: "form.type", alias: "save_button"}
2- then I defined its template (on that same file Im using to override the form theme
{% block save_button_widget %}
<div class="col-sm-6 col-sm-offset-1">
<div class="pull-right marginL25">
<button type="submit" class="btn btn-success">Save</button>
</div>
<div class="pull-right">
Back
</div>
</div>
{% endblock %}
This type render the submit and the back button at once.
3- finally, since I could not find a way to prevent the rendering of another submit button, I overrided the submit_widget:
{% block submit_widget %}
{% endblock submit_widget %}
A lot more complicated than I was expecting, but it does works.
Thank you Chausser for all your help.
Personally I would write a Custom Field Type and make if for back buttons. Something that will accept a route name and route params.
//Acme/DemoBundle/Form/Type/BackButtonType
<?php
namespace Acme\DemoBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\OptionsResolver\Options;
class BackButtonType extends AbstractType
{
protected $router;
public function __construct($router)
{
$this->router = $router;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$attr = function(Options $options, $value){
return array(
'href'=>$this->_router->generate($options['route'], $options['params']),
'class'=>'btn btn-warning'
);
};
$resolver
->setDefaults(array(
'attr' => $attr,
'mapped' => false
'params' => array(),
))
->setRequired(array(
'route',
))
->setNormalizers(array(
'attr'=>$attr
));
}
public function getName()
{
return 'back_button';
}
}
Then you need to register this as a new form type:
parameters:
acme_demo.form.type.back_button.class: Acme\DemoBundle\Form\Type\BackButtonType
services:
acme_demo.form.type.back_button:
class: %acme_demo.form.type.back_button.class%
arguments: ["#router"]
tags:
- { name: "form.type", alias: "back_button" }
Now you can use this in your normal forms. Last step is to create the twig block to render this form.
//Acme/DemoBundle/Resources/Twig/fields.html.twig
{% block back_button_widget %}
<a {{block('widget_attributes')}}>{{block('form_label')}}</a>
{% endblock %}
Then you need to add this file to the form resources for twig:
//app/config/config.yml
twig:
debug: %kernel.debug%
strict_variables: %kernel.debug%
form:
resources:
- 'AcmeDemoBunlde:Twig:fields.html.twig'
After you have done that you need to clear your caches:
php app/console cache:clear --env=dev
php app/console cache:clear --env=prod
Now how to use this:
//In your Form
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// Add other fields
->add('back', 'back_button',array('route'=>'my_custom_route_name','params'=>array('user_id'=>$this->getUser()->getUserId())))
->add('submit','submit',array('attr'=>array('class'=>'btn btn-primary')));
}
This should work. Hasnt been fully tested but if you have any issues with it let me know.
I believe that you don't need to pass variable, but only make it available (known) to Twig, since form rendering (as opposed to to extending and including other templates) is just block rendering. That is, those variables are in the same scope.
So:
Parent template
{% set back = "MyFooValue" %}
Form template:
<div class="{{ back }}">Some content</div>
Also, I like defining default as fallback in case I forget to (or do not at all) define variable:
<div class="{{ back|default("") }}">Some content</div>
Hope this helps...
I'm facing a problem that I can summarize as it follows:
I have a TWIG template page like this (reg.html.twig):
{% extends "::base.html.twig" %}
{% block body %}
<ul class="tabs">
<li class="left">tab1</li>
<li class="left">tab2</li>
<li class="left">tab3</li>
<li class="right">tab4</li>
</ul>
<div class="tabs_container">
<div id="tab1" class="blocco-tab">
<form action="{{ path('AAA') }}" method="post" {{ form_enctype(form) }}>
<div id="name_field">
{{ form_row(form.name) }}
</div><!-- /name_field -->
<div id="address">
{{ form_row(form.addresses[0].road) }}
</div><!-- /address_field -->
</form>
</div>
<div id="tab2" class="blocco-tab">
<form action="{{ path('BBB') }}" method="post" {{ form_enctype(form) }}>
<div id="surname_field">
{{ form_row(form.surname) }}
</div><!-- /surname_field -->
</form>
</div>
</div> <!-- contenitore_tabs -->
{% endblock %}
Fields name, surname and addresses belong to a sample Symfony2 entity Person.
addresses is the first and only element of a collection of addresses (I need this as collection for other reasons)
The working JS file is:
jQuery(document).ready(function() {
$(".blocco-tab").hide();
$("ul.tabs li:first").addClass("active").show();
$(".blocco-tab:first").show();
$("ul.tabs li").click(function() {
$("ul.tabs li").removeClass("active");
$(this).addClass("active");
$(".blocco-tab").hide();
var activeTab = $(this).find("a").attr("href");
$(activeTab).fadeIn();
return false;
});
});
The Entity file:
class Person {
protected $name;
protected $surname;
protected $addresses;
public function __construct(){
$this->addresses = new ArrayCollection();
}
}
And in the DefaultController:
public function tab1Action(Request $request){
$person = new Person();
$address = new Address();
$addr_coll = new ArrayCollection();
$addr_coll->add($address);
$tab1_type = new Tab1Type();
$person->setAddresses($addr_coll);
$form = $this->createForm($tab1_type, $person);
if ($request->getMethod() == 'POST')
{
$form->bindRequest($request);
if ($form->isValid())
/*ecc ecc ecc*/
}
public function tab2Action(Request $request){
$person = new Person();
$tab2_type = new Tab2Type();
$form = $this->createForm($tab2_type, $person);
if ($request->getMethod() == 'POST')
{
$form->bindRequest($request);
if ($form->isValid())
/*ecc ecc ecc*/
}
Actually I took the way of having every FormType having all fields that I don't need rendered but put 'hidden' and 'property_path' => false, because I can't render only my desired fields cause the other ones will cause errors at runtime (they're null) , but I still get problems handling both cases in a joined way.
Putting every form in a different page (== different Route), with different Controllers, everything works fine, so it's not a problem related to basic use of symfony, It's the integration of N forms in a single page with JQuery UI that makes me cry.
Fixed that I have to use this tabs, how can I solve?
Do I have to make a single Action handling everything?
Do I have to make a single form?
Do I miss something?
Thanks in advance, I hope I've been clear in explaining my issue.
You just used the same variable for different forms
<div id="tab1" class="blocco-tab">
<form action="{{ path('AAA') }}" method="post" {{ form_enctype(**form1**) }}>
<div id="name_field">
{{ form_row(**form1**.name) }}
</div><!-- /name_field -->
<div id="address">
{{ form_row(**form1**.addresses[0].road) }}
</div><!-- /address_field -->
</form>
</div>
<div id="tab2" class="blocco-tab">
<form action="{{ path('BBB') }}" method="post" {{ form_enctype(**form2**) }}>
<div id="surname_field">
{{ form_row(**form2**.surname) }}
</div><!-- /surname_field -->
</form>
</div>
Try with single form
{% extends "::base.html.twig" %}
{% block body %}
<form action="{{ path('AAA') }}" method="post" {{ form_enctype(form) }}>
<ul class="tabs">
<li class="left">tab1</li>
<li class="left">tab2</li>
<li class="left">tab3</li>
<li class="right">tab4</li>
</ul>
<div class="tabs_container">
<div id="tab1" class="blocco-tab">
<div id="name_field">
{{ form_row(form.name) }}
</div><!-- /name_field -->
<div id="address">
{{ form_row(form.addresses[0].road) }}
</div><!-- /address_field -->
</div>
<div id="tab2" class="blocco-tab">
<div id="surname_field">
{{ form_row(form.surname) }}
</div><!-- /surname_field -->
</div>
</div> <!-- contenitore_tabs -->
</form>
{% endblock %}
Then you have in ocontroller aaaAction()
public function aaaAction(Request $request){
$person = new Person();
$address = new Address();
$addr_coll = new ArrayCollection();
$addr_coll->add($address);
$person->setAddresses($addr_coll);
$form = $this->createForm(new PersonType(), $person);
if ($request->getMethod() == 'POST')
{
$form->bindRequest($request);
if ($form->isValid())
/*ecc ecc ecc*/
}
and class for form builder like
class PersonType extends AbstractType {
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('name', null, array())
->add('surname', null, array())
->add('addresses', null, array())
;
}
public function getName()
{
return 'person';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Acme\YourBundle\Entity\Person',
);
}
}
as I mentioned before I solved wrapping all tabs in a single form. Both of your solutions are ok, thank you for your time.
Linuxatico
In case anyone else has this problem, you can solve it like this (I am not sure if this is the most elegant solution but I think its better than making one big form for everything). This example mixes the profile and changePassword FOSUserBundle forms:
Inside your main show template (Profile:show.html.twig in my case):
{% if profileForm is defined %}
{% include 'MyBundle:Profile:edit.html.twig' with {'form':profileForm} %}
{% else %}
{% render 'MyBundle:Profile:edit' %}
{% endif %}
Repeat for changePassword:
{% if passwdForm is defined %}
{% include 'MyBundle:ChangePassword:changePassword.html.twig' with {'form':passwdForm} %}
{% else %}
{% render 'FOSUserBundle:ChangePassword:changePassword' %}
{% endif %}
In your controllers (add else):
if ($form->isValid()) {
.....
else {
return $this->container->get('templating')->renderResponse('FOSUserBundle:Profile:show.html.'.$this->container->getParameter('fos_user.template.engine'),
array('user' => $user, 'profileForm' => $form->createView()));
}
Add the profileForm and passwdForm accordingly. In my case the modified controllers were the ProfileController and ChangePasswordControllers (FOSUserBundle overrides).
As for your tabs, you can add javascript (or twig) to open the tab if any error is found.
Hope this helps :)