Group in activeDropDownList (or dropDownList) with related AR models?

Hello again,

I have two AR models

Account

  id

  name

  idType

and

Type

  id

  name 

Each Account have a certain Type (just one).

Examples of stored data:

Type

[table]

[tr]

[td]Type id[/td]

[td]Type name[/td]

[/tr]

[tr]

[td]1[/td][td]First[/td]

[/tr]

[tr]

[td]2[/td][td]Second[/td]

[/tr]

[tr][td]3[/td][td]Third[/td]

[/tr]

[/table]

Accounts

[table]

[tr]

[td]account id[/td]

[td]Account name[/td]

[td]Account type[/td]

[/tr]

[tr]

[td]1[/td][td]Test1[/td][td]1[/td]

[/tr]

[tr]

[td]2[/td][td] Test2[/td][td]2[/td]

[/tr]

[tr][td]3[/td][td] Test3[/td][td]3[/td]

[/tr]

[tr]

[td]4[/td][td] Test4[/td][td]2[/td]

[/tr]

[tr]

[td]5[/td][td] Test5[/td][td]3[/td]

[/tr]

[tr]

[td]6[/td][td] Test6[/td][td]1[/td]

[/tr]

[/table]

Now I want to list (in an dropDownList) all the Accounts grouped by type.

Then I do the following:

But this show me something like the image1.

What I need is to do something like this:

To obtain somethin like image2.

The first example works perfect but is not what I want.

The second simply does not work…

Any idea?

To solve this issue I do the following:

I override the listData method of the CHtml:

And may be used in the following way:

IMPORTANT:

Note that 'type' in 'type.name' must be the same to that one 'type' used for with().

And all works!!!

Please be free to improve this or to ask me you doubts.

PS: Qiang… exists the possibility to add this to the actual implementation of the listData in CHtml?

Of course, sure may be needs some enhancements…

Could you please create a ticket for this? Not only group field, we may also enhance name and value fields to allow dot syntax.

Created!

http://code.google.c…s/detail?id=268

Thanks!!!

Hi.  This is related so I thought I would ask it here.  I'm trying to generate a select box with options that have a value of "profile_id" and a name of "name_first name_last" … Am I only able to ask for one column for the name?

<?php echo CHtml::activeDropDownList($project_people,'profile_id',

CHtml::listData(Profile_contact::model()->findAll(),'profile_id','name_first')) ?>

That gives me a nice little box with:

'1' -> 'Byron'

'2' -> 'Jim'

'3' -> 'Jones'

I would like it to be:

'1' -> 'Byron Sorrells'

'2' -> 'Jim Johnson'

etc.

I was working on that too, just give me a little time to post it…



<?php 


private static function prepareField($model,$field,$textConcatenator = ', ')


{


   if (is_array($field)) $myfields = $field; // If the field is an array all is ok,


   else $myfields = array($field);           // else, make it part of an array.


		


   $preparedField = ''; // Initialize





   foreach ($myfields as $f):


      $semiPreparedField = $model; //initialization.





      if (!(strpos($f,'.') === FALSE))


      {


         $parts = split('[.]',$f);


         // Added, separate in $parts. Now I can pass things like $groupField = 'type.name';


         // Note that in the example 'type' is the name of a relation, and name is an atribute of the related AR.


         foreach ($parts as $part):


            if(is_object($model)) $semiPreparedField = $semiPreparedField->$part; // if an objet, the part is treated as a property


            else $semiPreparedField = $semiPreparedField[$part]; // if not, then the part is treated as an element af the array.


         endforeach;


      }


      else


      {


         if(is_object($model)) $semiPreparedField = $semiPreparedField->$f; // if an objet, the part is treated as a property


         else $semiPreparedField = $semiPreparedField[$f]; // if not, then the part is treated as an element af the array.


      }





      empty($preparedField)? $preparedField = $semiPreparedField : $preparedField.=  $textConcatenator.$semiPreparedField;





   endforeach;





   return $preparedField;


}





public static function listData($models,$valueField,$textField,$groupField='',$textConcatenator = ', ')


{


   $listData=array();





   foreach ($models as $model):


      $preparedTextField = self::prepareField($model, $textField,$textConcatenator);


      $preparedValueField= self::prepareField($model, $valueField,$textConcatenator);





      if ($groupField==='')


      {


         $listData[$preparedValueField] = $preparedTextField;


      }


      else


      {


         $preparedGroupField = self::prepareField($model, $groupField,$textConcatenator);


         $listData[$preparedGroupField][$preparedValueField] = $preparedTextField;


      }


   endforeach;


   


   return $listData;


}


