How to use jQuery Tag-it plugin with a Symfony form

This is Part 4 in a series on setting up Tags in a ManyToMany association with an Entity in Symfony. You can read the previous three articles here:

  1. Managing Tags in ManyToMany association with Symfony
  2. Setting up Form Collection for Tags in Symfony
  3. Using a Data Transformer in Symfony to handle duplicate tags

By now, you should have your Product and Tag entities set up, along with your forms, and a data transformer for preventing duplicate tags from being added to the database. At the end of the previous article, we also created a controller which renders the submitProduct.html.twig template that we’ll be using for displaying our form to the world.

As mentioned in the previous article, we are going to need to add some JavaScript to add/remove tag.name  inputs for our form. There are multiple ways you can do this, and the Symfony documention on Allowing “new” Tags with the “Prototype” shows you a good example.

In this article, we will be using the jQuery Tag-it plugin, which allows you to have a tag input box on your page which works something like this:

    You can read Tag-it’s documentation for more information on it. For this tutorial, go ahead and download/unzip Tag-it, and copy these files:

    css/jquery.tagit.css
    css/tagit.ui-zendesk.css
    js/tag-it.min.js

    Into your Symfony web assets folder:

    web/css/jquery.tagit.css
    web/css/tagit.ui-zendesk.css
    web/js/tag-it.min.js

    Make sure you also have jQuery and jQuery-ui included in your template, as they are required by Tag-it.

    Ok, now how to set up this plugin with our Symfony form?

    Remember in the Symfony example using prototype, that you are replacing __name__ with the index of the next item in the array:

    product[tags][__name__][name]

    Using a find replace on __name__ you can create new inputs for your form:

    The way that Tag-it works is to add a hidden form input for each tag. It has a fieldName option which allows you to set the name you want to use for your inputs. You might think of doing something like this there:

    That will actually work to add your tags, but will not play nicely when you later remove a tag from the middle of a group of tags associated with a Product, and add another to it for example. The index of the item in the array needs to be set in the field name, so that it knows exactly which item to remove/add.

    So how to increment the field name’s index in Tag-it?

    In the Tag-it documentation, you will notice there is a beforeTagAdded event. We can use this to increment our index, and reset the Tag-it fieldName value each time a new tag is added. Here is a basic example:

    Test that by typing 3 new tags into your Tag-it input box: car, Japanese, import

    And you’ll see that Tag-it adds the hidden inputs to the form just we as we want them:

    If you prefer not to hand code the field name value in, but want to use Symfony’s form prototype instead, you could do something like this:

    Notice the {% do form.tags.setRendered %} added in the first line there. You will need this if you are using Symfony’s {{ form_end(form) }} to end your form. The reason is that form_end will output anything it thinks is missing from your form. It will believe that 'tags' are missing since we are actually creating the tag inputs ourselves. So we tell it that form.tags  are already rendered to prevent that.

    Another option would be to just close the form yourself:  </form> . But don’t forget to render the CSRF token (or anything else your form requires) in that case.

    The final result of your form might look something like this (using Bootstrap here):

    If you want to edit the tags of an existing Product, you could pass that Product to the view in your controller, and then output all of its tags inside the unordered list like so:

    The final result is a setup which meets the requirements outlined in the first article of this tutorial.

    I hope that this has given you some ideas on ways you can manage tags for your entities in a ManyToMany association in Symfony. If you have any questions or suggestions, please leave them in the comments!

    Using a Data Transformer in Symfony to handle duplicate tags

    This is Part 3 in a series on setting up Tags in a ManyToMany association with an Entity in Symfony. You can read the previous two articles here:

    1. Managing Tags in ManyToMany association with Symfony
    2. Setting up Form Collection for Tags in Symfony

    In the last article, I talked about the problem of duplicate Tags being added when creating a new Product. Here, I will show one way to handle that using a Data Transformer.

    First, we will create the data transformer:

    Your data transformer implements the DataTransformerInterface, which requires these two methods:

    transform  allows you to alter the data going to your form. We don’t need to change this, so can just return the tags as is.

    reverseTransform  allows you to alter the data submitted by your form. This is where we want to make our changes.

    In the constructor, the ObjectManager  is injected for checking a Tag’s existence in the database.

    In the reverseTransform  method, an empty ArrayCollection is created to store all tags we’ll be returning. For each submitted Tag, it checks for its presence in the Tag repository. If present, we replace the form submitted Tag with the one already present in the Tag repository. If not, then we allow the new form submitted Tag into the $tagCollection .

    Finally, the $tagCollection  is returned.

    You can do any other filtering you like there. But for the purposes of this tutorial, we’ll keep it simple like that.

    The next thing we need to do is make a few changes to our ProductType:

    We’ll be using the ObjectManager  and our newly created TagsToCollectionTransformer :

    The ObjectManager  is injected in the constructor. (We’ll create a service for our ProductType class later)

    We also get 'tags'  from our form builder, add our transformer to it using the addModelTransformer  method, and pass in the  ObjectManager  dependency to our TagsToCollectionTransformer .

    Finally, we create a service for our ProductType  class which injects the ObjectManager  dependency to it:

    Our form is now ready to roll.

    Let’s create a controller for submitting new Products:

    This should be pretty self-explanatory if you’re familiar with controllers, routes, form submissions, and persisting objects to the database in Symfony. Basically we just create our form with a new Product  object, and have the form handle the Request. If a form was submitted, and is valid, we persist the Product to the database. Finally, we render the submitProduct template which will be created later.

    Note that while it’s not actually required to check $form->isSubmitted() , it is recommended per the Symfony best practices on form submissions for readability:

    Second, we recommend using $form->isSubmitted() in the if statement for clarity. This isn’t technically needed, since isValid() first calls isSubmitted(). But without this, the flow doesn’t read well as it looks like the form is always processed (even on the GET request).

    The last thing to do is create the Twig template submitProduct.html.twig which will display our form. Since we’ll be adding and removing Tags from Products, we’ll need some JavaScript to add/remove our tag.name  inputs for us. You could accomplish this using the example in the Symfony documentation, but in the next article, we’ll be using the jQuery Tag-it plugin instead.

    Next article: How to use jQuery Tag-it plugin with a Symfony form

    Setting up Form Collection for Tags in Symfony

    This is the second article in a series on setting up a ManyToMany association for tags in Symfony. You can read the previous article where we created our Product and Tag entities here:

    1. Managing Tags in ManyToMany association with Symfony

    Now that our entities have been created, it’s time to create our forms. Basically, what we want here is a Product submission form where you can add/remove a collection of Tags which will also be associated to our Product. First, let’s create a Tag form which will be added to our Product form later to collect tags.

    We only need a 'name'  for each tag.

    Next, we’ll create the Product form:

    We add 'tags'  here to the builder as a CollectionType , and set 'entry_type'  to TagType::class . That is the TagType form class we created above in TagType.php, with ::class to get the fully qualified class name.

    We set  'allow_add'  and 'allow_delete'  to  true  because we want to be able to add and delete the associations of Tags to each Product.

    Finally, I set 'required' => false , because I want adding Tags to be optional.

    We haven’t gotten to the point of setting up anything in our Controller or templates yet, but I want to show you what happens now if we leave things the way they are so far in this example.

    Currently, if you added one Product (BMW) with the following tags:

    new product BMW

    When you check your database, you would see this in your tables:

    product

    Product table

    product_tag

    product_tag table

    tag:

    tag table

    Great. Now imagine adding another Product (Honda) with a car tag again, and see what happens:

    add product Honda

    Check your tables and you will find:

    product

    product table 2

    product_tag

    product_tag table 2

    tag

    tag table 2

    As you can see, it is adding duplicate Tags to our tables. However, rather than adding an unnecessary duplicate car tag (tag.id: 3), we want the Honda (product.id: 2) to be associated to the original car tag (tag.id: 1) in the product_tag table. So how can we accomplish this?

    The method which I decided on was to setup a Data Transformer which checks if a tag already exists in the database. If so, it replaces the tag submitted by the form with the one already in the database. This way, the Product will be associated with the original tag in the database, and no new duplicate tag will be added.

    I cover this in the next article: Using a Data Transformer in Symfony to handle duplicate tags

    Managing Tags in ManyToMany association with Symfony

    Symfony: v3.1.3
    Doctrine: v2.5.4

    One of the things I found challenging when first working with Symfony was form collections. The Symfony documentation on how to embed a collection of forms as well as how to work with Doctrine associations / relations is a great place to get started. However, checking out other use cases people shared online really helped as well, so I’m going to share another example here which I hope might help others.

    For the following example, I will be setting up a Product entity, and a Tag entity. One Product can have multiple Tags, and one Tag can have multiple Products (Many To Many). Here’s how I want it to work:

    • New Tags can be added when adding/editing a Product.
    • No duplicate Tags should be added to the tag table.
    • If this Product is deleted later, any Tag which was added with it should remain in the database.
    • Use the jQuery Tag-it plugin to add the tags to our form for submission.

    Ok, let’s get started with our Product entity:

    Then, the Tag entity:

    A quick note about this line in my Product.php file:

    @ORM\ManyToMany(targetEntity="Tag", inversedBy="products", cascade={"persist"})

    I decided to pick Product as the owning side in this association, as Tags will only be created when a Product is added or edited. As you can see in the Doctrine documentation you can choose the owning side in a ManyToMany association. You do so by setting “inversedBy” to it, while setting “mappedBy” to the inverse side.

    targetEntity  is the class which is your target: Tag

    inversedBy  is the property used in the Tag entity for Products (line 27 in Tag.php): products

    cascade={"persist"}  will automatically persist any Tags associated to this Product.

    As far as the Tag.php file goes, you set  Product  as its targetEntity , and since Tag will be the inverse side in this association, you set  mappedBy  to the property in the Product entity used for Tags (line 27 in Product.php): tags

    With that setup, you can run this to generate your getters and setters:

    $ php bin/console doctrine:generate:entities AppBundle

    The final result of both entities should look like this:

    Product.php

    Tag.php

    Note that both the $tags  and $products  properties need to be set as an ArrayCollection in the constructor. This is explained in the Symfony documentation linked to in the beginning of this article on form collections.

    Last but not least, we need to update our database by running:

    $ php bin/console doctrine:schema:update –force

    If all goes well, then you should see a friendly little message like this afterwards:

    Doctrine schema update

    You should now have 3 tables added to your database:

    phpMyAdmin screenshot

    We will setup our forms in the next article: Setting up Form Collection for Tags in Symfony