Autor Wątek: Custom Field SF 2.0. Własny typ googleMap  (Przeczytany 10822 razy)

skowi

  • Newbie
  • *
  • Wiadomości: 30
    • Zobacz profil
Custom Field SF 2.0. Własny typ googleMap
« dnia: Lipca 31, 2011, 23:06:59 »
Hej, zostawiam dla wszystkich bo może ktoś będzie potrzebował  własnego typu w formularzu do zaznaczania miejsca na google maps.

Po pierwsze zakładamy klasę GmapsType wyglądającą tak:

<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Legionet\ZagrajmyrazemBundle\Form\Type;


use 
Symfony\Component\Form\AbstractType
use 
Symfony\Component\Form\FormBuilder;
use 
Symfony\Component\Form\FormInterface;
use 
Symfony\Component\Form\FormView;

class 
GmapsType extends AbstractType
{
    
      
/**
     * Configures the Type
     *
     * @param FormBuilder $builder
     * @param array       $options
     */
    
public function buildForm(FormBuilder $builder, array $options)
    {
        
           
$builder
            
->add('gmaps_search''search',array('required' => false))
            ->
add('gmaps_longitude''hidden',array('required' => true))
            ->
add('gmaps_latitude','hidden',array('required' => true));
    }
   
    
   
    
/**
     * {@inheritdoc}
     */
    
public function getParent(array $options)
    {
        return 
'form';
    }

    
/**
     * {@inheritdoc}
     */
    
public function getName()
    {
        return 
'gmaps';
    }
}

Składa się ona z pola typu search do wyszukania miejsca oraz dwóch pól ukrytych do przetrzymania współrzędnych.

Następnie w katalogu Resources/config naszego bundle w pliku services.xml między <services></services> wrzucamy :
        <service id="form.type.gmaps" class="Legionet\ZagrajmyrazemBundle\Form\Type\GmapsType">
            <tag name="form.type" alias="gmaps" />
        </service>

Aby wyświetlić nasze pole formularza musimy zdefiniować widok. Tworzymy w Resources/views/form plik form_div_layout.html.twig a w nim:
{% block gmaps_widget %}
{% spaceless %}
      <div id="{div.id}" class="{div.class}">
         {{ form_widget(form.gmaps_search) }} <input type="submit" value="Szukaj"  id="se" /> <br />
        {{ form_widget(form.gmaps_longitude) }}
        {{ form_widget(form.gmaps_latitude) }}
        <div id="mapa" style="width:600px; height:600px"></div>
      </div>

{% block javascripts %}
     <script type="text/javascript">
        jQuery(window).bind("load", function() {
          new sfGmapWidgetWidget({
            longitude: "{{ form.gmaps_longitude.vars.id }}",
            latitude: "{{ form.gmaps_latitude.vars.id }}",
            address: "{{ form.gmaps_search.vars.id }}",
            lookup: "se",
            lat:{% if form.gmaps_latitude.vars.value is empty %}   null  {% else %} "{{form.gmaps_latitude.vars.value}}" {% endif %},
            lng:{% if form.gmaps_longitude.vars.value is empty %} null  {% else %} "{{form.gmaps_longitude.vars.value}}" {% endif %},
            map: "mapa"
          });
        })
      </script>
{% endblock %}
{% endspaceless %}
{% endblock %}

Aby zobaczyć jak to wygląda musimy dodać nasze pole do formularza ( ->add('gmaps','gmaps') ):
<?php

namespace Legionet\ZagrajmyrazemBundle\Form;

use 
Symfony\Component\Form\AbstractType;
use 
Symfony\Component\Form\FormBuilder;

class 
PlaceType extends AbstractType
{
    public function 
buildForm(FormBuilder $builder, array $options)
    {
        
$builder
            
->add('name')
            ->
add('address')
            ->
add('description')
            ->
add('gmaps','gmaps');
    }
    public function 
getName(){
        return 
'Place';
    }
}

A następnie w widoku zawierającym formularz dorzucić prośbę o wykorzystywanie form_theme z naszego wcześniejszego pliku + jquery + gmaps.js ( zmodyfikowana przez mnie wersja widgeta sfGmapWidget )
{% block javascripts %}
<script type="text/javascript"
    src="http://maps.google.com/maps/api/js?sensor=false">
</script>
<script type="text/javascript"
    src="http://code.jquery.com/jquery-1.6.2.min.js">
