Symfony III. rész – beágyazott form
A symfony beágyazott (embedded) form megoldásáról szóló kis szösszenet kerül ide.
A segítségével két, master-detail kapcsolatban álló tábla egyidejű módosítását lehet megvalósítani.
Itt is az első leírásban létrehozott sandbox projektünket fogjuk átszerkeszteni.
Tehát állítsuk be a config könyvtárban lévő adatbázisokhoz tartozó fájlokat (propel.ini és databases.yml) az előzőleg létrehozott sandbox adatbázisra. Majd szerkesszük meg a config/schema.yml fájlt:
[code language=”sql”]
propel:
  _attributes:
    package: lib.model
    defaultIdMethod: native
  osztaly:
    _attributes: { phpName: Osztaly }
    id: { phpName: Id, type: INTEGER, size: ’11’, primaryKey: true, autoIncrement: true, required: true }
    kod: { phpName: Kod, type: CHAR, size: ‘1’, required: true }
    evfolyam: { phpName: Evfolyam, type: INTEGER, size: ’11’, required: true }
  tanulo:
    _attributes: { phpName: Tanulo }
    id: { phpName: Id, type: INTEGER, size: ’11’, primaryKey: true, autoIncrement: true, required: true }
    name: { phpName: Name, type: VARCHAR, size: ‘255’, required: true }
    szuletes: { phpName: Szuletes, type: DATE, required: true }
    osztaly_id: { phpName: OsztalyId, type: INTEGER, size: ’11’, required: false, foreignTable: osztaly, foreignReference: id, onDelete: CASCADE, onUpdate: CASCADE }
    _indexes: { category_id: [osztaly_id] }
[/code]
Majd futtassuk le az alábbi parancsokat is:
[code language=”bash”]
# hozzuk létre a formokat, filtereket, modeleket, és a táblákat is generáljuk le
# a kérdésre, miszerint kívánjuk-e hogy felülírja az adatbázisunkat válaszoljunk igennel (Y)
./symfony propel:build-all
# és a két táblánkhoz tartozó admin felületet készítsük el:
./symfony propel:generate-admin frontend osztaly
./symfony propel:generate-admin frontend tanulo
[/code]
Szükségünk lesz egy pluginra, a sfJqueryReloadedPluginra. A telepítése nem túl bonyolult. Ha az összes plugint engedélyeztük (a config/ProjectConfiguration.class.php fájlban), akkor elég bemásolni a plugin könyvtárba. Mivel a letöltött tömörített fájlban mindenféle kódszámot ír a könyvtárnév végére, így a könnyebb elérhetőség miatt használjuk inkább a sfJqueryReloadedPlugin könyvtárnevet.
A lib/model/Osztaly.php fájlt pedig egészítsük ki az alábbi metódussal
[code lang=”php”]
public function __toString()
{
  return $this->evfolyam.’/’.$this->kod;
}
[/code]
Az apps/frontend/config/routing.yml fájlt is módosítsuk, hogy az osztályok listája jelenjen meg alapértelmezetten.
[code lang=”php”]
homepage:
  url:   /
  param: { module: osztaly, action: index }
