Solution
Since the php is not going to be re-executed someone told me to use a return value from the controller as a parameter of the "success" function into ajax's request.
**CONTROLLER**
if(Yii::$app->request->isAjax){
$query = new \yii\db\Query();
$rows = $query->select([])
->from('task')
->where('date(start) <= date(\'' . $this->request->getBodyParam('start_date') . '\')')
->all();
$items = "";
foreach (ArrayHelper::map($rows,'id','name') as $key => $value)
$items .= '<option value="'. $key .'">'. $value .'</option>\n';
//<option value="16">test</option> example output
echo $items;
}
VIEW
$this->registerJs("$('#task-start').on('change',function(){
$(\".btn, .btn-success\").prop(\"disabled\",true);
var date = $(this).val();
$.ajax({
url: \"".Yii::$app->request->baseUrl."/task/create\",
data: {start_date: date},
type: \"post\",
success: function(dependency_options){
//dependency_options contains what's returned with 'echo' from the controller
$('#task-dependencies').find('option:not(:first)').remove();
$('#task-dependencies').append(dependency_options);
$(\".btn, .btn-success\").prop(\"disabled\",false);
},
error: function () {
console.log('ERROR')
$(\".btn, .btn-success\").prop(\"disabled\",false);
}
});
});",View::POS_READY);
Hoping this might help someone.
My problem
I have a DropDownList with no elements and I want to update the list with elements coming from a Query whenever the user changes the datepicker without refreshing the page.
An example of something similar to this would be those forms in which you type your region and it changes the dropdownlist based on what you choose on the field before.
Maybe I'm using a wrong approach to this cause I'm still new to the MVC model and the Yii2 framework so any idea on how to change it's well appreciated.
What I've tried
With this code below as is I had issues cause after the form was created the first time I could not change it later, I've tried to change the html, as you can see from the script in the success ajax function of the View but the script was executed only the first time the view was loaded.
Controller calling the render
public function actionCreate()
{
$model = new Task();
if(Yii::$app->request->isAjax){
$query = new \yii\db\Query();
$rows = $query->select([])
->from('task')
->where('date(start) <= date(\'' . $this->request->getBodyParam('start_date') . '\')')
->all();
$items = ArrayHelper::map($rows,'id','name');
$model->setItems($items);
return $this->renderPartial('_form',[
'partial' => true,
'model' => $model
]);
}
else if ($this->request->isPost) {
..unnecessary code..
return $this->render('create', [
'model' => $model,
]);
}
The view "create" basically renders the view _form (autogenerated by Gii)
View _form
<?php
//Here it takes the items in the model
//(which will contain the new items to append after ajax call)
$objects = json_encode($model->getItems() ?? []);
var_dump($model->getItems() ?? []);
//Here whenever the datepicker is change will fire ajax request
$this->registerJs("$('#task-start').on('change',function(){
var date = $(this).val();
var items = ". json_encode($model->getItems() ?? []) .";
alert(items);
$.ajax({
url: \"".Yii::$app->request->baseUrl."/task/create\",
data: {start_date: date},
type: \"post\",
success: function(){
//$('#task-dependencies').find('option:not(:first)').remove();
$.each(items, function(key, value) {
$('#task-dependencies')
.append($('<option>', { value : key })
.text(value));
});
},
error: function () {
console.log('ERROR')
}
});
});",View::POS_READY);
?>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'start')->widget(\kartik\date\DatePicker::className(), [
...datepicker that triggers onChange...
]) ?>
//The dropdownlist that should dynamically change
<?= $form->field($model, 'dependencies')->dropDownList(
$model->getItems(),
['prompt' => 'Seleziona una o piĆ¹ dipendenze', 'multiple' => true, 'selected' => 'selected'] // options
);
?>
<?php ActiveForm::end(); ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery#3.6.0/dist/jquery.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css">
You can use Pjax in, yii2:
Controller:
public function actionCreate() {
$model = new Task();
if( Yii::$app->request->post() && Yii::$app->request->isPjax ) {
$query = new \yii\db\Query();
$rows = $query->select([])
->from('task')
->where('date(start) <= date(\'' . $this->request->getBodyParam('start_date') . '\')')
->all();
$items = ArrayHelper::map($rows,'id','name');
$model->setItems($items);
// Alert widget renders a message from session flash.
// Yii::$app->session->setFlash('info', "Ok....");
return $this->renderAjax('_form',[ // Or renderPartial
'partial' => true,
'model' => $model
]);
}
// else if ($this->request->isPost) {
// ..unnecessary code..
return $this->render('create', [
'model' => $model,
]);
}
}
view
// submitEvent: The jQuery event that will trigger form handler. Defaults to "submit".
<?php Pjax::begin(['submitEvent' => 'change']); ?>
<?php $form = ActiveForm::begin([
'options' => ['data-pjax' => ''],
]);
// if(hasFlash ....){ // If used ...
// Yii::$app->session->getFlash('info')
// }
?>
<?= $form->field($model, 'start')->widget......
<?= $form->field($model, 'dependencies')->dropDownList(
$model->getItems(),
// ......
<?php ActiveForm::end(); ?>
<?php Pjax::end(); ?>
Tip: The above codes will meet your goal. But you can use the following code to change more and respond to Pjax events.
jQuery or JavaScript...
$(document).on('pjax:send', function() {
// code ... Example: $('#loading').css({"visibility":"visible"}); //show()
});
$(document).on('pjax:complete', function() {
// code ... // hide()
});
// Or use if Pjax::$submitEvent = 'submit'
// $(document).on('change', '#id', function(event) { $(this).submit(); });
If you still have problems, check the date format method in SQL and ....
Good luck.
Related
I'm migrating my site from Yii to Yii2 and trying to make a basic loan calculator. was reading the documentation and some answers on stack that suggested to use validationUrl.
Below is my code i've done so far. but i can't get my form validation to work. It allows me to submit an empty form. and doesn't show any errors?
Also where do i put my calculations, in actionloanValidate() or actionIndex() ?
<?php
$form = ActiveForm::begin([
'id' => $model->formName(),
'enableAjaxValidation' => true,
'validationUrl' => Url::toRoute(['calculator/default/loan-validate'])
]);
?>
<?= $form->field($model, 'price') ?>
<?= $form->field($model, 'downpayment') ?>
<?= $form->field($model, 'rate') ?>
<?= $form->field($model,'yearloan')->dropDownList(
['1' => '1 (12 months)', '2' => '2 (24 months)');
?>
<?= Html::submitButton('Submit', ['class' => 'btn btn-primary btn-block', 'tabindex' => '3']) ?>
<?php ActiveForm::end(); ?>
<script>
$(document).ready(function () {
var $form = $("#<?= $model->formName() ?>");
$form.on("beforeSubmit", function (event, messages) {
event.preventDefault();
$.ajax({
"type":"POST",
"url":$form.attr('action'),
"data":$form.serialize(),
"beforeSend":function( xhr ) {},
"dataType":"json",
"cache": true,
"success":function(data){
$("#totalLoanAmount").html(data.totalLoanAmount);
$("#monthlyInstallment").html(data.monthlyInstallment);
$("#loanCalcTable").html(data.loanCalcTable);
},
});
return false;
});
});
</script>
in my controller i have this
public function actionIndex() {
$model=new calculatorForm;
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
//DO CALCULATION HERE!!
}
return $this->render('loan-calculator', [
'model' => $model,
]);
}
public function actionloanValidate()
{
$model=new calculatorForm;
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
else {
return $this->renderAjax('loan-calculator', [
'model' => $model,
]);
}
}
my model rules are
public function rules() {
return [
//loan-calculator validation
[['price', 'downpayment', 'rate', 'yearloan'], 'required']
];
}
You are overriding the validation check by defining beforeSubmit in your javascript. You are also likely getting a cancelled ajax request as you need to call event.stopPropagation() after event.preventDefault()
If you are truly trying to achieve only required validation, don't use ajaxValidation.
On beforeSubmit validate the form
event.preventDefault();
event.stopPropagation(); //stops going to submit which will cancel your ajax
//a trick added to ensure validation is triggered on untouched fields
data = $form.data("yiiActiveForm");
$.each(data.attributes, function() {
this.status = 3;
});
$form.yiiActiveForm("validate");
if ($("#form").find(".has-error").length) {
return false;
}
$.ajax({
"type":"POST",
"url":$form.attr('action'),
"data":$form.serialize(),
"beforeSend":function( xhr ) {},
"dataType":"json",
"cache": true,
"success":function(data){
$("#totalLoanAmount").html(data.totalLoanAmount);
$("#monthlyInstallment").html(data.monthlyInstallment);
$("#loanCalcTable").html(data.loanCalcTable);
},
});
As well, the calculcation should be done in the actionIndex (the action of the form)
The goal is to set a variable (stored into AppController.php) whenever the user select a new value from a list.
nav-top.ctp
<?= $this->Form->create(false, ['id' => 'ajaxForm']) ?>
<?= __('Year') ?>
<?= AppController::year(); ?> <---- just for test
<select name="currentYear">
<option value="2020">2020</option>
<option value="2021" selected>2021</option>
</select>
<?= $this->Form->end(); ?>
...
<?php $this->append('script'); ?>
<script type="text/javascript">
$("#currentYear").on("change", function() {
let ajaxdata = $('#ajaxForm').serializeArray();
$.ajax({
type: "POST",
url: "app/setYear",
data: ajaxdata,
success: function(data) {
$(document.body).html(data);
},
error: function (xhr, status, error) {
$(document.body).html(error);
}
});
});
</script>
<?php $this->end(); ?>
AppController.php
class AppController extends Controller
{
private $_year = 2021;
...
public function year()
{
return new Date($this->_year . '-01-01');
}
public function setYear()
{
$this->_year = $this->request->getData('currentYear');
return $this->redirect($this->referer());
}
}
When I change the current item in the select the POST is correctly fired and I get the new value inside the setYear() method.
Then the page reloads, as expected. But all the queries that rely on the year() function, i.e.:
public function index()
{
$this->paginate = [
'contain' => [
'Customers',
'ProformaStates',
'ItemProformas' => ['ItemDeliveryNotes' => ['DeliveryNotes']]
],
'conditions' => [
'proforma_date >=' => AppController::year()
],
'order' => ['proforma_number' => 'desc']
];
$proformas = $this->paginate($this->Proformas);
$this->set(compact('proformas'));
}
still use the old value, like it has not changed.
Furthermore, if I enable the test line above (to printout in the reloaded page the actual value of year) I get the following error:
Using $this when not in object context
on Yii2 Advanced My form insert twice in table, if I click twice my submit button my form insert data to my table twice it's like I clicked two time on my submit button; I'm using ajax for submit my form; my form in view is
<script type="text/javascript">
$(document).ready(function (e) {
$("#upload-gallery").on('submit',(function(e) {
$form = $(this);
e.preventDefault();
$.ajax({
url: $form.attr('action'),
type: "POST",
data: new FormData(this),
contentType: false,
cache: false,
processData:false,
success: function(data)
{
document.getElementById("upload-gallery").reset();
$.pjax.reload({container: '#some_pjax_id', async: false});
},
error: function(){}
});
}));
});
</script>
<div class="post-gallery-form">
<?php $form = ActiveForm::begin(['id' => 'upload-gallery', 'options' => ['enctype' => 'multipart/form-data']]) ?>
<?= $form->field($model_PstGlr, 'PGalleryFile[]')->fileInput(['multiple' => true, 'accept' => 'image/*']) ?>
<?= $form->field($model_PstGlr, 'post_text')->textarea(['rows' => 2]) ?>
<?= $form->field($model_PstGlr, 'permission_id')->dropdownList($model_Permission->PermissionOn()) ?>
<div class="form-group">
<?= Html::submitButton('Save', ['class' => 'btn btn-success']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>
and my controller is
public function actionView($chn)
{
$model_Permission = new Permission;
$model_PstGlr = new PostGallery; $acc_PstGlr = new AxPostGallery;
if ($model_PstGlr->load(Yii::$app->request->post()) )
{
$acc_PstGlr->CreateGallery($model_PstGlr, $chn);
}
return $this->render('view', [
'model' => $this->findModel($chn),
'model_Permission' => $model_Permission,
'model_PstGlr' => $model_PstGlr,
]);
}
The upload gallery function is to upload images on my contents
--
public function CreateGallery ($MPGALLERY, $CHNID)
{
$PSTID = Yii::$app->params['PSTID'];
$USRID = Yii::$app->user->id;
$MPGALLERY->post_id = $PSTID;
$MPGALLERY->user_id = $USRID;
$MPGALLERY->channel_id = $CHNID;
$ly_lgIMGFolder = 'upload/gal/'. $CHNID . '/' . $PSTID . '/' .date('YzHis');
$GLRID = Yii::$app->params['GLRID'];
$MPGALLERY->PGalleryFile = UploadedFile::getInstances($MPGALLERY, 'PGalleryFile');
if( $MPGALLERY->save() )
{
mkdir($ly_lgIMGFolder, 0777, true);
foreach ($MPGALLERY->PGalleryFile as $GImage)
{
$MGALLERY = new Gallery;
$MGALLERY->post_id = $PSTID;
$GImage->saveAs($ly_lgIMGFolder ."/". $GLRID . '.' . $GImage->extension);
$MGALLERY->gallery_lgimage = $ly_lgIMGFolder ."/". $GLRID . '.' . $GImage->extension;
$MGALLERY->save(false);
}
}
}
Appatently, there are a few changes that you may need to make in your code.
For example, create the following function in your controller, and remove the code from your view.
public function actionSave(){
//This part is removed from the actionView, so that the request only saves and does nothing else;
//You can return it back if you test and see that it works
$model_PstGlr = new PostGallery; $acc_PstGlr = new AxPostGallery;
if ($model_PstGlr->load(Yii::$app->request->post()) )
{
$acc_PstGlr->CreateGallery($model_PstGlr, $chn);
}
}
Then on the view, modify your form initialization as follows
The reason for adding the forwad slash is to prevent apache from sending the request to a new URL that ends with the '/';
<?php $form = ActiveForm::begin(['action'=> 'id' => 'upload-gallery', 'options' => ['enctype' => 'multipart/form-data']]) ?>
I hope this one now helps;
I am totally confused about how should i save tag_id and post_id in the table when I still don't have the post_id. I made dropdownlist with options from database table
<?= Html::activeDropDownList($model, 'post_id', $ddItems, ['class' => 'form-control dd-list', 'prompt' => '']) ?>
That list contains tags(PHP, Java etc.).
User can have up to 3 tags on post. I tried to pass tags through GET and take in the controller with \Yii:$app->request->get(['id']) but without result.
my jQuery:
var ddList = $('.dd-list');
var tagList = $('.tag-container');
ddList.on('change', function () {
var tagHolder = document.createElement('div');
tagHolder.setAttribute('class', 'tag-holder');
var selected = $('.dd-list option:selected').text();
tagHolder.setAttribute('id', selected);
console.log(selected);
if(tagList.find('div').length > 2){
alert('You can have most 3 tags!');
return false;
}
if(tagList.find('#'+selected).length){
return false;
}else{
tagHolder.append(selected);
tagList.append(tagHolder);
$.ajax({
type : 'GET',
dataType : 'text',
url : '../post/create?tag=' + selected
});
}
});
actionCreate :
public function actionCreate()
{
$model = new Post();
$id = \Yii::$app->user->identity->id;
$ddItems = ArrayHelper::map(Tag::find()->all(),'tag_id', 'tag_name');
if ($model->load(Yii::$app->request->post())) {
date_default_timezone_set('Europe/Berlin');
$param = \Yii::$app->request->get();
$model->user_id = $id;
$model->date_create = date('m/d/Y');
if($model->save())
{
$tag = \Yii::$app->request->get(['tag']);
return $this->redirect(['view', 'id' => $model->post_id]);
}
throw new \yii\db\Exception("Some problem with DB connection ocurred!");
} else {
return $this->render('create', [
'model' => $model,
'ddItems' => $ddItems
]);
}
}
I am getting error : undefined tag. I am trying to get only one tag (because still don't know how to pass more).
What is the right way to reach my goal? I am in total mess from about 1 day! Ton of thanks in advance!
EDIT
I tried to send that tag with on click event like this :
$('.create-post').on('click', function () {
var tag = $('input[name=chosen-tag]').val();
console.log(tag);
$.ajax({
type : 'GET',
dataType : 'text',
url : '../post/create?tag=' + tag
});
});
I could not understand your programming scenario, But..
Change
$tag = \Yii::$app->request->get(['tag']);
To
$tag = \Yii::$app->request->get('tag');
I am using kartik select2 widget in my yii2 basic app. now i have to display province names in select2 widget on ajax call. It is working fine if i put it directly in form. however not working with ajax call.
Here are my form fields:
<?= $form->field($model, 'role')->dropDownList(
ArrayHelper::map(SubAdminRoles::find()->all(), 'id', 'role_name'),
[
'prompt' => 'Select Role',
'onchange' => '
if($(this).val() != 3) {
$( "#user_area" ).html("showLoading");
$.post( "fetch-area-list?id='.'"+$(this).val(),
function(data) {
$( "#user_area" ).html(data);
})
}'
]
) ?>
<div id="user_area">
</div>
And here is my action code
public function actionFetchAreaList($id) {
// $this->layout = 'none';
$data = [];
if($id == 1) {
$provinceList = \app\modules\adminpanel\models\ProvinceMaster::findAll(['status' => 1, 'is_deleted' => 0]);
foreach($provinceList as $obj) {
$data[$obj['id']] = $obj['province_name'];
}
//print_r($data);
//exit;
} else if($id == 2) {
$subDistrictList = \app\modules\adminpanel\models\SubDistrictMaster::findAll(['status' => 1, 'is_deleted' => 0]);
foreach($subDistrictList as $obj) {
$data[$obj['id']] = $obj['sub_district_name'];
}
}
echo '<label class="control-label">Select Province</label>';
echo Select2::widget([
'name' => 'state_2',
'value' => '1',
'data' => $data,
'options' => ['multiple' => true, 'placeholder' => 'Select Province']
]);
exit;
}
now when i try to get it through ajax i comes with display:none property so i am not able to show my select2 box.
I Also tried changing display:none to display:block in select2 class. In that case i got the select box, but is simple html multiple select box not select2 widget.
How to get it from controller using ajax call?
Thanks in advance.
It is bad practice to render html inside action.
In your case widget requires related JS for initialization. But it will not include in your response.
Move all your html to view area-list and render using following code:
public function actionFetchAreaList($id) {
$this->layout = false;
// ... preparing data
return $this->renderAjax('area-list', [
// ... some view data
]);
}
Method renderAjax renders a named view and injects all registered JS/CSS scripts and files. It is usually used in response to AJAX Web requests.
I also have similar project like this.
I have 2 combobox (using select2). When select a district from the first combobox. It will call an ajax request to get province list and fill into the second combobox.
Here is my solution:
Using Select2 widget as normally in form
Using javascript to call ajax request and change data of the second combobox.
My controller response data in json format.
$('#district-selector').on('change', function() {
var districtId = $(this).val();
var url = $(this).attr('tb_href');
$('#province-selector').html('');
$.get(
url,
{
city_id: districtId
},
function(response) {
if (response.error == 0 && response.data.length) {
$('#province-selector').append(new Option('', ''));
$.each(response.data, function() {
console.log(this.id + '--' + this.title);
var newOption = new Option(this.title, this.id);
$('#province-selector').append(newOption);
});
}
$('#province-selector').trigger('change');
}
);
});
Demo: demo link