Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "autocomplete"

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 

Add aditional parameters to Autocomplete

You can pass aditional parameters to input_auto_complete_tag helper. By default, autocompleter pass only object-referer value. For aditional parameter you need use 'with' in $completion_options array. in view layer:

<
<?php echo object_input_tag($model, 'getFilter', array ('size' => 20, 'control_name' => 'filter',));?>
?php echo input_auto_complete_tag('field_to_search','','module/autocomplete', array('autocomplete'=>'off'),array('use_style'=>true,'with'=> " value+'&filter='+$('filter').value"))?>

you must put '" value' (with whitespace) because javascript fail (a little symfony bug). Aditional parameters must go after value.

in control layer:

 public function executeAutocomplete(){
    $search=$this->getRequestParameter('field_to_search');
    $filter=$this->getRequestParameter('filter');
....
}

now you can use field_to_search and filter to construct any query in model layer ans show this in AutocompleteSuccess.php view or another view that you define. this was tested in synfony version > 0.9. Check your version, and review your javascripthelper helper to verify if your input_auto_complete_tag helper supports 'with' option

by Boris Duin on 2006-10-27, tagged ajax  autocomplete  javascript 

Ajax Autocomplete - how to trigger an event after selecting an option

The Problem

The Ajax helper function input_auto_complete_tag() is great stuff. You might have seen something comparable in Google Suggest. However, you might encounter some limitations when using it. Just imagine, you want to use it as a search field. Once you found the desired keyword, you might want to retrieve the whole record related to it. Unfortunately, after selecting an entry, the input field contains a search string, and mostly this is not unique in your database. As a consequence, you want to use the id of the record to perform the database select.

The Solution

First of all we need to have a technique to unambiguously identify all options of the AutoComplete field:

<ul>
<?php foreach ($object as $key => $value): ?>
  <li id="<?php echo $key ?>"><?php echo $value ?></li>
<?php endforeach; ?>
</ul>

It's simple: We just add an id attribute to the list element. Note that this example uses an associative array containing the unique id as key and the name (appearing as option) as value.

The trouble is now: how to access the id attributes of the list elements?

The input_auto_complete_tag() function can take an option called 'after_update_element' as a hook for a user-defined function. This receives 2 parameters:

(1) the autocompletion input field
(2) the selected item

For more Details take a glance at the documentation: http://wiki.script.aculo.us/scriptaculous/show/Ajax.Autocompleter

By including this option in our input_auto_complete_tag() function, we have the right tool for accessing the list element and doing nice things:

<?php echo form_remote_tag(array(
      'url'    => 'yourModule/calledAction',
      'update' => 'resultarea',
      'loading'  => "Element.show('indicator')",
      'complete' => "Element.hide('indicator')",
    )) ?>
    <?php echo input_hidden_tag('unique_id', '') ?>
    <?php echo input_auto_complete_tag('phrase', '', 'yourModule/autoCompleteAction', array('autocomplete' => 'off', 'size' => '30'), array('use_style' => 'true', 'after_update_element' => "function (inputField, selectedItem) { $('unique_id').value = selectedItem.id; }"));   
echo submit_tag('Go') ?>
</form>

In the function definded for 'after_update_element' we set the unique_id attribute of the previously definded hidden field by accessing the 2nd parameter. This hidden field will then provide the unique_id within the target action.

by Sascha Wiener on 2006-07-21, tagged ajax  autocomplete  event 
(3 comments)