Cibul Tech Blog - Fiddling with PHP, javascript and things

Posts Tagged ‘jQuery’

Drag and Drop two Markers linked with a line using Google Maps v3

Friday, July 8th, 2011

In this article we’ll use the google maps v3 API to show two draggable markers on a map linked with a line. As the markers are dragged, we want the line position to be updated so that it appears to always be linked and we also want to an updated indicator displaying the distance between the two markers in kilometers.

To achieve this, we will use the Markers MVCObject properties to bind their positions to a method which will update the line and indicator as the Markers are dragged around the map.

Getting started by creating a Map

We start with an empty html page in which we will write our script. This page shows a full page map with over it a box in which we’ll keep an updated value of the distance between the markers. Here we go:

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>Distance widget</title>
    <style type="text/css">
      html, body { height: 100%; margin: 0; padding:0; }
      #map-canvas { height: 100%; width: 100%; }
      #distance-display { position: absolute; bottom: 0; left: 0; width: 100px; height: 20px; z-index: 1; background: white; text-align: center; padding: 10px;}
    </style>
    <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>
    <script type"text/javascript">
      $(function(){
       
        var map = new google.maps.Map($('#map-canvas'), {
          center: new google.maps.LatLng(48.860932,2.335925),
          zoom: 13,
          mapTypeId: google.maps.MapTypeId.ROADMAP
        });

      });
    </script>
  </head>
  <body>
    <div id="distance-display">distance</div>
    <div id="map-canvas"></div>
  </body>
</html>