Now you can use listData in the following ways:



1. As ever


2. Passing a dot notation to work with related AR as shown in the previous posts.


3. Passing an array of one, two or more columns names, (these also can be with dot notation for related AR)


Qiang: This corresponds to the issue #268. Some thing can be better I know, but, can you check it and if is good enough make it part of the framework? Please PM me whatever thing you need…

I fount #268 (http://code.google.com/p/yii/issues/detail?id=268) is release in Yii 1.0.5. Does it mean we can generate a select element link image2 in the 1st post?

I use dropDownLlist with the following code, but it doesn't work. Is there any sample code for reference?

<?php

echo CHtml::dropDownList(


	&#039;attribute&#039;,


	$this-&gt;listData(Account::model()-&gt;with(&#039;type&#039;)-&gt;findAll(), &#039;id&#039;, 

'name','type.name'),

	array(&#039;empty&#039;=&gt;&#039;Seleccione una cuenta&#039;)

); ?>

you should have a relation named 'type' in the account model.

Sorry, for dropDownList, the 2nd parameter is valueField, I should put a empty string there. It's ok now.

<?php


   echo CHtml::dropDownList(


      'attribute', '',


      CHtml::listData(Account::model()->with('type')->findAll(), 'id', 


'name','type.name'),


      array('empty'=>'Seleccione una cuenta')


); ?>

And I have 2 more questions, could you help it?

  1. Could I only use customized method in model instead of findBy()? Because I already has a customized SQL.

  2. I also tried another case, my Tag model has a perend_id field for the parent tag. I can use above code to generate the dropDownList with group. But the problem is parent tag will show twice. I think if I have solution for 1st question, then I can fix this question by a SQL. (The SQL may be "where parent_id is not null")

  1. Yes, findBu is only for example porpoises.

  2. You have solution for 1st question.

Thanks, but I have a function getNames() but it doesn't work. I got "CActiveFinder does not have a method named "getNames"." message.

<?php


   echo CHtml::dropDownList(


   'attribute2', '',


   CHtml::listData(Tag::model()->with('parent')->getNames(), 'id', 'name','parent.name'),


   array('empty'=>'Seleccione una cuenta')


); ?>

But I think my function should be ok because the following code works.

<?php


   echo CHtml::dropDownList(


   'attribute2', '',


   Tag::model()->getNames(),


   array('empty'=>'Seleccione una cuenta')


); ?>

The first param of listData must be a list of model objects (this parameter can also be an array of associative arrays)

Check  http://www.yiiframew…listData-detail

Quote

The first param of listData must be a list of model objects (this parameter can also be an array of associative arrays)

Check  http://www.yiiframew…listData-detail

I tried to use this, it should be an associative arrays (am I right?) But still doesn't work…

  public function getNames() {


    $opts = array();


    $sql='select t.id, t.name from ...';


    $list=$this->findAllBySql($sql);


    foreach($list as $n) {


        $opts[$n->id] = $n->name;


    }


    return $opts;


  }

did you try to just return $list?

Quote

did you try to just return $list?

Yes, but it doesn't work too. The error message is the same "CActiveFinder does not have a method named "getNames"."

And, the following doesn't work too if I only return $list

<?php


   echo CHtml::dropDownList(


   'attribute2', '',


   Tag::model()->getNames(),


   array('empty'=>'Seleccione una cuenta')


); ?>

is getNames a function of model Tag?

Quote

is getNames a function of model Tag?

Yes, the following are the code (I removed rules and attributeLabels). Most code is generated by Yii directly.

<?php





class Tag extends CActiveRecord


{


	/**


	 * Returns the static model of the specified AR class.


	 * @return CActiveRecord the static model class


	 */


	public static function model($className=__CLASS__)


	{


		return parent::model($className);


	}





	/**


	 * @return string the associated database table name


	 */


	public function tableName()


	{


		return 'tag';


	}





	/**


	 * @return array relational rules.


	 */


	public function relations()


	{


		return array(


		  'parent'=>array(self::BELONGS_TO, 'Tag', 'parent_id'),


		);


	}





  public function getNames() {


    $opts = array();


    Yii::log('=== getNames ===', info, 'Tag.php');


    Yii::log('user_name='.Yii::app()->user->name, info, 'Tag.php');


    $sql='select t.id, t.name from ...';


    $list=$this->findAllBySql($sql);


    foreach($list as $n) {


        $opts[$n->id] = $n->name;


    }


    


    return $opts;


  }





}