Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "onetomany"

Many-to-many and one-to-many autocompletition

In here I will be using sfWidgetFormJQueryAutocompleterMany ( http://forum.symfony-project.org/index.php/m/77584/#msg_77462 ) to ease the relating of an object to other objects (hence keywords/tags/recepients... management becomes heaven based on non religious views).

The ORM used is Doctrine.

For this to make any sense I will tie the snippet to a quick tutorial like explanation. In the blog tutorials spirit I will now implement the widget into blog post tags management.

Getting the widget

Currently you can download it from the forum thread linked above. If let it will become part of sfFormExtraPlugin ( http://www.symfony-project.org/plugins/sfFormExtraPlugin ). If you find any annoyances / bugs just let me know.

Scheme

tableName: blog_post
 
  actAs:
    Timestampable:
    Sluggable: { fields: [title]}
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
 
  columns:
    id:
      type: integer
      primary: true
      autoincrement: true
 
    title:
      type: string(255)
      notnull: true
 
    content:
      type: clob
      notnull: true
 
    excerpt:
      type: clob
      notnull: true
 
  relations:
    Tags:
      class: Tag
      local: blog_post_id
      foreign: tag_id
      type: many
      foreignType: many
      foreignAlias: BlogPosts
      refClass: BlogPostTag
 
#------------------- Connection for publication -> tags  --------------------
BlogPostTag:
 
  tableName: blog_post_tag
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
  columns:
    blog_post_id:
      type: integer
      primary: true
    tag_id:
      type: integer
      primary: true
 
#------------------- Tag --------------------
Tag:
 
  tableName: tag
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
 
  actAs:
    Sluggable: { fields: [name]}
 
  columns:
    id:
      type: integer
      primary: true
      autoincrement: true
    name:
      type:  string(255)
 

For the applications depending on i18n.

tableName: blog_post
 
  actAs:
    Timestampable:
    I18n:
      fields: [title, content, excerpt]
      actAs:
        Sluggable: { fields: [title], uniqueBy: [lang, title] }
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
 
  columns:
    id:
      type: integer
      primary: true
      autoincrement: true
 
    title:
      type: string(255)
      notnull: true
 
    content:
      type: clob
      notnull: true
 
    excerpt:
      type: clob
      notnull: true
 
  relations:
    Tags:
      class: Tag
      local: blog_post_id
      foreign: tag_id
      type: many
      foreignType: many
      foreignAlias: BlogPosts
      refClass: BlogPostTag
 
#------------------- Connection for publication -> tags  --------------------
BlogPostTag:
 
  tableName: blog_post_tag
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
  columns:
    blog_post_id:
      type: integer
      primary: true
    tag_id:
      type: integer
      primary: true
 
#------------------- Tag --------------------
Tag:
 
  tableName: tag
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
 
  actAs:
    I18n:
      fields: [name]
      actAs:
        Sluggable: { fields: [name], uniqueBy: [lang, name] }
 
  columns:
    id:
      type: integer
      primary: true
      autoincrement: true
    name:
      type:  string(255)
 

Adding the widget to a form

Similar, if not identical, use as sfWidgetFormJQueryAutocompleter, applying a renderer class by doing so: /lib/form/doctrine/BlogPostForm.class.php

class BlogPostForm extends BaseBlogPostForm
{
    public function configure()
    {
        $autocompleteWidget = new sfWidgetFormChoice(array(
          'multiple'         => true,
          'choices'          => $this->getObject()->getTags(),
          'renderer_class'   => 'sfWidgetFormJQueryAutocompleterMany',
          'renderer_options' => array(
            'config' => '{
                json_url: "'.sfContext::getInstance()->getController()->genUrl('tag/autocomplete').'",
                json_cache: true,
                filter_hide: true,
                filter_selected: true,
                maxshownitems: 8        
              }')
        ));
        $this->widgetSchema['tags_list'] = $autocompleteWidget;
    }
}
 

If you support more than one languages / i18n use this set up instead:

class BlogPostForm extends BaseBlogPostForm
{
    public function configure()
    {
        $autocompleteWidget = new sfWidgetFormChoice(array(
          'multiple'         => true,
          'choices'          => $this->getObject()->getTags(),
          'renderer_class'   => 'sfWidgetFormJQueryAutocompleterMany',
          'renderer_options' => array(
            'config' => '{
                json_url: "'.sfContext::getInstance()->getController()->genUrl('tag/autocomplete').'",
                json_cache: true,
                filter_hide: true,
                culture: "'.$this->getCulture().'",
                filter_selected: true,
                maxshownitems: 8        
              }')
        ));
        $this->widgetSchema['tags_list'] = $autocompleteWidget;
    }
    protected function getCulture() {
        return isset($this->options['culture']) ? $this->options['culture'] : sfContext::getInstance()->getUser()->getCulture();
    }
}
 

While browsing a website you could have opened several windows/tabs and change the language in one of them. Then the session for the culture is changed. If the page with the autocompleter is not the one with the changed language the new suggestions will be made for the new language which is wrong. To maintain persistent culture before form submission use the method provided plus the configuration option for the javascript of the widget "culture".

Wiring to an action

Our module is Tag, our action: autocomplet stating this: /modules/tag/actions/actions.class.php

public function executeAutocomplete(sfWebRequest $request)
    {
      $this->getResponse()->setHttpHeader('Content-Type','application/json; charset=utf-8');
 
      $tags = Tag::retrieveSuggestions($request->getParameter('q'), $request->getParameter('l'),$request->getParameter('c'));
 
      return $this->renderText(json_encode($tags));
    }
 

Retrieve results

Our model method used here is retrieveSuggestions : /lib/model/doctrine/Tag.class.php

static public function retrieveSuggestions($q, $l,$c)
  {
  $tags = Doctrine_Query::create()
    ->select('t.*,LOCATE(:token_raw,t.name) AS index')
    ->from('Tag t')
    ->where('t.name LIKE :token')
    ->orderBy('index')
    ->limit($l)
    ->execute(array('token_raw' => $q , 'token' => '%'.$q.'%'));
 
    $jsonTags = array();
    foreach ($tags as $tag)
    {
      $jsonTags[] =array('caption' => (string) $tag->Translation[$culture]->name,'value'=> $tag->getPrimaryKey()) ;
    }
    return $jsonTags;
  }
 

And again for the i18ned applications:

static public function retrieveSuggestions($q, $l, $c)
  {
  $culture = ($c!=null) ? $c : sfContext::getInstance()->getUser()->getCulture();
  $tags = Doctrine_Query::create()
    ->select('t.id,tr.name,LOCATE(:token_raw,tr.name) AS index')
    ->from('Tag t')
    ->leftJoin('t.Translation tr')
    ->where('tr.lang = :culture AND tr.name LIKE :token')
    ->orderBy('index')
    ->limit($l)
    ->execute(array('culture' => $culture,'token_raw' => $q , 'token' => '%'.$q.'%'));
 
    $jsonTags = array();
    foreach ($tags as $tag)
    {
      $jsonTags[] =array('caption' => (string) $tag->Translation[$culture]->name,'value'=> $tag->getPrimaryKey()) ;
    }
    return $jsonTags;
  }
 

The important part here is that you return array with pair of keys:

If you omit key names or use numerical keys it won't work.

The beloved end.

by Anton Stoychev on 2009-04-30, tagged autocomplete  manytomany  onetomany  widget