If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeThe autocomplete setup works nicely on the edit page. But, if you click to create an article... it looks like it's working, but it's not! This is just the normal autocomplete from my browser.
There's no JavaScript error and we do have the class and the data-
attribute. We expected this: we just... haven't added the JavaScript to this page!
In edit.html.twig
, the javascripts
and stylesheets
blocks bring in the magic. Let's solve this in the simplest way possible. Copy both of these blocks. Open new.html.twig
and paste! Oh, and I mentioned earlier, that we're going to eventually tweak things so that the author field is only filled in on create: we're going to disable it on edit.
... lines 1 - 2 | |
{% block javascripts %} | |
{{ parent() }} | |
<script src="https://cdn.jsdelivr.net/autocomplete.js/0/autocomplete.jquery.min.js"></script> | |
<script src="{{ asset('js/algolia-autocomplete.js') }}"></script> | |
{% endblock %} | |
{% block stylesheets %} | |
{{ parent() }} | |
<link rel="stylesheet" href="{{ asset('css/algolia-autocomplete.css') }}"> | |
{% endblock %} | |
... lines 15 - 23 |
That means... we won't need any of this stuff on the edit page. Let's delete it now. But, if you did need some JavaScript and CSS on both templates and you did not want to duplicate the blocks, you could create a new template, like article_admin_base.html.twig
. It would extend content_base.html.twig
and include the javascripts
and stylesheets
blocks. Then, edit.html.twig
and new.html.twig
would extend this.
Anyways, now that the JavaScript and CSS live in the new template, when we refresh, we have autocomplete.
Before we move on, I have one more cool thing I want to show you! And, it solves a real problem... just not a problem we realize we had yet. Close a few files then go to UserSelectTextType
. The whole autocomplete system works because we are setting the attr
option with class
and data-autocomplete-url
keys. Now open ArticleFormType
where we use this field type. One of the things that we're allowed to do here is override that attr
option. But, if we did that, our custom attr
option would completely replace the attr
default from the type class! In other words, we would lose all of the special attributes that we need!
To fix this, at the bottom of UserSelectTextType
, go to the Code -> Generate menu, or command+N on a Mac, select Override methods and choose buildView()
. Oh, there's also a method called finishView()
and its purpose is almost identical to buildView()
- it's just called a bit later.
... lines 1 - 14 | |
class UserSelectTextType extends AbstractType | |
{ | |
... lines 17 - 48 | |
public function buildView(FormView $view, FormInterface $form, array $options) | |
{ | |
... lines 51 - 57 | |
} | |
} |
Here's what's going on: to render each field, Symfony creates a bunch of variables that are used in the form theme system. We already knew that: in register.html.twig
we're overriding the attr
variable. And in our form theme blocks, we use different variables to do our work.
And, of course, we know that, thanks to the profiler, we can see the exact view variables that exist for each field. But... where do these variables come from? For example, why does each field have a full_name
variable? Who added that?
The answer is buildView()
: Symfony calls this method on every field, and it is the place where these variables are created and can be changed.
We do that with this $view
variable, which is kind of a strange object. Start with $attr = $view->vars['attr'];
. This $view
object has a public ->vars
array property that holds all of the things that will eventually become the "variables". At this moment, the core form system has already set this variable up for us: it will either be equal to the attr
option passed for this field, or an empty array.
... lines 1 - 48 | |
public function buildView(FormView $view, FormInterface $form, array $options) | |
{ | |
$attr = $view->vars['attr']; | |
... lines 52 - 57 | |
} | |
... lines 59 - 60 |
Next: grab the class: if class
is set on $attr
, use it, but add a space on the end. If there is no class yet, set this to be blank. Now, here's the key: let's always append js-user-autocomplete
: that's the class we're using above. Call $attr['class'] =
to set the new class string back on.
... lines 1 - 48 | |
public function buildView(FormView $view, FormInterface $form, array $options) | |
{ | |
... line 51 | |
$class = isset($attr['class']) ? $attr['class'].' ' : ''; | |
$class .= 'js-user-autocomplete'; | |
$attr['class'] = $class; | |
... lines 56 - 57 | |
} | |
... lines 59 - 60 |
Oh, and we also need to add the data-autocomplete-url
attribute. Copy that from above and say $attr['data-autocomplete-url']
equals the generated URL. Perfect! Finally, set all of this back onto the view object with $view->vars['attr'] = $attr
.
... lines 1 - 48 | |
public function buildView(FormView $view, FormInterface $form, array $options) | |
{ | |
... lines 51 - 55 | |
$attr['data-autocomplete-url'] = $this->router->generate('admin_utility_users'); | |
$view->vars['attr'] = $attr; | |
} | |
... lines 59 - 60 |
Phew! We're done! Now that we're setting the attr
variable directly, we don't need to set the option anymore. And the best part is that we know our attributes will be rendered no matter what the user passes to the attr
option.
Let's try it! Move over, refresh and cool! Nice work team! The element still has the attributes we need.
Oh, and open the profiler for this form. Click on the author field and check out the View Variables. So cool! That's exactly what we set!
Next: the form component has a crazy powerful plugin system. Want to make some tweak to every form or even every field in your entire app? That's possible, and it's fun!
On the Edit page I am getting this error: Can only be used with user objects.
\Form\DataTransformer\FormToUserTransformer.php (line 32)
`if (!$value instanceof User) {
throw new \LogicException('Can only be used with user objects.');
}`
Hey Farry7,
Check your FormToUserTransformer, you wether apply that data transformer to a wrong entity, or it does not allow null probably. To know for sure, try to debug the $value, you can add "dd($value);" before throwing the exception - that will give you some hint about the value of that variable. Or maybe you just forgot to use the correct namespace for your User entity in FormToUserTransformer :)
I hope this helps!
Cheers!
Hi,
when i try to insert any parameter (for exemple i need RouterInterface $router) in the Article Constructor (my Article is Gruppi), i get the message
Too few arguments to function App\Form\Form\Gruppi\GruppiType::__construct(), 0 passed in C:\Users\Giacomo\Documents\GitHub\fgcweb\vendor\symfony\form\FormRegistry.php on line 91 and exactly 2 expected
I think this come from the controller's line:
$form = $this->createForm(GruppiType::class, $gr);
Now, there is a way to use RouterInterface from inside Gruppi without get that error, so i can set:
'data-autocomplete-url' => $this->router->generate('get_group_fields');
without the need to create a new form and use it in Gruppi's buildForm?
If i instance $this->router = RouterInterface::class; i
inside the constructor, the error is solved but i get:
Call to a member function generate() on string
in $attr['data-autocomplete-url'] = $this->router->generate('get_group_fields')
Reading docs i did:
`
$route = new Route('/gr/utility/gruppi', ['_controller' => GruppiController::class]);
$routes = new RouteCollection();
$routes->add('get_group_fields', $route);
$context = new RequestContext('/');
$generator = new UrlGenerator($routes, $context);
$url = $generator->generate('get_group_fields');
`
but it's very verbose and the output is:
data-autocomplete-url="//gr/utility/gruppi"
Any suggestion for a shorted and correct solution?
Thanks a lot!
Hey Giacomo V.
Which version of Symfony are you using? If you are using auto-wire and auto-configure features, then you should be able to just type-hint your services in the Form class and Symfony should do the rest (https://symfony.com/doc/cur... ). If not, you will have to define such Form class as a service
Cheers!
Hello there ,
This error occurs when saving the form.
Error: <blockquote>The choices "single" do not exist in the choice list.</blockquote>
I don't understand why I can't define the choices in the buildView method
Where can I do it? So that on the parent form I don't have to do it. Choices will be generated based on an option in the parent form called "entity_name"
namespace App\Form\Type;
use App\Service\Theme;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TemplateType extends AbstractType
{
private $theme;
public function __construct(Theme $theme)
{
$this->theme = $theme;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'expanded' => true,
'multiple' => false,
'required' => true,
'label' => false,
'entity_name' => '',
'default_template' => ''
]);
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
$entityName = $options[ 'entity_name' ];
$view->vars = array_replace($view->vars, [
'entity_name' => $entityName,
'default_template' => $options[ 'default_template' ],
'choices' => $this->theme->templatesByEntity($entityName),
]);
}
public function getParent(): string
{
return ChoiceType::class;
}
}
Ohh I see now why it's difficult, you are extending from ChoiceType
and no adding any new fields. Hmm, what you are doing seems OK but since you are relying on the "field variables" I think you need to create a ChoiceView
item per each option and put them onto an array. Check the "choices" definition here:https://symfony.com/doc/current/reference/forms/types/choice.html#field-variables
I haven't done that before so I might be wrong
Another thing you can try is to define the "choices" inside configureOptions()
method as you did to: expanded, multiple, etc
In configureOptions this is not possible, because the list depends on a newly defined option called 'entity_name'.
This is the parent form field:
.....
$builder->add('template', TemplateType::class, array(
'entity_name' => 'blog',
'default_template' => 'single.html.twig'
));
Have you tried adding the choices in there?
$builder->add('template', TemplateType::class, array(
'entity_name' => 'blog',
'default_template' => 'single.html.twig',
'choices' => $arrayWithYourChoices
));
Yeea, it's kind of the diffcult what you are trying to achieve. Did you try to create your own ChoiceView
list and use it in the buildView()
method? If that doesn't work I think the answer to your problem may be managed via FormEvents https://symfony.com/doc/current/form/events.html
BTW, when I am trying to put in twig this
`
{{ form_row(articleForm.author, {
attr: {class: 'the-author'}
}) }}
`
I am getting broken autocomplete. I mean it won't work. Still I have correctly appended the needed class...
Hey MASQUERADE promotion!
Do you get any JavaScript errors? And when you inspect your HTML, do you see the class being printed on your element? Finally, in our code, we added the class js-user-autocomplete
- but it looks like you're adding a class called the-author
- is that in purpose?
Cheers!
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
"knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
"knplabs/knp-time-bundle": "^1.8", // 1.8.0
"nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
"php-http/guzzle6-adapter": "^1.1", // v1.1.1
"sensio/framework-extra-bundle": "^5.1", // v5.2.1
"stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
"symfony/asset": "^4.0", // v4.1.6
"symfony/console": "^4.0", // v4.1.6
"symfony/flex": "^1.0", // v1.17.6
"symfony/form": "^4.0", // v4.1.6
"symfony/framework-bundle": "^4.0", // v4.1.6
"symfony/orm-pack": "^1.0", // v1.0.6
"symfony/security-bundle": "^4.0", // v4.1.6
"symfony/serializer-pack": "^1.0", // v1.0.1
"symfony/twig-bundle": "^4.0", // v4.1.6
"symfony/validator": "^4.0", // v4.1.6
"symfony/web-server-bundle": "^4.0", // v4.1.6
"symfony/yaml": "^4.0", // v4.1.6
"twig/extensions": "^1.5" // v1.5.2
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
"easycorp/easy-log-handler": "^1.0.2", // v1.0.7
"fzaninotto/faker": "^1.7", // v1.8.0
"symfony/debug-bundle": "^3.3|^4.0", // v4.1.6
"symfony/dotenv": "^4.0", // v4.1.6
"symfony/maker-bundle": "^1.0", // v1.8.0
"symfony/monolog-bundle": "^3.0", // v3.3.0
"symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.6
"symfony/profiler-pack": "^1.0", // v1.0.3
"symfony/var-dumper": "^3.3|^4.0" // v4.1.6
}
}
Not sure why but adding the `javascripts` block to the edit or new template make the debug bar disappear into thin air
Console shows the following error:
```js
Uncaught ReferenceError: Sfjs is not defined
at new:89:44359
at new:89:44392
```