osherdo's avatar

Saving data on click with ajax

Hello.

I am trying to get a selected value from a select I am making when clicking on a button of input type="submit.

I want to do it with AJAX. So far I have been able to get results from the database with ajax, but now I need to save a choice in the database. This is how I got the results I want: Controller code:

public function edit_routine(Request $request)
  {
    $exercise = Exercise::find($request->exercise_id); // exercise_id is taken from the jquery code (it's a variable there).

    // find() equals to select * from exercises where id is the same as given in the value attribute.
   // dd($exercise->category);
    $ajax_exercises = Exercise::where('category',$exercise->category)->get(); // Getting all the exercises belongs to the same category like the one being replaced now.

    
   return $ajax_exercises; // Store the $exercise variable into the data parameter of Ajax call. (Can be viewed in the browser's console)
  }

routes.php:

 Route::post('routine_details/edit_routine','RoutineController@edit_routine');

Next, I am trying to catch and save the selected exercise with this code in the view code:

if(id != exercise_id){ 
                element+= '<button class="pickexercise" type="submit" value="'+id+'">Pick this exercise</button>'; 

How should I define the ajax call to save the exercise that has been picked?

I already have route for saving the new exercise instead of the current one.

0 likes
15 replies
tykus's avatar

You will need to listen for the button click event; presuming you are using jQuery:

// change the button tag like so
<button class="pickexercise" data-id="'+id+'">Pick this exercise</button>

In your script, you can target the HTML5 data- attributes using the data() function:

    $('.pickexercise').click(function (event){
      var exercise_id = $(event.target).data('id');
      $.ajax({
        type: "POST",
        url: "/wherever/you/post/to/",
        data: {
          exercise_id: exercise_id
        },
        success: function (data) {
          // do whatever you want with a successful response
        }
      });
    });
osherdo's avatar

@tykus_ikus thanks for being a legendary guy and helping time after time. ok I am getting an error with the token, internal 500 server error.

this is the ajax call now:

$(document).on('click','.pickexercise', function(event)
  {
    // Getting the neccesary parameters to update to another exercise.

      var new_exercise = $(this).val(); // Getting the current value of the exercise being clicked (which is an id of the exercise).

      var routine = $('.routine').html(); //getting the current routine id. Putting it with .html  (since it's the content of the routine class)  
      //console.log(routine);

      var old_exercise = $(this).attr('id');
      //var token =$('.token').val();
      $.ajax({
        type: "POST",
        url: "update_routine",
        data: {
          // getting the picked exercise, old exercise and routine id with the variables declared above.
          //'_token':token,
          'chosen_exercise': new_exercise,
          'routine': routine,
          'exercise_to_replace': old_exercise
        },
        success: function (data) {
            console.log("updated");
        }
      });
   }); 
</script>

<div class="routine" style="visibility:hidden">{{ $routine }}</div> <!-- $routine represents the current routine id taken from the details() function in the controller.
 <!-- getting the current routine id from the details() function in the controller. -->

and the controller method:

public function update(Request $request)
  {
    $current_routine = ExerciseRoutine::where('routine_id',$request->routine)
    ->where('exercise_id',$request->exercise_to_replace) // getting the exercise to replace from the view.
    ->update('exercise_id',$request->chosen_exercise); // Updating the exercise_id column with the new picked exercise.
    // All this data is transferred to the controller using the POST request we're making from the script in the view.
  
  }

When trying to replace the exercise, the console returns this (check the screenshot): http://2.1m.yt/3Wzb_18.png

EDIT: another screenshot showing more on the issue in the console: http://2.1m.yt/or11sy0.png

I am wondering if there's something about the token. Here's the VerifyCsrfToken file contents.

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;

class VerifyCsrfToken extends BaseVerifier
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'routine_details/update_routine'
    ];

routes.php relevant routes: (routine_details/update_routine does the work)

