AJAX/AXAH

symfony: How to build a dojo dijit autocompleter sfForm widget

I was searching long time for a good auto completer widget, but I couldn’t find one. So I created it by my self.
I did the same with the build in symfony auto completer widget. When I have time I’ll post it, too.
I hope I could help someboby with this small tutorial. Please give me feedback if there are any questions or suggestions.

Now lets start:

1. Copy the dojo package to the js folder
Resulting path to dojo.js: web/js/dojoToolkit/dojo/dojo.js

2. Enable dojo.js
If not yet happend elsewhere:
Modify the view.yml: apps/[YOUR_APP]/config/view.yml

default:
javascripts: [dojoToolkit/dojo/dojo.js]

3. Create the widget
lib/widget/itWidgetFormInputTextDijitAutocomplete.php

class itWidgetFormInputTextDijitAutocomplete extends sfWidgetForm
{
/**
* Constructor.
*
* Available options:
*
* * model: Name of the Model
* * searchfield: Table field in which will be searched
* * action: Optional: request action or url
* * value_method: Optional: getter for form field value
* * output_method: Optional: getter for form field name
* * criteria_method: Optional: getter for additional cirteria
* * add_empty: Optional: add emtpy value option to the list
* * choices: An array of possible choices (n/a)
* * formatter: A callable to call to format the checkbox choices
*
* @see sfWidgetForm
*/
protected function configure($options = array(), $attributes = array())
{
$this->addRequiredOption('model');
$this->addRequiredOption('search_field');
$this->addOption('action', 'default/ajaxAutocomplete');
$this->addOption('value_method', 'getPrimaryKey');
$this->addOption('output_method', 'getPrimaryKey');
$this->addOption('choices', array());
$this->addOption('criteria_method', '');
$this->addOption('formatter', array($this, 'formatter'));
$this->addOption('add_empty', false);
}

/**
* Renders the widget as HTML.
*
* @param string The name of the HTML widget
* @param mixed The value of the widget
* @param array An array of HTML attributes
* @param array An array of errors
*
* @return string A HTML representation of the widget
*
* @see sfWidgetForm
*/
public function render($name, $value = null, $attributes = array(), $errors = array())
{
$choices = $this->getOption('choices');
if ($choices instanceof sfCallable)
{
$choices = $choices->call();
}

$baseAttributes = array(
'action' => $this->getOption('action'),
'name' => $name,
'type' => 'text',
'value' => $value,
'id' => $id = $this->generateId($name, self::escapeOnce($name)),
);

return call_user_func($this->getOption('formatter'), $this, $baseAttributes);
}

/**
* Renders the input fields as HTML.
*
* @param widget
* @param attributes
*
* @return string A HTML representation of the input fields
*/
public function formatter($widget, $attributes)
{
sfLoader::loadHelpers(array('Form', 'Url'));
$request = sfContext::getInstance()->getRequest();
$field_id = $attributes['id'];
$field_name = $attributes['name'];
$ajax_field_id = 'ajax_'.$attributes['id'];
$out_field_id = 'out_'.$attributes['id'];
$object = @call_user_func(array($this->getOption('model').'Peer', 'retrieveByPk'), $attributes['value']);
$default_out = $request->getParameter('ajax_'.$attributes['name'], is_object($object) ? call_user_func(array(&$object, $this->getOption('output_method'))) : '');
$default_value = $request->getParameter($attributes['name'], is_object($object) ? $attributes['value'] : '');

$html = '';
$ajaxParams=array(
'model' => $this->getOption('model'),
'value_method' => $this->getOption('value_method'),
'output_method' => $this->getOption('output_method'),
'search_field' => $this->getOption('search_field'),
'criteria_method' => $this->getOption('criteria_method'),
'field_name' => 'ajax_'.$attributes['name']
);
if($this->getOption('add_empty')===false) $ajaxParams['add_empty'] = 'false';
elseif($this->getOption('add_empty')===true) $ajaxParams['add_empty'] = 'true';
else $ajaxParams['add_empty'] = $this->getOption('add_empty');

$requestUrl = url_for($attributes['action'].'?'.http_build_query($ajaxParams), true);
$requestUrl = str_replace('frontend_dev.php/', '', $requestUrl);
$requestUrl = str_replace('backend_dev.php/', 'backend.php/', $requestUrl);

$html .= <<
<script type="text/javascript">
dojo.require("dojo.data.ItemFileReadStore");
dojo.require("dijit.form.FilteringSelect");
</script>
<div dojoType="dojo.data.ItemFileReadStore"
jsId="{$field_id}Store" url="{$requestUrl}"></div>
EOF;

$input_text = <<
<input dojoType="dijit.form.FilteringSelect"
store="{$field_id}Store"
class="medium"
id="{$field_id}"
name="{$field_name}"
value="{$default_value}"
hasDownArrow="true",
invalidMessage="",
ignoreCase="true" />
}

4. Create action for the AJAX request
apps/[APP_NAME]/modules/default/actions/actions.class.php

<?php
class defaultActions extends sfActions
{
public function executeAjaxAutocomplete($request)
{
$model = $request->getParameter('model');
$criteria_method = $request->getParameter('criteria_method');
$add_empty = $request->getParameter('add_empty', 'false');
$this->value_method = $request->getParameter('value_method');
$this->output_method = $request->getParameter('output_method');

$peer_object = $model.'Peer';

if($criteria_method!='') $criteria = call_user_func(array($peer_object, $criteria_method));
else $criteria = new Criteria();

$search_field = $request->getParameter('search_field');
$field_name = $request->getParameter('field_name');
$var = $request->getParameter($field_name);

$j_object = array('identifier' => 'abbreviation', 'items'=>array());

$search_field = strtoupper($search_field);

$sql_field = constant($peer_object.'::'.$search_field);

$c = $criteria;
$c->add($sql_field, $var . '%', Criteria::LIKE);
$this->list = call_user_func(array($peer_object,'doSelect'), $c);

if($add_empty!='false')
{
$j_object['items'][] = array(
'name' => $add_empty=='true' ? '' : $add_empty,
'label' => '',
'abbreviation' => ''
);
}

foreach($this->list AS $v)
{
$j_object['items'][] = array(
'name' => call_user_func(array(&$v, $this->output_method)),
'label' => call_user_func(array(&$v, $this->output_method)),
'abbreviation' => call_user_func(array(&$v, $this->value_method))
);
}
$this->j_object = $j_object;
}
}

5. Create template for the AJAX request
apps/[APP_NAME]/modules/default/templates/ajaxAutocompleteSuccess.php

<?php decorate_with(false); ?>
<?php print_r(json_encode($j_object)); ?>

6. Insert the new widget into your sfForm
apps/[APP_NAME]/modules/default/templates/ajaxAutocompleteSuccess.php

...
# $widgets['user_id'] = new sfWidgetFormInput();
$widgets['user_id'] = new stWidgetFormInputTextDijitAutocomplete(array(
'model' => 'User',
'search_field' => 'username',
'value_method' => 'getId',
'output_method' => 'getUsername',
'criteria_method' => 'getCriteriaRegisteredUsers'
));

6. Clear symfony cache

Links:

  • http://api.dojotoolkit.org/jsdoc/1.3.2/dijit.form.FilteringSelect

Funktion mit Parameter an event listener übergeben

Am meisten Kopfzerbrechen bereitete mir die Parameterübergabe beim Funktionsaufruf über einen eventListener. Ich musste erst unzählige Seiten im Netz durchforsten bis ich eine Lössung für mein Problem gefunden hatte.

Im folgenden liste ich die fehlgeschlagenen Ansätze auf gefolgt von der erfolgreichen Lösung:

[syntax,param_hand_over.js,javascript]

Links:
Prototype
Quick Guide To Prototype
Inoffizielle Prototype Dokumentation

wiki.script.aculo.us

1 Comment more...

Copyright © 1996-2010 iTopiaBlog. All rights reserved.
Jarrah theme by Templates Next | Powered by WordPress