[/code]
Ezzel az alap projektünk elkészült. Lehet listázni, új osztályt felvinni és módosítani. Azonban az osztályhoz még nem tudunk tanulót rögzíteni. Ehhez módosítsuk a lib/form/OsztalyForm.class.php fájlt.
Ez egy hosszabb lélegzetvétel lesz, mert nem aprózom el 🙂
[code lang=”php”]
class OsztalyForm extends BaseOsztalyForm
{
   /**
   * a form konfiguralasa
   * hozzaadjuk a tanulokat a formhoz
   * hozzadjuk a hozzaadas gombot (Uj) letrehozo widgetet is.
   */
   public function configure()
  {
    parent::setup();
    $context = sfContext::getInstance();
    $this->loadHelpers($context);
$tanulok = $this->getObject()->getTanulos();
    //Egy ures formba eltaroljuk az embeddelt TanuloFormokat
    $this->embedForm(‘tanulos’, new sfForm());
    $count = 0;
    foreach ($tanulok as $tanulo) {
      $this->addTanulo($count, $tanulo);
      $count ++;
    }
    // a hozzaadas gomb mint egy uj mezo kerul a formunkba
    $this->widgetSchema[‘addEmbedform’] = new embedderFormWidget(array(
        ‘url’=>url_for(‘osztaly/getEmbedForm’),
        ‘html_element’ => ".sf_admin_form_field_tanulos table:first",
        ));
    $this->widgetSchema->setLabel(‘addEmbedform’, false);
  }
  /**
   * kulonbozo helperek betoltese
   * Url helper az ajax hivashoz hasznalando helyes url megadasahoz
   * jQueryReloadedPlugin jQuery helpere
   * es a jQuery UI plugin javascript betoltese
   *
   * @param sfContext $context
   */
  public function loadHelpers($context)
  {
    $context->getConfiguration()->loadHelpers(array(‘jQuery’,’Tag’,’Url’));
    $context->getResponse()->addStyleSheet(sfConfig::get(‘sf_jquery_web_dir’, ‘/sfJqueryReloadedPlugin’) . "/css/ui-lightness/jquery-ui-1.8.2.custom.css");
    jq_add_plugins_by_name(array(‘ui’));
  }
  /**
   * A torles link a Tanulo mellett
   *
   * @param string $index
   * @return string
   */
  public function getEmbedFormLabel($index)
  {
    return jq_link_to_function(
      ‘<span class="ui-icon ui-icon-trash"></span>’,
      "jQuery(this).parents(‘tr:first’).remove(); jQuery(‘#osztaly_tanulos_".$index."_id’).remove();",
      array(‘confirm’ => ‘Sure???’));
  }
  /**
   * A Tanulo hozzaadasa az Osztalyhoz
   *
   * Erre akkor is szuksegunk lesz, amikor egy uj tanulot veszunk fel
   *
   * @param string $num
   * @param Tanulo $tanulo
   */
  public function addTanulo($num, $tanulo = null)
  {
    if (!$tanulo)
    {
      $tanulo = new Tanulo();
    }
    $tanulo->setOsztaly($this->getObject());
    $form = new TanuloForm($tanulo);
    $form->getWidgetSchema()->offsetUnset(‘osztaly_id’);
    $this->embeddedForms[‘tanulos’]->embedForm($num, $form);
    // torles link
    $label = $this->getEmbedFormLabel($num);
    $this->embeddedForms[‘tanulos’]->widgetSchema->setLabel($num,$label);
    $this->embedForm(‘tanulos’, $this->embeddedForms[‘tanulos’]);
  }
  /**
   *
   * Ha rogziteskor uj tanulot vettunk fel, akkor adjuk hozza az osztalyhoz
   * Ha pedig toroltunk akkor tavolitsuk el a formbol
   */
  public function bind(array $taintedValues = null, array $taintedFiles = null)
  {
    if (isset($taintedValues[‘tanulos’]))
    {
      foreach($taintedValues[‘tanulos’] as $key => $tanulo)
      {
        if (!isset($this[‘tanulos’][$key]))
        {
          $this->addTanulo($key);
        }
      }
    }
    foreach ($this->embeddedForms[‘tanulos’] as $key=>$form)
    {
      if (!isset($taintedValues[‘tanulos’][$key]) && $key<>’_csrf_token’)
      {
        $this->getWidgetSchema()->offsetGet(‘tanulos’)->offsetUnset($key);
        $this->getValidatorSchema()->offsetGet(‘tanulos’)->offsetUnset($key);
        unset(
          $taintedValues[‘tanulos’][$key],
          $taintedFiles[‘tanulos’][$key]);
      }
    }
    parent::bind($taintedValues, $taintedFiles);
  }
  /**
   * Tanulonak beallitjuk az osztalyat
   *
   * Amikor egy uj osztallyal egyutt viszunk fel egy tanulot, akkor
   * meg kell adnunk a tanulonak is, hogy melyik osztalyba tartozik.
   * Raadasul eltavolitottuk a tanulo from osztaly_id mezojet, így minden
   * egyeb esetben is szukseg van erre.
   *
   */
  public function saveEmbeddedForms($con = null, $forms = null)
  {
    $oszaly = $this->getObject();
    if ($forms)
    {
      foreach ($forms as $form)
      {
        if ($form instanceof sfFormObject)
        {
          $form->getObject()->setOsztaly($oszaly);
        }
      }
    }   
    return parent::saveEmbeddedForms($con, $forms);
  }
  /**
   * Ezt az osztalyfuggvenyt bovitjuk. A kikeressuk a postolt adatokbol a  tanulok
   * azonositojat (id). Amelyik tanulo azonositoja nincs benne a kapott eredmenyben,
   * toroljuk az osztalybol.
   */
  public function updateObjectEmbeddedForms ($values, $forms = null)
  {
    // $values[‘tanulos’][][id] elemeket, ahol id>0
    $ids = set::extract(‘/tanulos[id>0]/id’, $values);
    if (!$forms)
    {
      $c = new Criteria();
      $c->add(TanuloPeer::OSZTALY_ID, $this->getObject()->getId());
      $c->add(TanuloPeer::ID, $ids, Criteria::NOT_IN);
      TanuloPeer::doDelete($c);
    }
    return parent::updateObjectEmbeddedForms ($values, $forms);
  }
}
[/code]
Három dolgot használtunk fel a formban, amit még el kell készítenünk.
Egy widget, ami létrehozza a tanuló hozzáadása linket. Ezt embedderFormWidget néven neveztem el és a lib/widget mappába tegyük. Ez a mappa még nem létezik, létre kell hozni. Ebbe hozuk létre a embedderFormWidget.class.php fájlt:
[code lang=”php”]
<?php
/**
 * @author     nova
 * @version    1.0.0
 */