Route::post('routine_details/update_routine','RoutineController@update'); // Saving routine details (on submit), done from the routine_details view.

 Route::post('routine_details/edit_routine','RoutineController@edit_routine');

Would appreciate your take on this case.

tykus's avatar
tykus
Best Answer
Level 104

You can pass the token with the AJAX request - if you do not have a form, another common approach is to put the CSRF token in a meta tag in the page head, e.g.

<!doctype html>
<head>
    ...
    <meta name="_token" content="{{csrf_token()}}">
    ...

Now, you can access that token inside your javascript:

$(document).on('click','.pickexercise', function(event)
{
    var new_exercise = $(this).val();
    var routine = $('.routine').html(); // I don't like this; see note below
    var old_exercise = $(this).attr('id');
    $.ajaxSetup({
        headers: {
            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
        }
    });
    $.ajax({
        type: "POST",
        url: "update_routine",
        data: {
          'chosen_exercise': new_exercise,
          'routine': routine,
          'exercise_to_replace': old_exercise
        },
        success: function (data) {
            console.log("updated");
        }
      });
   }); 
</script>

Note This idea of putting data into a div is a pet peeve of mine; is there anything else that you can do to pass the routine id into your javascript?

<div class="routine" style="visibility:hidden">{{ $routine }}</div> // bad idea IMHO!

I would suggest slamming the routine directly into Javascript (inside a script tag before the script which makes the AJAX call):

<script>var routine = {{$routine}}</script>
1 like
osherdo's avatar

@tykus_ikus ok thanks for that! it was really that I have missed the headers:

    $.ajaxSetup({
        headers: {
            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
        }
    });

Can I ask why should I use this code instead of the current?

<script>var routine = {{$routine}}</script>

Thanks very much!

tykus's avatar

If you want a variable to be passed from PHP into Javascript, then do that. There is no reason to create a DOM element as a hidden container for the variable.

osherdo's avatar

@tykus_ikus okay. I have replaced the code with what you've suggested. But now I am not able to replace the exercise. Any idea why is it happening and how to debug it?

tykus's avatar

Which part have you changed specifically? Are there any error messages in the console?

osherdo's avatar

it returns "updated" , as I have written it to do in the ajax call:

$.ajax({
        type: "POST",
        url: "update_routine",
        data: {
          // getting the picked exercise, old exercise and routine id with the variables declared above.
          //'_token':token,
          'chosen_exercise': new_exercise,
          'routine': routine,
          'exercise_to_replace': old_exercise
        },
        success: function (data) {
            console.log("updated");
          //Comment out this line for debugging purposes.
            window.location.reload();
        },
        error: function(data)
        { // This is error callback function.Can be written for testing purposes, to see if the ajax function's is being executed well. 
          alert("An error occured.");
        }
      });
   }); 

But still the nothing happens in the view, and it remains the same exercise.

tykus's avatar

OK, so what should it do?

The view will not automagically update if you're not using a reactive javascript framework (Angular, Ember, Vue etc). You will have to use your success promise to make the relevant changes to the view. Something I have done in the past is return a view partial from the update method in the controller, and append it to a specific DOM element in the original view; but I don't know if this is what you are looking for???

EDIT just re-read that you are trying to reload the page after a successful update - so is there a problem adding the exercise to the routine in the Controller?

osherdo's avatar

I am looking for to update the replacement exercise in the database, and by that the page will be refreshed and the new image of the exercise will be viewed in the view.

Do you need extra code to understand the issue better? please let me know?

tykus's avatar

Yes, some extra code would help.

Here's the thing... if you reload the page whenever you have a successful response from the controller; then, why use AJAX at all??

osherdo's avatar

Okay. routes.php:

 Route::get('routine_details/{routine}','RoutineController@details'); // Getting the routine details on a separate view. {routine} is a placeholder for the number we're getting in view_routine.blade.php

