I am trying to create an expandable grid view in Yii2 but I have some problems.
I get this warning:
PHP Warning – yii\base\ErrorException
reset() expects parameter 1 to be array, null given
in C:\xampp\htdocs\advanced\vendor\yiisoft\yii2\grid\DataColumn.php at line 129
$provider = $this->grid->dataProvider;
if ($this->label === null) {
if ($provider instanceof ActiveDataProvider && $provider->query instanceof ActiveQueryInterface) {
/* #var $model Model */
$model = new $provider->query->modelClass;
$label = $model->getAttributeLabel($this->attribute);
} else {
$models = $provider->getModels();
129 if (($model = reset($models)) instanceof Model) {
/* #var $model Model */
$label = $model->getAttributeLabel($this->attribute);
} else {
$label = Inflector::camel2words($this->attribute);
}
}
} else {
$label = $this->label;
}
This is my search model code:
<?php
namespace app\models;
use Yii;
use yii\data\ActiveDataProvider;
use yii\base\Model;
use app\models\Articles;
/**
*
*/
class ArticlesSearch extends Model
{
/* your calculated attribute */
public $article_num;
public $title;
public $jour_id;
/* setup rules */
public function rules() {
return [
/* your other rules */
[['title'], 'safe']
];
}
public function search($params) {
$query = Articles::find()->select('*')
->where(['`journal_id`'=>$this->jour_id]);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$query->orderBy('`articles`.`publication_date` ASC ');
$query->andWhere(['LIKE','title',$this->title]);
// $query->orFilterWhere(['like', '`publishers`.`name`', $this->name]);
return $dataProvider;
}
public function getCount()
{
}
public function getModels()
{
}
}
I added two last methods to my class to bypass the following error:
Calling unknown method: app\models\ArticlesSearch::getCount()
Calling unknown method: app\models\ArticlesSearch::getModels()
I don't know why I need this two methods in my class. I have written two other search models and I didn't put these methods there and they work fine!!!
Controller code:
$dataProvider=new ArticlesSearch();
$dataProvider->jour_id=$param['journalID'];
$searchModel= $dataProvider->search(Yii::$app->request->queryParams);
return $this->render('index', [ 'searchModel' => $searchModel,
'dataProvider' => $dataProvider]);
View code:
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' =>$searchModel,
'columns' => [
[
'class'=>'kartik\grid\SerialColumn',
'contentOptions'=>['class'=>'kartik-sheet-style'],
//'width'=>'36px',
'header'=>'',
'headerOptions'=>['class'=>'kartik-sheet-style']
],
[
'class'=>'kartik\grid\CheckboxColumn',
'headerOptions'=>['class'=>'kartik-sheet-style'],
],
[
'class'=>'kartik\grid\ExpandRowColumn',
//'width'=>'50px',
'value'=>function ($model, $key, $index, $column) {
return GridView::ROW_COLLAPSED;
},
'detail'=>function ($model, $key, $index, $column) {
// return Yii::$app->controller->renderPartial('_expand-row-details', ['model'=>$articles]);
},
'headerOptions'=>['class'=>'kartik-sheet-style']
//'disabled'=>true,
//'detailUrl'=>Url::to(['/site/test-expand'])
],
[
'attribute'=>'Title',
//'value' => $model->title,
// 'width'=>'410px',
],
],
]);
?>
Any help would be much appreciated.
Try this amended code and see if it works. I've added some comments to explain what is going on.
<?php
namespace app\models;
use Yii;
use yii\data\ActiveDataProvider;
use app\models\Articles;
/**
** firstly, your search model needs to extend your original class, that was you have access to all the original attributes of your model, without having to declare them again.
**/
class ArticlesSearch extends Articles
{
//If your original model already has these properties, you don't need to declare them again.
public $article_num;
public $title;
public $jour_id;
/* Here you should declare rules for ALL the attributes that you want to use in search */
public function rules() {
return [
/* your other rules */
[['title', 'jour_id', 'article_num'], 'safe']
];
}
public function search($params) {
//Start by defining your basic search function
$query = Articles::find();
//Add in the dataProvider
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
//Start building the query that will be used to retrieve results
$query->where(['jour_id' => $this->jour_id]);
//Try to load the $params and validate them. If this fails, just return the dataProvider and do nothing else
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
//If we're still in the method, carry on building the query
$query->orderBy('`articles`.`publication_date` ASC ');
//andFilterWhere() is better because it ignores empty values
$query->andFilterWhere(['LIKE','title',$this->title]);
return $dataProvider;
}
//Finally, remove the two extras functions you put in. If everything is working, you shouldn't need them.
}
Next, in your controller, use this code;
//Here you are telling Yii what model you want to use for searching, and for generating the form for the grid search.
$searchModel = new ArticlesSearch();
//I'm not sure where this $param is coming from.
$dataProvider->jour_id=$param['journalID'];
//Now you are actually setting up the dataProvider for the grid view. Notice that your $searchModel->search() method always returns a dataProvider, so this is the correct way to do it. Yii::$app->request->queryParams is loading the parameters that the search method will use for it's parameters.
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', ['searchModel' => $searchModel, 'dataProvider' => $dataProvider]);
Now you should be able to use the grid widget as normal, use the dataProvider and searchModel for your dataProvider and filterModel respectively, but don't mix them up!
Related
I have a text field that was defined as required in its model. But a view needs not be required. I try this way to remove the required attribute but it doesn't work:
<?= $form->field($model, 'city')->textInput(['required' => false]) ?>
I need to change it in a view or in its controller. But not in its model (because others view needs the required attribute.).
I know how to do it using jQuery but I prefer with PHP/Yii2.
Update (requiered by the nice help of #Muhammad Omer Aslam):
My model is called Persons.
My view is called _form.
My controller is called PersonsControllers. It has the update function:
actionUpdate($id):
public function actionUpdate($id)
{
$model = $this->findModel($id); // How to add my new scenario here?
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id_person]);
}
return $this->render('update', [
'model' => $model,
]);
}
You can use scenarios to make the field required or not for the specific view. You can assign the active fields that are required for the scenario, and those fields will be the subject to validation.
I assume the model is Profile. In below example firstname, lastname and city is required in the default scenario.
A model may be used in different scenarios, by default the scenario default is used. Let's say in your case we can declare a scenario special that will only require firstname and lastname. In your model, you will declare a constant for the scenario name, and then override the scenarios() method, key=>value pairs with the active field names being passed in form of an array to the value will be assigned.
namespace app\models;
use yii\db\ActiveRecord;
class Profile extends ActiveRecord
{
const SCENARIO_SPECIAL = 'special';
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios[self::SCENARIO_SPECIAL] = ['firstname', 'lastname'];
return $scenarios;
}
}
and then inside your controller/action for that view where you do not want the city field to be required, initialize the Profile model object as below
public function actionProfile(){
$model = new \common\models\Profile(['scenario'=> \common\models\Profile::SCENARIO_SPECIAL]);
return $this->render('profile',['model'=>$model]);
}
Now if you submit the form inside this view it will ask only for the firstname and lastname whereas in your previous forms/views if you try to submit the form it will ask you to provide the city when trying to submit, you don't have to change or add anything for the rest of the forms or the rules.
As you are trying to update the record and do not want the city to be required when updating the record, the only difference that could be is to assign the scenario like below as you are not creating a new object for the model.
$model->scenario=\common\models\Profile::SCENARIO_SPECIAL;
In the model:
const SCENARIO_MYSPECIAL = 'myspecial';
public function rules()
{
return [
[['id_person', 'city'], 'required', 'on' => self::SCENARIO_DEFAULT],
[['id_person'], 'required', 'on' => self::SCENARIO_MYSPECIAL],
];
}
In the controller:
public function actionUpdate($id)
{
$model = $this->findModel($id);
$model->scenario = 'myspecial';
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id_person]);
}
return $this->render('update', [
'model' => $model,
]);
}
go to the model and remove the attribute
public function rules()
{
return [
[['id_person', 'city'], 'required'],
[['id_person'], 'required'],
];
}
EX:
public function rules()
{
return [
[['id_person'], 'required'],
[['id_person'], 'required'],
];
}
I'm making my first application using Yii and I have the next code:
public function actionCreate()
{
$model = new User();
$singUp = new \frontend\models\SingupForm;
if ($singUp->load(Yii::$app->request->post()) && $singUp->save()) {
$model = ModelName::findOne(['id' => $singUP->id]);
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
} else {
return $this->render('create', [
'model' => $model,
'singUp' => $singUp
]);
}
}
And I'm having the next error message when I try to go to that method:
Class 'frontend\models\SingupForm' not found
But I have the file saved on the directory as it is showed in the screenshot I attached. Additionally I added all the models folder of the Frontend on the controller I am using:
use Yii;
use \frontend\models;
use common\models\User;
use backend\models\search\UserSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
use yii\filters\AccessControl;
use common\models\PermissionHelpers;
So I don't know what I'm doing wrong. Please help
Screenshot
There is a typo in your code.
Please Change frontend\models\SingupForm to frontend\models\SignupForm.
I am trying to create a many to many relationship between a profile and several fields within that profile including languages and specialities. I have looked at several implementations and understand that there are several extensions, but I am required to minimise usage of extensions.
I have created the proper migration...this is a skeleton and the user table is purely for logins and OAuth...so keys can be ignored. As you can see in my controller I don't really know the way forward at this point. My language model is for all intensive purposes static from this controller(It is controlled by an admin backend). What is working? If I make a couple modifications to the below code then the checkboxlist will display with the proper checked items that I manually added to the lookup table. Trying to modify the lookup table from code, I have been unable to do unless I populate the $languageModel with a findOne(knownPK), however that is not usable, because multiple checkboxes can be selected resulting in an array and the link command requires ActiveRecordInterface which is singular. Ideally I would like to simply use
$languageModel->load(Yii::$app->request->post(),$trainerModel->formName());
but that isn't working.
Additionally, is there a mechanism within the framework to remove lookups or is that done manually. Any help or insight would be helpful. Thank you in advance.
public function safeUp()
{
$this->createTable('student', [
'id' => $this->primaryKey(),
'user_id' => $this->integer()->notNull(),
]);
$this->createTable('language', [
'id' => $this->primaryKey(),
'language' => $this->string(63),
]);
$this->createTable('student_language',[
'id' => $this->primaryKey(),
'language_id' => $this->integer(),
'student_id' => $this->integer()
]);
$this->addForeignKey('fk-student-language-language', 'student_language', 'language_id', 'language', 'id', 'CASCADE', 'CASCADE');
$this->addForeignKey('fk-student-language-student', 'student_language', 'student_id', 'student', 'id', 'CASCADE', 'CASCADE');
$this->addForeignKey('fk-student-user-user_id', 'student', 'user_id', 'user', 'id', 'CASCADE', 'CASCADE');
}
My student Active Record
/**
* #return \yii\db\ActiveQuery
*/
public function getstudentLanguages()
{
return $this->hasMany(studentLanguage::className(), ['student_id' => 'id']);
}
public function getLanguages(){
return $this->hasMany(Language::className(),['id'=>'language_id'])->viaTable('student_language',['student_id'=>'id']);
}
My Language Model
/**
* #return \yii\db\ActiveQuery
*/
public function getstudentLanguages()
{
return $this->hasMany(studentLanguage::className(), ['language_id' => 'id']);
}
public function getLanguages(){
return $this->hasMany(student::className(),['id'=>'student_id'])->viaTable('student_language',['language_id'=>'id']);
}
My Controller
public function actionProfile()
{
if (Yii::$app->user->isGuest) {
throw new UnauthorizedHttpException('This page requires you to be logged in');
} else {
$user_id = Yii::$app->user->identity->getId();
$studentModel = $this->findstudentModelByUserId($user_id);
if (is_null($studentModel)) {
$studentModel = new student();
$studentModel->setAttribute('user_id', $user_id);
$studentModel->save();
}
$languageModel = ??????????????????
$studentModel->load(Yii::$app->request->post()) && $studentModel->save() && $studentModel->link('languages',$languageModel);
}
return $this->render('profile', ['model' => $studentModel]);
}
My View
<?= $form->field($model, 'languages')->checkboxList(\common\models\Language::find()->select(
['language', 'id'])->indexBy('id')->column(), ['prompt' => 'select Language']); ?>
This is my solution at this point, however I am interested to hear improvements.
Note: There is no error checking etc...just functional code.
I added this to the student activerecord class
public function linkMultiple( $name,Array $models){
studentLanguage::deleteAll(['student_id' =>$this->id]);
foreach($models as $model){
$this->link($name, $model);
}
return true;
}
Modified my Controller action to the following
public function actionProfile()
{
if (Yii::$app->user->isGuest) {
throw new UnauthorizedHttpException('This page requires you to be logged in');
} else {
$user_id = Yii::$app->user->identity->getId();
$studentModel = $this->findstudentModelByUserId($user_id);
if (is_null($studentModel)) {
$studentModel = new student();
$studentModel->setAttribute('user_id', $user_id);
$studentModel->save();
}
$languages = Yii::$app->request->post('student')['languages'];
$languageModels = Language::findAll($languages);
$studentModel->load(Yii::$app->request->post()) && $studentModel->save() && $studentModel->linkMultiple('languages',$languageModels);
}
return $this->render('profile', ['model' => $studentModel]);
}
I'm using yii2-advanced. I've several table :
tb_user:(iduser(PK),username),
tb_profile:(id,iduser(FK)),
tb_status:(id,iduser(FK))
My question is how can i insert iduser(PK) from tb_user to iduser(FK) on tb_profile and tb_status after i push the signup button.
For a while i think i must to do some modification of bevahiours() function on User model and i found some error, or adding trigger syntax on the table ? (i think this is not a good ways).
Is there anyone who can help me, how to solve my problem ?
this is the User model before the modification :
<?php
namespace common\models;
use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
class User extends ActiveRecord implements IdentityInterface
{
const STATUS_DELETED = 0;
const STATUS_ACTIVE = 10;
/**
* #inheritdoc
*/
public static function tableName()
{
return '{{%user}}';
}
/**
* #inheritdoc
*/
public function behaviors()
{
return [
'timestamp' => [
'class' => TimestampBehavior::className(),
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => 'created_at',
ActiveRecord::EVENT_BEFORE_UPDATE => 'updated_at',
],
'value' => function () {return date('Y-m-d h:m:s');},
],
];
}
/**
* #inheritdoc
*/
public function rules()
{
return [
['status', 'default', 'value' => self::STATUS_ACTIVE],
['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],
];
}
/**
* #inheritdoc
*/
public static function findIdentity($id)
{
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
}
/**
* #inheritdoc
*/
public function getId()
{
return $this->getPrimaryKey();
}
}
?>
The Controller :
public function actionSignup()
{
$model = new SignupForm();
if ($model->load(Yii::$app->request->post())) {
if ($user = $model->signup()) {
if (Yii::$app->getUser()->login($user)) {
return $this->goHome();
}
}
}
return $this->render('signup', [
'model' => $model,
]);
}
I had similar situation in one of my project where i had 2 tables like user,user_image where user_id was foreign key to add the path.
For those kind of situation you can use either of following approach
1.Insert record in both table on click of signup button. You will have to write update action accordingly.
$user = new User();
$user->name = "John"
$user->email = "John#gmail.com"
//Add if any other fields in table
$user->save(); //save the record
$user_image = new UserImage();
$user_image->user_id = $user->id;
$user_image->image = "image path"
//Add any other images here
$user_image->save();//save the record
2.You can also call create action of UserImage and do the same. If you use this approach than you might also need to use any other unique column to find the id of that user and use it to insert new record,for example in my table email is unique column so i can write following code in UserImage and get the id
$user = User::findOne(['email' => 'john#gmail.com']);//this will return whole row
$user_image->user_id = $user->id;
$user_image->image = "image path"
//Add any other images here
$user_image->save();//save the record
And that way you can use the code as per it suits your need
Thank you
I would like / have to manage some settings in ZF1 style and provide the view with the infomation about the current environment.
/config/application.config.php
return array(
...
'module_listener_options' => array(
...
'config_glob_paths' => array('config/autoload/{,*.}{global,local}.php')
)
);
/config/autoload/env.local.php
return array(
// allowed values: development, staging, live
'environment' => 'development'
);
In a common view script I can do it over the controller, since the controllers have access to the Service Manager and so to all configs I need:
class MyController extends AbstractActionController {
public function myAction() {
return new ViewModel(array(
'currentEnvironment' => $this->getServiceLocator()->get('Config')['environment'],
));
}
}
Is it also possible to get the configs in a common view directly?
How can I access the configs in a layout view script (/module/Application/view/layout/layout.phtml)?
(My implementation/interpretation of) Crisp's suggestion:
Config view helper
<?php
namespace MyNamespace\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\View\HelperPluginManager as ServiceManager;
class Config extends AbstractHelper {
protected $serviceManager;
public function __construct(ServiceManager $serviceManager) {
$this->serviceManager = $serviceManager;
}
public function __invoke() {
$config = $this->serviceManager->getServiceLocator()->get('Config');
return $config;
}
}
Application Module class
public function getViewHelperConfig() {
return array(
'factories' => array(
'config' => function($serviceManager) {
$helper = new \MyNamespace\View\Helper\Config($serviceManager);
return $helper;
},
)
);
}
Layout view script
// do whatever you want with $this->config()['environment'], e.g.
<?php
if ($this->config()['environment'] == 'live') {
echo $this->partial('partials/partial-foo.phtml');;
}
?>
My implementation/interpretation of Sam's solution hint for the concret goal (to permit displaying the web analytics JS within the dev/staging environment):
DisplayAnalytics view helper
<?php
namespace MyNamespace\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\View\HelperPluginManager as ServiceManager;
class DisplayAnalytics extends AbstractHelper {
protected $serviceManager;
public function __construct(ServiceManager $serviceManager) {
$this->serviceManager = $serviceManager;
}
public function __invoke() {
if ($this->serviceManager->getServiceLocator()->get('Config')['environment'] == 'development') {
$return = $this->view->render('partials/partial-bar.phtml');
}
return $return;
}
}
Application Module class
public function getViewHelperConfig() {
return array(
'factories' => array(
'displayAnalytics' => function($serviceManager) {
$helper = new \MyNamespace\View\Helper\DisplayAnalytics($serviceManager);
return $helper;
}
)
);
}
Layout view script
<?php echo $this->displayAnalytics(); ?>
So this solution becomes more flexible:
/config/application.config.php
return array(
...
'module_listener_options' => array(
...
'config_glob_paths' => array('config/autoload/{,*.}{global,local}.php')
)
);
/config/autoload/whatever.local.php and /config/autoload/whatever.global.php
return array(
// allowed values: development, staging, live
'environment' => 'development'
);
ContentForEnvironment view helper
<?php
namespace MyNamespace\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\View\HelperPluginManager as ServiceManager;
class ContentForEnvironment extends AbstractHelper {
protected $serviceManager;
public function __construct(ServiceManager $serviceManager) {
$this->serviceManager = $serviceManager;
}
/**
* Returns rendered partial $partial,
* IF the current environment IS IN $whiteList AND NOT IN $blackList,
* ELSE NULL.
* Usage examples:
* Partial for every environment (equivalent to echo $this->view->render($partial)):
* echo $this->contentForEnvironment('path/to/partial.phtml');
* Partial for 'live' environment only:
* echo $this->contentForEnvironment('path/to/partial.phtml', ['live']);
* Partial for every environment except 'development':
* echo $this->contentForEnvironment('path/to/partial.phtml', [], ['development', 'staging']);
* #param string $partial
* #param array $whiteList
* #param array $blackList optional
* #return string rendered partial $partial or NULL.
*/
public function __invoke($partial, array $whiteList, array $blackList = []) {
$currentEnvironment = $this->serviceManager->getServiceLocator()->get('Config')['environment'];
$content = null;
if (!empty($whiteList)) {
$content = in_array($currentEnvironment, $whiteList) && !in_array($currentEnvironment, $blackList)
? $this->view->render($partial)
: null
;
} else {
$content = !in_array($currentEnvironment, $blackList)
? $this->view->render($partial)
: null
;
}
return $content;
}
}
Application Module class
public function getViewHelperConfig() {
return array(
'factories' => array(
'contentForEnvironment' => function($serviceManager) {
$helper = new \MyNamespace\View\Helper\ContentForEnvironment($serviceManager);
return $helper;
}
)
);
}
Layout view script
<?php echo $this->contentForEnvironment('path/to/partial.phtml', [], ['development', 'staging']); ?>