Grid View Label Value

I checked the guide and the other posts on gridview and they didn’t address the specific issue I’m trying to address, so I’m posting this as a new topic.

I’m storing the value of gender as an integer on a profile model, 0 for female, 1 for male, so when I display the index view of profile, I get a list of profiles and in the gender column, I see 0’s and 1’s. This is expected behavior, but I would rather show the text definition of gender, which is either ‘male’ or ‘female’.

Ideally I would like to do something like this on the grid widget:

‘columns’ => [

'id',


['label'=>'Gender', 'value'=>MyClass::myMethodToLookUpDisplayText($modelwewant, $attribute, $?)]

];

This is a static class I built to manage simple dropdown lists. $modelwewant and $attribute work perfectly. They tell the method which model we are working with and which attribute the options belong to. $? would be the specific value that let’s the method return the display text for that value. In the case of this example, where I’m trying to return gender on the profile model, it would look like this:

‘columns’ => [

'id',


['label'=>'Gender', 'value'=>MyClass::myMethodToLookUpDisplayText('profile', 'gender', $?)]

];

I’m stuck at how to pass in the value of gender at $?. It needs to be the integer value of the attribute, which is how it is stored in the db. Then MyClass will return the display text. Of course that doesn’t mean this example will work exactly. I tried passing in $searchModel->gender but that generated an error, where it said it couldn’t find Female as an attribute of profile. A string conversion problem? In this case, I think the grid expects ‘value’ to be an integer.

Also, although I would like to solve this and try it this way, I recognize this might not be the best solution. One problem I can see is that this creates a query for every row in the grid, which for large data sets would be inefficient use of the server or even downright crazy.

I’m trying to create a centralized solution with a ui for managing dropdown options, allowing option in range rules, and display text. I’ve been really happy with the results so far, but got stuck here. If I can get it working to my satisfaction I will share it in a wiki. Before I do that, I need to make sure that I’m on the right path with this idea.

So the second part of this question is to ask about best practices. Is there a way to do this with eager loading? I know I can use constants on the model to represent the display text of the values, but I’m trying to avoid this, it seems like really messy code to me to stick a bunch of constants on the models for this purpose. I could also create a lookup table to hold the display text and create relations between models, but this seems excessive to build a model to house two strings of text, and to do that everytime I want to display text instead of an integer. I’m trying to increase efficiency, optimize workflow, and keep the code clean. Any thoughts or help would be greatly appreciated, thank you.

A pretty lengthy post… not looked in detail (and may not be answering in entirety).

  1. For showing your grid column value as a name instead of integer – use a closure function.

  2. For filtering by name set the filter to an array. For example:




'columns' => [

    'id',

    [

        'attribute'=>'gender', 

        'value'=>function ($model, $index, $widget) { return YourClass::convert($model->gender) },

        'filter'=>[0=>'Male', 1=>'Female'], // you may create a static or model function for this as well

    ]

];



Thank you so much, that worked perfectly. Now that I’ve gotten it to work, I can focus on the other question, which is should I do it this way?

If I do it this way, I am causing a query for each row. This could be a huge resource drain on large data sets.

I went back through the older posts and found a way that was eager loading via relationship, however I couldn’t get it to work. I started by creating a relationship on the profile model:

public function getGenderName()

{


    return $this->hasMany(Droptions::className(), ['value' => 'gender'])->where("attribute_name='gender' AND model_name='profile'");


}

this relationship points to the droptions table, where I store all my drop down options.

Next, I modified ProfileSearch by adding the relationship to the query:

$query = Profile::find()->with(‘genderName’);

Then I inserted the following into the grid widget:

[

'attribute' => 'gender',


'label' => 'Gender',


'value' => function($model, $index, $dataColumn) {


    return $model->genderName->name;


},

],

This returns:

PHP Notice – yii\base\ErrorException

Trying to get property of non-object with this line highlighted:

return $model->genderName->name;

So, once again stuck. In the above, name is the column name in the droptions table, which is referenced in the genderName relationship from the profile model. Did I mess up the relationship?

Refer this wiki that I created for various grid column scenarios to sort and filter by calculated fields.

Great tutorial. I tired scenario #2, but I got an error that it was returning an array instead of an expected string. Reading through your wiki has made me rethink my approach to this because I’m not sure I’m going in the right direction at all. You can skip the rest of this response if you are short on time, but I’m going to explain fully for the sake of any other beginners who might read this.

My idea was to put all the value/name pairs of trivial dropdown options into a single table, things like gender, status, etc. This makes it incredible easy to create and deploy dropdown list options and using yii I had a nice ui to manage it.

But the problem is that it complicates relations or at it least it seems that way. For example, If I had a model profile with a gender attribute that stored it’s values as integers, I could have a second table (let’s call it gender) to hold the names of the values and create a relation on profile to gender with a foreign key. This is basic stuff and would look like this:

public function getGender()

{


	return $this->hasOne(User::className(), ['id' => 'gender_id']);


}

Then name would be accessible though all of the AR methods (please correct me if I’m wrong). I’m just learning this so I’m not yet familiar with all ways to use relationships, so I was probably trying to solve a problem that didn’t exist.

If I go the other way, where I put all of these trivial values into a single table named droptions, it complicates the relationship:

public function getGenderName()

{


    return $this->hasOne(Droptions::className(), ['value' => 'gender_id'])->where("attribute_name='gender_id' AND model_name='profile'");


}

In this example, value matches gender_id, which is fine, but it gets complicated quickly. One table holding many diverse attributes is not as accessible because you have to build one jumbled up model for it, if in the future you want to do more with the attributes.

I think I was shying away from setting up relationships because I’m a beginner and it just seemed easier to do in one table and use static methods to query the table. But as even I suspected, you run into scale issues because of lazy loading and data structure and have to come back to relationships anyway. So it’s best to dig into relationships and get it all optimized in the first place. Like I said, your wiki helped bring some clarity to this. Thanks again for your help and your wiki.

Assuming you have good background on RDBMS, database components, and SQL you may do some additional reading on Relational Data Querying in Yii 2.0.

If you have framed up your application and data design, and are using a DATABASE to manage data… its important to understand and use table relations in your design rightly. Its not difficult once you use some of these concepts.

Also note that while storing all such static master data in database looks good at first glance… you may reconsider the design.

Certain elements (that are really static) can be just global class constants or arrays for better performance.

For example you may have one class called Setup and manage your genders this way:





class Setup {


    /* Your global constants for access */

    const MALE = 0;

    const FEMALE = 1;

    const UNKNOWN = 2;


   /* Your dropdown lists */

    private static $_genders = [

        self::MALE => 'Male',

        self::FEMALE => 'Female',

        self::UNKNOWN => 'Unknown'

   ];




   public static function genderList() {

        return self::$_genders;

   }


   public static function getGender($gender) {

       return !empty(self::$_genders[$gender]) ? self::$_genders[$gender) : null;

   }

}



  • Setup::genderList() can be used for dropdown lists.

  • Setup::getGender($gender_id) can be used to fetch a gender description.

That’s a great option, but I think the best way is with relationships, there’s no need to avoid them. The project will be more extensible in the long run. I tried my example with a relationship between Profile and Gender model and was amazed at how easy it was by following your examples from several posts (including the one below). Autogenerated models from gii get you most of the way there out of the box. Also implemented scenario 2 of your wiki for eager loading and sorting in about 5 mintues. Great stuff. Thank you so much!

relations/droptdowns

eager load and filter wiki for gridview