class embedderFormWidget extends sfWidgetForm
{
  /**
   * Configures the current widget.
   *
   * Available options:
   *
   * @param string html_element – az elem, amihez hozzafuzunk
   * @param string url – az ajay keres url-je
   *
   * @see sfWidgetForm
   */
  protected function configure($options = array(), $attributes = array())
  {
    $this->addOption(‘html_element’);
    $this->addOption(‘url’);
    parent::configure($options, $attributes);
  }
  /**
   * @param  string $name        The element name
   * @param  string $value       The date displayed in this widget
   * @param  array  $attributes  An array of HTML attributes to be merged with the default HTML attributes
   * @param  array  $errors      An array of errors for the field
   *
   * @return string An HTML tag string
   *
   * @see sfWidgetForm
   */
  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    $attributes = $this->getAttributes();
    $html =jq_link_to_function(
        ‘<span class="ui-icon ui-icon-circle-plus fright"></span><span class="fright">Új</span>’,
        "jQuery.get(‘".$this->getOption(‘url’)."’,function(data){
            jQuery(‘".$this->getOption(‘html_element’)."’).append(data);
        });",
        $attributes);
    return $html;
  }
}
[/code]
Aztán gondoskodnuk kell az ajax hívás válaszáról a szerver oldalon. Módosítsuk az apps/fronntedn/modules/osztaly/actions/actions.class.php fájlt az alábbi metódussal:
[code lang=”php”]
  /**
   * Visszaad egy új sort (widgetet) a tanulo formhoz
   *
   * @param sfWebRequest $request
   */
  public function executeGetEmbedForm(sfWebRequest $request)
  {
    $form = $this->configuration->getForm();
    $num = ‘tmp_’.time();
    $form->addTanulo($num);
    return $this->renderText($form->getWidget(‘tanulos’)->getWidget($num)->render($num));
  }
[/code]
Ezen kívül még felhasználtam egy konkurens MVC rendszerben található igencsak hasznos osztályt, a
CakePHP set osztályát
Ezt persze módosítanom kellett, hogy szeresse a Symfony is. Ide tettem fel.
Letölthetővé tettem a teljes projektet is, innen.
Sajnos annyi energiám nem volt hogy lépésről lépésre építsem fel a projektet, remélem így is hasznosnak találja, aki elolvassa. Nekem meg jó jegyzet 🙂
Symfony III. rész – beágyazott form bejegyzéshez a hozzászólások lehetősége kikapcsolva
