The Mystery of the Disappearing Checkmarks

The Mystery of the Disappearing Checkmarks

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!

How to restart current_level for every third missed_day (3 strikes you're out!)?

Here is my solution:

You need to keep track the days you lost at the moment you get 3 missed days (you need to add a field in level):

migration file.rb

class AddDaysLostToLevels < ActiveRecord::Migration
def change
add_column :levels, :days_lost, :integer, default: 0
end

end

Then change controller days_missed to reset when you get 3 missed days and store the days you lost when you start again level (in the variable days_lost):

class DaysMissedController < ApplicationController

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
...remain the same

Now we need to define "calculate_days_lost" and "real_missed_days" methods in habit.rb.

Remember to use self.real_missed_days instead self.missed_days in current_level method: (they are diferent because you can start again a level). New habit.rb will be like this:

class Habit < ActiveRecord::Base
belongs_to :user
has_many :comments, as: :commentable
has_many :levels
serialize :committed, Array
validates :date_started, presence: true
before_save :current_level
acts_as_taggable
scope :private_submit, -> { where(private_submit: true) }
scope :public_submit, -> { where(private_submit: false) }

attr_accessor :missed_one, :missed_two, :missed_three

def save_with_current_level
self.levels.build
self.levels.build
self.levels.build
self.levels.build
self.levels.build
self.save
end

def self.committed_for_today
today_name = Date::DAYNAMES[Date.today.wday].downcase
ids = all.select { |h| h.committed.include? today_name }.map(&:id)
where(id: ids)
end

def current_level_strike
levels[current_level - 1] # remember arrays indexes start at 0
end

def real_missed_days
value = 0
levels.each do |level|
if level.missed_days != nil
value += level.missed_days + level.days_lost
end
end
value
end

def committed_wdays
committed.map do |day|
Date::DAYNAMES.index(day.titleize)
end
end

def n_days
((date_started.to_date)..Date.today).count do |date|
committed_wdays.include? date.wday
end - self.real_missed_days
end

def calculate_days_lost
case n_days
when 0..9
n_days
when 10..24
n_days-10
when 25..44
n_days-25
when 45..69
n_days-45
when 70..99
n_days-70
else
n_days-100
end
end

def current_level
return 0 unless date_started
case n_days
when 0..9
1
when 10..24
2
when 25..44
3
when 45..69
4
when 70..99
5
else
6
end
end

end

How to fix AJAX to always fire when checking box?

This might be the problem with Turbolinks, could you try changing your javascript:

$(document).ready(function()
{
// ...
}

to look like:

$(document).on("ready page:load", function() {
// ..
}

Please, let me know if it helped!

Good luck!

Why does my CheckedTextView icon jump around to different elements in my GridView?

The problems lie in the way you track checked items:

GridView gridView = (GridView) findViewById(R.id.gridview);
View selected =gridView.getChildAt(position);
CheckedTextView selectedCheck = (CheckedTextView)selected.findViewById(R.id.imageTick);
selectedCheck.setChecked(true);
selectedCheck.setVisibility(View.VISIBLE);

1) getChildAt will not give you the correct view if you've scrolled into content and you're indexing by adapter position. When referencing an active view in a GridView or ListView you want to do something like this:

final int index = position - gridView.getFirstVisiblePosition();
View selected = gridView.getChildAt(index);

The reason is that your GridView only keeps child views for adapter items that it is currently displaying. It may only have child views for elements 4 to 23 if elements 0 to 3 were scrolled off of the top earlier.

This is why you're getting exceptions when you do View selected =gridView.getChildAt(position); a view for that position does not actually exist when it's off-screen, so getChildAt returns null.

2) When a ListView or GridView has its content change or it otherwise needs to re-layout its child views, it does so by re-binding existing views using the convertView parameter to your adapter. Your adapter never adjusts the checked state of your item views, so a checked view can be reused for an item that should be unchecked later.

To solve this you should have your data model that your adapter presents track the checked state of your items rather than relying on the item views to do it. This means that your adapter should always verify/set the checked state of the item in getView. It may look something like this:

imageView = (ImageView) v.findViewById(R.id.image);
imageView.setImageBitmap(mThumbsIds.get().get(position));

CheckedTextView checkView = (CheckedTextView) v.findViewById(R.id.imageTick);

if (mData.isItemChecked(position)) {
checkView.setChecked(true);
checkView.setVisibility(View.VISIBLE);
} else {
checkView.setVisibility(View.GONE);
}

Then when your option is selected that should change the checked state of an item, instead of the code snippet above do something like:

data.setItemChecked(position, true);
adapter.notifyDataSetChanged();

notifyDataSetChanged() will tell the GridView that it should re-bind the item views, which will have your getView method fix up the checked states properly.

I can't get magnific-popup animations to work

I ran into this same problem and after banging my head against all the hard surfaces in my office I discovered that I need to rename the css classes to match the fade example he provided here.

So for example the mfp-zoom-out animation:

.mfp-zoom-out .mfp-with-anim should be .mfp-zoom-out.mfp-bg

.mfp-zoom-out.mfp-bg stays the same

.mfp-zoom-out.mfp-ready .mfp-with-anim should be .mfp-zoom-out.mfp-ready .mfp-content

.mfp-zoom-out.mfp-ready.mfp-bg should be .mfp-zoom-out.mfp-bg.mfp-ready

.mfp-zoom-out.mfp-removing .mfp-with-anim should be .mfp-zoom-out.mfp-removing .mfp-content

.mfp-zoom-out.mfp-removing.mfp-bg should be .mfp-zoom-out.mfp-bg.mfp-removing



Related Topics



Leave a reply



Submit