PHP

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

PHP: String nach Wortende kürzen

Das Problem hatte vielleicht schon manch einer. Hier eine Lösung:

function getShortTextByWord($string, $len)
{
$string = substr($string, 0, $len);
$string_end = strrpos($string, ' ');
if($string_end) $string = substr($string, 0, $string_end);
return $string;
}


PHP, RegExp: Match exact string with preg_replace

Kurz um, um einen exakten String zu ersetzten, muss man preg_replace mit dem “\b” – Parameter (word boundary) benutzen:

$new_string = preg_replace('/\b'.$exact_string.'\b/', $replace_with);

Ich kannte es sonst nur so:

$new_string = preg_replace('^'.$exact_string.'$', $replace_with);

Das warf bei mir aber immer den Fehler:

Warning: preg_replace() [function.preg-replace]: No ending delimiter '^' found in /home/....php on line ...

Einige werden sagen, warum macht er das denn nicht mit dem viel performanteren str_replace?
Ich nehme preg_replace, weil ich in dem übergebenen String den zu ersetzenden Teilstring nur einmal ersetzen will.
Als dritten Parameter kann man angeben wie oft der Teilstring ersetzt werden soll.

$new_string = preg_replace('/\b'.$exact_string.'\b/', $replace_with,1);

Wenn es case insensitive sein soll einfach ein “i” anhängen:

$new_string = preg_replace('/(\b'.$exact_string.'\b)/i', $replace_with);

Edit:

Plötzlich tauchte diese Warnung bei der Übergabe eines ganz normalen Strings auf:

preg_replace() [function.preg-replace]: Unknown modifier 't'

Abhilfe schaffte die Änderung der Delimiter von Slash (/) auf Pipe (|):

$new_string = preg_replace('|(\b'.$exact_string.'\b)|i', $replace_with);


JavaScript: unterminated string literal

Was hab ich mich damit rum geschlagen und kein einziges Suchergebnis bei g* konnte mir die Lösung bieten.
Kurz zum Problem. Ich lese mit PHP Text aus einer MySQL-Datenbank, welcher auch Zeilenumbrüche enthalten kann.
Diese Zeilenumbrüche werden mit

\n

oder

\r\n

codiert. Ich weiß, dass in JS Zeilenumbrüche in Strings verboten sind und wollte diese nun ersetzten. Dafür gibt es mich folgende Möglichkeiten:

nl2br brachte nichts und kann in der Ausführung auch nicht wirklich beeinflusst werden. Ok dann mit str_replace oder preg_replace.
Was ich versucht habe war nun folgendes:

$str = str_replace('\r\n','
',$str);
$str = str_replace('\r','
',$str);
$str = str_replace('\n','
',$str);

Nichts hat geholfen, der Fehler war noch da und der Umbruch war sogar noch im Quellcode zu sehen.
Dann fiehl mir Gott sei Dank ein, dass PHP einen Unterschied macht ob man SingleQuotes (‘) oder DoubleQuotes (“) benutzt. Code in SingleQuotes wird bspw. nicht nach PHP-Variablen geparst. Also versuchte ich es mal so:

$str = str_replace("\r\n","
",$str);

Und siehe da, die Sonderzeichen wurden wie gewünscht ersetzt und der JavaScript-Fehler unterminated string literal war verschwunden. Bitte. Danke.


PHP, MySQL: Datensatz innerhalb einer Tablle kopieren

Da es ja eigentlich nicht erlaubt ist einen Datensatz innerhalb einer Tabelle zu kopieren. Z.B. per

INSERT INTO tablle(Felder_ohne_Primary_Key) SELECT Felder_ohne_Primary_Key WHERE Primary_Key=Wert_des_zu_kopiernenden_Datensatzes

weil die Datenbank die Abfragen dann nicht mehr korrekt trennen kann, habe ich eine kleine Funktion geschrieben, welches dies, ohne die Regeln zu verletzten durchführt und die ID bzw. den Wert des Primary Keys des neuen Datensatzes zurückliefert:

[syntax,copy_mysql_dataset.php,php]

Man kann sicher hier und da ein paar Sachen einfacher programmieren, ich freue mich über Feedback.

1 Comment more...

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