</script>

 {% endblock %}


{% javascripts '@LegionetZagrajmyrazemBundle/Resources/public/js/*' %}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}

{% form_theme form 'LegionetZagrajmyrazemBundle:Form:form_div_layout.html.twig' %}


Tutaj zmodyfikowana wersja sfGmapWidget  działająca na gmaps api V3 :
function sfGmapWidgetWidget(options){
    // this global attributes
    this.lng      = null;
    this.lat      = null;
    this.address  = null;
    this.map      = null;
    this.geocoder = null;
    this.options  = options;
    this.marker   = null;
    this.init();
}
 
sfGmapWidgetWidget.prototype = new Object();
 
sfGmapWidgetWidget.prototype.init = function() {
 
    // retrieve dom element
    this.lng      = jQuery("#" + this.options.longitude);
    this.lat      = jQuery("#" + this.options.latitude);
    this.address  = jQuery("#" + this.options.address);
    this.lookup   = jQuery("#" + this.options.lookup);
    this.geocoder = new google.maps.Geocoder();
    this.vis = true;
    var zoom = 16;
   
    if(this.options.lat == null && this.options.lng == null){
        this.options.lng = 19.145136;
        this.options.lat = 51.919438;
        this.vis = false;
        zoom = 6;
    }
   
    var latlng = new google.maps.LatLng(this.options.lat, this.options.lng);
    var myOptions = {
        zoom: zoom,
        mapTypeId: google.maps.MapTypeId.HYBRID,
        center: latlng
    };
   
    this.map = new google.maps.Map(jQuery("#" + this.options.map).get(0),myOptions);
    this.marker = new google.maps.Marker({
        visible: this.vis,
        map: this.map,
        draggable: true,
        position: latlng
    });
   
    this.map.sfGmapWidgetWidget = this;
    this.geocoder.sfGmapWidgetWidget = this;
    this.lookup.get(0).sfGmapWidgetWidget = this;
    this.marker.sfGmapWidgetWidget = this;
   
    google.maps.event.addListener(this.map, 'click', mapClick);
    this.lookup.bind('click', searchInputClick)
    google.maps.event.addListener(this.marker, 'dragend', dragEnd);
}
 
function mapClick(event) {
    sfGmapWidgetWidget.activeWidget = this.sfGmapWidgetWidget;
    var widget = sfGmapWidgetWidget.activeWidget;
    afterChange(widget,event.latLng);
}

function searchInputClick(){
    sfGmapWidgetWidget.activeWidget = this.sfGmapWidgetWidget;
    var widget = sfGmapWidgetWidget.activeWidget;
    widget.geocoder.geocode( {
        'address': this.sfGmapWidgetWidget.address.val()
    }, function(results, status) {
        if (status == google.maps.GeocoderStatus.OK) {
            sfGmapWidgetWidget.activeWidget = null;
            afterChange(widget,results[0].geometry.location);
        } else {
    //alert("Geocode was not successful for the following reason: " + status);
    }
    });
    return false;
}

function dragEnd(event) {
    sfGmapWidgetWidget.activeWidget = this.sfGmapWidgetWidget;
    var widget = sfGmapWidgetWidget.activeWidget;
    afterChange(widget,event.latLng);
}


function afterChange(widget, latLng){
    widget.map.setCenter(latLng);
    widget.marker.setPosition(latLng);
    widget.marker.setVisible(true);
    widget.lat.val(latLng.lat());
    widget.lng.val(latLng.lng());
}

Żeby to wszystko miało ręce i nogi dorzucamy własny Validator. Idąc za Cookbokiem to w swoim bundlu tworzymy katalog Validator/Constraint a w nim dwa pliki
Gmaps.php :
<?php

namespace Legionet\ZagrajmyrazemBundle\Validator\Constraint;

/** @Annotation */
class Gmaps extends \Symfony\Component\Validator\Constraint
{
    public 
$correct 'Wprowadzone wartości są niepoprawne.';
    public 
$required 'Pole wymagane.';
    
      
/**
     * {@inheritdoc}
     */
    
public function validatedBy()
    {
        return 
'legionet.gmapsvalidator';
    }
}

oraz GmapsValidator.php
<?php

namespace Legionet\ZagrajmyrazemBundle\Validator\Constraint;

