From e6a9e00f828806ca09fa75a378ebdd926048e4d5 Mon Sep 17 00:00:00 2001 From: Dan Elbert Date: Mon, 15 Aug 2016 17:43:02 -0500 Subject: [PATCH] logs --- app/assets/javascripts/logs.js | 10 ++ app/assets/javascripts/recipes.js | 2 + app/assets/javascripts/star_rating.js | 155 +++++++++++++++--------- app/assets/stylesheets/star_rating.scss | 17 ++- app/controllers/logs_controller.rb | 12 +- app/decorators/log_decorator.rb | 6 +- app/decorators/recipe_decorator.rb | 4 + app/helpers/application_helper.rb | 1 + app/views/logs/_form.html.erb | 9 +- app/views/logs/index.html.erb | 40 ++++++ app/views/recipes/index.html.erb | 10 +- app/views/shared/_error_list.html.erb | 2 +- config/routes.rb | 2 +- 13 files changed, 190 insertions(+), 80 deletions(-) create mode 100644 app/assets/javascripts/logs.js diff --git a/app/assets/javascripts/logs.js b/app/assets/javascripts/logs.js new file mode 100644 index 0000000..94cbaf7 --- /dev/null +++ b/app/assets/javascripts/logs.js @@ -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); \ No newline at end of file diff --git a/app/assets/javascripts/recipes.js b/app/assets/javascripts/recipes.js index e9eacfb..0e6a548 100644 --- a/app/assets/javascripts/recipes.js +++ b/app/assets/javascripts/recipes.js @@ -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); diff --git a/app/assets/javascripts/star_rating.js b/app/assets/javascripts/star_rating.js index c1e5100..54ca1e2 100644 --- a/app/assets/javascripts/star_rating.js +++ b/app/assets/javascripts/star_rating.js @@ -1,76 +1,115 @@ (function ($) { - var pluginName = "starRating"; - var defaultOptions = { - starCount: 5 - }; + var pluginName = "starRating"; + var defaultOptions = { + starCount: 5, + readOnly: false, + interval: 1, + size: '30px' + }; - var methods = { - initialize: function(opts) { - return this.each(function() { - var options = _.extend({}, defaultOptions, opts); + var methods = { + initialize: function(opts) { + return this.each(function() { + var $input = $(this); - var $input = $(this); - options.$input = $input; + if ($input.data(pluginName.toLowerCase()) === true) { + if (console && console.log) { + console.log("star rating has already been initialized; skipping..."); + } + return; + } - var $widget = $("").addClass("star-rating"); + $input.attr("data-" + pluginName.toLowerCase(), "true"); - for (var x = 1; x <= options.starCount; x++) { - $widget.append( - $("").addClass("star").data("rating", x) - ); + var options = _.extend({}, defaultOptions, opts); + + var $widget = $("").addClass("star-rating").css({'font-size': options.size}); + var $emptySet = $("").addClass("empty-set").appendTo($widget); + var $filledSet = $("").addClass("filled-set").appendTo($widget); + + options.$input = $input; + options.$emptySet = $emptySet; + + for (var x = 1; x <= options.starCount; x++) { + $emptySet.append( + $("").addClass("star empty") + ); + + $filledSet.append( + $("").addClass("star full") + ); + } + + $widget.data(pluginName + ".options", options); + + $input.data(pluginName, true).hide().after($widget); + + privateMethods.updateStars($widget); + + 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() { + privateMethods.updateStars($widget); + }); + }); } + }; - $widget.data(pluginName + ".options", options); + var privateMethods = { + updateStars: function($widget, value) { + var options = $widget.data(pluginName + ".options"); + value = (value == null ? (parseFloat(options.$input.val() || 0)) : value); + $widget.find(".filled-set").css({width: privateMethods.calculateWidth($widget, value)}); + }, - $input.hide().after($widget); + setValue: function($widget, value) { + var options = $widget.data(pluginName + ".options"); + options.$input.val(value); + privateMethods.updateStars($widget); + }, - privateMethods.updateStars($widget); + calculateWidth: function($widget, value) { + var options = $widget.data(pluginName + ".options"); + var width = options.$emptySet.width(); + return ((value / options.starCount) * 100).toString() + "%"; + }, - $widget.on("click." + pluginName, "span.star", function(e) { - var value = $(this).data("rating"); - privateMethods.setValue($widget, value); - }); + // 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)); - $input.on("change." + pluginName, function() { - privateMethods.updateStars($widget); - }); - }); - } - }; + return Math.ceil(options.starCount * (1 / options.interval) * ratio) / (1 / options.interval); + } + }; - var privateMethods = { - updateStars: function($widget) { - 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"); + $.fn[pluginName] = function(method) { + if (methods[method]) { + return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === 'object' || ! method) { + return methods.initialize.apply(this, arguments); } else { - $star.addClass("empty"); + $.error('Method ' + method + ' does not exist on jQuery.' + pluginName); } - }); - }, - - setValue: function($widget, value) { - var options = $widget.data(pluginName + ".options"); - options.$input.val(value); - privateMethods.updateStars($widget); - } - }; - - $.fn[pluginName] = function(method) { - if (methods[method]) { - return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); - } else if (typeof method === 'object' || ! method) { - return methods.initialize.apply(this, arguments); - } else { - $.error('Method ' + method + ' does not exist on jQuery.' + pluginName); - } - }; + }; })(jQuery); \ No newline at end of file diff --git a/app/assets/stylesheets/star_rating.scss b/app/assets/stylesheets/star_rating.scss index 20faf1e..44f1186 100644 --- a/app/assets/stylesheets/star_rating.scss +++ b/app/assets/stylesheets/star_rating.scss @@ -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 { diff --git a/app/controllers/logs_controller.rb b/app/controllers/logs_controller.rb index 67b2389..7162da9 100644 --- a/app/controllers/logs_controller.rb +++ b/app/controllers/logs_controller.rb @@ -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 \ No newline at end of file diff --git a/app/decorators/log_decorator.rb b/app/decorators/log_decorator.rb index 0712b07..c37e14b 100644 --- a/app/decorators/log_decorator.rb +++ b/app/decorators/log_decorator.rb @@ -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 diff --git a/app/decorators/recipe_decorator.rb b/app/decorators/recipe_decorator.rb index 49de7d2..4224500 100644 --- a/app/decorators/recipe_decorator.rb +++ b/app/decorators/recipe_decorator.rb @@ -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 \ No newline at end of file diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index fe2e5a1..aadfaaa 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -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') ] diff --git a/app/views/logs/_form.html.erb b/app/views/logs/_form.html.erb index 60ae68f..c1bf31e 100644 --- a/app/views/logs/_form.html.erb +++ b/app/views/logs/_form.html.erb @@ -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 %> - \ No newline at end of file diff --git a/app/views/logs/index.html.erb b/app/views/logs/index.html.erb index e69de29..111fd27 100644 --- a/app/views/logs/index.html.erb +++ b/app/views/logs/index.html.erb @@ -0,0 +1,40 @@ +
+
+ + + + <% if @logs.empty? %> +

No Entries

+ <% else %> + +
+ + + + + + + + + + + + <% decorate(@logs, LogDecorator).each do |log| %> + + + + + + + <% end %> + +
RecipeDateRatingNotes
<%= link_to log.recipe.short_name, log %><%= log.date %><%= %>
+
+ + <% end %> + +
+
+ diff --git a/app/views/recipes/index.html.erb b/app/views/recipes/index.html.erb index a38ca59..833b331 100644 --- a/app/views/recipes/index.html.erb +++ b/app/views/recipes/index.html.erb @@ -10,10 +10,11 @@ <% else %>
- +
+ @@ -27,6 +28,13 @@ <% decorate(@recipes, RecipeDecorator).each do |recipe| %> + diff --git a/app/views/shared/_error_list.html.erb b/app/views/shared/_error_list.html.erb index e0564ec..8ad7638 100644 --- a/app/views/shared/_error_list.html.erb +++ b/app/views/shared/_error_list.html.erb @@ -1,6 +1,6 @@ <% if model.errors.any? %>
-

<%= pluralize(model.errors.count, 'error') %> prohibited this <%= model.class.model_name.human %> from being saved:

+

<%= pluralize(model.errors.count, 'error') %> prohibited this <%= model.model_name.human %> from being saved:

    <% model.errors.full_messages.each do |msg| %> diff --git a/config/routes.rb b/config/routes.rb index 2e214fa..8495a40 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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]
NameRating Yields Time Created
<%= link_to recipe.short_name, recipe %> + <% if recipe.average_rating > 0 %> + + <% else %> + -- + <% end %> + <%= recipe.yields %> <%= recipe_time(recipe) %> <%= timestamp(recipe.created_at) %>