Select2 Widget & Pjax Yii2 - php

I'm trying to add multiple Select2 widgets dynamically in the TabularForm using Pjax (build-in Ajax component in Yii2). For some reason the Select2 input is rendering on the wrong place at the top of the view (see gif1 below). As I understood, this issue is related specifically to the Select2 widget as everthing is working fine if I use some default component e.g. Html::a (see gif2 below).
gif1: https://i.imgur.com/YMh5dNb.gif
gif2: https://i.imgur.com/sJkTDkO.gif
How I can get rid of that strange behaviour with the Select2 widget? Thanks is advance!
Controller:
class ProfileController extends Controller
{
// ...
public function actionCreate()
{
if (Yii::$app->request->isAjax) {
// some logic here ...
return $this->renderAjax('object/create', [
// ...
]);
}
}
// ...
}
View:
// ...
use kartik\select2\Select2;
use kartik\select2\Select2Asset;
Select2Asset::register($this);
// a bunch of html code
<?php Pjax::begin(['id' => 'product-add']); ?>
$form1 = ActiveForm::begin();
$attribs = [
'name' => [
'type' => TabularForm::INPUT_RAW,
'value' => function($productModel) {
return Select2::widget([
'name' => 'state_10',
'data' => ['1' => '1', '2' => '2'],
'pjaxContainerId' => 'product-add',
'options' => [
'placeholder' => $productModel->tmpId,
'multiple' => true
],
]);
//return Html::a('product ' . $productModel->tmpId); <- works fine if I use this piece of code
},
],
// ...
Html::a("Add", ['profile/create'], ['class' => 'btn btn-primary'])
// ...
<?php ActiveForm::end(); ?>
<?php Pjax::end(); ?>
// ...

After some closer examination I found the solution. For those who will also face the same issue, you need to initialize your widget (Select2 in my case) before the pjax response, e.g. in your Controller:
class ProfileController extends Controller
{
// ...
public function actionCreate()
{
if (Yii::$app->request->isAjax) {
// some logic here ...
// initialize the widget with an appropriate id
$this->view->registerJs("$({$product->tmpId}).select2();");
return $this->renderAjax('object/create', [
// ...
]);
}
}
// ...
}
And somewhere in your View:
Select2::widget([
'id' => $productModel->tmpId, // set your unique id here
'name' => $productModel->tmpId,
'data' => ['1' => '1', '2' => '2'],
// ...
]);

Related

Yii2 redirecting to previous page after login

how to redirect the users after login,
i tried to use all of these :
Yii::$app->request->getReferrer(); // printing the refferrer url to screen
$this->redirect(\Yii::$app->request->referrer)
return \Yii::$app->getResponse()->redirect(\Yii::$app->getUser()->getReturnUrl($defaultUrl));
none of these above is working
public function actionLogin()
{
if (!\Yii::$app->user->isGuest) {
$this->goHome();
// $this->goHome();
}
/** #var LoginForm $model */
$model = \Yii::createObject(LoginForm::className());
$event = $this->getFormEvent($model);
$this->performAjaxValidation($model);
$baseurl = \Yii::$app->request->getAbsoluteUrl();
$this->trigger(self::EVENT_BEFORE_LOGIN, $event);
if ($model->load(\Yii::$app->getRequest()->post()) && $model->login()) {
$this->trigger(self::EVENT_AFTER_LOGIN, $event);
// return $this->redirect(\Yii::$app->request->referrer);
// return \Yii::$app->getResponse()->redirect(\Yii::$app->getUser()->getReturnUrl($defaultUrl));
// return \Yii::$app->request->getReferrer(); // printing the refferrer url to screen :(!!
return $this->redirect($baseurl);
// return $this->redirect(\Yii::$app->request->referrer);; //this one is returning everthing to main page, because of line 147
// return $this->goBack();
}
return $this->render('login', [
'model' => $model,
'module' => $this->module,
]);
}
In the web.php config i have this :
$config =[
..//
'modules' => [
'user' => [
'class' => 'dektrium\user\Module',
'enableUnconfirmedLogin' => true,
'confirmWithin' => 21600,
'cost' => 12,
'enableFlashMessages' => true,
'admins' => ['a'],
],
//..
all of these above is just sending the user after login to the home page
what should i do please , i have searched every where read the documentations
After 4 hours of trying i tried to "Echo" the URL after requesting referrer ;
the referrer is working fine the problem is after login the page loads more than one time and here is the problem why it's not sending back to that page but sending people to the current page (login page) then if he is a user he is automatically sending home.
The problem is common and it is related to the issue that at the time when you post login, your actual referrer is login page - actionLogin(), so you are redirected back again and off course you get passed throughout the
condition that you are not the Guest. In order to handle this, you have to assign a referrer to a modal field, so it can be posted with the login information. So at the time when login is validated, you have the required referrer url in your field. Check if you have this field identified in your form:
<?php $form = ActiveForm::begin(['id' => 'login-form']); ?>
<?= $form->field($model, 'referer')->hiddenInput()->label(false) ?>
Controller
$form = new LoginForm();
//get previos viewed page url and store in the new model
$form->referer = Yii::$app->request->referrer;
if ($form->load(Yii::$app->request->post())) {
if($form->login()){
return $this->goBack((($form->referer) ? $form->referer : null));
}
}
LoginForm() model
public $referer;
/**
* {#inheritdoc}
*/
public function rules()
{
return [
//...
['referer', 'string'],
];
}
After that, when it will be post request, this field will contain a referrer, which you will pass in your controller.
Further, if you use the yii2-user module, now it is possible and necessary in the config in the controllerMap to remove all forced redirects for the event "after logging in" (I commented them out):
...
'modules' => [
'user' => [
'class' => \dektrium\user\Module::className(),
'admins' => ['adminname'],
'enableConfirmation' => false,
'modelMap' => [
'User' => 'app\models\User',
'UserSearch' => 'app\models\UserSearch',
'Profile' => 'app\models\Profile',
],
'controllerMap' => [
'profile' => 'app\controllers\user\ProfileController',
'security' => [
'class' => \dektrium\user\controllers\SecurityController::className(),
'on ' . \dektrium\user\controllers\SecurityController::EVENT_AFTER_LOGIN => function ($e) {
/*if (Yii::$app->user->can('student free')) {
Yii::$app->response->redirect(array('/course'))->send();
}
if (Yii::$app->user->can('admin')) {
Yii::$app->response->redirect('http://site.ru/user/')->send();
}*/
//Yii::$app->response->redirect(Yii::$app->request->referrer)->send();
// Yii::$app->response->redirect(array('/user/'.Yii::$app->user->id))->send();
//Yii::$app->end();
}
],
],
],
...
I did it, with the help of #serghei Leonenco,
but i had these problems :
The page reloaded after pressing the button submit so the referrer was the login page always and this is why it kept sending users to main page.
I had to get the referrer before the form begin.
I had to pass the value of the referrer to a hidden input.
I had to check if the referrer is from my website or the guest got on login page from a 3rd party site.
Passed the value after checking, used redirect and Url::to('link', true) because the value i got from referrer was a full URL and i couldn't just redirect to a full link this is why i used Url::to with the true condition which means creating a new URL.
View
Notice that i used the referrer value before the form.
<div class="panel-body arab">
**<?php $referer = \Yii::$app->request->referrer;?>**
<?php $form = ActiveForm::begin([
'id' => 'login-form',
'enableAjaxValidation' => true,
'enableClientValidation' => false,
'validateOnBlur' => false,
'validateOnType' => false,
'validateOnChange' => false,
]) ?>
**<?= $form->field($model, 'referer')->hiddenInput(['value' => $referer])->label(false) ?>**
<?php if ($module->debug): ?>
<?= $form->field($model, 'login', [
'inputOptions' => [
'autofocus' => 'autofocus',
'class' => 'form-control',
'tabindex' => '1']])->dropDownList(LoginForm::loginList());
?>
<?php else: ?>
<?= $form->field($model, 'login',
['inputOptions' => ['autofocus' => 'autofocus', 'class' => 'form-control', 'tabindex' => '1']]
);
?>
<?php endif ?>
<?php if ($module->debug): ?>
<div class="alert alert-warning">
<?= Yii::t('user', 'Password is not necessary because the module is in DEBUG mode.'); ?>
</div>
<?php else: ?>
<?= $form->field(
$model,
'password',
['inputOptions' => ['class' => 'form-control', 'tabindex' => '2']])
->passwordInput()
->label(
Yii::t('user', 'Password')
. ($module->enablePasswordRecovery ?
' (' . Html::a(
Yii::t('user', 'Forgot password?'),
['/user/recovery/request'],
['tabindex' => '5']
)
. ')' : '')
) ?>
<?php endif ?>
<?= $form->field($model, 'rememberMe')->checkbox(['tabindex' => '3']) ?>
<?= Html::submitButton(
Yii::t('user', 'Sign in'),
['class' => 'btn btn-success btn-block', 'tabindex' => '4']
) ?>
<?php ActiveForm::end(); ?>
Model
Notice I used regex to make sure that the user got to login page using my website.
public function getReferer()
{
$getLink = \Yii::$app->request->post('login-form')['referer'];
if(preg_match('/tajrobtak/', $getLink)){
return $getLink;
} else {
return "";
}
}
Controller
public function actionLogin()
{
if (!\Yii::$app->user->isGuest) {
$this->goHome();
}
/** #var LoginForm $model */
$model = \Yii::createObject(LoginForm::className());
$referery = $model->getReferer();
$event = $this->getFormEvent($model);
$this->performAjaxValidation($model);
$this->trigger(self::EVENT_BEFORE_LOGIN, $event);
if ($model->load(\Yii::$app->getRequest()->post()) && $model->login()) {
$this->trigger(self::EVENT_AFTER_LOGIN, $event);
$this->redirect(Url::to($referery,true));
}
return $this->render('login', [
'model' => $model,
'module' => $this->module,
]);
}

Yii2 Creating Dynamic Navigation using database

I'm implementing a dynamic navbar in Yii2 which displays a dropdown menu picking items from the database. Now, the problem is that when I call the function in which I fill the array, the system crash with the error:
"Invalid argument supplied for foreach()"
since it doesn't find the variable with the array of items. I don't know which controller should pass the arguments to the main view, I just need an array of all items in a data model (i.e. Course).
I've tried out with this but still doesn't work.
/* #var $courses \app\models\Course[] */
layouts/main
function items($courses)
{
$items = [];
foreach ($courses as $course) {
array_push($items, ['label' => $course->title, 'url' =>
Url::to(['course', 'id' => $course->id])]);
}
return $items;
}
$menuItems = [
// other items ...
'label' => 'Courses', 'items' => items($courses)
];
echo Nav::widget([
'options' => ['class' => 'uk-navbar-item'],
'encodeLabels' => false,
'items' => $menuItems
]);
How can I pass the $courses variable to the layouts/main view? Thanks everyone in advance.
You should extract this code into widget:
class MainMenu extends Widget {
public function run() {
echo Nav::widget([
'options' => ['class' => 'uk-navbar-item'],
'encodeLabels' => false,
'items' => $this->getItems(),
]);
}
protected function getItems() {
return [
// other items ...
['label' => 'Courses', 'items' => $this->getCoursesItems()],
];
}
protected function getCoursesItems() {
$items = [];
foreach (Course::find()->all() as $course) {
$items[] = [
'label' => $course->title,
'url' => Url::to(['/course', 'id' => $course->id]),
];
}
return $items;
}
}
Then in your layout you're just calling:
<?= MainMenu::widget() ?>
In this way you can keep your controller and view clean.

Yii2 custom client validation with ajax rendering in modal

I have a model with a custom validation method. For testing it always returns an error message.
public function rules()
{
return [
...
['staff_ids', 'each', 'rule' => ['string']],
[['staff_ids'], 'validateStaffIds'],
...
];
}
public function validateStaffIds($attribute, $params, $validator) {
$this->addError($attribute, 'There is an error in the staff ids');
}
In the view.php is the modal element
<p>
<?= Html::button('Add Ensemble Staff',
['value' => Url::to(['ensemble/add', 'id' => $model->id]),
'title' => 'Adding New Ensemble Staff',
'class' => 'showModalButton btn btn-primary']);
?>
</p>
<?php
Modal::begin([
'closeButton' => [
'label' => 'x',
],
'headerOptions' => ['id' => 'modalHeader'],
'id' => 'modal',
'size' => 'modal-lg',
]);
echo "<div id='modalContent'></div>";
Modal::end();
?>
The js code which fires everything up...
$(function(){
$(document).on('click', '.showModalButton', function(){
if ($('#modal').data('bs.modal').isShown) {
$('#modal').find('#modalContent')
.load($(this).attr('value'));
} else {
//if modal isn't open; open it and load content
$('#modal').modal('show')
.find('#modalContent')
.load($(this).attr('value'));
}
//dynamiclly set the header for the modal
...
});
});
And the ensemble controller which handles the add action
public function actionAdd($id)
{
$model = $this->findModel($id);
// in the post ( 'ensembleStaff_ids' => [0 => '2']); where the id actually is staff_id
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $id]);
} else {
return $this->renderAjax('add', [
'model' => $model,
]);
}
}
And the form which is injected by the js into the model (Url::to(['ensemble/add', 'id' => $model->id]), )
<?php $form = ActiveForm::begin(['id' => 'add-theater-stuff-form']); ?>
<?= $form->field($model, 'staff_ids')->widget(Select2::className(), [
'model' => $model,
'data' => ArrayHelper::map(app\models\TheaterStaff::find()->where(['theater_id' => $model->theater_id])->all(), 'staff_id', 'staff.fullname'),
'options' => [
'multiple' => true,
'prompt' => 'Ensemble Staff',
],
'pluginOptions' => [
'tags' => true
]
]); ?>
<div class="form-group">
<?= Html::submitButton('Add', ['class' => 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>
Clicking on the Add Ensemble Staff Button works fine and brings up the modal window. The form itself works fine so far; also the default validation works. Even the custom validation is called, but return $this->renderAjax(...) isn't load in the modal window anymore; it is separately.
A picture showing the modal loaded, the result after submit and a modal with default validation.
I found a similar problem here. But adding an id to the form, doesn't solve the problem. So how to get the default validation showing up properly in the modal window? Does anyone have a clue?
Solution
Thanks for the response. For me the solution was:
Enable ajax in the form
<?php $form = ActiveForm::begin(['id' => 'add-ensemble-stuff-form', 'enableAjaxValidation' => true]); ?>
And to add the following logic in the controller
public function actionAdd($id)
{
$model = $this->findModel($id);
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
} else {
// in the post ( 'ensembleStaff_ids' => [0 => '2']); where the id actually is staff_id
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $id]);
} else {
return $this->renderAjax('add', [
'model' => $model,
]);
}
}
}
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}else{/* your code */}
add this in controller use yii\web\Response

