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'],
];
}
Related
In model I have defined multiple scenarios:
public function rules() {
return [
[['in_quantity'], 'required','on'=>['stockIn']],
[['out_quantity'], 'required','on'=>['stockOut']],
];
}
Is it possible to use both scenario stockIn and stockOut for single model validation?
$StockModel->scenario[] = 'stockOut';
$StockModel->scenario[] = 'stockIn';
or
$StockModel->scenario = ['stockOut','stockIn'];
You can't have multiple scenarios for model. But you can have multiple scenarios for rule:
public function rules() {
return [
[['in_quantity'], 'required', 'on' => ['stockIn', 'stockOut']],
[['out_quantity'], 'required', 'on' => ['stockIn', 'stockOut']],
];
}
If you need multiple scenarios for model, it means that you're overusing scenarios feature.
Also note that it is not recommended to use too many scenarios in one model - scenarios work fine for simple cases, but more complicated cases should be handled by separate models for each scenario.
You can create multiple scenarios this way in model
class MyModel extends \yii\db\ActiveRecord {
const SCENARIO_CREATE = 'scenario_create';
const SCENARIO_UPDATE = 'scenario_update';
// get scenarios
public function scenarios()
{
return [
self::SCENARIO_CREATE => ['user_id', 'name', 'desc', 'published','date_create'],
self::SCENARIO_UPDATE => ['user_id', 'name', 'desc', 'date_update'],
];
}
public function rules()
{
[['user_id'], 'integer'],
[['name','desc'], 'string', 'max' => 70],
[['date_create', 'date_update'], 'date', 'format' => 'php:Y-m-d H:i:s'],
];
}
}
and you can use this way anywhere
public function actionIndex() {
$model = new MyModel;
$model->scenario = MyModel::SCENARIO_CREATE;
if ($model->load(\Yii::$app->request->post())){
if($model->save()){
// some operations
}
}
}
You could if you extend the rule with when for server validation:
[
['in_quantity'],
'required',
'when' => function ($model) {
return $model->scenario === 'stockIn' || $model->scenario === 'stockOut';
}
]
Also if you want to validate in the form (aka client side validation) you could also use the whenClient that expect a js function:
'whenClient' => "function (attribute, value) {
const scenario = $('#stock-scenario').val()
return scenario === 'stockIn' || scenario = 'stockOut';
}"
I’m trying to update 'page' column but it is not working. I want to update only one column by changing the data from 4 to 5. The data type is an integer.
View
<div class="row">
<div class="col-sm-12 text-center">
<?= Html::a('Add as memoriam', ['update-status', 'id' => $model->ID], [
'class' => 'btn bg-maroon',
'data' => [
'confirm' => 'Are you sure you want to add '.$model->name.' into the dearly departed?',
'method' => 'post',
],
]) ?>
</div>
</div>
Controller
public function actionUpdateStatus($id)
{
$model = $this->findModel($id);
$model->page = 5;
if ($model->save())
$this->redirect(array('view', 'id' => $model->id));
return $this->redirect(['my-obituary']);
}
1. Using save()
This method will call insert() when $isNewRecord is true, or update() when $isNewRecord is false.
public function actionUpdateStatus($id)
{
$model = $this->findModel($id);
$model->page = 5;
if ($model->save(true, ['page'])) {
$this->redirect(array('view', 'id' => $model->id));
}
return $this->redirect(['my-obituary']);
}
2. Using updateAttributes()
This method is a shortcut to update() when data validation is not needed and only a small set attributes need to be updated. You may specify the attributes to be updated as name list or name-value pairs. If the latter, the corresponding attribute values will be modified accordingly. The method will then save the specified attributes into database.
Note that this method will not perform data validation and will not trigger events.
public function actionUpdateStatus($id)
{
$model = $this->findModel($id);
$model->page = 5;
if ($model->updateAttributes(['page' => 5])) {
$this->redirect(array('view', 'id' => $model->id));
}
return $this->redirect(['my-obituary']);
}
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]);
}
Being trying to sort this out but going nowhere with it. I have got an array as attribute for a model and I am trying to create custom validation for some of the keys in the array as required. Or even can't figure out how the attribute labels will work? Here is my code:
MODEL
...
public $company = [
'name' => '',
'trading_name' => '',
'type' => '',
];
public function attributeLabels(){
return [
'company[name]' => 'Company Name',
];
}
public function rules(){
return [
[['company[name]','company[trading_name'], 'safe'],
[['company[name]'], 'return_check','skipOnEmpty'=> false],
];
}
public function return_check($attribute, $params){
$this->addError($attribute ,'Required ');
return false;
}
...
I have even tried to pass the whole array and check in the validator method for the keys and values but the custom validator is not even triggered.
I think you need separated model for company.
I've used custom rule functions, and they all worked. Try removing the return clause at the end of the return_check function.
Here's what has worked for me:
class Essid extends ActiveRecord {
public function rules() {
return [
['network_name', 'checkNetworkName']
]
}
public function checkNetworkName($attribute, $params){
if (!$this->hasErrors()) {
if ( !ctype_alnum($this->network_name) )
$this->addError($attribute, Yii::t('app', 'Not a valid Network Name'));
}
}
}
Hope it helps
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!