The Mystery of the Disappearing Checkmarks

2020-04-27 04:10发布

If a user clicks a checkbox the below code will fire, but the checkmark will sometimes disappear, the box is therefore unchecked when it should be checked.

$(document).on("page:change", function() {
  $(".habit-check").change(function()
  {
    habit = $(this).parent().siblings(".habit-id").first().attr("id");
    level = $(this).siblings(".level-id").first().attr("id");
    if($(this).is(":checked"))
    {
       $.ajax(
       {
         url: "/habits/" + habit + "/levels/" + level + "/days_missed",
         method: "POST"
       });
    }
    else
    {
       $.ajax(
       {
         url: "/habits/" + habit + "/levels/" + level + "/days_missed/1",
         method: "DELETE"
       });
    }
  });
});

# I ALSO TRIED USING LOCAL STORAGE, BUT IT HAD THE SAME PROBLEM.
# VERSION 2

$(document).on("page:change", function() {
{
  $(".habit-check").change(function()
  {
    habit = $(this).parent().siblings(".habit-id").first().attr("id");
    level = $(this).siblings(".level-id").first().attr("id");
    if ($(this).is(":checked")) {
          $.ajax({
            url: "/habits/" + habit + "/levels/" + level + "/days_missed",
            method: "POST"
          });
          localStorage.setItem("habit_"+habit+"_"+level, true);
        } else {
          $.ajax({
            url: "/habits/" + habit + "/levels/" + level + "/days_missed/1",
            method: "DELETE"
          });
          localStorage.setItem("habit_"+habit+"_"+level, false);
        }
  });
});

The show page calls the AJAX

<div class="strikes">
  <% if @habit.current_level_strike %> 
    <div class="btn" id="red"> <label id="<%= @habit.id %>" class="habit-id">Strikes:</label>
  <% else %> 
    <div class="btn" id="gold"> <label id="<%= @habit.id %>" class="habit-id-two">Strikes:</label>
  <% end %>
    <% @habit.levels.each_with_index do |level, index| %>
      <% if @habit.current_level >= (index + 1) %>
        <p>
          <% if @habit.current_level_strike %> 
            <label id="<%= level.id %>" class="level-id">Level <%= index + 1 %>:</label> 
          <% else %> 
            <label id="<%= level.id %>" class="level-id-two">Level <%= index + 1 %>:</label> 
          <% end %>
          <%= check_box_tag nil, true, level.missed_days > 0, {class: "habit-check"} %>
          <%= check_box_tag nil, true, level.missed_days > 1, {class: "habit-check"} %>
          <%= check_box_tag nil, true, level.missed_days > 2, {class: "habit-check"} %>
       </p>
      <% end %>
    <% end %>
  </div>
</div>

This is what the AJAX fires to, days_missed_controller.rb.

class DaysMissedController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]

  def create
    habit = Habit.find(params[:habit_id])
    habit.missed_days = habit.missed_days + 1
    @habit.save!
    level = habit.levels.find(params[:level_id])
    level.missed_days = level.missed_days + 1
    if level.missed_days == 3
      level.missed_days = 0
      level.days_lost += habit.calculate_days_lost + 1
    end
    level.save!
    head :ok # this returns an empty response with a 200 success status code
  end

  def destroy
    habit = Habit.find(params[:habit_id])
    habit.missed_days = habit.missed_days - 1
    habit.save!
    level = habit.levels.find(params[:level_id])
    level.missed_days = level.missed_days - 1
    level.save!
    head :ok # this returns an empty response with a 200 success status code
  end
end

Here's the gist of it. Please don't hesitate to ask for further code or clarification :]

I also ran into this problem when I removed turbolinks thinking that might be the issue, but it's not.

1条回答
对你真心纯属浪费
2楼-- · 2020-04-27 04:38

Ok, tracked this nasty one down!

The problem lays in your HTML generated. It turns out, that the problematic ones end up performing AJAX calls to... invalid URLs (causing 404's)!

In your show view, you have code like:

<% if @habit.current_level_strike %> 
  <div class="btn" id="red"> <label id="<%= @habit.id %>" class="habit-id">Strikes:</label>
<% else %> 
  <div class="btn" id="gold"> <label id="<%= @habit.id %>" class="habit-id-two">Strikes:</label>
<% end %>

<!-- [...] -->

<% if @habit.current_level_strike %> 
  <label id="<%= level.id %>" class="level-id">Level <%= index + 1 %>:</label> 
<% else %> 
  <label id="<%= level.id %>" class="level-id-two">Level <%= index + 1 %>:</label> 
<% end %>

Why is it problematic? Well, in your JavaScript, you're relying on exact classes of .habit-id and .level-id:

habit = $(this).parent().siblings(".habit-id").first().attr("id");
level = $(this).siblings(".level-id").first().attr("id");

While according to HTML from show view, sometimes the proper classes are generated, and sometimes there are classes with appendix of *-two (habit-id-two and level-id-two).

If you try fixing the class names, so all are of the same form expected by your JavaScript (.siblings(".habit-id") and .siblings(".level-id")), the problem disappears.

Better solution (yes, it is possible to simplify it a bit ;))

What if we pregenerate urls, and set them in HTML like so:

<div class="strikes">
  <!-- [...] -->
    <% @habit.levels.each_with_index do |level, index| %>
      <% if @habit.current_level >= (index + 1) %>
        <p data-submit-url="<%= habit_level_days_missed_index_path({ habit_id: @habit.id, level_id: level.id }) %>"
           data-delete-url="<%= habit_level_days_missed_path({ habit_id: @habit.id, level_id: level.id, id: 1 }) %>">
          <!-- [...] -->
        </p>
      <% end %>
    <% end %>
  </div>
</div>

Then, your JavaScript can be simplified to:

$(document).on("page:change", function() {
  $(".habit-check").change(function()
  {
    var submitUrl = $(this).parents("p").data("submit-url");
    var deleteUrl = $(this).parents("p").data("delete-url");

    if($(this).is(":checked"))
    {
       $.ajax(
       {
         url: submitUrl,
         method: "POST"
       });
    }
    else
    {
       $.ajax(
       {
         url: deleteUrl,
         method: "DELETE"
       });
    }
  });
});

Please, be warned, that when generating delete-url, I've used hardcoded value of id, which is 1 (trying to reproduce your original behaviour), in:

data-delete-url="<%= habit_level_days_missed_path({ habit_id: @habit.id, level_id: level.id, id: 1 }) %>"

which corresponds to:

url: "/habits/" + habit + "/levels/" + level + "/days_missed/1"

in your code. Are you 100% sure this is what you want?

Hope that helps! If you have any questions - I'm more than happy to help/explain!

Good luck!

查看更多
登录 后发表回答