I would like to combine 2 functions together, to consolidate code, and to be dynamic depending on how it is used. I do not know if this is possible.
First, lets lay out the basic use. In my example, I have Post and PostCategory models (and CRUD built). You create a category, then create a new post and assign it to the category. A post has the ability to be enabled or disabled. Essentially, you can create some new posts and have them not be viewable to the end users until your ready. One use-case would be a drip feed system, where you could add 100 posts and have one switch to enabled every x days. That is beyond the scope of this.
views\post\_form.php
<div class="post-form">
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'category_id')->dropDownList(
$model->getPostCategoryConst(),
['prompt'=> '- Category -']
)->label('Category')
?>
<?= $form->field($model, 'name')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'text')->textarea(['rows' => 6]) ?>
<?= $form->field($model, 'status')->dropDownList(
$model->getPostStatusConst(),
['prompt'=> '- Status -']
) ?>
<div class="form-group">
<?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
Notice the drop down lists for category_id and status and the functions they call
\common\models\Post.php
const STATUS_ENABLED = 1;
const STATUS_DISABLED = 0;
public function getCategory()
{
return $this->hasOne(PostCategory::className(), ['id' => 'category_id']);
}
/* -- Added -- */
public function getPostCategoryConst()
{
return ArrayHelper::map(PostCategory::find()->orderBy('name DESC')->all(), 'id', 'name');
}
public function getPostStatusConst()
{
return [
self::STATUS_DISABLED => 'Disabled',
self::STATUS_ENABLED => 'Enabled',
];
}
Now this works just fine :) However, I don't like using get as in getPostStatusConst() as it isn't accessed like $model->postStatusConst similar to how relations are with the "getter".
I would like to use these as "getters" as well though. In the index and view, it would be nice to also call the same functions. Instead of returning an array, to return a "nice name" such as "Enabled" or "Disabled"
For the sake of this, I won't rename the function, as I don't want to add any more confusion.
views\post\view.php
<?= DetailView::widget([
'model' => $model,
'attributes' => [
'id',
'category.name',
'name',
'text:ntext',
'postStatusConst', // <-- Calls getPostStatusConst()
'created_at:datetime',
'updated_at:datetime',
],
]) ?>
Notice postStatusConst is the same function as we used in _form for our create action. In the _form, it needed to return an array for the drop down list. In our view, it just needs to return a nice name such as Enabled or Disabled.
I Tried
I tried this function in the Post model:
public function getPostStatusConst()
{
if ( isset($this) ) {
return ($this->status === self::STATUS_ENABLED) ? 'Enabled' : 'Disabled';
}
return [
self::STATUS_DISABLED => 'Disabled',
self::STATUS_ENABLED => 'Enabled',
];
}
That obviously didn't work :) I didn't expect it to, because I know $this references itself in a class. It just shows what I am going for.
In relations, the hasOne() seems to know whether we are using it as a direct call (Post::getCategory) or inline ($model->category->name)..
Question
Is it possible to have getPostStatusConst() do the same? To use as $model->postStatusConst to display Enabled or Disabled nicely, or as Post::getPostStatusConst() to get the array for the drop down.
It is possible but really not worth all the changes in code. You would have to override magic __get() method and think about some way to store and access both returns in one structure.
I would leave getPostStatusConst() with current status name and add other method (even static) with the list of statuses for dropdown list.
I was pretty close. I wasn't thinking PHP OOP lines, but more Yii. A few Google searches and I slapped my forehead. When using frameworks, you forget your even writing in PHP sometimes ;)
public function getPostStatus()
{
if ( isset($this) && get_class($this) == __CLASS__) {
// not static
return ($this->status === self::STATUS_ENABLED) ? 'Enabled' : 'Disabled';
}
return [
self::STATUS_DISABLED => 'Disabled',
self::STATUS_ENABLED => 'Enabled',
];
}
I renamed the function so that it makes more sense.
It works everywhere. Lets see the index of my CRUD:
views\post\index.php
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'id',
'category.name',
'name',
//'text:ntext',
'postStatus',
// 'created_at',
// 'updated_at',
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
views\post\view.php
<?= DetailView::widget([
'model' => $model,
'attributes' => [
'id',
'category.name',
'name',
'text:ntext',
'postStatus',
'created_at:datetime',
'updated_at:datetime',
],
]) ?>
and views\post\_form.php
<?= $form->field($model, 'status')->dropDownList(
Post::getPostStatus(),
['prompt'=> '- Status -']
) ?>
All those cases seem to work fine. Anyone have any cases this would not work?
Related
I have a quick question. I implemented a dropdownList in a _form.php. Now my action crate won't work properly anymore. I am not sure if there is an issue with me sending the request to the action. But it's not really doing the trick anymore.
With the $form->field($model, 'team_idteam')->textInput() it worked just fine. So, this is what I have so far on the _form.php:
<div class="user-has-team-form">
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'team_idteam')->textInput() //<-- This works perfectly. ?>
<?= $form->field($model, 'teamIdteam')->dropDownList(ArrayHelper::map(Team::find()->all(), 'idteam', 'name')) <-- This does not work at all ?>
<?= $form->field($model, 'user_iduser')->textInput() ?>
<?= $form->field($model, 'oncallduty')->checkbox() ?>
<div class="form-group">
<?= Html::submitButton(Yii::t('app', 'Save'), ['class' => 'btn btn-success']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
My actionCreate looks like this:
public function actionCreate()
{
$model = new UserHasTeam();
if ($this->request->isPost) {
Yii::info("Test1"); // <-- It get's up to this point.
if ($model->load($this->request->post()) && $model->save()) {
Yii::info("Test2");
return $this->redirect(['view', 'team_idteam' => $model->team_idteam, 'user_iduser' => $model->user_iduser]);
}
} else {
$model->loadDefaultValues();
}
return $this->render('create', [
'model' => $model,
]);
}
The visuals work perfect and I can even chose different teams. If I create new teams, or delete old ones, they are shown or not shown as well. I have to admit I am a bit lost here.
EDIT
I dumped the $_POST array after the $model = new UserHasTeam(); and it gave out the following array:
[
'_csrf' => '0rkl0EAuFwjy9kdJNVQfVTQOkT22Kzo8bdvLAg2X0P_i0Ui-DEAkQ6WxfzsEAkwfBE_1UvNCDlsEjLtOefXmyA==',
'UserHasTeam' => [
'teamIdteam' => '3',
'user_iduser' => '1',
'oncallduty' => '0',
],
]
Yep. I am quite an idiot every now and then.
This is how I solved it:
<?= $form->field($model, 'team_idteam')->dropDownList(ArrayHelper::map(Team::find()->all(), 'idteam', 'name')) ?>
I am quite new to Yii 2 and I am currently trying to display data from the Teams model in the User view in a GridView. In Yii1 I used a function with the $data variable. In Yii2 it does not seem to work that way.
I have tried the following in the /user/index.php
<?php
use yii\helpers\Html;
use yii\grid\GridView;
/* #var $this yii\web\View */
/* #var $dataProvider yii\data\ActiveDataProvider */
$this->title = Yii::t('app', 'Users');
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="user-index">
<h1><?= Html::encode($this->title) ?></h1>
<p>
<?= Html::a(Yii::t('app', 'Create User'), ['create'], ['class' => 'btn btn-success']) ?>
</p>
<?= GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
// ['class' => 'yii\grid\SerialColumn'],
// 'iduser',
'username',
'surname',
'givenname',
'email:email',
'mobile',
'admin:boolean',
'language',
// Below is what I have been trying to do so far...
['class' => 'yii\grid\DataColumn',
'label' => 'Teams',
'value' => 'getTeams($data-teams)'
],
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
</div>
<?php
// And this is the function I use. In Yii1 it worked just fine.
function getTeams($data) {
$tmp = "";foreach ($data as $team) {
$tmp .=$team['name'].", ";
}
return trim($tmp, ", ");
}
?>
EDIT
In the User model this is what I have set the relation as followed:
public function getTeamIdteams()
{
return $this->hasMany(Team::className(), ['idteam' => 'team_idteam'])->viaTable('user_has_team', ['user_iduser' => 'iduser']);
}
While in the Team model I have set following relations:
public function getUserIdusers()
{
return $this->hasMany(User::className(), ['iduser' => 'user_iduser'])->viaTable('user_has_team', ['team_idteam' => 'idteam']);
}
I have been looking around but can't seem to find a solution for this one. Basically I simply want the Team to be displayed in which the user is a part of. There is a table called UserHasTeam in which the id's of the teams and users are saved.
I am a little lost on how to go about the entire thing. Any help is greatly appreciated.
I assume you have properly set relation to Team model in User model. See the Relations via Junction Table part of guide if you don't know how to set the relation.
You can use closure in $value property of DataColumn to control what value is used for column.
<?= GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
// ... other columns ...
[
'label' => 'Teams',
'value' => function($model) {
$tmp = "";
foreach ($model->teams as $team) {
$tmp .= $team->name .", ";
}
return trim($tmp, ", ");
}
],
],
]); ?>
You might also want to consider using eager loading to load all related at once instead of running query for each user in grid.
I used the foreign key in order to obtain database values from one table over another, such as this...
public function getAuthor() {
return $this->hasOne(SiteUsers::className(), ['id' => 'authorId']);
}
... or anonymous functions within the CRUD view files, such as:
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
//'id',
'hotel_id' => [
'attribute' => 'hotel_id',
'value' => function ($value) {
return \common\models\Hotels::find()
->where(['id' => $value->hotel_id])
->one()['name'];
}
],
'country_id' => [
'attribute' => 'country_id',
'value' => function ($value) {
return \common\models\Countries::find()
->where(['id' => $value->country_id])
->one()['name'];
}
],
'room_type',
'max_persons',
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
Now, the question is: is one method more efficient than the other? And why?
You must keep in consideration that if you use activeRecord the relation getAuthor() is anyway performed, and this is performed for each model) involved in dataProvider.
In general the direct access is ever more fast that the ORM based access. And the access by anonymous function performed in rendering èphase is substantially equivalentd to the access peformed by relation .. the best performance are based on direct command avoiding ORM or activeRecord modelling. but this implies the lost of the level of abstraction granted by ORM.
Remember that if you have both (relation an anonymous function ) you perform the query two times ..
i'm developing a web application with Yii2 framework, and i'm facing a problem right now. I want to display the data from a many-to-many relation in a gridview and be able to filter from those fields later on.
I've read the official documentation here, some stackoverflow post like this and other resources but can't seem to get it to work. I have 3 tables: actividad, plan_actividad and circulo_icare, actividad is related to plan_actividad and circulo_icare is also related to it (plan_actividad is the junction table). So i have defined the following relations in my Actividad model:
class Actividad extends \yii\db\ActiveRecord
{
....
public function getPlanActividad()
{
return $this->hasMany(PlanActividad::classname(), ['act_id' => 'act_id']);
}
public function getCirculo()
{
return $this->hasMany(CirculoIcare::classname(), ['cirica_id' => 'act_id'])->via('planActividad');
}
...
}
The in my view index.php i'm trying to show the values in a gridview like this:
<?= GridView::widget([
'dataProvider' => $dataProvider,
// 'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
// 'act_id',
['attribute' => 'Codigo Evento', 'value' => 'act_numorden'],
['attribute' => 'Nombre Evento', 'value' => 'act_nombre'],
['attribute' => 'Fecha Evento', 'value' => 'act_fecha'],
['attribute' => 'Locacion', 'value' => 'locacion.loc_nombre'],
[
'attribute' => 'Circulo',
'value' => 'circulo.cirica_nombre',
],
['attribute' => 'Circulo id',
'value' => 'planActividad.cirica_id',
],
// 'act_horaini',
// 'act_horafin',
// 'act_idencuesta',
// 'act_vigencia:boolean',
// 'loc_id',
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
The problem is, i can't get any values to show with the circulo relation, it always shows (not set). If i change hasMany in getPlanActividad() with hasOne() then it shows some values (only 2 of 11 it should, based on the cirica_id that exist on plan_actividad table) but these are not correct anyway. I know that i can filter for those fields later on in search view but i don't really understand why the relations doesn't work as i expected.
Any help would be greatly appreciated, let me know if more info is needed and thank you in advance.
Answering my own question (credits to softark from the yii official forums).
In order for the relation to work as expected, I had to change:
public function getCirculos()
{
return $this->hasMany(CirculoIcare::classname(), ['cirica_id' => 'act_id'])->via('planActividad');
}
to
public function getCirculos()
{
return $this->hasMany(CirculoIcare::classname(), ['cirica_id' => 'cirica_id'])->via('planActividad');
}
and use a callback function in the gridview to display the correct values, since a hasMany relation gives an array of models and not a single model. So I modified the gridview code to:
<?= GridView::widget([
'dataProvider' => $dataProvider,
// 'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
...
['attribute' => 'circulo',
'value' => function($model){
$items = [];
foreach($model->circulos as $circulo){
$items[] = $circulo->cirica_nombre;
}
return implode(', ', $items);
}],
...
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
This gives the expected results. You can then apply filter by the relation fields easily by adapting the search model.
I'm using wbraganca dynamic forms. I have these codes in my controller :
public function actionView($id)
{
$rackObjects = RackObjects::find()->where(['rackID' => $id])->one();
if (!$rackObjects) {
throw new NotFoundHttpException('Empty rack');
}
return $this->render('view', [
'model' => $this->findModel($id),
'rackObjects' => $rackObjects,
]);
}
my view file :
<div class="col-lg-6">
<?= DetailView::widget([
'model' => $model,
'attributes' => [
'rackID',
'height',
'width',
],
]) ?>
</div>
<div class="col-lg-6">
<h3>Objects in <?= $this->title ?></h3>
<?= DetailView::widget([
'model' => $rackObjects,
'attributes' => [
'ObjectTitle',
'objectID',
'rack_unit',
'Attached'
],
]) ?>
</div>
I want to have all my rackObjects in multiple detailview not only one detailview like this example : http://wbraganca.com/yii2extensions/dynamicform-demo1/view?id=721
how should I write the codes like example? There is no codes in demos!
$rackObjects = RackObjects::find()->where(['rackID' => $id])->all();
probably as u have already tried all(),
i'd suggest a try in your view file along with querying all()
foreach ($rackObjects as $rackObject) {
<?= DetailView::widget([
'model' => $rackObject,
'attributes' => [
'ObjectTitle',
'objectID',
'rack_unit',
'Attached'
],
]) ?>
}
what it does is, when you have queried all, you get an array of objects and you might want to loop your widget over each of the objects. and gives you multiple detailViews as you desire.
hope this helps.
You are returnig only one row, as you specify
$rackObjects = RackObjects::find()->where(['rackID' => $id])->one();
Replace that with ->all();
Modify value of columns in you detail view if you want everything in one grid.
Amir zaghari.
I'm newbie in YII2 and I use wbraganca\dynamicform\DynamicFormWidget;
I try to follow your step to show data in Detailview but I got an error.
Undefined variable: id
maybe I made something wrong. so can you explain how to coding in
public function actionView($id)
in controller.php
and
DetailView::widget
to display data in _view.php
thank you, sir