Yii2 URL manager rules and forms with GET method - php

I have a global search form that submits to search action of a controller:
<?=Html::beginForm(['/feqh/search'], 'get', ['class' => 'navbar-form navbar-left', 'role' => 'search', 'id' => 'searchForm']);?>
<div class="form-group has-feedback Right">
<input id="q" type="text" class="form-control" placeholder="<?=yii::t('app','Search');?>" name="q" value="<?= Html::encode(\Yii::$app->getRequest()->getQueryParam('q',""));?>" />
<i class="form-control-feedback glyphicon glyphicon-search"></i>
</div>
<button type="submit" class="btn btn-default"><?=yii::t('app','Submit');?> <i class="glyphicon glyphicon-ok"></i></button>
</form>
I decided to make pretty URL for search through rules as following:
'search/<q:\w+>' => 'feqh/search',
However, submitting the form always generate the following URL:
example.com/feqh/search?q=anySearchString
However, example.com/search/anySearchString is accessible. Here the problem with submitting using the form.
I tried to change the form action URL:
<?=Html::beginForm(['feqh/search'] i.e removing the initial / but It does not make any difference.
By the way, the following rule is working too:
'search' => 'feqh/search', it makes example.com/search?q=anySearchString. However, the applying of this rule preventexample.com/search/anySearchString`

This has nothing to do with your pretty URL configuration (and not even Yii)... It's a browser thing. It only knows how to submit a form is posted as either a GET or a POST.
So since you are posting in GET mode it will simply add the inputs as query parameters to your URL.
If you want the URL in the address bar to represent your pretty URL you'll have to take control over the submit and perhaps issue a redirect instead?
$('#searchForm').submit(function() {
window.location = $(this).attr("action") + '/' + $('#q').val();
return false;
});
It's the only way I can think of right now.

You can try something like:
'search/<q:w>' => 'feqh/search/variable_name/<q>'
Then in your
actionSearch()
Do something like
$query = isset($_REQUEST['variable_name']) ? $_REQUEST['variable_name'] : '';

Or u can try to make redirect action next to your search action and change the search form so it leads to redirect.
Put this into common/main.php rules(advanced app):
'controller/action/<param:[\w-]+>/<page:[\d]+>' => 'controller/action',
'controller/action/<param>' => 'controller/action',
You need to change "controller","action", and "param" into your controller action and parameter.
This is mainly for search problem i encountered so i posted it here in hope it helps someone.

Related

Disable multiple button clicks works but is not updating my data

I am trying to prevent a button to be clicked multiple times to avoid resending requests. The disabling works but my data to be sent or updated is not executed.
<form class="detail_form" method="POST" action="{{ url('update', $id) }}" enctype="multipart/form-data">
#csrf
<button class="btn btn-update accntfrm_btn" type="submit" id="btn-update">Update</button>
</form>
$("#btn-update").on('click', function (event) {
event.preventDefault();
$(this).prop('disabled', true);
setTimeout(function(){el.prop('disabled', false); }, 3000);
});
How can I execute my updates and disallow the multiple clicks at the same time?
Use like this in action attribute of Form,
{{ route('update', ['id'=>$id]) }}
I guess it is your route,
Route::post('/update/{id}','YourController#your_function')->name('update');
and in your controller,
public function your_function(Request $request, $id){ // your code }
and if you want to go pure laravel,
use Form class
{!! Form::open(['route' => ['update', 'id'=>$id], 'files' => true, 'class' => 'detail_form']) !!}
event.preventDefault();
prevents default action of the form, this means that your form is not going to submit to the server. what you can do is use ajax or maybe axios if you have it installed to send your information to the server. Since you obviously have jquery, you can make an ajax request to your server to update your information like so
`const route = "{{ route('update', $id)}}";`
or
const route = "/route/to/your/server";
`$.post(route,
{//add a body if you need to send some information to the server
//it is optional},
function(data, status){// in this callback you can create a feedback
//from a successful trip to and from the server
//for your users
})`
.fail(function(error){
//in this callback you can handle errors from a failed trip
//to and from the server
});

i cant pass the parameter id on the URI slim framework

i have an update form to update the product details :
<form action="{{ path_for('product.update', {id: product.id}) }}" method="get">
// another fields
<input type="hidden" name="_METHOD" value="PUT">
<input type="submit" name="submit" class="submit" value="update">
routes :
$app->get('/admin/product/edit/{id}', ['maimana\Controllers\AdminController','editupdateProduct'])->setName('product.edit');
$app->get('/admin/product/update/product/{id}', ['maimana\Controllers\AdminController','updateProduct'])->setName('product.update');
and the controller :
public function updateProduct($id, Request $request, Response $response){
$product = $this->product->where('id',$id)->first()->update([
'title' => $request->getParam('title'),
'category' => $request->getParam('category'),
'slug' => $request->getParam('slug'),
'description' => $request->getParam('description'),
'price' => $request->getParam('price'),
'image' => $request->getParam('image'),
'stock' => $request->getParam('stock')
]);
return $response->withRedirect($this->router->pathFor('admin.product'));
}
everytime i hit the update button, i cant pass the parameter is automatically and it turns PAGE NOT FOUND. but when i add the id manually it works and redirect back to admin.product.
please help me im getting stuck for about 4 days and i need this for my college task
Slim also uses Restful URLs it seems. When you have the _method set to 'put' in the form and sends the request, it will interpret it as a PUT request rather than a GET. Since this is your college task, I think I've helped you with 99% of the problem here.

Can't query JSON (Laravel + VueJS)

my problem exactly smiliar with this one cant't query json data in laravel 5.2
Already try to implement the right answer from it but still, no luck.
I don't know why....
Previous, i found this Laravel 5.2 Codeception functional test issue with PUT / PATCH requests too, already try to use suggestion from him, but no luck too.
Here's my Laravel Controller
public function update(Request $request, $id)
{
$phonebook = Phonebook::findOrFail($id);
$phonebook->update($request->all());
// even i try this
// Phonebook::findOrFail($id)->update($request->all());
// return Response::json() or return response()->json();
// No luck
}
My function in vue script for update data
editContact: function(id)
{
this.edit = true
var contactid = this.newContact.ID
this.$http.patch('/api/contact/' + contactid, this.newContact, function (data) {
console.log(data)
})
},
Change my vue script to be like the right answer from question above, same result. No effect.
And my button to do edit like this
<form action="#" #submit.prevent="addNewContact">
<div class="form-group">
<label for="contactName">Name : </label>
<input type="text" v-model="newContact.CONTACTNAME" class="form-control" id="contactName">
</div>
<div class="form-group">
<label for="phoneNumber">Phone number : </label>
<input type="text" v-model="newContact.PHONENUMBER" class="form-control" id="phoneNumber">
</div>
<div class="form-group">
<button class="btn btn-primary btn-sm" type="submit" v-if="!edit">Add new Contact</button>
<button class="btn btn-primary btn-sm" type="submit" v-if="edit" #click="editContact(newContact.ID)">Edit Contact</button>
</div>
</form>
Note :
My route file using resource or manual route always same
Route::resource('/api/contact/', 'PhonebookController');
or
patch('/api/contact/{id}', ['uses' => 'PhoneboookController#update']);
And then, there something strange.
(Maybe i am wrong) there no issue or error if we look the detail. But, if we change to response tab the result was empty
After all that process, nothing happen with the data.
CONTACTNAME should be "Mizukiaaaaaaaa" like first screenshot instead of "Mizuki"
Am I missing something??
Any advise?
Thanks
As I suggested to you, try to invert the params in your update method in your controller.
And to get a response, you have to send it back (with code 200, 400, 401, whatever you want).
public function update($id, Request $request)
{
$phonebook = Phonebook::findOrFail($id);
$phonebook->update($request->all());
// your treatment
return Response::json([
'param' => 'value'
], 200);
}
If you want to debug and see it in you response, you can make a dd('debug')in your method, you'll see it in the Ajax request response.
That should work for you !
After browsing and ask so much people about this, finally found it! There's nothing wrong with the request or response. My mistakes are mutator update that i used and my model.
Updated answer
Reason answered here and then I just changed update function on controller. Here the result
public function update(Phonebook $phonebook, Request $request, $id)
{
// You can add any fields that you won't updated, usually primary key
$input = $request->except(['ID']);
// Update query
$saveToDatabase = $phonebook->where('ID', '=', $id)->update($input);
return $saveToDatabase;
}
My previous answer updated all fields including the primary key, somehow it successful update data, but it leave error for sure (duplicate primary key). The query looks like UPDATE SET field = 'value' without condition.
This case is for model that doesn't have any relation with other models (tables), or the model act as master.

How to post data to database using Ajax call in Laravel?

I have a subscribe box :
My goal is when the user enter the email, I want to save it to my database.
I already know how to achieve this using form post via Laravel.
Form POST via Laravel
public function postSubscribe() {
// Validation
$validator = Validator::make( Input::only('subscribe_email'),
array(
'subscribe_email' => 'email|unique:subscribes,email',
)
);
if ($validator->fails()) {
return Redirect::to('/#footer')
->with('subscribe_error','This email is already subscribed to us.')
->withErrors($validator)->withInput();
}else{
$subscribe = new Subscribe;
$subscribe->email = Input::get('subscribe_email');
$subscribe->save();
return Redirect::to('/thank-you');
}
}
Now, I want to achieve this using Ajax call to avoid the page load and also to learn more about Ajax. Here are what I've tried :
Form
{!! Form::open(array('url' => '/subscribe', 'class' => 'subscribe-form', 'role' =>'form')) !!}
<div class="form-group col-lg-7 col-md-8 col-sm-8 col-lg-offset-1">
<label class="sr-only" for="mce-EMAIL">Email address</label>
<input type="email" name="subscribe_email" class="form-control" id="mce-EMAIL" placeholder="Enter email" required>
<!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
<div style="position: absolute; left: -5000px;"><input type="text" name="b_168a366a98d3248fbc35c0b67_73d49e0d23" value=""></div>
</div>
<div class="form-group col-lg-3 col-md-4 col-sm-4"><input type="submit" value="Subscribe" name="subscribe" id="subscribe" class="btn btn-primary btn-block"></div>
{!! Form::close() !!}
Ajax Call
<script type="text/javascript">
$(document).ready(function(){
$('#subscribe').click(function(){
$.ajax({
url: '/subscribe',
type: "post",
data: {'subscribe_email':$('input[name=subscribe_email]').val(), '_token': $('input[name=_token]').val()},
dataType: 'JSON',
success: function (data) {
console.log(data);
});
});
});
</script>
Controller
public function postSubscribeAjax() {
// Getting all post data
if(Request::ajax()) {
$data = Input::all();
die;
}
dd($data);
}
Route
Route::post('/subscribe','SubscribeController#postSubscribeAjax');
Result
I keep getting:
Undefined variable: data
Which mean my Request::ajax() is not containing anything? Why is that?
How can I achieve this using an Ajax call?
This was resolved in Chat
We had to fix a couple syntax errors and change the input button to a html button so that it would prevent the form from submitting. There are other ways we could have done this, but this worked.
So I'm going to address a few things here. Starting from bottom moving on up!!
Route
You can do this without the slash.
Route::post('subscribe','SubscribeController#postSubscribeAjax');
Controller
Your Controller is good enough, but you need to actually display the result from the ajax. We will handle that.
Remove the die in your check if there is an Ajax request.
Ajax
<script type="text/javascript">
$(document).ready(function(){
$('#subscribe').click(function(e){
e.preventDefault(); // this prevents the form from submitting
$.ajax({
url: '/subscribe',
type: "post",
data: {'subscribe_email':$('input[name=subscribe_email]').val(), '_token': $('input[name=_token]').val()},
dataType: 'JSON',
success: function (data) {
console.log(data); // this is good
}
});
});
});
</script>
Form
Pretty sure the form is good.
Assuming the "Undefined variable" error is happening in your postSubscribeAjax method, it looks like you have a problem with scope. Specifically, when the PHP interpreter gets to the line dd($data), the variable $data has not been defined yet.
This is not surprising, since $data is assigned inside the block of code following an if statement - it is not available outside that block.
You can solve this problem by adding $data = null; before the if statement, BUT... this is only going to show you that you have a different problem. Here's why:
You're getting an "undefined variable" error. The means that your code is skipping the if block (how do I know this? because if it didn't skip it, it would execute the die statement and you'd never see the error). This means that your code is not correctly identifying the request as an AJAX request.
What I would do is this: skip the if block entirely. Simply get your data and process it.
It's been a while since I looked at this stuff, but IIRC, there is no reliable way to detect AJAX requests. To the server, an AJAX request is just like any other request. Some Javascript libraries (jQuery included, if I'm not mistaken) set a header on the request to identify it as AJAX, but it is neither universally required nor universally detected.
The only benefit to this AJAX detection is to prevent your users from accessing the AJAX URL directly from a browser. And, ultimately, if someone really wants to do that, you can't stop them since they can (with a little work) spoof any request.

Persist Form Data Across Multiple Steps in Laravel

When I've made multistep forms in the past I would generally store the form data in the session before returning it to the view, that way the data persists if the user refreshes the page or clicks the browser's native back buttons.
Transferring my past logic to Laravel I built the following form consisting of three stages:
[Input -> Confirm -> Success]
Routes.php
Route::group(array('prefix' => 'account'), function(){
Route::get('register', array(
'before' => 'guest',
'as' => 'account-create',
'uses' => 'AccountController#getCreate'
));
Route::post('register', array(
'before' => 'guest|csrf',
'as' => 'account-create-post',
'uses' => 'AccountController#postCreate'
));
Route::get('register/confirm', array(
'before' => 'guest',
'as' => 'account-create-confirm',
'uses' => 'AccountController#getCreateConfirm'
));
Route::post('register/confirm', array(
'before' => 'guest|csrf',
'as' => 'account-create-confirm-post',
'uses' => 'AccountController#postCreateConfirm'
));
Route::get('register/complete', array(
'before' => 'guest',
'as' => 'account-create-complete',
'uses' => 'AccountController#getCreateComplete'
));
});
AccountController.php
<?php
class AccountController extends BaseController {
private $form_session = 'register_form';
public function getCreate()
{
if(Session::has($this->form_session))
{
// get forms session data
$data = Session::get($this->form_session);
// clear forms session data
Session::forget($this->form_session);
// load the form view /w the session data as input
return View::make('account.create')->with('input',$data);
}
return View::make('account.create');
}
public function postCreate()
{
// set the form input to the session
Session::set($this->form_session, Input::all());
$validation_rules = array(
'email' => 'required|max:50|email|unique:users',
'password' => 'required|max:60|min:6',
'password_conf' => 'required|max:60|same:password'
);
$validator = Validator::make(Input::all(), $validation_rules);
// get forms session data
$data = Session::get($this->form_session);
// Return back to form w/ validation errors & session data as input
if($validator->fails()) {
return Redirect::back()->withErrors($validator);
}
// redirect to the confirm step
return Redirect::route('account-create-confirm');
}
public function getCreateConfirm()
{
// prevent access without filling out step1
if(!Session::has($this->form_session)) {
return Redirect::route('account-create');
}
// get forms session data
$data = Session::get($this->form_session);
// retun the confirm view w/ session data as input
return View::make('account.create-confirm')->with('input', $data);
}
public function postCreateConfirm()
{
$data = Session::get($this->form_session);
// insert into DB
// send emails
// etc.
// clear forms session data
Session::forget($this->form_session);
// redirect to the complete/success step
return Redirect::route('account-create-complete');
}
public function getCreateComplete() {
return View::make('account.create-complete');
}
}
create.blade.php
<form action="{{ URL::route('account-create-post') }}" method="post">
Email: <input type="text" name="email" value="{{ (isset($input['email'])) ? e($input['email']) : '' }}">
#if($errors->has('email'))
{{ $errors->first('email') }}
#endif
<br />
Password: <input type="text" name="password" value="">
#if($errors->has('password'))
{{ $errors->first('password') }}
#endif
<br />
Password Confirm: <input type="text" name="password_conf" value="">
#if($errors->has('password_conf'))
{{ $errors->first('password_conf') }}
#endif
<br />
{{ Form::token() }}
<input type="submit" value="Confirm">
</form>
create-confirm.blade.php
Email: {{ $input['email']; }}
Password: {{ $input['password']; }}
<form action="{{ URL::route('account-create-confirm-post') }}" method="post">
{{ Form::token() }}
return
<input type="submit" name="submit_forward" value="Submit">
</form>
The above works fine, however I am wondering if this is the best way to approach multi-step forms in Laravel?
When I have created multi-part forms, I have always done it in a way so that the user can always come back and finish the form later, by making each form persist what it has to the database.
For instance
Step 1 - Account Creation
I would have the user create their authentication details at this step, create the user account (with password) here and also log the user in, redirecting to the dashboard. There I can do a check to see if the user has a profile and if they don't, redirect them to the profile creation form.
Step 2 - Profile Creation
Because we have an authenticated user, the profile creation form can save its data to the currently logged in user. Subsequent sections follow the same process but check the existence of the previous step.
Your question seems to be about confirming whether a user wishes to create an account. What I would do in your situation would be, on the form you created to confirm the user account, I would keep the user's data in hidden input fields.
Email: {{ $input['email'] }}
Password: {{ $input['password'] }}
<form action="{{ URL::route('account-create-confirm-post') }}" method="post">
<input type="hidden" name="email" value="{{ $input['email'] }}">
<input type="hidden" name="password" value="{{ $input['password'] }}">
{{ Form::token() }}
return
<input type="submit" name="submit_forward" value="Submit">
</form>
Although displaying the user's chosen password back to them on this page seems to be a bit superfluous when you ask them to confirm their password on the previous page, plus some users might question why their password is being shown in plaintext on the screen, especially if they are accessing the site from a public computer.
The third option I would suggest would be to create the user account and soft-delete it (Laravel 4.2 Docs / Laravel 5 Docs), returning the user's account number to the new form:
Email: {{ $input['email'] }}
Password: {{ $input['password'] }}
<form action="{{ URL::route('account-create-confirm-post') }}" method="post">
<input type="hidden" name="id" value="{{ $user_id }}">
{{ Form::token() }}
return
<input type="submit" name="submit_forward" value="Submit">
</form>
then undo the soft-delete when the user confirms their account. This has the added bonus that you could track people trying to sign up multiple times for an account and not completing the process and see if there's a problem with your UX.
Conclusion
Of course, you could also still do it the way you always have with a session, all I have tried to do here is show you some other ways you can approach it, as with everything to do with the best way of doing something, this is a highly opinionated subject and is likely to get many opposing views on how it should be done. The best way to do it is the way that works best for you and your users... mainly your users.
There are two ways to do it (that i can think of). I prefer second one.
Client side - everything can be handled by javascript. Basic validation (if field is email, if field has enough characters etc.) would be checked with javascript. After confirmation, AJAX request would go through server side validation and if anything went wrong you could highlight invalid inputs. "check if email is available" button (via AJAX) would be great too.
Server side - pretty much what you did but I would move it to service - it would make it much cleaner.
public function getCreate() {
if ($this->formRememberService->hasData()) {
return View::make('account.create')
->with('input', $this->formRememberService->getData());
}
return View::make('account.create');
}
public function postCreate() {
$this->formRememberService->saveData(Input::all());
// ...
}
public function postCreateConfirm() {
// ...
$this->formRememberService->clear();
return Redirect::route('account-create-complete');
}
Adding "forget me" action would be nice (especially if form requires more private data).
Why getCreate() has Session::forget()? If someone goes back to change something and accidently leaves your site his data will be lost.
1st) Create a custom hidden field in the form containing a random md5 character set to submit it with the form... (it can be the timestamp, the user ip address, and country concatenated together as 3 md5 strings separated by whatever character , or #, so it can be working as a token of the form)
2nd) pass the hidden field into your controller and validate it after getting the user input from the form by generating the same values in your controller, encrypting these values as md5 too, then concatenate them all together, and compare the values that is coming from the user input form with the values you are generating in your controller.
3rd) Put the values of the form in your controller in a session then regenerate the session id every visit to every view the user is going to visit.
4th) update the timestamp in your session according the timestamp the user is visiting every page.
Just because you know Laravel, does not mean you have to do everything in Laravel.
Multi-step forms should never involve server-side magic. The best and easiest you can do is to hide certain steps with display:none; and switch to the next step using javascript toggling visibilities only.

Categories