I have a city and voters entity in a one to many relationships.I want to convert the entity field(thousands of records in a dropdown) to a text input so that I can implement the JQuery autocomplete when users start typing 2 letters.After almost two weeks,I successfully created the DataTransformer which transform entity field in to a text input.Now my problem is I am still learning the JQuery/Ajax and I am confused how to implement it in Symfony2 forms.
//formtype.php
private $entityManager;
public function __construct(ObjectManager $entityManager)
{
$this->entityManager = $entityManager;
}
$builder
->add('address', null, array(
'error_bubbling' => true
))
->add('city', 'text', array(
'label' => 'Type your city',
//'error_bubbling' => true,
'invalid_message' => 'That city you entered is not listed',
))
$builder->get('city')
->addModelTransformer(new CityAutocompleteTransformer($this->entityManager));
//datatransformer.php
class CityAutocompleteTransformer implements DataTransformerInterface
{
private $entityManager;
public function __construct(ObjectManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function transform($city)
{
if (null === $city) {
return '';
}
return $city->getName();
}
public function reverseTransform($cityName)
{
if (!$cityName) {
return;
}
$city = $this->entityManager
->getRepository('DuterteBundle:City')->findOneBy(array('name' => $cityName));
if (null === $city) {
throw new TransformationFailedException(sprintf('There is no "%s" exists',
$cityName
));
}
return $city;
}
}
//controller.php
public function createAction(Request $request)
{
$entity = new Voters();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
$validator = $this->get('validator');
$errors = $validator->validate($entity);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
$this->addFlash('danger', 'You are successfully added!, Welcome to the growing Supporters, dont forget to share and invite this to your friends and relatives, click share buttons below, have a magical day!');
//return $this->redirect($this->generateUrl('voters_show', array('id' => $entity->getId())));
return $this->redirect($this->generateUrl('voters_list'));
} else {
$this->addFlash('danger', 'Oppss somethings went wrong, check errors buddy!');
return $this->render('DuterteBundle:Voters:neww.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
}
/**
* Creates a form to create a Voters entity.
*
* @param Voters $entity The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(Voters $entity)
{
$entityManager = $this->getDoctrine()->getManager();
$form = $this->createForm(new VotersType($entityManager), $entity, //here i passed the entity manager to make it work
array(
'action' => $this->generateUrl('voters_create'),
'method' => 'POST',
));
$form->add('submit', 'submit', array(
'label' => 'I Will Vote Mayor Duterte'
));
return $form;
}
With this code, I can successfully create a new voter and will throw validation errors(invalid_message in formtype) when a user entered a city name that does not match those already saved in the database.What I am lacking now is I want to implement the JQuery autocomplete when the user type enter at least two letters
The Twig part
//twig.php
{{ form_start(form, {attr: {novalidate: 'novalidate'}} ) }}
{{ form_errors(form) }}
{{ form_row(form.comments,{'attr': {'placeholder': 'Why You Want '}}) }}
{{ form_row(form.email,{'attr': {'placeholder': 'Email is optional, you may leave it blank.But if you want to include your email, make sure it is your valid email '}}) }}
{{ form_end(form) }}
As you can see, the form itself is consist of many fields, aside from the city field.Here, the city field is a dropdown consists of more than one thousand entries from database.I can successfully convert this dropdown into textfield by using DataTransformer.So the problem here is how to implement JQuery Autocomplete inside this form with many fields.
Any help is appreciated
Update
Based on user Frankbeen's answer, I Added an action inside my controller
public function autocompleteAction(Request $request)
{
$names = array();
$term = trim(strip_tags($request->get('term')));
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('DuterteBundle:City')->createQueryBuilder('c')
->where('c.name LIKE :name')
->setParameter('name', '%'.$term.'%')
->getQuery()
->getResult();
foreach ($entities as $entity)
{
$names[] = $entity->getName()."({$entity->getProvince()})";
}
$response = new JsonResponse();
$response->setData($names);
return $response;
}
And also the js file
{% block javascripts %}
{{ parent() }}
<script src="//code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<script>
$(function() {
function log( message ) {
$( "<div>" ).text( message ).prependTo( "#log" );
$( "#log" ).scrollTop( 0 );
}
$( "#project_bundle_dutertebundle_voters_city").autocomplete({
source: "{{ path('city_autocomplete') }}",
minLength: 2,
select: function( event, ui ) {
log( ui.item ?
"Selected: " + ui.item.value + " aka " + ui.item.id :
"Nothing selected, input was " + this.value );
}
});
});
</script>
{% endblock %}
In this case, the
$( "#project_bundle_dutertebundle_voters_city").autocomplete({
part is actually the default id of city field provided by Symfony2 when rendering the form.The JQuery autocomplete now is working , but the problem is , I cannot save the selected option, the invalid_message validation I created inside FormType.php is triggered as well as the JQuery script when submit button is clicked
Selected: Basista (Pangasinan Province) aka undefined
which tells that the Id of the selected value is undefined
$( "#project_bundle_dutertebundle_voters_city").autocomplete({
source: "{{ path('city_autocomplete') }}",
minLength: 2,
select: function( event, ui ) {
log( ui.item ?
"Selected: " + ui.item.value + " aka " + ui.item.id ://this throw undefined
"Nothing selected, input was " + this.value );
}
});
First you have to start creating a route and action that returns json data. JQuery's autocomplete remote gives you a $_GET variabele with the index 'term' and wants to receive JSON back. Here is an example that uses an Entity with the name City and a property $name
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
/**
* City controller.
*
* @Route("/city")
*/
class CityController extends Controller
{
/**
* @Route("/autocomplete", name="city_autocomplete")
*/
public function autocompleteAction(Request $request)
{
$names = array();
$term = trim(strip_tags($request->get('term')));
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('AppBundle:City')->createQueryBuilder('c')
->where('c.name LIKE :name')
->setParameter('name', '%'.$term.'%')
->getQuery()
->getResult();
foreach ($entities as $entity)
{
$names[] = $entity->getName();
}
$response = new JsonResponse();
$response->setData($names);
return $response;
}
}
Secondary you can make a twig view just like the source from jQuery's autocomplete. The only difference is the source variable in the autocomplete() function . There you have to specify te twig's path() function with your route key eg city_autocomplete.
(This view needs another route and another (normal) action.)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>jQuery UI Autocomplete - Remote datasource</title>
<link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css">
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
<link rel="stylesheet" href="/resources/demos/style.css">
<style>
.ui-autocomplete-loading {
background: white url("images/ui-anim_basic_16x16.gif") right center no-repeat;
}
</style>
<script>
$(function() {
function log( message ) {
$( "<div>" ).text( message ).prependTo( "#log" );
$( "#log" ).scrollTop( 0 );
}
$( "#birds" ).autocomplete({
source: "{{ path('city_autocomplete') }}",
minLength: 2,
select: function( event, ui ) {
log( ui.item ?
"Selected: " + ui.item.value + " aka " + ui.item.id :
"Nothing selected, input was " + this.value );
}
});
});
</script>
</head>
<body>
<div class="ui-widget">
<label for="birds">Birds: </label>
<input id="birds">
</div>
<div class="ui-widget" style="margin-top:2em; font-family:Arial">
Result:
<div id="log" style="height: 200px; width: 300px; overflow: auto;" class="ui-widget-content"></div>
</div>
</body>
</html>
And finaly you can slightly change this view and use your own form.
Finally, after digging deeper in to my Symfony code, I finally found the solution.Using the code provided by user Frankbeen, I add some 'tweak' inorder for the JQuery to finally work.The culprit is in the controller.
$names[] = $entity->getName()."({$entity->getProvince()})";
The city entity is related to province entity in one to many relationship.Since the city entity has thousands of name(records) chances are some values will have same name,so appending related province is useful to avoid confusions in users
San Francisco(Russia Province), San Francisco(Chinese Province),San Francisco(Portugal Province)
Now since names where now 'different' in the names already saved in database, the invalid_message validation will trigger the errors.My solution is to 'clean' the submitted data by removing the appended provinces before comparing the user submitted values to the values in the database. Inside the DataTransformer, I added some code
public function reverseTransform($cityNameConcat)
{
$cityName = preg_replace("/\([^)]+\)/", "", $cityNameConcat);
if (!$cityName) {
return;
}
$city = $this->entityManager
->getRepository('DuterteBundle:City')->findOneBy(array('name' => $cityName));
if (null === $city) {
throw new TransformationFailedException(sprintf('There is no "%s" exists',
$cityName
));
}
return $city;
}
Now the JQuery is finally working and saving to database is also successful.And lastly,
$( "#project_bundle_dutertebundle_voters_city").autocomplete({
source: "{{ path('city_autocomplete') }}",
minLength: 2,
select: function( event, ui ) {
log( ui.item ?
"Selected: " + ui.item.value + " aka " + ui.item.id ://this throw undefined
"Nothing selected, input was " + this.value );
}
});
Is changed to
<script>
$(function() {
function log( message ) {
$( "<div>" ).text( message ).prependTo( "#log" );
$( "#log" ).scrollTop( 0 );
}
$( "#project_bundle_dutertebundle_voters_city").autocomplete({
source: "{{ path('city_autocomplete') }}",
minLength: 2,
select: function( event, ui ) {
log( ui.item ?
"Selected: " + ui.item.value + " aka " + ui.item.label:
"Nothing selected, input was " + this.value );
$("#project_bundle_dutertebundle_voters_city").val(ui.item.label);
$("#project_bundle_dutertebundle_voters_city").val(ui.item.value);
return false;
},
change: function( event, ui ) {
$( "#project_bundle_dutertebundle_voters_city" ).val( ui.item? ui.item.value : 0 );
}
});
});
</script>
Now the 'Undefined' error is gone.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With