# Gemfile
source 'https://rails-assets.org' do
gem 'rails-assets-interact'
end
# application.js
//= require interact
...
var dragMoveListener;
dragMoveListener = function(event) {
var target, x, y;
target = event.target;
x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
target.style.webkitTransform = target.style.transform = 'translate(' + x + 'px, ' + y + 'px)';
target.setAttribute('data-x', x);
return target.setAttribute('data-y', y);
};
window.dragMoveListener = dragMoveListener;
interact('*[data-draggable="true"]').draggable({
inertia: true,
autoScroll: true,
onmove: dragMoveListener
});
$(document).on('turbolinks:load', function(){
interact('#favorite_foods').dropzone({
accept: '*[data-draggable="true"]',
overlap: 0.75,
ondropactivate: function(event) {},
ondragenter: function(event) {
event.target.classList.add('drop-target');
event.relatedTarget.classList.add('can-drop');
return $.get(event.relatedTarget.attributes['data-url'].value, {
favorite: true
});
},
ondragleave: function(event) {
event.target.classList.remove('drop-target');
event.relatedTarget.classList.remove('can-drop');
return $.get(event.relatedTarget.attributes['data-url'].value, {
favorite: false
});
},
ondrop: function(event) {},
ondropdeactivate: function(event) {
event.target.classList.remove('drop-active');
return event.target.classList.remove('drop-target');
}
});
var ingredients = [];
interact('#have_ingredients').dropzone({
accept: '*[data-draggable="true"]',
overlap: 0.75,
ondropactivate: function(event) {},
ondragenter: function(event) {
event.target.classList.add('drop-target');
event.relatedTarget.classList.add('can-drop');
ingredients.push(event.relatedTarget.attributes['data-ingredient'].value);
return $.get(event.relatedTarget.attributes['data-url'].value, { ingredients: ingredients });
},
ondragleave: function(event) {
event.target.classList.remove('drop-target');
event.relatedTarget.classList.remove('can-drop');
ingredients = jQuery.grep(ingredients, function(value) {
return value != event.relatedTarget.attributes['data-ingredient'].value;
});
return $.get(event.relatedTarget.attributes['data-url'].value, { ingredients: ingredients });
},
ondrop: function(event) {},
ondropdeactivate: function(event) {
event.target.classList.remove('drop-active');
return event.target.classList.remove('drop-target');
}
});
});
# coffeescript equivalent
dragMoveListener = undefined
dragMoveListener = (event) ->
target = undefined
x = undefined
y = undefined
target = event.target
x = (parseFloat(target.getAttribute('data-x')) or 0) + event.dx
y = (parseFloat(target.getAttribute('data-y')) or 0) + event.dy
target.style.webkitTransform = target.style.transform = 'translate(' + x + 'px, ' + y + 'px)'
target.setAttribute 'data-x', x
target.setAttribute 'data-y', y
window.dragMoveListener = dragMoveListener
interact('*[data-draggable="true"]').draggable
inertia: true
autoScroll: true
onmove: dragMoveListener
$(document).on 'turbolinks:load', ->
interact('#favorite_foods').dropzone
accept: '*[data-draggable="true"]'
overlap: 0.75
ondropactivate: (event) ->
ondragenter: (event) ->
event.target.classList.add 'drop-target'
event.relatedTarget.classList.add 'can-drop'
$.get event.relatedTarget.attributes['data-url'].value, favorite: true
ondragleave: (event) ->
event.target.classList.remove 'drop-target'
event.relatedTarget.classList.remove 'can-drop'
$.get event.relatedTarget.attributes['data-url'].value, favorite: false
ondrop: (event) ->
ondropdeactivate: (event) ->
event.target.classList.remove 'drop-active'
event.target.classList.remove 'drop-target'
ingredients = []
interact('#have_ingredients').dropzone
accept: '*[data-draggable="true"]'
overlap: 0.75
ondropactivate: (event) ->
ondragenter: (event) ->
event.target.classList.add 'drop-target'
event.relatedTarget.classList.add 'can-drop'
ingredients.push event.relatedTarget.attributes['data-ingredient'].value
$.get event.relatedTarget.attributes['data-url'].value, ingredients: ingredients
ondragleave: (event) ->
event.target.classList.remove 'drop-target'
event.relatedTarget.classList.remove 'can-drop'
ingredients = jQuery.grep(ingredients, (value) ->
value != event.relatedTarget.attributes['data-ingredient'].value
)
$.get event.relatedTarget.attributes['data-url'].value, ingredients: ingredients
ondrop: (event) ->
ondropdeactivate: (event) ->
event.target.classList.remove 'drop-active'
event.target.classList.remove 'drop-target'
return
# visitors.scss
.dropzone {
height: 180px;
background-color: #ccc;
border: dashed 4px transparent;
border-radius: 4px;
margin: 10px auto 30px;
padding: 10px;
width: 80%;
transition: background-color 0.3s;
}
.drop-active {
border-color: #aaa;
}
.drop-target {
background-color: #29e;
border-color: #fff;
border-style: solid;
}
.drag-drop {
display: inline-block;
min-width: 40px;
padding: 1em 0.75em;
color: #fff;
background-color: #29e;
border: solid 2px #fff;
-webkit-transform: translate(0px, 0px);
transform: translate(0px, 0px);
transition: background-color 0.3s;
}
.drag-drop.can-drop {
color: #000;
background-color: #4e4;
}
# foods_controller.rb
class FoodsController < ApplicationController
def opinion_on_food
@food = Food.find(params[:id])
@food.update_attribute(:favorite, params[:favorite])
@food.save
head :ok
end
def what_to_cook
@foods = Food.includes(:recipes).all.select {|i| i.recipes.map(&:ingredient_id).to_set.subset?(params[:ingredients].to_a.map(&:to_i).to_set) }
end
end
# models/food.rb
class Food < ApplicationRecord
has_many :recipes, dependent: :destroy, class_name: 'Recipe'
has_many :ingredients, through: :recipes
def self.favorite_foods
where(favorite: true)
end
def self.no_opinion_foods
where(favorite: false)
end
end
# models/ingredient.rb
class Ingredient < ApplicationRecord
has_many :recipes, dependent: :destroy, class_name: 'Recipe'
has_many :foods, through: :recipes
end
# models/recipe.rb
class Recipe < ApplicationRecord
belongs_to :food
belongs_to :ingredient
end
# visitors_controller.rb
class VisitorsController < ApplicationController
def favorite_foods
end
def ingredients
@ingredients = Ingredient.all
end
end
# foods/_food.html.erb
<li class='list-group-item'>
<strong><%= food.name %></strong>
<%= content_tag :span, 'and it is one of your favorites' if food.favorite? %>
</li>
# foods/what_to_cook.js.erb
<% if @foods.size > 0 %>
$('#foods').html('<%= j render @foods %>');
<% else %>
$('#foods').html('<li class="list-group-item">Sorry, you may go hungry...</li>');
<% end %>
# visitors/favorite_foods.html.erb
<% Food.no_opinion_foods.each do |food| %>
<%= content_tag :div, food.name, class: 'drag-drop', data: { draggable: true, url: opinion_on_food_path(food) } %>
<% end %>
<div id="favorite_foods" class="dropzone">
<h1>Favorite Foods</h1>
<% Food.favorite_foods.each do |food| %>
<%= content_tag :div, food.name, class: 'drag-drop can-drop', data: { draggable: true, url: opinion_on_food_path(food) } %>
<% end %>
</div>
# visitors/ingredients.html.erb
<% @ingredients.each do |ingredient| %>
<%= content_tag :div, ingredient.name, class: 'drag-drop', data: { draggable: true, url: what_to_cook_path, ingredient: ingredient.id } %>
<% end %>
<div id="have_ingredients" class="dropzone">
<legend>Ingredients I have</legend>
</div>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">You have the ingredients to make</h3>
</div>
<ul id='foods' class='list-group'>
<li class="list-group-item">Sorry, you may go hungry...</li>
</ul>
</div>