Author: francois
Date: 2010-05-14 17:14:34 +0200 (Fri, 14 May 2010)
New Revision: 29467

Added:
   plugins/sfPropel15Plugin/trunk/lib/widget/sfWidgetFormDelete.class.php
Modified:
   plugins/sfPropel15Plugin/trunk/lib/form/sfFormPropel.class.php
   plugins/sfPropel15Plugin/trunk/lib/form/sfFormPropelCollection.class.php
Log:
[sfPropelPlugin] Added the ability to delete related objects in a collection 
form

Modified: plugins/sfPropel15Plugin/trunk/lib/form/sfFormPropel.class.php
===================================================================
--- plugins/sfPropel15Plugin/trunk/lib/form/sfFormPropel.class.php      
2010-05-14 13:16:18 UTC (rev 29466)
+++ plugins/sfPropel15Plugin/trunk/lib/form/sfFormPropel.class.php      
2010-05-14 15:14:34 UTC (rev 29467)
@@ -20,10 +20,25 @@
  */
 abstract class sfFormPropel extends sfFormObject
 {
+  /**
+   * List of fields that cannot be changed by the user, but still take a 
default value
+   * @var array
+   */
   protected $fixedValues = array();
+  
+  /**
+   * List of forms that can be added by the user
+   * @var array[sfForm]
+   */
   protected $optionalForms = array();
   
   /**
+   * Name of the field used for deletion.
+   * @var string
+   */
+  protected $deleteField;
+  
+  /**
    * Constructor.
    *
    * @param mixed  A object used to initialize default values
@@ -143,17 +158,114 @@
       }
     }
   }
+
+  /**
+   * Adds a widget to the form, and declare this widget as the delete control.
+   * If the bound widget value is true, then the related object will be deleted
+   *
+   * @param string       $name   The field name
+   * @param sfWidgetForm $widget The widget
+   *
+   * @return sfPropelForm The current form instance
+   */
+  public function setDeleteWidget($name, $widget)
+  {
+    $this->setWidget($name, $widget);
+    $this->setValidator($name, new sfValidatorPass(array('required' => 
false)));
+    $this->setDeleteField($name);   
+    
+    return $this;
+  }
   
   /**
+   * Updates and saves the current object.
    * @see sfFormObject
+   *
+   * If you want to add some logic before saving or save other associated
+   * objects, this is the method to override.
+   *
+   * @param mixed $con An optional connection object
    */
+  protected function doSave($con = null)
+  {
+    if (null === $con)
+    {
+      $con = $this->getConnection();
+    }
+
+    $this->updateObject();
+    
+    // this is Propel specific
+    if(!$this->getObject()->isDeleted())
+    {
+      $this->getObject()->save($con);
+    }
+
+    // embedded forms
+    $this->saveEmbeddedForms($con);
+  }
+  
+  /**
+   * Updates the values of the object with the cleaned up values.
+   *
+   * If you want to add some logic before updating or update other associated
+   * objects, this is the method to override.
+   * @see sfFormObject
+   *
+   * @param array $values An array of values
+   */
   protected function doUpdateObject($values)
   {
+    if ($this->hasDeleteField())
+    {
+      if (isset($values[$this->getDeleteField()]) && 
$values[$this->getDeleteField()])
+      {
+        $this->getObject()->delete();
+        return;
+      }
+    }
     $values = array_merge($values, $this->getFixedValues());
     $this->getObject()->fromArray($values, BasePeer::TYPE_FIELDNAME);
   }
 
   /**
+   * Saves embedded form objects.
+   * @see sfFormObject
+   *
+   * @param mixed $con   An optional connection object
+   * @param array $forms An array of forms
+   */
+  public function saveEmbeddedForms($con = null, $forms = null)
+  {
+    if (null === $con)
+    {
+      $con = $this->getConnection();
+    }
+
+    if (null === $forms)
+    {
+      $forms = $this->embeddedForms;
+    }
+
+    foreach ($forms as $form)
+    {
+      if ($form instanceof sfFormObject)
+      {
+        $form->saveEmbeddedForms($con);
+        // this is Propel specific
+        if(!$form->getObject()->isDeleted())
+        {
+          $form->getObject()->save($con);
+        }
+      }
+      else
+      {
+        $this->saveEmbeddedForms($con, $form->getEmbeddedForms());
+      }
+    }
+  }
+  
+  /**
    * Processes cleaned up values with user defined methods.
    *
    * To process a value before it is used by the updateObject() method,
@@ -322,6 +434,11 @@
     }
   }
   
+  /**
+   * Get the name of the Peer class of the form's model, e.g. 'AuthorPeer'
+   *
+   * @return string A Peer class name
+   */
   public function getPeer()
   {
     return constant(get_class($this->getObject()).'::PEER');
@@ -371,7 +488,7 @@
   
   /**
    * Overrides sfForm::mergeForm() to also merge embedded forms
-   * Allows autosave of marged collections
+   * Allows autosave of merged collections
    *
    * @param  sfForm   $form      The sfForm instance to merge with current form
    *
@@ -387,18 +504,27 @@
   }
   
   /**
-   * Merge Relation form into this form
+   * Merge a Collection form based on a Relation into this form.
+   * Available options:
+   *  - add_empty: Whether to allow the user to add new objects to the 
collection. Defaults to true
+   * Additional options are passed to sfFromPropel::getRelationForm()
+   *
+   * @param string $relationName The name of a relation of the current Model, 
e.g. 'Book'
+   * @param array  $options      An array of options
+   *
+   * @return sfPropelForm        The current form instance
    */
   public function mergeRelation($relationName, $options = array())
   {
     $options = array_merge(array(
-      'add_empty'           => false,
+      'add_empty'           => true,
     ), $options);
     
     $relationForm = $this->getRelationForm($relationName, $options);
 
     if ($options['add_empty'])
     {
+      unset($options['add_empty']);
       $emptyForm = $this->getEmptyRelatedForm($relationName, $options);
       $emptyName = 'new' . $relationName;
       $relationForm->embedOptionalForm($emptyName, $emptyForm);
@@ -406,20 +532,36 @@
     }
     
     $this->mergeForm($relationForm);
+    
+    return $this;
   }
-  
+
+  /**
+   * Embed a Collection form based on a Relation into this form.
+   * Available options:
+   *  - title: The title of the colleciton form once embedded. Defaults to the 
relation name.
+   *  - decorator: The decorator for the sfWidgetFormSchemaDecorator
+   *  - add_empty: Whether to allow the user to add new objects to the 
collection. Defaults to true
+   * Additional options are passed to sfFromPropel::getRelationForm()
+   *
+   * @param string $relationName The name of a relation of the current Model, 
e.g. 'Book'
+   * @param array  $options      An array of options
+   *
+   * @return sfPropelForm        The current form instance
+   */
   public function embedRelation($relationName, $options = array())
   {
     $options = array_merge(array(
       'title'               => $relationName,
       'decorator'           => null,
-      'add_empty'           => false,
+      'add_empty'           => true,
     ), $options);
     
     $relationForm = $this->getRelationForm($relationName, $options);
     
     if ($options['add_empty'])
     {
+      unset($options['add_empty']);
       $emptyForm = $this->getEmptyRelatedForm($relationName, $options);
       $emptyName = 'new' . $relationName;
       $relationForm->embedOptionalForm($emptyName, $emptyForm);
@@ -427,21 +569,34 @@
     }
     
     $this->embedForm($options['title'], $relationForm, $options['decorator']);
+    
+    return $this;
   }
   
+  /**
+   * Get a Collection form based on a Relation of the current form's model.
+   * Available options:
+   *  - hide_on_new: If true, returns null for new objects. Defaults to false.
+   *  - collection_form_class: class of the collection form to return. 
Defaults to sfFormPropelCollection.
+   * Additional options are passed to sfFormPropelCollection::__construct()
+   *
+   * @param string $relationName The name of a relation of the current Model, 
e.g. 'Book'
+   * @param array  $options      An array of options
+   *
+   * @return sfFormPropelCollection A form collection instance
+   */
   public function getRelationForm($relationName, $options = array())
   {
     $options = array_merge(array(
+      'hide_on_new'           => false,
       'collection_form_class' => 'sfFormPropelCollection',
-      'embedded_form_class'   => null,
-      'item_pattern'          => '%index%',
-      'hide_on_new'           => false,
     ), $options);
     
     if ($this->getObject()->isNew() && $options['hide_on_new'])
     {
       return;
     }
+    unset($options['hide_on_new']);
     
     // compute relation elements
     $relationMap = $this->getRelationMap($relationName);
@@ -458,20 +613,29 @@
     
     // create the relation form
     $collectionFormClass = $options['collection_form_class'];
-    $collectionForm = new $collectionFormClass($collection, array(
-      'embedded_form_class' => $options['embedded_form_class'],
-      'item_pattern'        => $options['item_pattern'],
-      'remove_fields'       => $relationFields,
-    ));
+    unset($options['collection_form_class']);
     
+    $collectionForm = new $collectionFormClass($collection, $options);
+    
     return $collectionForm;
   }
 
+  /**
+   * Get an empty Propel form based on a Relation of the current form's model.
+   * Available options:
+   *  - embedded_form_class: The class of the form to return
+   *  - empty_label: The label of the empty form
+   *
+   * @param string $relationName The name of a relation of the current Model, 
e.g. 'Book'
+   * @param array  $options      An array of options
+   *
+   * @return sfFormPropel A Propel form instance
+   */
   public function getEmptyRelatedForm($relationName, $options = array())
   {
     $options = array_merge(array(
-      'embedded_form_class'   => null,
-      'empty_label'           => null,
+      'embedded_form_class' => null,
+      'empty_label'         => null,
     ), $options);
     
     // compute relation elements
@@ -530,7 +694,22 @@
   {
     return $this->fixedValues;
   }
+  
+  public function setDeleteField($fieldName)
+  {
+    $this->deleteField = $fieldName;
+  }
 
+  public function hasDeleteField()
+  {
+    return null !== $this->deleteField;
+  }
+  
+  public function getDeleteField()
+  {
+    return $this->deleteField;
+  }
+
   public function __clone()
   {
     $this->object = clone $this->object;

Modified: 
plugins/sfPropel15Plugin/trunk/lib/form/sfFormPropelCollection.class.php
===================================================================
--- plugins/sfPropel15Plugin/trunk/lib/form/sfFormPropelCollection.class.php    
2010-05-14 13:16:18 UTC (rev 29466)
+++ plugins/sfPropel15Plugin/trunk/lib/form/sfFormPropelCollection.class.php    
2010-05-14 15:14:34 UTC (rev 29467)
@@ -1,18 +1,49 @@
 <?php
 
-
+/**
+ * sfFormPropelCollection represents a form based on a collection of Propel 
objects.
+ *
+ * @package    symfony
+ * @subpackage form
+ * @author     Francois Zaninotto
+ */
 class sfFormPropelCollection extends sfForm
 {
   protected $model;
   protected $collection;
   protected $isEmpty = false;
   
+  /**
+   * Form constructor. 
+   *
+   * Available options:
+   * - item_pattern:  The pattern used to name each embedded form. Defaults to 
'%index%'.
+   * - add_delete:    Whether to add a delete widget for each object. Defaults 
to true.
+   * - delete_name:   Name of the delete widget. Defaults to 'delete'.
+   * - delete_widget: Optional delete widget object. If left null, uses a 
sfWidgetFormDelete instance.
+   * - alert_text:    The text of the Javascript alert to show
+   * - hide_parent:   Whether to hide the parent form when clicking the 
checkbox
+   * - parent_level:  The number of times parentNode must be called to reach 
the parent to hide.
+   *                  Recommended values: 6 for embedded form, 7 for merged 
form
+   * - remove_fields: The list of fields to remove from the embedded object 
forms
+   *
+   * @param PropelCollection $collection A collection of Propel objects 
+   *                                     used to initialize default values
+   * @param array            $options    An array of options
+   * @param string           $CSRFSecret A CSRF secret (false to disable CSRF 
protection, null to use the global CSRF secret)
+   *
+   * @see sfForm
+   */
   public function __construct($collection = null, $options = array(), 
$CSRFSecret = null)
   {
     $options = array_merge(array(
-      'item_pattern' => '%index%',
+      'item_pattern'  => '%index%',
+      'add_delete'    => true,
+      'delete_name'   => 'delete',
+      'delete_widget' => null,
       'remove_fields' => array(),
     ), $options);
+    
     if (!$collection)
     {
       $this->model = $options['model'];
@@ -35,6 +66,9 @@
     parent::__construct(array(), $options, $CSRFSecret);
   }
   
+  /**
+   * Configures the current form.
+   */
   public function configure()
   {
     $formClass = $this->getFormClass();
@@ -46,27 +80,70 @@
       {
         unset($form[$field]);
       }
+      if ($this->getOption('add_delete'))
+      {
+        if (!($deleteWidget = $this->options['delete_widget']))
+        {
+          $options = array();
+          if ($alertText = $this->getOption('alert_text', false))
+          {
+            $options['alert_text'] = $alertText;
+          }
+          if ($hideParent = $this->getOption('hide_parent', false))
+          {
+            $options['hide_parent'] = $hideParent;
+          }
+          if ($parentLevel = $this->getOption('parent_level', false))
+          {
+            $options['parent_level'] = $parentLevel;
+          }
+          $deleteWidget = new sfWidgetFormDelete($options);
+        }
+        $form->setDeleteWidget($this->getOption('delete_name'), $deleteWidget);
+      }
       $name = strtr($this->getOption('item_pattern'), array('%index%' => $i, 
'%model%' => $this->getModel()));
       $this->embedForm($name, $form);
       $i++;
     }
   }
   
+  /**
+   * Getter for the internal collection object
+   *
+   * @return PropelCollection
+   */
   public function getCollection()
   {
     return $this->collection;
   }
   
+  /**
+   * Getter for the name of the Propel model used by the collection
+   *
+   * @return string
+   */
   public function getModel()
   {
     return $this->model;
   }
   
+  /**
+   * Check whether the embedded colleciton is empty
+   *
+   * @return boolean
+   */
   public function isEmpty()
   {
     return $this->isEmpty;
   }
   
+  /**
+   * Getter for the embedded form class.
+   * Uses the embedded_form_class option if available,
+   * or falls back to the default form for the model.
+   *
+   * @return string
+   */
   public function getFormClass()
   {
     if (!$class = $this->getOption('embedded_form_class', false))

Added: plugins/sfPropel15Plugin/trunk/lib/widget/sfWidgetFormDelete.class.php
===================================================================
--- plugins/sfPropel15Plugin/trunk/lib/widget/sfWidgetFormDelete.class.php      
                        (rev 0)
+++ plugins/sfPropel15Plugin/trunk/lib/widget/sfWidgetFormDelete.class.php      
2010-05-14 15:14:34 UTC (rev 29467)
@@ -0,0 +1,65 @@
+<?php
+
+/*
+ * This file is part of the symfony package.
+ * (c) Fabien Potencier <[email protected]>
+ * 
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * sfWidgetFormDelete represents a delete widget for an embedded object form
+ *
+ * @package    symfony
+ * @subpackage widget
+ * @author     Francois Zaninotto
+ */
+class sfWidgetFormDelete extends sfWidgetFormInputCheckbox
+{
+  /**
+   * Constructor.
+   *
+   * Available options:
+   *
+   *  - alert_text: The text of the Javascript alert to show
+   *  - hide_parent: Whether to hide the parent form when clicking the checkbox
+   *  - parent_level: The number of times parentNode must be called to reach 
the parent to hide.
+   *                  Recommended values: 6 for embedded form, 7 for merged 
form
+   *
+   * @param array  $options     An array of options
+   * @param array  $attributes  An array of default HTML attributes
+   *
+   * @see sfWidgetFormInput
+   */
+  public function __construct($options = array(), $attributes = array())
+  {
+    parent::__construct($options, $attributes);
+    
+    if ($this->getOption('hide_parent'))
+    {
+      $hideParentCode = 'this' . str_repeat('.parentNode', 
$this->getOption('parent_level')) . '.style.display="none";';
+    }
+    else
+    {
+      $hideParentCode = '';
+    }
+    if ($this->getOption('alert_text'))
+    {
+      $this->setAttribute('onclick', sprintf('if(confirm("%s")) { %s } else 
return false;', $this->translate($this->getOption('alert_text')), 
$hideParentCode));
+    }
+    else
+    {
+      $this->setAttribute('onclick', $hideParentCode));
+    }
+  }
+
+  protected function configure($options = array(), $attributes = array())
+  {
+    parent::configure($options, $attributes);
+
+    $this->addOption('alert_text', 'Are you sure you want to delete this 
item?\nThe deletion will be complete once the form is saved.');
+    $this->addOption('hide_parent', true);
+    $this->addOption('parent_level', 6);
+  }
+}
\ No newline at end of file


Property changes on: 
plugins/sfPropel15Plugin/trunk/lib/widget/sfWidgetFormDelete.class.php
___________________________________________________________________
Added: svn:executable
   + *
Added: svn:keywords
   + "Id Rev Revision Author"
Added: svn:eol-style
   + native

-- 
You received this message because you are subscribed to the Google Groups 
"symfony SVN" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/symfony-svn?hl=en.

Reply via email to