With this we have a working full page map ready to receive some markers and a polyline. The libraries we are using are the google maps v3 (obviously) and the jquery library which we use to handle things on the page (like $(‘#map-canvas’)) and also to ensure that the javascript is not executed before the page is fully loaded (that is what the $(function(){}) is for).

Add two markers and a line

Now we add our two markers. One on the Louvre Museum and the second on the Eiffel Tower. Add this after the map declaration:

        var marker = new google.maps.Marker({
          map: map,
          position: new google.maps.LatLng(48.860932,2.335925),
          draggable: true,
          raiseOnDrag: false
        });

        var marker2 = new google.maps.Marker({
          map: map,
          position: new google.maps.LatLng(48.858221,2.29449),
          draggable: true,
          raiseOnDrag: false
        });

We also create the line, which is a Polyline with only two positions, one for each marker:

        var line = new google.maps.Polyline({
          path: [marker.getPosition(), marker2.getPosition()],
          map: map
        });

Calculate the distance between two markers

Before we start binding the markers with the line, we need to be able to calculate the distance between the markers. There used to be a practical method to do this in a previous version the the google maps API but it does not exist in the latest version (last time I checked anyways). So we will add it to the LatLng class so that we can easily determine the distance between two points. This methods uses the Haversine formula to calculate the distance. Add it before the $(function(){… line:

      google.maps.LatLng.prototype.distanceFrom = function(newLatLng) {
        if (newLatLng==undefined) return false;

        var dLat = (newLatLng.lat()-this.lat()) * Math.PI / 180;
        var dLon = (newLatLng.lng()-this.lng()) * Math.PI / 180;
        var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(this.lat() * Math.PI / 180 ) * Math.cos(newLatLng.lat() * Math.PI / 180 )* Math.sin(dLon/2) * Math.sin(dLon/2);
        return 6371000 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
      }

With this, we can now simply determine the distance between two points. We apply it on the position of our markers at the end of the script:

$('#distance-display').html(Math.round(marker.getPosition().distanceFrom(marker2.getPosition())/10)/100 + ' km');

Here we are calculating the distance, rounding it up to have something in kilometers and we put it in our display

Tie everything up using MVCObject properties

For the time being, everything is initialized, the two markers are displayed with a line in between them, the distance between the two markers is displayed as well at the bottom of the screen, but nothing happens when one of the markers is dragged. What we need is to update the position of the line and update the distance display as the drag occurs. This is where the MVCObject properties of our objects are useful. Using them, we can bind attributes (in our case, the position of the markers) to methods which will update the line position and the distance display any time the attributes are changed.

First we create the method that will update the line and the display (before the $(function(){ again):

      var update_line_and_display = function(line, display, position1, position2){

        // update the line
        line.getPath().setAt(0, position1);
        line.getPath().setAt(1, position2);
       
        // update the display
       
        $(display).html(Math.round(position1.distanceFrom(position2)/10)/100 + ' km');
      }

We want that function to be called whenever the position of the markers change. As markers inherit the MVCObject, their attributes are bound to methods which if defined are triggered any time the attribute is changed. The names of these methods derive from the attribute. In our case, we use the position_changed method for each marker. We add this at the end of the script and we are done!

        marker.position_changed = function() {
          update_line_and_display(line, '#distance-display', this.get('position'), marker2.getPosition());
        }

        marker2.position_changed = function() {
          update_line_and_display(line, '#distance-display', marker.getPosition(), this.get('position'));
        }

You can see it working here.

Tags: , ,
Posted in Uncategorized | 6 Comments »

Geocode with Google Maps API v3

Monday, May 24th, 2010

The purpose of this post is to show how to implement a simple address search tool with auto-completion using Google’s Google Maps API v3 with both client side geocoding and reverse geocoding.

The search menu will propose to search for addresses in two ways: the first will be direct address typing with suggestions for auto-completion and the second will finding addresses by dragging a marker on a map.

address field with autocomplete

See it working here

This is done in 5 steps:

  • create a page and download the jquery libraries
  • initialize a map, a geocoder and a marker
  • setup the jquery search widget to work with the geocoder to fetch suggestions and pin a marker down upon selection of a result
  • configure a listener to reverse geocode marker position when it is being dragged

Create a page

We start by creating a file structure which includes an index.html file, a css/ and a js/ folders. We need as well to go on the jquery website to download the autocomplete widget library as well as the base jquery library. We add a main.css file in the css folder to add some styling later on. Once this is done, edit the html file to include links to the js files, a div for the map, a search field and two fields for latitude and longitude:

<html>
  <head>
    <title>Geocoding with GMap v3</title>
    <link type="text/css" href="css/main.css" rel="stylesheet" />
    <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
    <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
    <script type="text/javascript" src="js/jquery-ui-1.8.1.custom.min.js"></script>
    <script type="text/javascript" src="js/main.js"></script>
  </head>
  <body>
    <label>Address: </label><input id="address"  type="text"/>
    <div id="map_canvas" style="width:300px; height:300px"></div><br/>
    <label>latitude: </label><input id="latitude" type="text"/><br/>
    <label>longitude: </label><input id="longitude" type="text"/>
  </body>
</html>

Initialize a map

In the js folder, we create a main.js file in which we will write all of our js code. We start with initializing a map, a geocoder and a marker:

var geocoder;
var map;
var marker;
   
function initialize(){
//MAP
  var latlng = new google.maps.LatLng(41.659,-4.714);
  var options = {
    zoom: 16,
    center: latlng,
    mapTypeId: google.maps.MapTypeId.SATELLITE
  };
       
  map = new google.maps.Map(document.getElementById("map_canvas"), options);
       
  //GEOCODER
  geocoder = new google.maps.Geocoder();
       
  marker = new google.maps.Marker({
    map: map,
    draggable: true
  });
               
}

Instead of launching the initialize function in the onload statement of body, we can execute it once the jquery object is loaded. In main.js we add the statement:

$(document).ready(function() {
         
  initialize();
}

The page should be complete, with a working map and 3 empty fields.

Fetching auto-complete suggestions with the Geocoder

Geocode means figuring out a latitude and longitude pair from an address. We will let the geocoder do that work and we’ll use it to get the results and pin them on the map.

Now that the project is setup, in order to have a working auto-complete field for addresses we need to have two components working together: the jquery autocomplete widget and the google maps API geocode function. Everytime the user types in something in the search field, the autocomplete widget must use the geocoder to fetch a list of suggestions to display.

This is what the autocomplete method enables us to do. It can take quite a few options, but in our application we will only need two:

  1. source: it defines where the data displayed in the autocomplete list comes from (in our case, its the result of the geocoder)
  2. select: it defines what happens when the user selects a result of the list. Here we want it to display a marker on the map

All this comes right after the objects have been initialized:

$(document).ready(function() {
         
  initialize();
                 
  $(function() {
    $("#address").autocomplete({
      //This bit uses the geocoder to fetch address values
      source: function(request, response) {
        geocoder.geocode( {'address': request.term }, function(results, status) {
          response($.map(results, function(item) {
            return {
              label:  item.formatted_address,
              value: item.formatted_address,
              latitude: item.geometry.location.lat(),
              longitude: item.geometry.location.lng()
            }
          }));
        })
      },
      //This bit is executed upon selection of an address
      select: function(event, ui) {
        $("#latitude").val(ui.item.latitude);
        $("#longitude").val(ui.item.longitude);
        var location = new google.maps.LatLng(ui.item.latitude, ui.item.longitude);
        marker.setPosition(location);
        map.setCenter(location);
      }
    });
  });

Try it out. It works but its not too pretty… Add some style to the list box by adding this to the main.css file:

.ui-autocomplete {
    background-color: white;
    width: 300px;
    border: 1px solid #cfcfcf;
    list-style-type: none;
    padding-left: 0px;
}

Reverse Geocoding

This means figuring out the address from a latitude and longitude pair. What we want to do now is to execute a reverse geocoding call when the marker is being dragged on the map and update the address field. This works as well using the geocoder, only instead giving it an address we just give it a latitude longitude pair and it sends back an address if it finds any.

At the end of the js file, we add a on-drag behavior to the marker:

  google.maps.event.addListener(marker, 'drag', function() {
    geocoder.geocode({'latLng': marker.getPosition()}, function(results, status) {
      if (status == google.maps.GeocoderStatus.OK) {
        if (results[0]) {
          $('#address').val(results[0].formatted_address);
          $('#latitude').val(marker.getPosition().lat());
          $('#longitude').val(marker.getPosition().lng());
        }
      }
    });
  });

Drag and drop the marker and see what happens!

Tags: , ,
Posted in Geolocation | 137 Comments »

Embedded forms with symfony 1.4 and jQuery

Tuesday, April 13th, 2010

How to implement a single interface to insert multiple entries linked by one-to-many relations…

I’ve been looking recently for a way to build a form that would allow the input of data (edition or adding new data) into multiple related objects. Turns out the way to do this in symfony is to use embedded forms and this what this post is about. I am not an expert in symfony nor in jQuery so if find better ways to do some of the things described here I’ll be happy to read your comments.

We’ll build a listing of events which will be editable to the user. Each event has one or more occurrences (a place and a time). Forms shown should allow the user to edit each event and their related occurrences under the same interface plus leaving them the possibility to add new occurrences if needed.

We will do this in 5 steps:

  1. Setting up the project: we’ll create a symfony project and mysql database
  2. Editing Events: we’ll create a form where both events and occurrences can be edited using embedded forms
  3. Adding new occurrence fields: we’ll add the functionality to add more blank occurrence fields using jQuery Ajax functions
  4. Removing new occurrence fields: still using jQuery, we’ll allow the user to remove blank occurrence fields
  5. Creating a new Event: We’ll modify the form template slightly to allow the user to add more events to the list

Ok, this should take a little while so let’s get started!

1. Setting up the project and showing the index page

We start by setting up a symfony project and building a database based on the following schema and fixtures:

The schema:

#config/doctrine/schema.yml
Event
:
  columns
:
    id
:
      type
: integer
      primary
: true
      autoincrement
: true
    title
:
      type
: string(255)
      notnull
: true
  relations
:
    Occurrences
:
      type
: many
      class
: Occurrence
      local
: id
      foreign
: event_id
      onDelete
: CASCADE

Occurrence
:
  columns
:
    id
:
      type
: integer
      primary
: true
      autoincrement
: true
    location
:
      type
: string(255)
      notnull
: true
    date
:
      type
: date
      notnull
: true
    event_id
:
      type
: integer
      notnull
: true
  relations
:
    Event
:
      type
: one
      local
: event_id

… and the fixtures:

#data/fixtures/fixtures.yml
Event
:
  apero_geant
:
    title
: Apero Geant
  rugby_night
:
    title
: Rugby Night

Occurrence
:
  apero_geant1
:
    Event
: apero_geant
    location
: Marseille, France
    date
: '2009-08-29'
  apero_geant2
:
    Event
: apero_geant
    location
: Nantes, France
    date
: '2009-11-10'
  apero_geant3
:
    Event
: apero_geant
    location
: Rennes, France
    date
: '2010-03-25'
  rugby_night1
:
    Event
: rugby_night
    location
: Yvetot, France
    date
: '2010-03-20'

After generating a frontend app, we can create an ‘event’ module by just adding the event/actions and event/templates directories under the modules directory of the app.

In an action.class.php file, we create a first index action that sends a list of events to an indexSuccess.php template.

<?php // apps/frontend/modules/event/actions/actions.class.php

  class eventActions extends sfActions{

    public function executeIndex(sfWebRequest $request){
      $this->events = Doctrine_Core::getTable('Event')->findAll();
    }

  }

To make things simple, we’ll set the index action as default in the routing, removing the default content.

#app/frontend/config/routing.yml
homepage
:
  url
:  /
  param
: { module: event, action: index }

The occurrences are listed under each event in the indexSuccess.php template:

<!--apps/frontend/modules/templates/indexSuccess.php -->
<h2>Events Index</h2>
<?php foreach($events as $event): ?>
<div class="event-item">
  <h3><?php echo $event->getTitle() ?></h3>
  <ul>
    <?php foreach($event->getOccurrences() as $occurrence): ?>
      <li><?php echo $occurrence->getDate()?> - <?php echo $occurrence->getLocation() ?></li>
    <?php endforeach;?>
  </ul>
</div>
<?php endforeach; ?>

There, the first step is done and the project is set up with a default index page showing the events and their occurrences

2 – Editing existing events & their occurrences

Now we have to setup the embedded form structure

We want to be able to edit any of the listed events and their occurrences in the same form. Let’s start by creating an edit action and associated template and link it to the index page. We’ll start simple and setup a standard form that will allow us to update the title field of the event. Once this is running we will start working with embedded forms to add the possibility to edit the related occurrences as well.

To get things working we need to create an edit action and template to launch the edition form, a submit action for updating the database when the form has been filled, and we’ll add some links to the index page to be able to access the edit menu for each event.

  • Let’s start with the executeEdit() action: It simply creates a form and sends it to the editSuccess.php template
  • // apps/frontend/modules/event/actions/actions.class.php

        public function executeEdit(sfWebRequest $request){
          $this->forward404Unless($event = Doctrine::getTable('Event')->find(array($request->getParameter('id'))), sprintf('Event does not exist (%s).', $request->getParameter('id')));
          $this->form = new EventForm($event);
        }
  • The template editSuccess(): It lays out the form and has it ready to send the result to the submit action
  • <!--apps/frontend/modules/templates/editSuccess.php-->
    <h2>Edit Event</h2>

    <form action="<?php echo url_for('@submit') ?>" method="post">
      <?php echo $form->renderHiddenFields() ?>
      <?php echo $form['title']->renderLabel()?> <?php echo $form['title']->renderError()?> <?php echo $form['title'] ?>
      <input type="submit" value="Save" />
    </form>

    <a href="<?php echo url_for('@homepage')?>">Back to index</a>
  • The routing has to be updated with both edit and submit routes:
  • #app/frontend/config/routing.yml
    edit
    :
      url
    : /edit
      param
    : { module: event, action: edit }

    submit
    :
      url
    : /submit
      param
    : { module: event, action: submit }
  • And the submit action, executeSubmit(): it extracts from the request the values passed on by the form, gets the event object based on the extracted id, makes a form from it, binds it with the request data and updates the database. It is important here to focus on what binding a form means: basically, when the form is created from an event object generated from the database, the values stored are the ones of the database, not the ones input by the user in the previous page; these are still stored in the request object. The bind action consists in taking the values of the request and using them to update the freshly created form, checking the validity of the data in the process, as per the form’s rules. If the validity is confirmed, the database is updated by the save method.
  • // apps/frontend/modules/event/actions/actions.class.php

        public function executeSubmit(sfWebRequest $request){
          $tainted_values = $request->getParameter('event');
          $event = Doctrine::getTable('Event')->find($tainted_values['id']);

          $this->form = new EventForm($event);

          if ($request->isMethod('post') && $this->form->bindAndSave($tainted_values))
            $this->redirect('@homepage');

          $this->setTemplate('edit');
        }
  • The index should have link for each event to allow users to show the edit page, add this under the event title:
  • <!--apps/frontend/modules/templates/indexSuccess.php -->

    <a href="<?php echo url_for('event/edit?id='.$event->getId())?>">edit event</a>

    Now you should have a straightforward way of updating event titles. But here comes the tricky part: how to updated the data of the occurrences of the event as well? This can actually be done easily by fiddling with the event form and embedding occurrence forms to it. But we only want to embed the occurrences which are linked to the event. A method serves that purpose and can be used to do just that in one line: embedRelation(). This method basically takes all related objects and generates the corresponding embedded forms. Enough bla bla, lets update the EventForm class with it:

    <?php //lib/form/doctrine/EventForm.class.php

    class EventForm extends BaseEventForm
    {
      public function configure()
      {
        $this->embedRelation('Occurrences');
      }
    }

    Open the OccurrenceForm.class.php and unset the variables that should’nt be in the form:

    <?php //lib/form/doctrine/OccurrenceForm.class.php

    class OccurrenceForm extends BaseOccurrenceForm
    {
      public function configure()
      {
        unset($this['event_id']);
      }
    }

    Now update as well the editSuccess.php template to show the occurrences of an event when they exist… add this bit under the event title fields:

    <!--apps/frontend/modules/templates/editSuccess.php-->
    <ul>
        <?php foreach ($form['Occurrences'] as $occurrence):?>
        <li>
          <?php echo $occurrence['date']->renderLabel() ?>  <?php echo $occurrence['date']->renderError() ?>
          <?php echo $occurrence['date'] ?>
           -
          <?php echo $occurrence['location']->renderLabel() ?>  <?php echo $occurrence['location']->renderError() ?>
          <?php echo $occurrence['location'] ?>
        </li>
        <?php endforeach ?>
      </ul>

    There. Your form allows you to update the occurrences as well.

    3 – Adding new occurrence field sets with jQuery

    But what if you want to add new occurrences to your event? Wouldn’t it be swell to have some neat way to add new occurrence fields as you please now wouldn’t it?

    Sure it would. And we’ll make it happen using some ajax functions with jQuery.

    To do this, we’ll add some javascript mechanisms on the edition page that will consist in querying the server for some bits of forms, and once receive, appending them to the rest of the page without the need for a refresh. On the server side, a new action will handle the ajax request and re-assemble the forms to send the right piece back.

    We start by adding the jquery library (I’m using jquery 1.4.2) to the js folder as well as an empty js file (eventform.js). In the layout.php of the frontend app we add a reference to each file in the header

    <!--app/frontend/template/layout.php-->
    ...
        <?php include_stylesheets() ?>
        <?php use_javascript('jquery-1.4.2.min.js') ?>
        <?php use_javascript('eventform.js') ?>
        <?php include_javascripts() ?>
    ...

    Now in the editSuccess.php templage, we’ll add a button (a link actually) after the occurrence list. We’ll bind it with javascript behavior to add the new fields.

    <!--apps/frontend/modules/templates/editSuccess.php-->
    ...
    </ul>
    <a id="addoccurrence" href="#">Add an occurrence</a>

    In the javascript file (eventform.js), add the following:

    /* web/js/eventform.js */
    newfieldscount = 0;

    function addNewField(num){
      return $.ajax({
        type: 'GET',
        url: '/add?num='+num,
        async: false
      }).responseText;
    }

    $(document).ready(function(){
      $('#addoccurrence').click(function(e){
        e.preventDefault();
        $('ul').append(addNewField(newfieldscount));
        newfieldscount = newfieldscount + 1;
      });
    });

    Everytime the page element with #addoccurrence as id will be clicked, a new field will be fetched through an ajax request and be appended to the occurrence list. The missing piece now is the processing on the server side of the request. First thing is to update the routing by adding the route we describe in the javascript file…

    #app/frontend/config/routing.yml
    add
    :
      url
    : /add
      param
    : { module: event, action: add }

    … and add the corresponding action, which should work only when the request comes from an XmlHttpRequest.

    // apps/frontend/modules/event/actions/actions.class.php

        public function executeAdd(sfWebRequest $request){
          $this->forward404unless($request->isXmlHttpRequest());
          $number = intval($request->getParameter("num"));

          $this->form = new EventForm();

          $this->form->addNewFields($number);

          return $this->renderPartial('addNew',array('form' => $this->form, 'number' => $number));
        }

    The action calls a method of the EventForm class that will add the number of occurrences needed to replicate the current structure of the form displayed.

    //lib/form/doctrine/EventForm.class.php

      public function addNewFields($number){
        $new_occurrences = new BaseForm();

        for($i=0; $i <= $number; $i+=1){
          $occurrence = new Occurrence();
          $occurrence->setEvent($this->getObject());
          $occurrence_form = new OccurrenceForm($occurrence);

          $new_occurrences->embedForm($i,$occurrence_form);
        }

        $this->embedForm('new', $new_occurrences);
      }

    As you can see, Forms behave as arrays. When you need to create a form with multiple embedded forms, you first create an array of form and you embed in it all the forms that you need. Once you’re done, you embedd that form array into your main form.

    Now we need to create the partial that contains the html to send back to the ajax call. It should contain only the last embedded form (the one to be added).

    //apps/frontend/modules/event/templates/_addNew.php
    <li>
      <?php echo $form['new'][$number]['date']->renderLabel() ?>  <?php echo $form['new'][$number]['date']->renderError() ?>
      <?php echo $form['new'][$number]['date'] ?>
       -
      <?php echo $form['new'][$number]['location']->renderLabel() ?>  <?php echo $form['new'][$number]['location']->renderError() ?>
      <?php echo $form['new'][$number]['location'] ?>
    </li>

    One last thing: now, when the event form is submitted and received by the submit action, the action creates a form to which the data in the request will be bound (the data you just submitted). But before this can happen, the structure of the form needs to match the data received: the number of new embedded forms should be the same as the number submitted. The bind method in the EventForm does just that: it recreates the structure of the request data in the created form before the form binding is done.

    //lib/form/doctrine/EventForm.class.php

      public function bind(array $taintedValues = null, array $taintedFiles = null){

        $new_occurrences = new BaseForm();
        foreach($taintedValues['new'] as $key => $new_occurrence){
          $occurrence = new Occurrence();
          $occurrence->setEvent($this->getObject());
          $occurrence_form = new OccurrenceForm($occurrence);

          $new_occurrences->embedForm($key,$occurrence_form);
        }

        $this->embedForm('new',$new_occurrences);

        parent::bind($taintedValues, $taintedFiles);
      }

    4 – Removing new occurrence field sets with jQuery

    What about removing added empty occurrences? This is less tricky as it does not require any server side processing. All we will need to do now is add a remove link for each of the new occurrence fields and add behavior to them.

    First things first, the remove link has to be added in the addNew.php partial:

    <!--apps/frontend/modules/event/templates/_addNew.php-->
    ...
    <a class="removenew" href="#">Remove</a>
    </li>

    Now some behavior needs to be bound to it. One small thing: as this link is loaded through javascript, the behavior needs to be loaded after the item has been loaded.

    We have to update the eventform.js with a removeNew function which will be launched everytime the remove button is pressed.

    /* web/js/eventform.js */

    var removeNew = function(){
      $('.removenew').click(function(e){
        e.preventDefault();
        $(this).parent().remove();
      })
    };

    Still in the javascript file, update the #addoccurrence button behavior:

    /* web/js/eventform.js */

    $(document).ready(function(){
      $('#addoccurrence').click(function(e){
        e.preventDefault();
        $('ul').append(addNewField(newfieldscount));
        newfieldscount = newfieldscount + 1;
        $('.removenew').unbind('click');
        removeNew();
      });
    });

    Et Voila.

    5 – Adding new events

    Now one last thing we want to do: add a link to the index page to display a form for adding new events. When a new event is created, it should at least contain one occurrence. This should be simple: we will re-use the indexSuccess.php template by modifying it slightly and add a new action.

    We can create the link in the index page (at the end):

    <!--apps/frontend/modules/templates/indexSuccess.php -->

    <a href="<?php echo url_for('@new')?>">Add an Event</a>

    Update the routing.

    #app/frontend/config/routing.yml
    new
    :
      url
    : /new
      param
    : { module: event, action: new }

    … and create the executeNew action

        public function executeNew(sfWebRequest $request){
          $event = new Event();
          $this->form = new EventForm($event);

          $this->form->addNewFields(0);

          $this->setTemplate('edit');
        }

    … and as well update the editSuccess.php template so that one new field is readily available (and non removable) when the form is for a new event. Add this just after the ul tag:

    <!--apps/frontend/modules/templates/editSuccess.php-->
        <?php if ($form->getObject()->isNew()): ?>
        <script type="text/javascript">newfieldscount = 1;</script>
          <li>
            <?php echo $form['new'][0]['date']->renderLabel() ?>  <?php echo $form['new'][0]['date']->renderError() ?>
            <?php echo $form['new'][0]['date'] ?>
             -
            <?php echo $form['new'][0]['location']->renderLabel() ?>  <?php echo $form['new'][0]['location']->renderError() ?>
            <?php echo $form['new'][0]['location'] ?>
          </li>
        <?php endif ?>

    I’ll leave you to do something similar with the title to replace “Edit Event” with “New Event”

    And we’re done! This should allow you to edit and add events and their occurrences, using embedded forms and dynamic functions to add more occurrences. I hope this will be useful for you. Again, your input and comments are welcome.

    By the way, you can download the code here.

    Thanks to Nacho and Nicolas, their posts were very useful. You can find find another interesting tutorial on embedded forms here.

    Tags: , ,
    Posted in Symfony | 44 Comments »