This commit is contained in:
Dan Elbert 2016-08-15 17:43:02 -05:00
parent 72fffcfbca
commit e6a9e00f82
13 changed files with 190 additions and 80 deletions

View File

@ -0,0 +1,10 @@
(function($) {
$(document).on("turbolinks:load", function() {
$(".log-form input.datepicker").datepicker({autoclose: true, todayBtn: "linked", format: "yyyy-mm-dd"});
$(".log-form input.rating").starRating();
$(".log-table input.rating").starRating({readOnly: true});
});
})(jQuery);

View File

@ -3,6 +3,8 @@
$(document).on("turbolinks:load", function() {
$(".recipe-view ul.ingredients").checkable();
$(".recipe-view ol.steps").checkable();
$(".recipe-table input.rating").starRating({readOnly: true, interval: 0.25, size: '20px'});
});
})(jQuery);

View File

@ -3,37 +3,68 @@
var pluginName = "starRating";
var defaultOptions = {
starCount: 5
starCount: 5,
readOnly: false,
interval: 1,
size: '30px'
};
var methods = {
initialize: function(opts) {
return this.each(function() {
var $input = $(this);
if ($input.data(pluginName.toLowerCase()) === true) {
if (console && console.log) {
console.log("star rating has already been initialized; skipping...");
}
return;
}
$input.attr("data-" + pluginName.toLowerCase(), "true");
var options = _.extend({}, defaultOptions, opts);
var $input = $(this);
options.$input = $input;
var $widget = $("<span />").addClass("star-rating").css({'font-size': options.size});
var $emptySet = $("<span />").addClass("empty-set").appendTo($widget);
var $filledSet = $("<span />").addClass("filled-set").appendTo($widget);
var $widget = $("<span />").addClass("star-rating");
options.$input = $input;
options.$emptySet = $emptySet;
for (var x = 1; x <= options.starCount; x++) {
$widget.append(
$("<span />").addClass("star").data("rating", x)
$emptySet.append(
$("<span />").addClass("star empty")
);
$filledSet.append(
$("<span />").addClass("star full")
);
}
$widget.data(pluginName + ".options", options);
$input.hide().after($widget);
$input.data(pluginName, true).hide().after($widget);
privateMethods.updateStars($widget);
$widget.on("click." + pluginName, "span.star", function(e) {
var value = $(this).data("rating");
if (!options.readOnly) {
$widget
.on("click." + pluginName, function(e) {
var value = privateMethods.calculateRating($widget, e.pageX);
privateMethods.setValue($widget, value);
})
.on("mousemove." + pluginName, function(e) {
var value = privateMethods.calculateRating($widget, e.pageX);
privateMethods.updateStars($widget, value);
})
.on("mouseleave." + pluginName, function (e) {
privateMethods.updateStars($widget);
});
}
$input.on("change." + pluginName, function() {
$input
.on("change." + pluginName, function() {
privateMethods.updateStars($widget);
});
});
@ -41,25 +72,33 @@
};
var privateMethods = {
updateStars: function($widget) {
updateStars: function($widget, value) {
var options = $widget.data(pluginName + ".options");
var value = parseInt(options.$input.val());
$widget.find(".star").each(function(idx, elem) {
var $star = $(elem);
$star.removeClass("empty full");
if ($star.data("rating") <= value) {
$star.addClass("full");
} else {
$star.addClass("empty");
}
});
value = (value == null ? (parseFloat(options.$input.val() || 0)) : value);
$widget.find(".filled-set").css({width: privateMethods.calculateWidth($widget, value)});
},
setValue: function($widget, value) {
var options = $widget.data(pluginName + ".options");
options.$input.val(value);
privateMethods.updateStars($widget);
},
calculateWidth: function($widget, value) {
var options = $widget.data(pluginName + ".options");
var width = options.$emptySet.width();
return ((value / options.starCount) * 100).toString() + "%";
},
// Given a screen X coordinate, calculates the nearest valid value for this rating widget
calculateRating: function($widget, screenX) {
var options = $widget.data(pluginName + ".options");
var offset = options.$emptySet.offset();
var width = options.$emptySet.width();
var ratio = (screenX - offset.left) / width;
ratio = Math.max(0, Math.min(1, ratio));
return Math.ceil(options.starCount * (1 / options.interval) * ratio) / (1 / options.interval);
}
};

View File

@ -2,10 +2,23 @@
span.star-rating {
display: block;
font-size: 30px;
display: inline-block;
color: gold;
cursor: default;
position: relative;
white-space: nowrap;
.empty-set {
color: gray;
opacity: 0.5;
}
.filled-set {
overflow-x: hidden;
position: absolute;
top: 0;
left: 0;
}
span.star {

View File

@ -3,14 +3,11 @@ class LogsController < ApplicationController
before_action :ensure_valid_user
before_action :set_log, only: [:show, :edit, :update, :destroy]
before_action :set_recipe, only: [:index, :new, :create]
before_action :set_recipe, only: [:new, :create]
before_action :require_recipe, only: [:new, :create]
def index
@logs = Log.for_user(current_user)
if @recipe
@logs = @logs.for_recipe(@recipe)
end
@logs = Log.for_user(current_user).order(:date)
end
def show
@ -41,8 +38,9 @@ class LogsController < ApplicationController
def create
@log = Log.new
@log.recipe = @recipe.log_copy(current_user)
@log.assign_attributes(log_params)
@log.recipe.is_log = true
@log.recipe.user = current_user
@log.user = current_user
@log.source_recipe = @recipe
@ -79,7 +77,7 @@ class LogsController < ApplicationController
end
def log_params
params.require(:log).permit(:date, :rating, recipe_attributes: [:name, :description, :source, :yields, :total_time, :active_time, recipe_ingredients_attributes: [:name, :ingredient_id, :quantity, :units, :preparation, :sort_order, :id, :_destroy], recipe_steps_attributes: [:step, :sort_order, :id, :_destroy]])
params.require(:log).permit(:date, :rating, :notes, recipe_attributes: [:name, :description, :source, :yields, :total_time, :active_time, recipe_ingredients_attributes: [:name, :ingredient_id, :quantity, :units, :preparation, :sort_order, :id, :_destroy], recipe_steps_attributes: [:step, :sort_order, :id, :_destroy]])
end
end

View File

@ -1,9 +1,11 @@
class LogDecorator < BaseDecorator
def recipe
RecipeDecorator.decorate(wrapped.recipe, h)
end
def date
v = super
puts v
puts '================'
if v && v.respond_to?(:strftime)
v.strftime("%Y-%m-%d")
else

View File

@ -24,4 +24,8 @@ class RecipeDecorator < BaseDecorator
end
end
def average_rating
@average_rating ||= (Log.for_recipe(wrapped).for_user(self.user).where('rating IS NOT NULL').average(:rating) || 0)
end
end

View File

@ -21,6 +21,7 @@ module ApplicationHelper
nav = [
nav_item('Recipes', recipes_path, 'recipes'),
nav_item('Ingredients', ingredients_path, 'ingredients'),
nav_item('Logs', logs_path, 'logs'),
nav_item('Calculator', calculator_path, 'calculator'),
nav_item('About', about_path, 'home')
]

View File

@ -1,6 +1,6 @@
<% @log = decorate(@log, LogDecorator) %>
<%= form_for([@recipe, @log]) do |f| %>
<%= form_for([@recipe, @log], html: {class: 'log-form'}) do |f| %>
<%= render partial: 'shared/error_list', locals: {model: @log} %>
@ -44,10 +44,3 @@
<% end %>
<script type="text/javascript">
$(document).on("turbolinks:load", function() {
$("input.datepicker").datepicker({autoclose: true, todayBtn: "linked", format: "yyyy-mm-dd"});
$("input.rating").starRating();
});
</script>

View File

@ -0,0 +1,40 @@
<div class="row">
<div class="col-xs-12">
<div class="page-header">
<h1>Log Entries</h1>
</div>
<% if @logs.empty? %>
<p>No Entries</p>
<% else %>
<div class="table-responsive">
<table class="log-table table table-striped table-hover">
<thead>
<tr>
<th>Recipe</th>
<th>Date</th>
<th>Rating</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<% decorate(@logs, LogDecorator).each do |log| %>
<tr>
<td><%= link_to log.recipe.short_name, log %></td>
<td><%= log.date %></td>
<td><input type="hidden" class="rating" value="<%= log.rating %>" /></td>
<td><%= %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
</div>
</div>

View File

@ -10,10 +10,11 @@
<% else %>
<div class="table-responsive">
<table class="table table-striped table-hover">
<table class="recipe-table table table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>Rating</th>
<th>Yields</th>
<th>Time</th>
<th>Created</th>
@ -27,6 +28,13 @@
<% decorate(@recipes, RecipeDecorator).each do |recipe| %>
<tr>
<td><%= link_to recipe.short_name, recipe %></td>
<td>
<% if recipe.average_rating > 0 %>
<input type="hidden" class="rating" value="<%= recipe.average_rating %>" />
<% else %>
--
<% end %>
</td>
<td><%= recipe.yields %></td>
<td><%= recipe_time(recipe) %></td>
<td><%= timestamp(recipe.created_at) %></td>

View File

@ -1,6 +1,6 @@
<% if model.errors.any? %>
<div id="error_explanation" class="alert alert-danger">
<h4><%= pluralize(model.errors.count, 'error') %> prohibited this <%= model.class.model_name.human %> from being saved:</h4>
<h4><%= pluralize(model.errors.count, 'error') %> prohibited this <%= model.model_name.human %> from being saved:</h4>
<ul>
<% model.errors.full_messages.each do |msg| %>

View File

@ -1,7 +1,7 @@
Rails.application.routes.draw do
resources :recipes do
resources :logs, only: [:index, :new, :create]
resources :logs, only: [:new, :create]
end
resources :logs, except: [:new, :create]