Friday, December 31, 2010

The minimum necessary site structure to run Yii

The minimum files and folder structure necessary to run Yii:
/index.php
/protected/components/controller.php
/protected/config/main.php
/protected/controllers/SiteController.php
/protected/runtime/
/protected/views/site/index.php
The minimum necessary configuration file is (/protected/config/main.php):
return array('import'=>array('application.components.*'));
SiteController.php should have this method:
public function actionIndex()
{
  $this->render('index');
}
That's it!


If you want use a layout, add:
/protected/views/layouts/main.php
/protected/views/layouts/column1.php
To enable gii, you need to add the /assets folder, and add this to your config:
'modules'=>array(
  'gii'=>array(
    'class'=>'system.gii.GiiModule',
    'password'=>'password',
    ),
),
For Model and Crud to work, you need to setup a db connection.

Friday, December 17, 2010

A very simple JSON API

See also "How to output related values in JSON"

Let's assume you have a database table like this:
CREATE TABLE IF NOT EXISTS `tbl_cars` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`dealer_id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;
And you create a standard controller and model using Gii.

To make a very simple JSON web service API, add this to the controller (don't forget to change the access rules!):
public function actionGetCar($id)
{
  header('Content-type: application/json');

  $car = Cars::model()->findByPK((int)$id);

  echo CJSON::encode($car);

  Yii::app()->end();
}
And when you make a request like this /index.php/cars/getcar?id=3 (or like /getcar/id/3), you will get back a JSON formatted response, like this:
{"id":"3","dealer_id":"6","name":"honda"}
And now you have a real simple JSON API

Setting up internationalization (i18n)

In this example, the default language of the website is English (en), and we will add some French (fr) components.

1. In your application configuration (protected/config/main.php), set the sourceLanguage parameter to English:

return array(
...
, 'sourceLanguage'=>'en'
...
)

2. In a view, use the Yii::t() function to translate strings:

echo Yii::t('strings','the quick brown fox jumped over the lazy dog').'<br/>';

echo Yii::t('strings','you have {count} new emails', array('{count}'=>5)).'<br/>';

echo Yii::t('strings','n==1#one book|n>1#many books', array(2)).'<br/>';

The return value will default to the English value in the quotes, but if the appropriate translation file is found, then the translated value will be returned. Note the use of {parameters}. You can also format dates and numbers:

echo Yii::app()->dateFormatter->formatDateTime(time()).'<br/>';

echo Yii::app()->numberFormatter->formatDecimal(3.14);

3. Add a folder "fr" under /protected/messages, and a file called "strings.php". The content of strings.php should be this:

<?php

return array(
'the quick brown fox jumped over the lazy dog' => 'le renard marron agile saute par dessus le chien  paresseux'
, 'you have {count} new emails' => 'vous avez {count} nouveaux e-mails'
, 'n==1#one book|n>1#many books '=> 'n==1#un livre|n>1#de nombreux livres'
);
?>

It's an associative array where the first value is the key, and the second value is the translated version. It's easier just to use the English value as the key than to use a numbering system.

Then, you can set the language. This is best done in the beginRequest event handler based on user preferences, but here's how to do it manually:

Yii::app()->language='fr';

Here are the results in 'en':

the quick brown fox jumped over the lazy dog
you have 5 new emails
many books
Dec 17, 2010 11:27:23 PM
3.14

and 'fr':

le renard marron agile saute par dessus le chien paresseux
vous avez 5 nouveaux e-mails
de nombreux livres
17 déc. 2010 23:24:12
3,14

4. You can also create views specific to a language. Under protected/views/site/, create a 'fr' folder, and add a page index.php . This will be the French index page for the site, and can be entirely different from the English index page if you desire.

Friday, December 3, 2010

YII » On Many Many Relationships

1. If you have a composite primary key, make sure you override the primaryKey method in your model, like this:

public function primaryKey()
{
  return array('field_one', 'field_two');
}

2. Now, you cannot generate Gii CRUD operations when you have a composite primary key, but you can make a model and a controller and forms, so you can just do some of it manually.

You can do something like this in your controller, with a (blog) Posting table with many-many related tags:

public function actionDelete($id)
{
  $posting_id=$_GET['posting_id'];

  $pk = array('posting_id'=>$posting_id, 'tag_id'=>$id);
  PostingTag::model()->deleteByPk($pk);
}

YII » How to display a related HAS_MANY grid

In this example, we have a Cars table, as well as a Dealers table. A dealer has many cars.

I want to show the (single) Dealer view, but also all of the cars that belong to this dealer in a grid on the same page. Here's how:

In your Dealer model, you should have something like this:

public function relations()
{
return array('cars' => array(self::HAS_MANY, 'Cars', 'dealer_id'),);
}

In your view, you have to convert the related cars data to a CArrayDataProvider for it to work with the CGridview. You also need to adjust the button Urls in the CGridView.

In your Dealer view "view.php", below your CDetailView, add this:

$config = array();
$dataProvider = new CArrayDataProvider($rawData=$model->cars, $config);

$this->widget('zii.widgets.grid.CGridView', array(
    'dataProvider'=>$dataProvider
    , 'columns'=>array(
        'id'
        , 'name'
        , array(
            'class'=>'CButtonColumn'
            , 'viewButtonUrl'=>'Yii::app()->createUrl("/Cars/view", array("id"=>$data["id"]))'
            , 'updateButtonUrl'=>'Yii::app()->createUrl("/Cars/update", array("id"=>$data["id"]))'
            , 'deleteButtonUrl'=>'Yii::app()->createUrl("/Cars/delete", array("id"=>$data["id"]))'
            )
    )
));

Note that the viewButtonUrl is a PHP expression, but it's quoted. The $data field is the name of the row object inside the grid.

Thursday, December 2, 2010

YII » Using JQuery UI Dialog Boxes

In a view, add this (within php tags):

$this->beginWidget('zii.widgets.jui.CJuiDialog');

echo 'dialog contents here';

$this->endWidget('zii.widgets.jui.CJuiDialog');

That's your bare minimum.

Here it is modal, with a title, and an "OK" button that closes the dialog:
$this->beginWidget('zii.widgets.jui.CJuiDialog'
        , array('options'=>array(
            'title'=>'My Title'
            , 'modal'=>true
            , 'buttons'=>array('OK'=>'js:function(){$(this).dialog("close")}')
            ))
);

echo 'I am a modal dialog with an OK button';

$this->endWidget('zii.widgets.jui.CJuiDialog');

YII » How to setup an AJAX autocomplete field

In your view (_form.php), add this:

<?php
$this->widget('zii.widgets.jui.CJuiAutoComplete', array(
 'name'=>'name',
 'sourceUrl'=>'suggestName'
, 'value'=>'some initial value;
 ));
?>
If you are using a model, use the model and attribute parameters for the widget instead of the name and value parameters

In your controller, add a method:

public function actionSuggestName($term)
{
  //the $term parameter is what the user typed in on the control

  //send back an array of data:
  echo CJSON::encode(array('one', 'two', 'three'));

  Yii::app()->end(); 
}

In your controller, in accessRules(), allow authenticated users to use the suggestName action:

array('allow', 
  'actions'=>array('create','update', 'suggestName'),
  'users'=>array('@'),
   ),

NetBeans » How to stop the debugger from pausing on the first line

If you try to debug your Yii application with Netbeans, it will pause on the first line for every request, which is incredibly annoying. Here's how to turn that "feature" off:

In Netbeans (6.9.1 on Windows anyways):

Menu bar > Tools > Options > PHP > Debugging > Stop at First Line (uncheck)

YII » How to use AJAX form validation

Yii supports AJAX form validation, which essentially posts the form values to the server, validates them, and sends back the validation errors, all without leaving the page. It does this every time you tab out of a (changed) field.

As of 1.1.7, Yii supports regular Javascript validation in addition to AJAX validation, but I'll talk about that in another post.

Here's how Yii's AJAX validation works:

  1. in your yii form declaration, put:
    <php $form = $this->beginWidget('CActiveForm', array(
    'id'=>'lowercasemodelname-form', //not technically required but works w gii generated controllers
    'enableAjaxValidation'=>true //turn on ajax validation on the client side
    ));
    
    And have at least one form element with a matching error function:
    <?php echo $form->textField($model, 'my_attribute'); ?>
    <?php echo $form->error($model, 'my_attribute'); ?>
    
    This makes Yii include the JQuery javascript library, as well as a Yii javascript file called jquery.yiiactiveform.js
  2. In your controller, in create or update, after you load the model, but before you load it from POST, call this
    if(Yii::app()->getRequest()->getIsAjaxRequest()) {
    echo CActiveForm::validate( array( $model)); 
    Yii::app()->end(); 
    }
    
    Which is sligtly different than how Gii generates it, but no big diff. CActiveForm::validate() can take an array of models, which is not clear the way Gii does it.
  3. Also make sure that your model has at lease one validation rule for the insert or update scenario. After you tab out of a changed field, Yii sends a standard AJAX POST to the server, and gets back a JSON response like this:
    {"Field_id":["Validation error a"],"Another_field_id":["Validation error B"]}
    
    which yii then plugs into the error field below your field.
  4. When you use the $form->error() function, Yii adds a hidden div after your form element:
    <div id="Model_attributename_em_" class="errorMessage" style="display:none"></div>
    If that field has a validation error, then Yii sets the display to block, writes the validation error message to its innerHtml, and then you see the error. If it later validates, yii hides it again.
  5. Yii will also add class names to the parent container of the field that it's validating. In most cases, this is a <div class="row">. When a form field is valid, it adds "success" class to the div - which makes it green. When it's invalid, it adds "error" class, which makes it red. It also quickly adds a "validating" class, which does nothing, but you can supply it yourself and change the look of a field while it's validating.