Yii2 virtual attribute out of an attribute of a SqlDataProvider - yii2

My AddressController:
public function actionIndex() {
$searchModel = new AddressSearch;
$dataProvider = $searchModel->search($_GET);
return $this->render('index', [
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
]);
}
My model AddressSearch:
class AddressSearch extends Model {
public function search($params) {
$dataProvider = new SqlDataProvider([
'sql' => '
SELECT
name
FROM...
View:
GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
'name2',
I have an attribute called name in this dataProvider. I would like to create a new virtual attribute called name2 out of name by preg_replace()-ing a few parts of it.
The function itself is working. I have tried a lot of different things, but I still can't make it to fill the attribute name2 with data. name2 is always empty. Can you please point me to the right direction? Many thanks!
UPDATE: based on the brilliant ideas of #Imaginaroom and #rob006 I've done the following:
moved getName2() and the attributes I'm filling with SqlDataProvider from base model Address to AddressSearch
deleted the empty base model Address because I don't need it anyway. Less is more!
in search() I've added:
foreach ($dataProvider->getModels() as $row) {
$model = new AddressSearch;
$model->setAttributes($row, false);
$models[] = $model;
}
$dataProvider->setModels($models);
It works! Many thanks guys! It's fantastic that you are there and help!!!

The problem is that you're using SqlDataProvider which returns rows from the table as array instead of model instance. So that's why your getter (virtual-attribute) does not work in GridView - GridView does not work on Address model instance, but on raw array without name2 field.
You should change your SearchModel to use ActiveQuery:
$dataProvider = ActiveDataProvider([
'query' => Address::find()->select(['name']) //and whatever your query contains besides this
])
...
UPDATE: If you don't want to do it like this, you can add your logic directly in GridView like so:
GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
[
'header' => 'Name 2',
'value' => function($model) {
if (isset($this->_name2)) {
return $this->_name2;
}
return $this->_name2 = preg_replace([...
}
]

If you really need Address model instance and use SqlDataProvider at the same time, you may convert array to model instance manually:
$models = [];
foreach ($dataProvider->getModels() as $row) {
$model = Address::instantiate($row);
Address::populateRecord($model, $row);
$models[] = $model;
}
$dataProvider->setModels($models);
You can place this in AddressSearch::search() or create custom SqlDataProvider and override prepareModels().

Related

Custom Data Provider using foreach yii2

I'm trying to create a custom dataProvider made out of Professor's id’s (teachers id’s). My site hosts information about teachers, courses, grades, etc. for schools.
ER model
The user Alumno (student) will see a gridview of it’s professors for each course he’s registered in, so, I’m trying to return that information in a dataProvider.
First, I ask if the user is an Alumno.
Then I search for the Asignaturas the Alumno is registered in.
With that information, I search for the professors that teaches that Cursos, to return it’s id’s as a dataProvider, so I made a foreach cycle.
What I need is an array of Profesores id’s so I can show Profesors’s names in the grid view. The actual code is querying the last Profesor id into the dataProvider.
public function actionIndex()
{
$this->layout = 'main';
$searchModel = new ProfesorSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
if(User::isUserAlumno(Yii::$app->user->identity->id)){
$alumno = Alumno::find()->where(['id_usuario' => Yii::$app->user->identity->id])->one();
$asignaturas = Asignatura::find()->where(['id_curso' => $alumno->id_curso])->all();
foreach ($asignaturas as $asignatura){
$dataProvider = new ActiveDataProvider([
'query' => Profesor::find()->where(['id' => $asignatura->id_profesor])
]);
}
}
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
Any help would be appreciated.
You can use column() to get all id_profesor from query.
$profesorIds = Asignatura::find()->select('id_profesor')->where(['id_curso' => $alumno->id_curso])->column();
$dataProvider = new ActiveDataProvider([
'query' => Profesor::find()->where(['id' => $profesorIds])
]);

How can I display in view multiple GridViews with one DataProvider group it by some field?

A have a table 'operations' with finance operations for all users, including 'id', 'user_id', 'sum', 'name', 'date_picked' etc.
I have created one ActiveDataProvider that gets all operations for concrete User. Now I would like to display in view data from this dataprovider - not in one GridView, but in several GridViews which data grouped by 'date_picked' value.
I know that I can make a new dataprovider for the each 'date_picked' but there will be a large amount of requests to database.
Is there a way to display grouped data in several GridViews based on one dataprovider's data?
Controller:
/** #var OperationComponent $comp */
$comp = Yii::$app->operation;
$userId = Yii::$app->user->id;
$filterModel = $comp->getOperationSearch();
$operations = $comp->getSearchProvider($userId, Yii::$app->request->get());
OperationComponent:
public function getOperationSearch()
{
return new OperationSearch();
}
public function getSearchProvider($user_id, $params)
{
$model = new OperationSearch();
return $model->search($user_id, $params);
}
OperationSearch:
public function search($user_id, $params)
{
$query = Operation::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => 10,
],
// сортировка по умолчанию
'sort' => [
'defaultOrder' => [
'date_picked' => SORT_DESC
]
]
]);
return $dataProvider;
View:
....
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $filterModel,
.....
]); ?>
....
There is an Yii2 extension for complex GridViews: kartik-v/yii2-grid
It contains collapsible rows for showing details for a row.
You can view a demo here.
What you are looking for is the Expand Row Column Widget. It allows to expand a row by just loading the required data for this row details.
This extension has a good documentation is quite stable and tested and can be used straight forward.

Yii2 - Intercept Render Action

I'm writing Yii2 basic application. Sometimes I have to render the views when an action of a controller is accessed as partials, sometimes I have to render them with the layout. This is based on a GET parameter in the request so my code in an action looks like this:
public function actionIndex() {
$isApi = Yii::$app->request->get('api');
$dataProvider = new ActiveDataProvider([
'query' => Participant::find(),
]);
if ($isApi) {
return json_encode($this->renderPartial('index', [
'dataProvider' => $dataProvider,
]));
} else {
return $this->render('index', [
'dataProvider' => $dataProvider,
]);
}
}
I have exactly the same logic for each action in each controller. I have to first check the GET request for the api variable and then based on that I call either the render or renderPartial function.
Is there a way to intercept the render part in each action and write this code to work globally for each action instead of copy and pasting it everywhere?
You can write method for this:
public function actionIndex() {
$dataProvider = new ActiveDataProvider([
'query' => Participant::find(),
]);
return $this->renderForApi('index', [
'dataProvider' => $dataProvider,
]);
}
protected function renderForApi($view, $params = []) {
$isApi = Yii::$app->request->get('api');
if ($isApi) {
return $this->asJson($this->renderPartial($view, $params));
}
return $this->render($view, $params);
}
You can put renderForApi() method into parent controller in your app or put it into trait and use it in every controller.

Yii2 show active() records on a new index page of the same controller

I don't even know how to define my question, so I couldn't really search for an answer, so it would be appreciated if you could point me to the right direction.
I have a model a view and a controller. Normally if I'm calling index, I'm getting all records. That works fine. Now I would like to query from the DB only the active() records. So I would like to see on a new index page only the active records. Where do I have to define this? Of course, the function itself is already defined in ModelQuery. But in Controller, I have to create a new function, in order to be able to call a new view.
public function actionIndex2() {
$searchModel = new ModelSearch;
$dataProvider = $searchModel->search($_GET);
...
return $this->render('index2', [
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
]);
}
I have tried to add ->active() to all possible places here but no luck. I can't mess with ModelSearch, because then I would ruin basic index site functionality also. It's clear that I can use it when I'm doing so: Order::find()->active(), but now it's not the case. Do I have to create a new ModelSearch also? I hope it's not necessary. Many thanks!
Assuming you want filter by $id eg in your url /index2?id=10) you could try this way
public function actionIndex2($id) {
$searchModel = new ModelSearch;
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$dataProvider->query->
andWhere(['id' => $id]);
...
return $this->render('index2', [
'dataProvider' => $dataProvider,
'searchModel' => $searchModel,
]);
}
So officially you have to add your ActiveQueries to the search method of your ModelSearch.
public function search($params) {
$query = Model::find()->activeQuery1()->activeQuery2();
$dataProvider = new ActiveDataProvider([
'query' => $query,
...
or
public function search($params) {
$query = Model::find();
$dataProvider = new ActiveDataProvider([
'query' => $query->activeQuery1()->activeQuery2(),
...
This means you have to create a new ModelSearch or a new search method in ModelSearch as many times as many combinations of ActiveQueries you wish to use.

Dynamic Multilevel Drop-down menu in Yii2

I want to create a dynamic menu with my table (db). I have followed some instructions which are given below:
Table : "menupanal"
Step 01: I just create a super controller in app\components\Controller.php
Here is the code:
namespace app\components;
use app\models\MenuPanal;
class Controller extends \yii\web\Controller
{
public $menuItems = [];
public function init(){
$items = MenuPanal::find()
->where(['c_type' => 'MENU'])
->orderBy('id')
->all();
$menuItems = [];
foreach ($items as $key => $value) {
$this->menuItems[] =
['label' => $value['c_name'],
'items'=> [
['label' => $value['c_redirect'], 'url' => ['#']],
],
];
}
parent::init();
}
Step 02: Changed n main layout page:
echo Nav::widget([
'options' => ['class' => 'navbar-nav navbar-right'],
'items' => Yii::$app->controller->menuItems,
]);
It is working in only one level. My question::
Question : how can I add multilevel menu using Super controller ?
I am new in Yii2. Helps are highly appreciated.
Create New MenuHelper in Component folder. There is no default component folder. Please create by yourself.
<?php
namespace app\components;
use app\models\MenuPanel;
use app\models\Zuser;
use app\models\Vwrole;
use app\assets\AppAsset;
class MenuHelper
{
public static function getMenu()
{
$role_id = 1;
$result = static::getMenuRecrusive($role_id);
return $result;
}
private static function getMenuRecrusive($parent)
{
$items = MenuPanel::find()
->where(['c_parentid' => $parent])
->orderBy('c_sortord')
->asArray()
->all();
$result = [];
foreach ($items as $item) {
$result[] = [
'label' => $item['c_name'],
'url' => ['#'],
'items' => static::getMenuRecrusive($item['id']),
'<li class="divider"></li>',
];
}
return $result;
}
}
in Main Layout Page put the following code
echo Nav::widget([
'options' => ['class' => 'navbar-nav navbar-right'],
'items' => app\components\MenuHelper::getMenu(),
Enjoy Coding!!
You may use nested sets. Look at this extension for Yii: http://www.yiiframework.com/extension/nestedsetbehavior/ and its documentation. All you need to do is component with dynamic creation of menu items array for nested sets.
I found that there is a Yii2 extension version: http://www.yiiframework.com/extension/yii2-nestedsetbehavior/
Good luck
You may use this extension for multilevel dropdownMulti level Dropdown