Yii2: my new action is not found - 404 - yii2

I am trying to call a custom action by ajax, but the response returned is 404, I am pretty sure its a routing issue, but I can't figure how to solve it.
here is my code:
action
public function actionGetOne($id){
$model = Driver::findOne($id);
if(!empty($model)){
$data = [];
$row = [
'id'=>$model->id,
'full_name'=>$model->full_name,
'email'=>$model->email,
'nationality_id'=>$model->nationality_id,
'current_location'=>$model->current_location,
'medical_check_id'=>$model->medical_check_id,
'img'=>$model->img,
'current_fleet_id'=>$model->current_fleet_id,
'availability'=>$model->availability
];
$data[] = $row;
echo json_encode(['driver-getOne'=>'success','data'=>$data]);
} else{
echo json_encode(['driver-getOne'=>'failure']);
}
}
ajax
$.ajax({
url:'<?= urldecode(Url::toRoute(['driver/get-one'])); ?>?id=<?= $id; ?>',
method:'post',
dataType:'json',
success:function(response){}
error:function(){
alert('target action is not found!');
}
}
backend/config/params.php
<?php
return [
'adminEmail' => 'admin#example.com',
'urlRules' => [
'' => 'site/index',
'login/' => 'site/login',
'signup/' => 'site/signup',
'<controller:[\w-]+>/<action:\w+>' => '<controller>/<action>',
'<controller:[\w-]+>/<action:\w+>/<id:\d+>' => '<controller>/<action>',
'<controller:[\w-]+>/<id:\d+>' => '<controller>/view',
'<controller:[\w-]+>/create' => '<controller>/create',
'<controller:[\w-]+>/update/<id:\d+>' => '<controller>/update',
'<controller:[\w-]+>/delete/<id:\d+>' => '<controller>/delete',
'<controller:[\w-]+>/get-all' => '<controller>/get-all',
'<controller:[\w-]+>/get-one' => '<controller>/get-one',
'<controller:[\w-]+>/update-status' => '<controller>/update-status',
]
];

Change few things and try again.
Action:
public function actionGetOne($id)
{
\Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
$model = Driver::findOne($id);
if (empty($model)) {
return ['driver-getOne' => 'failure'];
}
return [
'driver-getOne' => 'success',
'data' => [[
'id' => $model->id,
'full_name' => $model->full_name,
'email' => $model->email,
'nationality_id' => $model->nationality_id,
'current_location' => $model->current_location,
'medical_check_id' => $model->medical_check_id,
'img' => $model->img,
'current_fleet_id' => $model->current_fleet_id,
'availability' => $model->availability
]],
];
}
Action should return something to properly finish response sequence otherwise unwanted things can happen. By setting response format you can get JSON encoded array automatically.
AJAX:
$.ajax({
url:'<?= Url::to(['driver/get-one', 'id' => $id]) ?>',
method:'post',
dataType:'json',
success:function(response){}
error:function(){
alert('target action is not found!');
}
}
Get your URL using proper syntax.
Params:
'urlRules' => [
'' => 'site/index',
'login' => 'site/login',
'signup' => 'site/signup',
'<controller:[\w-]+>/<id:\d+>' => '<controller>/view',
'<controller:[\w-]+>/<action:[\w-]+>/<id:\d+>' => '<controller>/<action>',
'<controller:[\w-]+>/<action:[\w-]+>' => '<controller>/<action>',
]
I'm assuming you are passing urlRules to components > urlManager > rules otherwise URL rules won't work.
I removed redundant rules. In general add general rules last and specific rules first.

Related

Listview pagination and GET parameters on the next page

I have this UrlManager rule:
'<pageSlug>-pa<pageId:\d+>' => 'page/page',
Then I have page with url like this domain.com/blog-pa54
In this example I have
pageSlug = blog and pageId = 54
In my view I have ListView with my blog posts.
I set my dataprovider like this:
$dataProvider = new ActiveDataProvider([
'query' => BlogPost::find()
->where([
'active' => 1
]),
'pagination' => [
'defaultPageSize' => 3,
//'params' => [], when I set this, it only shows get param "page"
'pageParam' => 'page',
'route' => $pageURL, // this variable is equal to "blog-pa54"
],
]);
My route is current url slug - blog-pa54
When I go to next page I recieve this url: blog-pa54?pageSlug=blog&pageId=54&page=2
How can I remove $_GET params pageSlug and pageId from url?
I try to set pagination > params = [] and it remove this get param, but when I go to other page it doesn't change items in my ListView
Here is also my ListView and LinkPager
<div class="blog-post-wrapper">
<?= ListView::widget([
'dataProvider' => $blogPostsDataprovider,
'itemOptions' => ['tag' => null],
'options' => [
'tag' => false,
],
'layout' => "{summary}<div class='row'>{items}</div>",
'itemView' => function ($model, $key, $index, $widget) use ($page) {
return $this->render('//blog-post/_blogPostList', [
'page' => $page,
'model' => $model
]);
},
]); ?>
</div>
<?= \yii\widgets\LinkPager::widget([
'pagination' => $blogPostsDataprovider->pagination,
'linkContainerOptions' => [
'class' => 'page-item',
],
'linkOptions' => [
'class' => 'page-link',
],
]); ?>
Okay, I figured it out.
params attribute must not be empty.
You can set it like this:
'params' => [
'page' => isset($_GET['page']) ? $_GET['page'] : 1,
]
Or if you have another get params like get filters:
$getParams = [];
if(isset($_GET)){
foreach($_GET as $getKey => $getValue){
if(in_array($getKey, ['pageSlug', 'pageId'])){ //here I skip my params from UrlManager rule
continue;
}
$getParams[$getKey] = $getValue;
}
}
.....
'params' => $getParams

How to add new values to multiple Select2 from Kartik instead of replace old ones?

I use Select2 widget in multiply mode in YII2 framework. "kartik-v/yii2-widget-select2": "#dev" - this one I have downloaded via composer.
kartik-v/yii2-widget-select2 dev-master dd09e46
I added initial values with ajax on widget init ('initSelection'). And added another ajax method to suggest new values on user's typing. When user select one from list, it replaced initial values what were added on init. New values replace initial values, but don't another new.
I want new values to add to initial instead of replace it.
<?= $form->field($model, 'security[]')->widget(Select2::class, [
'attribute' => 'security',
'hideSearch' => true,
'data'=>$security_data,
'options' => [
'placeholder' => 'Security',
'multiple' => true,
],
'pluginOptions' => [
'allowClear' => true,
'minimumInputLength' => 1,
'ajax' => [
'url' => Url::toRoute([ '/admin/security/select-items' ]),
'dataType' => 'json',
'data' => new JsExpression('function(params) { return {q:params.term}; }'),
'results' => new JsExpression('function(data,page) { return {results:data.results}; }'),
],
'initSelection' => new JsExpression('function(element, callback) { var id = '.Yii::$app->request->getQueryParams()['id'].';if(id !== "") {$.ajax("' . \yii\helpers\Url::toRoute([ '/admin/security/init-items' ]) . '", {data: {id: id},dataType: "json"}).done(function(data) {callback(data.results);});}}'),
],
]); ?>
And here is my api methods:
public function actionSelectItems($q = null){
Yii::$app->response->format = Response::FORMAT_JSON;
$out = ['results' => []];
if(!empty($q)){
$items = Security::find()->where(['like', 'title', $q])->all();
foreach ($items as $item){
$out['results'][] = ['id'=>$item->id, 'text'=>$item->title];
}
}
return $out;
}
public function actionInitItems($id = null){
Yii::$app->response->format = Response::FORMAT_JSON;
$adv = Adv::findOne($id);
$security = #json_decode($adv->security, true);
$out = ['results' => []];
foreach ($security as $item){
$text = Security::findOne($item)->title;
$out['results'][] = ['id'=>$item, 'text'=>$text];
}
return $out;
}
Is there some sort of settings or I missed something when handle http result?

Dropdown in list data

This is my code:
[
'attribute' => 'status',
'value' => function ($model) {
return Html::dropDownList('status', ['10' => 'Active', '20' => 'Deactive']);
},
],
I just want dropdown in status column. If record is active or deactive it will be selected.
You need to use 'format' => 'raw' for the column options and your definition for the dropDownList() is wrong you need to have the selection string as the second parameter and the dropdown options as the third parameter. Change your code to below:
[
'attribute' => 'status',
'format' => 'raw',
'value' => function ($model) {
return Html::dropDownList('status', $model->status, ['10' => 'Active', '20' => 'Deactive']);
},
],
EDIT
You didnt had in the initial requirements that you waned to update the status too when the drop down is changed. You can bind ajax call to the drop-down.
Add the following javascript on top of your view where you are initializing the GridView.
NOTE: Change the url:'controller/update-status?id'+id in the ajax call to the relative controller where you want to update the status for the row, but dont remove the id
$js = <<<JS
$(document).on('ready pjax:success',function(){
$(".switch-status").on('change',function(){
var data={};
data[$(this).attr("name")]=$(this).val();
var id=$(this).closest("tr").data('key');
$.ajax({
method:'post',
url:'/controller/update-status?id='+id,
data:data,
success:function(data){
if(!data.success){
alert(data.message);
}else{
alert("Status updated.");
}
},
error:function(jqXHR, textStatus, errorThrown ){
alert(errorThrown);
}
});
});
});
JS;
$this->registerJs($js, yii\web\View::POS_END);
Then inside your GridView column for status change the dropdown to the following
return Html::dropDownList(Html::getInputName($model, 'active'), $model->active, [10 => 'Active', 20 => 'Deactive'], ['class' => 'switch-status']);
And the go to your controller and add the action code for updating the status
Note: Change the Model in the first line $model = Model::findOne($id); name with the respective model you are using.
public function actionUpdateStatus($id) {
$model = Affiliate::findOne($id);
$app = Yii::$app;
$request = $app->request;
if($request->IsAjax && $request->isPost) {
Yii::$app->response->format = Response::FORMAT_JSON;
if($model->load($request->post()) && $model->save()) {
return ['success' => true];
} else {
return [
'success' => false,
'message' => implode('<br />', ArrayHelper::getColumn($model->errors, '0'))
];
}
}
}
Use content property to render HTML elements. For example:
[
'attribute' => 'status',
'content' => function ($model) {
return Html::dropDownList('status', $model->status, ['10' => 'Active', '20' => 'Deactive']);
},
],

Yii2 Dynamic form update fail on kartik-Select2

I am using wbraganca dynamic form widget. It works fine for the Create action.
Let me thanks for those guys making great tutorial video on youtube!!!
I am working on the Update action now. I work it on a purchase order function.
the controller of update action :
public function actionUpdate($id)
{
$model = $this->findModel($id);
$modelsItem = $model->purchaseitems;
if ($model->load(Yii::$app->request->post()) ) {
$oldIDs = ArrayHelper::map($modelsItem, 'purchaseitem_id', 'purchaseitem_id');
$modelsItem = Model::createMultiple(Purchaseitem::classname(), $modelsItem);
Model::loadMultiple($modelsItem, Yii::$app->request->post());
$deletedIDs = array_diff($oldIDs, array_filter(ArrayHelper::map($modelsItem, 'purchaseitem_id', 'purchaseitem_id')));
$valid = $model->validate();
$valid = Model::validateMultiple($modelsItem) && $valid;
if ($valid) {
$transaction = \Yii::$app->db->beginTransaction();
try {
if ($flag = $model->save(false)) {
if (! empty($deletedIDs)) {
Purchaseitem::deleteAll(['purchaseitem_id' => $deletedIDs]);
}
foreach ($modelsItem as $modelItem) {
$modelItem->purchaseitem_purchaseorder_id = $model->purchaseorder_id;
$modelItem->purchaseitem_description = Inventory::findOne($modelItem->purchaseitem_inventory_id)->inventory_partno;
if (! ($flag = $modelItem->save(false))) {
$transaction->rollBack();
break;
}
}
}
if ($flag) {
$transaction->commit();
return $this->redirect(['view', 'id' => $model->purchaseorder_id]);
}
} catch (Exception $e) {
$transaction->rollBack();
}
}
//return $this->redirect(['view', 'id' => $model->purchaseorder_id]);
} else {
return $this->render('update', [
'model' => $model,
'modelsItem' => (empty($modelsItem)) ? [new Purchaseitem] : $modelsItem
]);
}
}
But I think the problem may happen on the view file, as the Select2 field can show the value, which is the 'id' of the product rather than the product code.
view:
<div class="panel panel-default">
<div class="panel-body">
<?php DynamicFormWidget::begin([
'widgetContainer' => 'dynamicform_wrapper',
'widgetBody' => '.container-items',
'widgetItem' => '.item',
'limit' => 50,
'min' => 1,
'insertButton' => '.add-item',
'deleteButton' => '.remove-item',
'model' => $modelsItem[0],
'formId' => 'dynamic-form',
'formFields' => [
'purchaseitem_inventory_id',
'purchaseitem_qty',
'purchaseitem_cost_usd',
'purchaseitem_deliverydate',
],
]); ?>
<?php foreach ($modelsItem as $i => $modelItem): ?>
<div class="item">
<?php
// necessary for update action.
if (! $modelItem->isNewRecord) {
echo Html::activeHiddenInput($modelItem, "[{$i}]purchaseitem_id");
}
?>
<div class="row">
<?= $form->field($modelItem, "[{$i}]purchaseitem_inventory_id")->widget(
Select2::classname(), [
'pluginOptions' => [
'allowClear' => true,
'minimumInputLength' => 2,
'language' => [
'errorLoading' => new JsExpression("function () { return 'Error on finding results...'; }"),
],
'ajax' => [
'url' => Url::toRoute('inventory/inventorylist'),
'dataType' => 'json',
'data' => new JsExpression('function(params) { return {q:params.term}; }')
],
'escapeMarkup' => new JsExpression('function (markup) { return markup; }'),
'templateResult' => new JsExpression('function(purchaseitem_inventory_id) { return purchaseitem_inventory_id.text; }'),
'templateSelection' => new JsExpression('function (purchaseitem_inventory_id) { return purchaseitem_inventory_id.text; }'),
],
])->label(false) ?>
<?= $form->field($modelItem, "[{$i}]purchaseitem_qty")->textInput(['maxlength' => true])->label(false) ?>
<?= $form->field($modelItem, "[{$i}]purchaseitem_cost_usd")->textInput(['maxlength' => true])->label(false) ?>
<?= $form->field($modelItem, "[{$i}]purchaseitem_deliverydate")->widget(
DatePicker::className(), [
'options' => [
['placeholder' => 'Please enter delivery date'],
],
'removeButton' => false,
'pluginOptions' => [
'autoclose'=>true,
'format' => 'yyyy-mm-dd',
'todayHighlight' => true,
]
]
)->label(false) ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
I have a thought that the problem maybe related to that few lines of JsExpression function.
'escapeMarkup' => new JsExpression('function (markup) { return markup; }'),
'templateResult' => new JsExpression('function(purchaseitem_inventory_id) { return purchaseitem_inventory_id.text; }'),
'templateSelection' => new JsExpression('function (purchaseitem_inventory_id) { return purchaseitem_inventory_id.text; }'),
For the Select2 query URL method is here:
public function actionInventorylist($q = null, $id = null) {
Yii::$app->response->format = yii\web\Response::FORMAT_JSON;
$out = ['results' => ['id' => '', 'text' => '']];
if (!is_null($q)) {
$query = new Query;
$query->select('inventory_id AS id, inventory_partno AS text')
->from('inventory')
->where(['like', 'inventory_partno', $q])
->limit(10);
$command = $query->createCommand();
$data = $command->queryAll();
$out['results'] = array_values($data);
}
elseif ($id > 0) {
$out['results'] = ['id' => $id, 'text' => Inventory::find($id)->inventory_partno];
}
return $out;
}
I can load the record, when I click in the update view. Most of the data are feed in right place of the form, except the 'partno' field. I use Select2 to let user select partno by text and store the 'id' in table. It works on the Create view.
but in the update view, it only show the 'id' instead of the 'partno'.
if I make input to the field, I can select 'other' partno only, let me explain here:
if there are 2 code, "ABC" with 'id' is 1, "XYZ" with 'id' 2.
the record original is "ABC", the field show "1".
If I input "XYZ", it will show "XYZ" as normal effect of widget. But, if I change back to "ABC", it will show "1" instead of "ABC".
And the form also cannot submit for update. the button click with no effect.
I am new to Yii2 framework, and quite stuck on this issue, does anyone knows how can I solve this?
THANKS!!!!
I just solve the problem, a few issue happened actually.
To solve the Select2 widget cannot display the partno instead of the ID, I find the partno by the ID and feed it with initValueText in Select2. For my case:
$partno = empty($modelItem->purchaseitem_inventory_id) ? '':Inventory::findOne($modelItem->purchaseitem_inventory_id)->inventory_partno;
$form->field($modelItem, "[{$i}]purchaseitem_inventory_id")->widget(Select2::classname(), ['initValueText' => $partno, ...
About the update POST fail issue, I got error of Getting unknown property: backend\models\Purchaseitem::id, and I found it happened on the Dynamic-form widgets Model.php line 25. That lines is $keys = array_keys(ArrayHelper::map($multipleModels, 'id', 'id'));
If I change the 'id' to my ID field name, i.e 'purchaseitem_id', it will work, but this model will only work for this Id field name afterward. So I get the model's primaryKey and make it work for my other model.
add this line $primaryKey = $model->tableSchema->primaryKey; and modify above line $keys = array_keys(ArrayHelper::map($multipleModels, $primaryKey, $primaryKey));

Set enablePushState = false on specific urls inside Pjax container (Yii2)

I need pjax to work on change status click and it's working fine but don't want it to change the URL as well. Below is the code:
<?php Pjax::begin(['id'=>'pjax-container-agency-index', 'timeout' => 10000, 'enablePushState' => true]); ?>
<?= GridView::widget([...,
[
'label' => 'Status',
'format' => 'raw',
'value' => function ($data) {
if ($data->deactive == 0) {
return Html::a(FA::i('circle'), ['agency/change-status', 'id' => $data->member_id, 'set' => 1], ['onclick' => "return confirm('Deactive this state/site?');", 'class' => 'status-inactive']);
} else {
return Html::a(FA::i('circle'), ['agency/change-status', 'id' => $data->member_id, 'set' => 0], ['onclick' => "return confirm('Active this state/site?');", 'class' => 'status-active']);
}
},
],
]);
<?php Pjax::end(); ?>
actionChangeStatus() is as below:
public function actionChangeStatus($id, $set) {
if (!empty($id) && isset($set)) {
$localGovt = $this->findModel($id);
$localGovt->deactive = $set;
if ($localGovt->save()) {
Yii::$app->getSession()->setFlash('success-status', 'State Status Changed');
} else {
Yii::$app->getSession()->setFlash('error-status', 'There is some error. Please consult with Admin.');
}
$searchModel = new AgencySearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider
]);
}
}
Note: I need 'enablePushState' => true for other events, so I can't change it to false inside Pjax::begin
I believe it's
'enableReplaceState' => true
Pass this attribute to Pjax:
Pjax::begin(['enablePushState' => false]);
This worked for me.