Toggle Button State Using Ajax

In this post, we'll learn how to toggle between two icons using AJAX, a lovely extendable piece of code that can prove useful for a wide variety of projects and scenarios. 

To begin with, let's include some simple HTML. For this example, my button will include the word "Save", then toggle to "Saved" when clicked. Here I'm using Laravel Blade on the front-end, but you can write your if statement in any front-end compiler or even just use a single file.

<button class="btn" onclick="save">
  @if ($saved == true)
    <i id="save-icon" class="fas fa-star"></i><span id="save">Saved!</span>
  @else
    <i id="save-icon" class="far fa-star"></i><span id="save">Save</span>
  @endif
</button>

The icons in this example come from font awesome, so if you want to do the same make sure at some point in the layout you've included the proper script tag. It should be something like: 

<script defer src="https://use.fontawesome.com/releases/v5.0.4/js/all.js"></script>

Now let's dive into the Javascript. First, we'll target the icon and the data prefix of that icon. The iconType will either be "fas" or "far"

function save() {
    var saveEl = document.getElementById("save-icon");
    var iconType = saveEl.getAttribute("data-prefix");
}

Using these local variables we can easily set up some simple if-else logic. If the icon type is 'fas', that means the recipe has already been saved, so we should un save it. We will first make the call to unsave the recipe, then update the icon if the ajax response is successful. 

if ( iconType === 'fas' ) {
            $.ajax({
                url: "/unsave-recipe",
                cache: false,
                data: {
                    slug: recipeSlug,
                    userId: {{ $userId }}
                }
            })
                .done(function (response) {
                    document.getElementById("save-icon").classList.add("far");
                    document.getElementById("save").innerHTML="Save";
                })
                .fail(function () {
                    console.log('failure');
                });

Next, let's handle the else branch of the same function. Here, we'll need to do pretty much the same thing in reverse. 

        } else {
            $.ajax({
                url: "/save-recipe",
                cache: false,
                data: {
                    slug: recipeSlug,
                    userId: {{ $userId }}
                }
            })
                .done(function (response) {
                    document.getElementById("save-icon").classList.add("fas");
                    document.getElementById("save").innerHTML = "Saved!";
                })
                .fail(function () {
                    console.log('failure');
                });
        }

At this point, we're finished with the javscript elements, but we need to specify what the application should do when it hits one of those endpoints. Let's start with how to delete a saved recipe. 

Notice that in the data section of the ajax call, we are passing through two elements: a slug and a userId. We will use these two data points to identify the specific saved recipe row from a table, then delete it. 

    public function unsave(Request $request) {
        $recipe = recipe::where('slug', $request->input('slug'))->first();
        $savedRecipe = savedRecipes::where([
            ['user_id', $request->input('userId')],
            ['recipe_id', $recipe->id]
        ])->first();

        $savedRecipe->delete();
    }

Next, how about saving a recipe? Again we'll use the ajax call's recipe slug and user id to determine what data we should save, but this time we'll add a row to the database instead of deleting it! 

    public function save(Request $request) {
        $recipe = recipe::where('slug', $request->input('slug'))->first();

        $savedRecipe = new savedRecipes();
        $savedRecipe->user_id = $request->input('userId');
        $savedRecipe->recipe_id = $recipe->id;
        $savedRecipe->save();
    }

At this point we've integrated two different aspects of functionality. One is responsible for handling the javascript changes on the front-end by toggling the button state and its corresponding text. The second is for making sure everything works on the back-end, enabling the application to save data over time. 

Any questions? Something not clear? Let me know in the comments below.