Error when trying to load Dual Listbox in Yii2

In the project that I want to develop, user can upload an Excel file (xls,xlsx) to system.
The excel sheet has headers in the first row, and value in another row. System has a default excel template that
consist the rule for headers sequence such as (Name, Age, Sex), but sometimes user use their own excel template so sometimes the header sequence become like this (Sex, Name, Age).
To handle this sequence, I've a plan to make a mapping process to handle the headers sequence before save the value to database. I wanna use dual list box.
I've 2 a table in database for this process:
Header -> has to column (header_id, header_name), all the headers from file has been save in here.
Each headers saved with their own header_id and header_name
Info -> the value from the excel file save here.
and I also has a pure (not generated by Gii) CostumizedHeaderController.php, CostumizeHeader.php, Index.php
This is code in CostumizeHeaderController:
class CostumizeHeaderController extends Controller {
//put your code here
public function actionShowHeaders() {
$model = new CostumizeHeader();
$model->loadHeaders();
$items = \backend\models\CostumizeHeader::getAllHeader();
return $this->render('index', [
'model' => $model,
'items' => $items
]);
}}
Code in model (CostumizeHeader.php)
class CostumizeHeader {
//put your code here
/**
* #var array IDs of the favorite foods
*/
public $list_headers = [];
public function rules() {
return [
[['list_headers'], 'string', 'max' => 255],
];
}
/**
* #return array customized attribute labels
*/
public function attributeLabels() {
return [
'list_headers' => 'list Costumized Header',
];
}
public function loadHeaders() {
$this->list_headers = [];
$headers = Header::find()->all();
foreach ($headers as $ff) {
$this->list_headers[] = $ff->header_id;
}
}
public static function getAllHeader() {
$headers = Header::find()->asArray()->all();
$items = ArrayHelper::map($headers, 'header_id', 'nama_header');
return $items;
}
code in index.php
<?php
$form = ActiveForm::begin([
'id' => 'favorite-form',
'enableAjaxValidation' => false,
]);
?>
<?= $form->field($model->list_headers, 'list_headers')->widget(DualListbox::className(), [
'model' => $model,
'items' => $items,
'name'=>'nama_header',
'attribute' => 'list_headers',
'options' => [
'multiple' => true,
'size' => 15,
],
'clientOptions' => [
'nonSelectedListLabel' => 'Available Header',
'selectedListLabel' => 'Final Header',
'moveOnSelect' => false,
],
])
->hint('Select the header according the sequence.');
?>
I've try to var_dump in controller and got this Array ( [1] => age [2] => sex [3] => name .
And I've been check the header table, and all the headers from excel file have been imported to database.
But I still got an error, like this Call to a member function formName() on a non-object.
I wish anybody can help me to solve this problem. Thankyou
Inside your view page I think you are using field incorretly $model->list_headers should be $model only.
your index.php must be as follows:
<?php
$form = ActiveForm::begin([
'id' => 'favorite-form',
'enableAjaxValidation' => false,
]);
?>
<?= $form->field($model, 'list_headers')->widget(DualListbox::className(), [
'model' => $model,
'items' => $items,
'name'=>'nama_header',
'attribute' => 'list_headers',
'options' => [
'multiple' => true,
'size' => 15,
],
'clientOptions' => [
'nonSelectedListLabel' => 'Available Header',
'selectedListLabel' => 'Final Header',
'moveOnSelect' => false,
],
])
->hint('Select the header according the sequence.');
?>

How do I enable my Query button in my view?

I add another table field in my view , but the search button disappeared , How can I retrieve this form button ?
SaleItems = And a table mysql database.
<?php
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'formatter' => ['class' => 'yii\i18n\Formatter','nullDisplay' => ''],
'columns' => [
['class' => 'yii\grid\SerialColumn'],
**[
'attribute' => 'Data',
'content' => function(SaleItems $model, $key, $index, $column) {
return date('d/m/Y', strtotime($model->sale->date));
},
'header' => 'DATA'
],**
]); ?>
</div>
The search field disappears because in not setted properly in searchModel ..
for this you must extend the related modelSeacrh adding the relation with the related model and the filter for the field you need ..
for this you must set in the base mode, the relation
public function getSale()
{
return $this->hasOne(Sale::className(), ['id' => 'sale_id']);
}
/* Getter for sale data */
public function getSaleData() {
return $this->sale->data;
}
then setting up the search model declaring
/* your calculated attribute */
public $date;
/* setup rules */
public function rules() {
return [
/* your other rules */
[['data'], 'safe']
];
}
and extending
public function search($params) {
adding proper sort, for data
$dataProvider->setSort([
'attributes' => [
.....
'data' => [
'asc' => ['tbl_sale.data' => SORT_ASC],
'desc' => ['tbl_sale.data' => SORT_DESC],
'label' => 'Data'
]
]
]);
proper relation
if (!($this->load($params) && $this->validate())) {
/**
* The following line will allow eager loading with country data
* to enable sorting by country on initial loading of the grid.
*/
$query->joinWith(['sale']);
return $dataProvider;
}
and proper filter condition
// filter by sale data
$query->joinWith(['sale' => function ($q) {
$q->where('sale.data = ' . $this->data . ' ');
}]);
Once you do al this the searchModel contain the information for filter and the search field is showed in gridview
What in this answer is just a list of suggestion
you can find detailed tutorial in this doc (see the scenario 2 and adapt to your need)
http://www.yiiframework.com/wiki/621/filter-sort-by-calculated-related-fields-in-gridview-yii-2-0/

Categories