Visualizzazione post con etichetta ajax. Mostra tutti i post
Visualizzazione post con etichetta ajax. Mostra tutti i post

mercoledì, febbraio 13, 2013

Yii: click on a link, post your request

In a RESTful application written with Yii, I have an action that fixes some data in the db (like, for instance, the width and the height of an image), by checking some extra sources (the file). I needed to show a link that, when clicked, fired a POST request, and not a GET one. The obvious solution was to find out how to do it with some ajax call, but I wanted to see what Yii gave me.

I was happy to find out that Yii's CHtml::link() function already has everything needed. In particular, just adding 'submit'=>true in the $htmlOptions array, like in

<p><?php echo CHtml::link(
    'Fix picture',
    $url=CHtml::normalizeUrl(array('picture/fix','id'=>$model->id)),
    array(
      'submit' => $url,
      'title' => 'Check real size and type and fix data base entries',
      'csrf'=>true,
    )
  )
?></p>

is enough to produce a nice unobtrusive javascript code, like the following:
(in the body)

<p><a title="Check real size and type and fix data base entries" href="/yii/photoalbum/index.php?r=picture/fix&amp;id=6" id="yt0">Fix picture </a></p>

(in the jquery code produced, at the end of the page)

jQuery('body').on('click','#yt0',function(){jQuery.yii.submitForm(this,'/yii/photoalbum/index.php?r=picture/fix&id=6',{'YII_CSRF_TOKEN':'168375210910867a3dfab3db8750c4ad5f5aee2a'});return false;});

I needed to set csrf to true because my main config file has CRSF protection enabled.
I wanted the application to work even if javascript is disabled (Progressive Enhancement), so I prepared a fix.php view anyway, with a standard form containing only the submit button, like here:
<?php $form=$this->beginWidget('CActiveForm'); ?>
    <div class="row submit">
        <?php echo CHtml::submitButton('Fix'); ?>
    </div>
<?php $this->endWidget(); ?>
The form won't be shown if users have their javascript enabled.

sabato, febbraio 02, 2013

Full example of Ajax-powered text field with Yii

Some days ago I needed to transform a simple text field into an AJAX-powered widget, so that values could be obtained from asyncronous queries to the webserver.

I used a widget from the framework itself, zii.widgets.jui.CJuiAutoComplete, but I had to figure out how to actually use it. I will recap here what I did.

Imagine you have a "tag" field, used to add tags to something. In your view, replace your ordinary widget:

<?php echo $form->textField($tagform,'tag'); ?>

with the new one, AJAX-powered:
<?php $this->widget('zii.widgets.jui.CJuiAutoComplete', array(
  'id'=>'tag',
  'name'=>'PictureAddTagForm[tag]',
  'source'=>$this->createUrl('tag/suggest'),
   'options'=>array(
    'delay'=>500,
    'minLength'=>2,
    ),
  'htmlOptions'=>array(
     'size'=>'20'
     ),
  ))
?>

The "options" array allows you to define a delay after which the AJAX call will be executed, and a minimal number of characters to wait.
Since the source for data is defined as 'tag/suggest', you'll need to define an action in the tag controller:


/**
 * Serves a list of suggestions matching the term $term, in form of
 * a json-encoded object.
 * @param string $term the string to match
 */
public function actionSuggest($term='')
{
  $this->serveJson(Tag::model()->findMatches($term));
}

This action will call a custom function that sends the HTTP header and the JSON-encoded list of tags. The parameter's name, that we'll refer to as $term, is hardcoded in the underlying jqueryui autocomplete widget.

The custom function must be written in the controller or, even better, in the Controller class, since this way it is shared with the other controllers:
// in protected/components/Controller.php
/**
 * Serves an object as a json-encoded string via HTTP.
 * @param string $object the object to send
 */  
public function serveJson($object)
{
  $this->serveContent('application/json', CJSON::encode($object), false);
}

/**
 * Serves a content via HTTP.
 * @param string $type the Internet Media Type (MIME) of the content
 * @param string $content the content to send
 */  
public function serveContent($type, $content)
{
  $this->_serve($type, $content, false);
}

/**
 * Serves a file via HTTP.
 * @param string $type the Internet Media Type (MIME) of the file
 * @param string $file the file to send
 */  
public function serveFile($type, $file)
{
  $this->_serve($type, $file, true);
}

/**
 * Serves something via HTTP.
 * @param string $type the Internet Media Type (MIME) of the content
 * @param string $content the content to send
 * @param boolean $is_file whether the content is a file
 */  
private function _serve($type, $content, $is_file=false)
{
  header("Content-Type: " . $type);
  if ($is_file)
  {
    readfile($content);
  }
  else
  {
    echo $content;
  }
  Yii::app()->end();
}
Now, it's the model's job to get the data that match the term.

/**
 * Retrieves tags that match a term.
 * @param string $term a term to use for matching tags.
 * @return array tags titles that match the term.
 */
public function findMatches($term='')
{
  if(''==$term)
  {
   return array();
  }
  $q = new CDbCriteria();
  $q->addSearchCondition('title', $term);
  $q->select=array('title');
  $tags = self::model()->findAll($q);

  $results=array();
  foreach($tags as $tag)
  {
    $results[]=$tag->title;
  }
  return $results;
}



This function will retrieve from the table all the rows that match the criteria, and return an array with the values actually needed.