web-dev-qa-db-fra.com

Ajout de la saisie semi-automatique JQuery dans le champ Entité Symfony2

Je souhaite convertir le champ entité (des milliers d'enregistrements dans une liste déroulante) en une entrée de texte afin que je puisse implémenter la saisie semi-automatique JQuery lorsque les utilisateurs commencent à taper 2 lettres.Après presque Deux semaines plus tard, j'ai créé avec succès DataTransformer, qui transforme le champ d'entité en une entrée de texte. Maintenant, mon problème est que j'apprends encore le JQuery/Ajax et je ne sais pas comment l'implémenter dans les formulaires Symfony2.

//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;
}

Avec ce code, je peux créer avec succès un nouveau votant et jetterai des erreurs de validation (invalid_message dans le type de formulaire) lorsqu'un utilisateur a entré un nom de ville qui ne correspond pas à ceux déjà enregistrés dans la base de données. Ce qui me manque maintenant, c’est que je souhaite implémenter le JQuery autocomplete lorsque le type d'utilisateur saisit au moins deux lettres 

La partie brindille

//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) }}    

 enter image description here

Comme vous pouvez le constater, le formulaire lui-même est constitué de nombreux champs, à part le champ ville. Ici, le champ ville est une liste déroulante composée de plus de mille entrées de la base de données. Je peux convertir avec succès ce menu déroulant en champ de texte à l'aide de DataTransformer. Le problème ici est donc de savoir comment implémenter JQuery Autocomplete dans ce formulaire avec de nombreux champs.

Toute aide est appréciée

Mettre à jour

Basé sur la réponse de l'utilisateur Frankbeen, j'ai ajouté une action à l'intérieur de mon contrôleur

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;
}

Et aussi le fichier js

{% 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 %}

Dans ce cas, le 

  $( "#project_bundle_dutertebundle_voters_city").autocomplete({

la partie est en réalité l'identifiant par défaut du champ ville fourni par Symfony2 lors du rendu du formulaire. La saisie semi-automatique JQuery fonctionne à présent, mais le problème est que je ne peux pas enregistrer l'option sélectionnée, la validation invalid_message que j'ai créée dans FormType.php est déclenchée, ainsi que le script JQuery lorsque vous cliquez sur le bouton d'envoi

Sélectionné: Basista (Province du Pangasinan) dit non défini

qui indique que l'ID de la valeur sélectionnée est indéfini

$( "#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 );
            }
        });
8

Tout d'abord, vous devez commencer à créer une route et une action qui renvoie les données JSON. La télécommande semi-automatique de JQuery vous donne une variabele $ _GET avec l'index ' term ' et souhaite recevoir le retour JSON. Voici un exemple qui utilise une entité portant le nomVilleet une propriété$ 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;
    }
}

Secondaire, vous pouvez créer une vue de brindille, tout comme la source de la saisie semi-automatique de jQuery. La seule différence est la variablesourcedans la fonctionautocomplete (). Là, vous devez spécifier la fonctionchemin ()avec votre clé de route, par exemplecity_autocomplete

(Cette vue nécessite une autre route et une autre action (normale).)

<!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>

Enfin, vous pouvez modifier légèrement cette vue et utiliser votre propre formulaire.

6
Frank B

Enfin, après avoir approfondi mon code Symfony, j'ai finalement trouvé la solution. À l'aide du code fourni par l'utilisateur Frankbeen, j'ai ajouté un «Tweak» pour que le JQuery fonctionne enfin. Le coupable se trouve dans le contrôleur.

$names[] = $entity->getName()."({$entity->getProvince()})";

L'entité de ville est liée à l'entité de province dans une relation un à plusieurs.Etant donné que l'entité de ville a des milliers de noms (enregistrements), il est probable que certaines valeurs auront le même nom. Il est donc utile d'ajouter une province associée pour éviter les confusions chez les utilisateurs.

San Francisco (Province de Russie), San Francisco (Province de Chine), San Francisco (Province du Portugal)

 enter image description here

Maintenant, puisque les noms sont maintenant "différents" dans les noms déjà enregistrés dans la base de données, la validation invalid_message déclenchera les erreurs. Ma solution consiste à "nettoyer" les données soumises en supprimant les provinces ajoutées avant de comparer les valeurs soumises par l'utilisateur aux valeurs du base de données. Dans le DataTransformer, j’ai ajouté du 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;
}

Maintenant, JQuery fonctionne enfin et la sauvegarde dans la base de données est également un succès.Et enfin, 

$( "#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 );
        }
    });

Est changé en

<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>

Maintenant, l'erreur 'Undefined' est partie.

1

Voici une solution pour ajouter un champ concernant la réponse donnée par le contrôleur Symfony. En cas de succès, ajoutez les champs souhaités en renvoyant un objet. Ensuite, dans la sélection, vous pouvez y accéder par ui .item.fields

$('#mySelector').autocomplete({

        source : function(requete, reponse) {

            lettre = {
                lettre: $('#lettre').val()
            };

            $.ajax({
                url: Routing.generate('chargementSource'),
                dataType: 'json',
                data : lettre,
                success: function (donnee) {
                    
                    reponse(
                            $.map(donnee, function (objet) {
                                return { 
                                  value: '' + objet.nom + ' ' + objet.prenom +', '+ objet.adresse +' '+ objet.codepostal +', '+ objet.ville + '', 
                                 id: objet.id 
                               }
                            })
                    );

                }

            });
        },
        select: function (event, ui) {

            $('#myId').val(ui.item.id);
            //alert(ui.item.id);
            //$('#myId').val(ui.elem.value);

            return false;

        }

    });

0
Laurent Lolo