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)
Related
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.
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
I don't want to submit a form but i want to get the value of a input field and send it to controller via ajax to be save in database.
I have this below JS code to help me get the content of the input field and send to the server side after 3 second of user input
<?php
$script = <<< JS
$(document).ready(function(){
//setup before functions
var typingTimer;
var doneTypingInterval = 3000;
var \$TitleInput = $('#product-product_title');
//on keyup, start the countdown
\$TitleInput.on('keyup input change paste',function(){
clearTimeout(typingTimer);
if (\$TitleInput.val()) {
typingTimer = setTimeout(doneTyping, doneTypingInterval);
}
});
//user is "finished typing," do something
function doneTyping () {
data = \$TitleInput.val();
$.ajax({
url: '/trobay/draft/create',
type: 'POST',
data: data,
success: function (data) {
alert(data)
},
error: function(jqXHR, errMsg) {
// handle error
alert(errMsg);
}
});
}
});
JS;
$this->registerJs($script);
?>
in my controller i have this
public function actionCreate()
{
$model = new Draft();
if ($model->load(Yii::$app->request->post())) {
$model->created_at = \time();
if($model->save()){
echo draftId;
}else{
echo '0';
}
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
I want to echo back the draftid if the title was draft was save successfully
how to make this work plase any help on this
below is my view
<?php $form = ActiveForm::begin([
'id' => $model->formName(),
'enableClientValidation' => true,
'fieldConfig' => ['template' => '{label}{input}{hint}']
]); ?>
<div class="row">
<div class="col-md-12">
<?= $form->field($model, 'product_title')->textInput([
'class' => 'title-input',
'placeholder' => 'Give us a title for your items(include size,brand,color,material. e.t.c)',
])->label(false) ?>
</div>
<div class="col-md-12 text-muted">E.g Men's blue addidas glide running shoes size 11 </div>
</div>
<?= $form->field($model, 'user_id')->textInput() ?>
<?= $form->field($model, 'product_name')->textInput(['maxlength' => true]) ?>
<?= $form->field($model, 'product_description')->textarea(['rows' => 6]) ?>
<?= $form->field($model, 'category_id')->textInput() ?>
i have this in my view but i would like to only save the value of the first input field after 3 second that user entered there value
Try this JS, but change $('#form') on your ID form
$(document).ready(function(){
//setup before functions
var typingTimer;
var doneTypingInterval = 3000;
var $form = $('#form');
//on keyup, start the countdown
$form.find('input[type="text"]').on('keyup input change paste',function(){
clearTimeout(typingTimer);
if ($(this).val()) {
typingTimer = setTimeout(doneTyping, doneTypingInterval);
}
});
//user is "finished typing," do something
function doneTyping () {
$.ajax({
url: '/trobay/draft/create',
type: 'POST',
data: $form.serialize(),
success: function (data) {
alert(data)
},
error: function(jqXHR, errMsg) {
// handle error
alert(errMsg);
}
});
}
});
I can't find a way in Yii2 for the following:
First validate my form via AJAX because I have a UNIQUE field.
Then if the validation is correct submit the form data to the form action via AJAX.
Here is what I got so far:
In my view:
<?php
$form = ActiveForm::begin([
'id' => 'subscribe-form',
'action' => ['site/subscribe'],
'validationUrl' => ['site/validate-subscribe'],
'validateOnSubmit' => true,
'enableAjaxValidation' => true,
]) ?>
<?= $form->field($model, 'name')->input('text', ['class' => 'w-input']) ?>
<?= $form->field($model, 'email')->input('text', ['class' => 'w-input']) ?>
<div class="form-group">
<?= Html::submitButton('SUSCRIBIRSE', ['class' => 'w-inline-block btn', 'name' => 'login-button']) ?>
</div>
<?php ActiveForm::end() ?>
In my controller:
public function actionValidateSubscribe(){
if (Yii::$app->request->isAjax) {
Yii::$app->response->format = Response::FORMAT_JSON;
$model = new Subscription(Yii::$app->getRequest()->getBodyParams()['Subscription']);
if (!$model->validate()) {
return ActiveForm::validate($model);
}
}
}
public function actionSubscribe()
{
if (Yii::$app->request->isAjax) {
Yii::$app->response->format = Response::FORMAT_JSON;
$model = new Subscription(Yii::$app->getRequest()->getBodyParams()['Subscription']);
if(!$model->validate()){
throw new NotAcceptableHttpException('Tiene errores de validación en la forma');
}
if($model->save()){
Yii::$app->mailer->compose('subscription', ['model'=>$model])
->setTo([$model->name => $model->email])
->setFrom(['Test' => 'info#test.com'])
->setSubject('Por favor confirme su suscripción')
->send();
}
$response = [
'data'=>$model->getAttributes(),
'success'=>'true'
];
return $response;
}
}
In my JS:
$(document).ready(function () {
$('body').on('beforeSubmit', 'form#subscribe-form', function () {
var form = $(this);
// return false if form still have some validation errors
if (form.find('.has-error').length) {
return false;
}
// submit form
$.ajax({
url : form.attr('action'),
type : 'post',
data : form.serialize(),
success: function (response) {
// do something with response
},
error : function () {
}
});
return false;
});
});
The problem is that yii.ActiveForm.js is not triggering the afterVAlidate or beforeSubmit events when I set "enableAjaxValidation" to true in my ActiveForm.
Try $('form#subscribe-form').on('beforeSubmit', function () {...});
I'm using Yii2's advanced template, and looking for a way to display a dialog with 'Please wait...' message while sending an login form to the server.
Here is my active form code:
<?php $form = ActiveForm::begin([
'id' => $model->formName(),
'enableAjaxValidation' => true,
]); ?>
<fieldset>
<?= $form->field($model, 'username', [
'inputOptions' => [
'placeholder' => $model->getAttributeLabel('username'),
],
])->label(false); ?>
<?= $form->field($model, 'password', [
'inputOptions' => [
'placeholder' => $model->getAttributeLabel('password'),
],
])->label(false)->passwordInput() ?>
<?= $form->field($model, 'rememberMe')->checkbox() ?>
<?= Html::submitButton('Login', ['class' => 'btn btn-lg btn-success btn-block', 'name' => 'login-button']) ?>
</fieldset>
<?php ActiveForm::end(); ?>
And my server side action:
public function actionLogin()
{
if (!\Yii::$app->user->isGuest) {
return $this->goHome();
}
$model = new LoginForm();
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
if ($model->load(Yii::$app->request->post()) && $model->login()) {
return $this->goBack();
} else {
return $this->render('login', [
'model' => $model,
]);
}
}
I'm successfully validating the inputs / sending the form, but need to display a dialog, so if the connection is slow the user will get an idea that the form is actually sending and needs more time to complete.
For ActiveForm you need to use according events. Currently it's managed with Javascript (see official upgrade info).
$('#myform').on('ajaxBeforeSend', function (event, jqXHR, settings) {
// Activate waiting label
}).on('ajaxComplete', function (event, jqXHR, textStatus) {
// Deactivate waiting label
});
Here is more detailed info about these two events.
ajaxBeforeSend:
ajaxBeforeSend event is triggered before sending an AJAX request for
AJAX-based validation.
The signature of the event handler should be:
function (event, jqXHR, settings)
where
event: an Event object.
jqXHR: a jqXHR object
settings: the settings for the AJAX request
ajaxComplete:
ajaxComplete event is triggered after completing an AJAX request for
AJAX-based validation. The signature of the event handler should be:
function (event, jqXHR, textStatus)
where
event: an Event object.
jqXHR: a jqXHR object
textStatus: the status of the request ("success", "notmodified", "error", "timeout", "abort", or "parsererror").
Also check this extension, maybe it will be useful for this purpose.
Please use the following javascript solution to listen to 'before submit'
$('body').on('beforeSubmit', 'form#yourFormId', function() {
$('#loading').show(); //show the loading div
var form = $(this);
$.ajax({
url: form.attr('action'),
type: 'post',
data: form.serialize(),
success: function(data) {
// do processing of data
$('#loading').hide(); //hide it
}
});
return false;
});
You will need to add a div with id loading and use suitable css for that
<div id='loading'> Loading ... </div>
Adding a maunal close button to this div is also recommended for cases with network fluctuations