Laravel Select2 old input after validation
Asked Answered
A

9

11

I'm using Select2 in my webapplication. I load my Select2 boxes with Ajax. When validation fails, all the inputs are filled as before except the Select2 box. How can I restore the old value after the form validation fails? My bet was using Request::old('x'), but this inserts the value (in my case an user ID) instead of the selected text. So for example the text John would become 27 in the selectbox. How can I get the text back?

<select id="customer" name="customer" class="searchselect searchselectstyle">
</select>

The js:

token = '{{csrf_token()}}';

$(".searchselect").select2({
    ajax: {
        dataType: "json",
        type: "POST",
        data: function (params) {
            return {
                term: params.term,
                '_token': token,
                'data' : function(){
                    var result = [];
                    var i = 1;
                    $('.searchselect').each(function(){
                        result[i] = $(this).val();
                        i++;
                    });
                    return result;
                }
            };
        },
        url: function() {
            var type = $(this).attr('id');
            return '/get' + type;
        },
        cache: false,
        processResults: function (data) {
            return {
                results: data
            };
        }
    }
});

Edit

The only (dirty) solution I found so far is the following:

 <select id="customer" name="customer" class="searchselect searchselectstyle">
    @if(Request::old('customer') != NULL)
        <option value="{{Request::old('customer')}}">{{$customers->where('id', intval(Request::old('customer')))->first()->name}}</option>
    @endif
</select>

$customers is a list of all customers, so this means that for each Select2 box I need to query a big list of items in order to make it work. This will be pretty inefficient if we're talking about thousands of rows per Select2 box.

I guess there must be a better solution. Who can help me?

Adscription answered 19/4, 2016 at 17:2 Comment(0)
M
2

Normally to programmatically set the value of a select2, you would expect to use the .val() method followed by a .trigger('change') call as per their documentation (and other queries like this on SO). However, select2 themselves have something in their documentation about preselecting options for remotely sourced data.

Essentially their suggestion boils down to (after initalizing your AJAX-driven <select>):

  • make another AJAX call to a new API endpoint using the pre-selected ID
  • dynamically create a new option and append to the underlying <select> from a promise function (.then()) after the AJAX call is finished
    • could also use some of the regular jQuery callback chaining functions for this
  • trigger a change event
  • trigger a select2:select event (and pass along the whole data object)

Assuming you're already flashing the old data to the session, Laravel provides handy access to the previously requested input in a variety of ways, notably these three:

  • static access via the Request class e.g. Request::old('customer') as in the OP
  • the global old() helper e.g. old('customer'), which returns null if no old input for the given field exists, and can have a default as a second parameter
  • using the old() method on the Request instance from the controller e.g. $request->old('customer')

The global helper method is more commonly suggested for use inside Blade templates as in some of the other answers here, and is useful when you don't need to manipulate the value and can just plug it straight back in, which you would with things like text inputs.