use 
Symfony\Component\Validator\Constraint;
use 
Symfony\Component\Validator\Exception\UnexpectedTypeException;
use 
Symfony\Component\HttpFoundation\Request;
use 
Legionet\ZagrajmyrazemBundle\Form\Type;

class 
GmapsValidator extends \Symfony\Component\Validator\ConstraintValidator
{

    
    public function 
isValid($valueConstraint $constraint)
    {
        
//if (!is_object($value) && !$value instanceof Type\GmapsType) {
        //    throw new UnexpectedTypeException($value, 'Legionet\ZagrajmyrazemBundle\Form\Type\GmapsType');
        //}
        
if($value['gmaps_longitude'] === '' || $value['gmaps_latitude'] === ''){
            
$this->setMessage($constraint->required, array());
            return 
false;
        }
        
        if(
$value['gmaps_latitude'] < -90 || $value['gmaps_latitude'] > 90){
            
$this->setMessage($constraint->correct, array());
            return 
false;
        }
        
        if(
$value['gmaps_longitude'] < -180 || $value['gmaps_longitude'] > 180){
            
$this->setMessage($constraint->correct, array());
            return 
false;
        }
        return 
true;
    }
}

W services.xml tam gdzie poprzednio dokładamy linijkę :

       <service id="validator.gmaps" class="Legionet\ZagrajmyrazemBundle\Validator\Constraint\GmapsValidator">
            <tag name="validator.constraint_validator" alias="legionet.gmapsvalidator" />
        </service>

Uffff. Ostatnia rzecz to dodać to wszystko do jakiegos Entity. U mnie jest to Place w którym mam adres, szerokosc i dlugosc geograficzna. Dodajemy więc do tego virtualne pole $gmaps które będzie sprawdzane przez nasz validator. A w geterze i seterze odpowiednie zmiany "prawdziwych" pól.
    /**
     *
     *
     * @LegionetAssert\Gmaps
     */
    private $gmaps;

    public function getGmaps() {
        $this->gmaps = array();
        $this->gmaps['gmaps_latitude'] = $this->latitude;
        $this->gmaps['gmaps_longitude'] = $this->longitude;
        $this->gmaps['gmaps_search'] = $this->address;
        return $this->gmaps;
    }
   
    public function setGmaps($gmaps){
        $this->gmaps = $gmaps;
        $this->latitude = $gmaps['gmaps_latitude'];
        $this->longitude = $gmaps['gmaps_longitude'];
        $this->address = $gmaps['gmaps_search'];
    }



Tutaj są linki z których korzystałem:
http://www.symfony-project.org/more-with-symfony/1_4/en/05-Custom-Widgets-and-Validators
http://www.symfony-project.org/more-with-symfony/1_4/en/A-sfWidgetFormGMapAddress
http://blog.bearwoods.dk/custom-validator-and-fields-in-symfony2
http://symfony2.ylly.fr/the-way-to-add-custom-field-form-jordscream/
http://groups.google.com/group/symfony2/browse_thread/thread/7573bdac07fcc6f7/dfd4c83ffb102d08?lnk=raot
http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html
http://symfony.com/doc/current/cookbook/validation/custom_constraint.html
http://symfony.com/doc/current/cookbook/form/form_customization.html


Zdaje sobie sprawę, że mój kod pozostawia dużo do życzenia więc proszę o podawanie pomysłów co można ulepszyć.
Pozdro :)

« Ostatnia zmiana: Sierpnia 01, 2011, 11:22:32 wysłana przez skowi »

axzx

  • Newbie
  • *
  • Wiadomości: 1
    • Zobacz profil
Odp: Custom Field SF 2.0. Własny typ googleMap
« Odpowiedź #1 dnia: Lipca 21, 2012, 15:13:52 »
hej,
fajne to:)
może by zrobić z tego bundle?
tak żeby można było tylko dodać bundle i już można by używać tego typu.

l3l0

  • Administratorzy
  • Full Member
  • *****
  • Wiadomości: 201
    • Zobacz profil
    • l3l0 blog
Odp: Custom Field SF 2.0. Własny typ googleMap
« Odpowiedź #2 dnia: Lipca 22, 2012, 11:30:45 »
Witam,

Potrzebowałem czegoś podobnego i już chciałem robić bundle gdy koleś opublikował to:

https://github.com/ollietb/OhGoogleMapFormTypeBundle#readme

:) Pozdrawiam l3l0