Controller:

  public function details ($routine) // Getting the routine id number from the view.$routine is a placeholder.
  {
    $user=Auth::user();

    $exercises_routines=ExerciseRoutine::where('routine_id',$routine)->where('user_id',Auth::user()->id)->get(); 
  // TO DO : 
    // if (count($exercises_routines == 0){echo "no exercises found"})
//SELECT * FROM exercises_routines WHERE routine_id= 30
    $exercises_in_routine = array();
    $count = 0;
    foreach($exercises_routines as $single_routine)
    {
      //echo $single_routine->exercise_id; accessing the ExerciseRoutine , and get the exercise id's associated with the current routine.
      
      $exercise_id = $single_routine->exercise_id; //So far we accessed the routine_id column. Now we're assigning all the exercises id's to the $single_routine.and now it has all its exercises. 
      $exercise =Exercise::find($exercise_id); // Accessing the Exercise Model, and looking for those exercises we're looking for, and making a match (for each loop - for one exercise only).
      //Select * from exercises where id  = 11
      $exercises_in_routine[$count]['id'] = $exercise->id; // for each loop, we're getting the exercise id of that loop. We found the exercise through the Exercise model.
      $exercises_in_routine[$count]['exercise_name'] = $exercise->name; //for each loop, we're getting the exercise name of that loop.
      $exercises_in_routine[$count]['image_path'] = "images/".$exercise->image_path; //for each loop, we're getting the exercise image_path of that loop.
      $exercises_in_routine[$count]['category'] = $exercise->category; //for each loop, we're getting the exercise category of that loop.
      $count++; // Insert it all in the array ,as done above. and then iterate again, and add 1 to the count.
      
    }
//Todo : Ask Karthik if there is a better way to bring details.
    /*
   echo "Print Exercises in routin";
   echo "<pre>";
       print_r($exercises_in_routine);
       echo "<pre/>";  
    */

// Arranging the array in form of categories:

    $exercise_category = array();  //Stores the exercise category.
    $list_of_exercises = array();  //Stores exercises but ordered by a category.

    for($x=0; $x<count($exercises_in_routine); $x++)
    {
      $category = $exercises_in_routine[$x]['category']; // Get the current exercise category, from the foreach loop above (it's a value that we get there)
      // We're accessing a property called 'category'.
      if(!in_array($category, $exercise_category))
      {
        //If category is not inside the array - create a new key
        $list_of_exercises[$category][0]['exercise_name'] = $exercises_in_routine[$x]['exercise_name']; // We're accessing again the array above and getting the exercise_name there, and assigning it to the new array name.
        // We're using here an asssociative array, so we're assigning a value to the key 'exercise_name' in this array.
        $list_of_exercises[$category][0]['image_path'] = $exercises_in_routine[$x]['image_path']; // Same thing goes here, like with exercise_name one line above.
        $list_of_exercises[$category][0]['id'] = $exercises_in_routine[$x]['id'];  // Same thing goes here, like with exercise_name 2 lines above.

        array_push($exercise_category, $category); //Push the category to exercise category
      }
      else
      {     // exercise_name and image_path and id are all used for the array.
        $count = count($list_of_exercises[$category]); // Gives count of array elements in php. we're counting the exercises of the same category, so it could increase the index number by one each time.
        $list_of_exercises[$category][$count]['exercise_name'] = $exercises_in_routine[$x]['exercise_name']; //If we already have the category name in the TRUE condition above, we're trying to get the rest of the exercises of the same group.

        $list_of_exercises[$category][$count]['image_path'] = $exercises_in_routine[$x]['image_path']; // Trying to get the image_path of the image from the same category we're on now (stored in $category). Also getting the current count value.
        $list_of_exercises[$category][$count]['id'] = $exercises_in_routine[$x]['id'];  // Trying to get the id of the image from the same category we're on now (stored in $category). Also getting the current count value.
      }
    }

   // dd($list_of_exercises);

return view('routine_details',compact('user','exercises_routines','routine','list_of_exercises','count'));

View:

@extends('layouts.master')
    <meta name="_token" content="{!! csrf_token() !!}"/>

@section('scripts')
<script type="text/javascript">

/* 
Live Binding(for future events): When the browser will detect a class called pickexercise, it'll trigger the function.

This won't work: ( the browser here expects to identify this class on document ready.)
 $('.pickexercise').click(function (event)
  {
      console.log("hello");
    });

*/
  $(document).on('click','.pickexercise', function(event)
  {
    // Getting the neccesary parameters to update to another exercise.

      var new_exercise = $(this).val(); // Getting the current value of the exercise being clicked (which is an id of the exercise).

      var routine = $('.routine').html(); //getting the current routine id. Putting it with .html  (since it's the content of the routine class)  
      //console.log(routine);

      var old_exercise = $(this).attr('id');
      //var token =$('.token').val();

      $.ajaxSetup({
        headers: {
            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
        }
     });


      $.ajax({
        type: "POST",
        url: "update_routine",
        data: {
          // getting the picked exercise, old exercise and routine id with the variables declared above.
          //'_token':token,
          'chosen_exercise': new_exercise,
          'routine': routine,
          'exercise_to_replace': old_exercise
        },
        success: function (data) {
            console.log("updated");
          //Comment out this line for debugging purposes.
            window.location.reload();
        },
        error: function(data)
        { // This is error callback function.Can be written for testing purposes, to see if the ajax function's is being executed well. 
          alert("An error occured.");
        }
      });
   }); 
</script>
<!-- CSRF Token. -->
<meta name="_token" content="{{csrf_token()}}">

<script type="text/javascript">
$(document).ready(function(){ // . is used for class identify. # is for id.
  $('.replaceExercise').on('click', function(e){
    $('.modal-body').html(''); // clear the modal-body each time we're opening the modal-body div.
      var exercise_id = $(this).val(); // Getting the current value attribute of the replaceExercise button (replaceExercise is defined in line 22).
    console.log("Exercise "+exercise_id);

      $.ajax({
          url: 'edit_routine',
          type: "POST",
          data: {'exercise_id':exercise_id}, // First parameter is a name given for the data. Second parameter (exercise_id) is the variable declared above (value).
          success: function(data){
            console.log(data);
            var count;
            // Accessing the data in this format: Access of index and then accesing its key (which could be (in this instace, take from exercises table: id, image_path etc).
            for(count  = 0; count < data.length; count++)
            {

              var name= data[count].name; // Accessing the data object,than accessing the current count of the iteration (first is 0). Then accessing the name property in the Exercise model. The name is accessed throught the data parameter.
              var id= data[count].id;

              var image_path= data[count].image_path;
              //console.log(location.origin);
              image_path = location.origin + '/images/' + image_path;
              element = '<li>';
              /*
              if(count==0)
              {
                element+='<input type="hidden" class="token" id="token" value="{{ csrf_token() }}”>';
              }
              */

              element += '<img src='+image_path+'>';
              element+= '<span>' + name + '</span>';
              // check if the exercise id number is NOT equal to the current picked exercise_id, so we could show the pick exercise buttton to the different exercises and not the same one.
              if(id != exercise_id){ 
                // Getting the old exercise id, and in the value attribute we're getting the new id of the new exercise picked. 
                element+= '<button class="pickexercise" id='+exercise_id+' type="submit" value="'+id+'">Pick this exercise</button>'; 
              }
              // Catch the id of the exercises, when you click the button.
              element+= '</li>';
              //console.log(name); // showing the exercises name of the picked exercise mutual category.
              //console.log(element);

              $('.modal-body').append(element); // replace the entire modal-body div with the element contents we are passing.

            }           
        }
      });

  });
});
</script>

<!-- Saving the replacement exercise to the server. -->
<script type="text/javascript">
// Should be triggered only once when clicking on "Replace this exercise".
 

</script>

<script type="text/javascript">
$.ajaxSetup({
   headers: { 'X-CSRF-Token' : $('meta[name=_token]').attr('content') }
});
</script>

@stop

@section('content')
    {!! csrf_field() !!}

@if (count($errors) > 0)
  <div class="alert alert-danger">
        <strong>Whoops!</strong> There were some problems with your input.<br><br>
        <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
@endif




<p>Hello, {{ $user->name }}.</p>

<!-- Key is: category_name and Value is $exercise array (using the foreach loop with associative array). --> 
<div class="routine" style="visibility:hidden">{{ $routine }}</div>
<!--<script></script> !--><!-- $routine represents the current routine id taken from the details() function in the controller.
 <!-- getting the current routine id from the details() function in the controller. -->
<div class="panel-group" id="accordion">

 <?php $accordion_count = 0; ?> <!-- $accordion_count is used to iterate over accordion collapse divs (generated with a foreach (the divs)) -->
 <!-- 
 'abdomnials'
    [0]=>exercise_name,
    [1]=>exercise_name, 
  biceps
    [0]
    [1]
    [2]
  -->
@foreach($list_of_exercises as $category_name=>$exercise) {{--$exercise is getting values of the $list_of_exercises by their category name. $exercises is just a placeholder is this case. --}}
<div class="panel panel-default">
    <div class="panel-heading">
  <h4 class="panel-title">
   <a data-toggle="collapse" data-parent="#accordion" href="#collapse<?php echo $accordion_count; ?>"> <!-- Echoing the current $accordion_count value. -->
        {{ $category_name }} </a>
      </h4>
    </div>
      <div id="collapse<?php echo $accordion_count; ?>" class="panel-collapse collapse in"> <!-- attaching the current accordion_count value to the collapse name (collapse1,collapse2,etc...) -->

  @for($x = 0; $x < count($exercise); $x++) <!-- we're iterating over exercises categories avobe. Now we're iterating over their individual exercises -->
       <img src="{{ asset($exercise[$x]['image_path']) }}" />
      <b>{{ $exercise[$x]['exercise_name'] }}</b>
      <!-- Button trigger modal -->
<button type="button" id="replaceExercise" value="{{ $exercise[$x]['id'] }}" class="btn btn-primary btn-lg replaceExercise" data-toggle="modal" data-target="#myModal">
  Replace this exercise
</button>

     <!-- Iterating over the number of exercises the catergory (above) have, and going out once finised.Then increasing the number of $accordion_count.--> 

      <br/>
  @endfor
      </div>

</div>
<?php $accordion_count++; ?>
@endforeach 



<!-- Modal -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title" id="myModalLabel">Modal title</h4>
      </div>
      <div class="modal-body">
        ...
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>
<div class="container">

Notice that in the ajax call I am writing window.location.reload(); to refresh the page after successfully replacing the image.

I have been told it's good for not overloading the server. Something like this. But I am sure you have better suggestion for this code? probably you would. @tykus_ikus

osherdo's avatar

Also, would it be possible for you to identify why when I am changing an exercise, it is changing other exercises from the same muscle group ,but now the particular being choosen?

here's a screenshot of how it looks when I am trying to change an image: http://4.1m.yt/cIDGftt.png

It happens on some of the exercises, but not in all of them.

tykus's avatar

:O

There is a lot to tidy up in there!!!

The controller method at the end of the update_routine route seems to be missing.

osherdo's avatar

Oh I am sorry I thought it would be good to give you the whole picture ! Just tell me what to do and I will do it right away.

Are you talking about this route?

 Route::post('routine_details/update_routine','RoutineController@update'); // Saving routine details (on submit), done from the routine_details view.

I do have the update method in place in this controller (RoutineController): (I might have omitted it accidentally,sorry)

public function update(Request $request)
  { 
    
   $current_routine = ExerciseRoutine::where('routine_id',$request->routine)->where('exercise_id',$request->exercise_to_replace)
    ->update(['exercise_id'=>$request->chosen_exercise]); 
  }

Please or to participate in this conversation.