The last method probably provides you with the answer you're looking for - instead of querying the entire collection from inside of the view, you're able to either manipulate the collection from the controller (similar to the OP, but should be nicer since it's not parsing it in the view) or make another query from the controller based on the old ID and fetch the data you want without having to trawl the collection (less overhead):

$old_customer = Customer::find($request->old('customer'));

Either way, you'd have the specific data available at your fingertips (as a view variable) before the blade template processes anything.

However you choose to inject the data, it would still follow the pattern suggested by select2:

  • get the pre-selected data
  • create an option for it
  • trigger the appropriate events

The only difference being you don't need to fetch the data from another API endpoint (unless you want/need to for other programmatic reasons).

Metronome answered 30/12, 2017 at 8:7 Comment(0)
G
1

I end up using similar flow like your. But my blade template is using htmlcollection package.

Controller:-

Let's say you are in create() method. When validation failed, it will redirect back to the create page. From this page, you can repopulate the list.

$customer_list = [];
if(old('customer') != NULL){
    $customer_list = [old('customer') => $customers->where('id', old('customer'))->first()->name];        
}

Blade View:

{{ Form::select('customer', $customer_list, null,  ['class' => 'searchselect searchselectstyle', 'id' => 'customer']) }}
Grishilda answered 27/12, 2017 at 8:54 Comment(0)
P
1

I did it with an input hidden for the text and it works well:

This form is showed in a Popup and ajax (using Jquery-UJS)

Form:

<form action="{{ route('store_item', $order) }}" method="POST" data-remote="true">
    {{ csrf_field() }}
    <div class="form-group{{ $errors->has('item_id') ? ' has-error' : '' }}">
        <label class="control-label col-sm-2" for="item_id">Item: </label>
            <div class="col-sm-10">
                <select name="item_id" class="form-control" id="item_id">
                    @if(old('item_id')  != null)
                        <option value="{{ old('item_id') }}" selected="selected">
                            {{ old('item_title') }}
                        </option>
                    @endif
                </select>
            </div>
            {!! $errors->first('item_id', '<p class="text-center text-danger"<strong>:message</strong></p>') !!}
        </div>
        <input type="hidden" id="item_title" name ="item_title" value="{{ old('item_title') }}" />
        <div class="form-group{{ $errors->has('quantity') ? ' has-error' : '' }}">
            <label class="control-label col-sm-2" for="quantity">Cantidad: </label>
            <div class="col-sm-10">
                <input name="quantity" type="number" class="form-control" id="quantity" value="{{ old('quantity') }}"/>
            </div>
            {!! $errors->first('quantity', '<p class="text-center text-danger"><strong>:message</strong></p>') !!}
        </div>
        <button type="button" class="btn btn-default" data-dismiss="modal">Cancelar</button>
        <button type="submit" class="btn btn-primary" data-disable-with="Guardando...">Guardar</button>
    </form>

JAVASCRIPT:

<script type="text/javascript">
        $(document).ready(function(){
            $('#item_id').select2({
                placeholder: 'Elige un item',
                ajax: {
                    url: '{{ route('select_item_data') }}',
                    dataType: 'json',
                    delay: 250,
                    processResults: function (data) {
                        return {
                            results:  $.map(data, function (item) {
                                return {
                                    text: item.title,
                                    id: item.id
                                }
                            })
                        };
                    },
                    cache: true
                }
            });

            $('#item_id').on('change', function(e){
                var title = $(this).select2('data')[0].text;
                $('#item_title').val(title);
            });
        });
    </script>

VALIDATION IN STORE METHOD (CONTROLLER):

$validator = Validator::make($request->all(), [
            'item_id' => 'required',
            'quantity' => 'required'
        ]);

        if ($validator->fails()) {
            return redirect()
                ->route('create_item', $order)
                ->withInput($request->all())
                ->withErrors($validator);
        }

It's very important to send 'withInput' and 'withErrors' in the redirection, because we are working with a popup and ajax that is created again and doesn't keep the old values.

Presentday answered 31/7, 2019 at 9:49 Comment(0)
G
0

Maybe you can try (once the ajax call has ended) :

var oldCustomer = $('#customer > option[value={{ Request::old('customer') }}]');

if (oldCustomer.length > 0) {
    oldCustomer.attr('selected', 'selected');
}
Graniela answered 19/4, 2016 at 21:51 Comment(1)
Wouldn't work, because this will set the ID in the box instead of the associated text. Keep in mind that the data for select2 looks like this: [id : ..., text: ...] So your solution will set the id as the text for the option instead of the id's associated textAdscription
E
0

Same problem; I'm using a similar solution: If the old $id is set, I get the name and I use it as a variable for the view; Note that I also forward the id because I also used this method to pre-fill the form (coming from another place), but in this case, the name only should have been used, and for the id {{ old('author_id') }} can be used in the view:

In the controller:

elseif (($request->old('author_id') !== null) && ($request->old('author_id') != '')) {
   $my_author_id = $request->old('author_id');
   $my_name = Author::find($my_author_id)->name;
   return view('admin/url_author.create', compact('my_name', 'my_author_id'));
}

And in the view (more precisely, in a partial used for creation & edition):

@if (isset($record)) // for use in edit case with laravelcollective)
   <select class="form-control js-data-author-ajax" id="author_id" name="author_id">
      <option value="{{ $record->author_id }}">{{ $record->author->name }}</option>
   </select>
@else
@if (isset($my_name)) // old input after validation + pre-filling cases
   <select class="form-control js-data-author-ajax" id="author_id" name="author_id">
      <option value="{{ $my_author_id }}">{{ $my_name }}</option>
   </select>
@else // for create cases
   <select class="form-control js-data-auteur-ajax" id="auteur_id" name="auteur_id">
      <option></option>
   </select>
@endif
@endif
Earache answered 12/10, 2016 at 20:59 Comment(0)
S
0

Your code is bit confusing. I don't understand why you are using a POST request to get data using ajax to fill a select2 box.

Assuming the data returned using ajax call is in the below format.

   [
     {
     "id": "Some id",
     "text": "Some text"
     },
     {
     "id": "ID 2",
     "text": "Text 2"
     },
    ]

Now what you can do is pass in an extra parameter to your ajax call as below

url: function() {
                    var type = $(this).attr('id');
                   @if(old('customer'))
                     return '/get' + type + '?customer='+ {{ old('customer') }};
                   @else
                     return '/get' + type;
                   @endif
                } 

Now in your controller while returning data you can throw an extra attribute selected:true for an ID matching that particular ID.

if( Request::has('customer') && Request::input('customer') == $id ) 
{
 [
 "id" => $id,
 "text" => $text,
 "selected" => "true"
 ]
}
else
{
 [
 "id" => $id,
 "text" => $text,
 ]
}
Scammony answered 26/12, 2017 at 15:20 Comment(1)
your url: function() { is call when the page redirect back once the validation fail, but how are you going to stay at the select2 preselect box?Grishilda
H
0

If I understood you right I can recommend you to have for each your select2 box hidden input <input type="hidden" name="customer_name" value="{{old('customer_name', '')}}"> where after change event for select2 you can insert selected name (etc. John). So if validation is fails you have:

<select id="customer" name="customer" class="searchselect searchselectstyle">
@if(!is_null(old('customer')))
    <option value="{{old('customer')}}">{{old('customer_name')}}
   </option>
@endif
</select>
Holter answered 27/12, 2017 at 21:46 Comment(0)
R
0

I think your own solution is pretty much correct. You say the list of $customers will get pretty big.

$customers->where('id', intval(Request::old('customer')))->first()

Do you need to have the list stored in a variable $customers? You could just search the id you want

App\Customer::where('id', intval(Request::old('customer')))->first()

Searching by id should not be inefficient. Otherwise you could send the name with the form and store it in the old request. Shown below with some (dirty) javascript.

$("#form").submit( function() {
  var sel = document.getElementById("customer");
  var text= sel.options[sel.selectedIndex].text;
$('<input />').attr('type', 'hidden')
  .attr('name', "selected_customer_name")
  .attr('value', text)
  .appendTo('#form');
  return true;
});

Then like yrv 16s answer:

<option value="{{old('customer')}}">{{old('selected_customer_name')}}
Racehorse answered 28/12, 2017 at 21:30 Comment(0)
C
-1

You could do something like this:

First in controller pass tags to view using pluck helper like below:

public function create()
{
    $tags= Customer::pluck('name','name');

    return view('view',compact('tags'));
}

Then in your form try this:

   {!! Form::select('tag_list[]',$tags,old('tag_list'),'multiple','id'=>'tag_list']) !!}

Don't forget to call the select2 function.

    $('#tag_list').select2();

And finally in controller:

public function store(ArticleRequest $request)
 {
    $model = new Model;
    $tags=$request->input('tag_list');

    $model->tag($tags);
  }  

Notice tag function is not a helper in Laravel, You implement it! The function takes names and attaches them to the instance of some thing.

Good Luck.

Cocainize answered 28/12, 2017 at 19:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.