Drag and Drop question

I have an app in production that I am adding a module to for Lunch Ordering

What I have is a Menu, which has Meals, and the Meals have Meal Items in Categories like Entree, Vegetable, Fruit, and Drink

What I would like to do it allow Drag and Drop to build meals, and Drag and Drop to build Menus

I made a standard Rails form with a Drag and Drop area for the meal with the items as draggable and sorted in groups on the right…

I don’t think I need an Ajax call as I am building or editing the meal. What I need to be able to do is manipulate the array that gets passed back for the Meal Items…

Any suggestions or links I can look at to do this?

John Sanderbeck

What happens now when you drop the element? Do you see anything in your browser's JavaScript console indicating that the drop event fired? Did you do anything beyond CSS (to make it look a particular way) to define the draggable and droppable behaviors for these elements? Do the draggable elements have unique IDs or data-attributes or some other sort of identification scheme?

Here's where I would start looking for more information: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API

Walter

Sorry, I should have elaborated a little more Walter…

I implemented Interact.js and used an example from a Drifting Ruby Episode… https://www.youtube.com/watch?v=fhnHA7PWq0g

Dragging is working but drop doesn’t “look” to be firing…

My form code currently is like this

<% if action_name == “new” %>

Creating New Meal

<% else %>

Editing Meal

<% end %>

<div class="col-sm-4 col-md-4">

	<%= form_for @meal do |f| %>

		<% if @meal.errors.any? %>

			<div id="error_explanation">

				<h3><%= pluralize(@meal.errors.count, 'error') %> prohibited this meal from being saved:</h3>

				<ul>

					<% @meal.errors.full_messages.each do |message| %>

						<li><%= message %></li>

					<% end %>

				</ul>

			</div>

		<% end %>

		<div>

			<table>

				<tr>

					<th align='left'>Name:</th>

					<td><%= f.text_field(:name) %></td>

				</tr>

				<tr>

					<th align='left'>Description:</th>

					<td><%= f.text_area(:description) %></td>

				</tr>

				<tr>

					<th align='left'>Available Daily:</th>

					<td><%= f.check_box(:available_daily, 'data-toggle' => "toggle", 'data-size' => "mini", 'data-on' => "Yes", 'data-off' => "No", 'data-style' => "ios") %></td>

				</tr>

				<tr>

					<th align='left'>Not Available:</th>

					<td><%= f.check_box(:not_available, 'data-toggle' => "toggle", 'data-size' => "mini", 'data-on' => "Yes", 'data-off' => "No", 'data-style' => "ios") %></td>

				</tr>

				<tr>

					<th align='left'>Meal Items:</th>

					<td>

						<div id="outer-dropzone">

							<div id="meal_items" class="dropzone">

<% unless @meal.mitems.nil? %>

<% @meal.mitems.each do |mealitem| %>

<%= content_tag :div, mealitem.name, class: ‘drag-drop can-drop’, data: { draggable: true, url: meal_path(mealitem) } %>

<% end %>

<% end %>

							</div>

						</div>

					</td>

				</tr>

			</table>

			<div class="form-submit-buttons">

				<%= f.submit class: 'btn btn-success' %>

				<%= link_to 'Cancel', meals_path, class: 'btn btn-danger' %>

			</div>

		</div>

	<% end %>

</div>

<div class="col-sm-2 col-md-2 entrees">

	<div class='meal_item_title'>Entrees</div>

	<% Mitem.entrees.each do |food| %>

		<%= content_tag :div,

		                food.name,

		                class: 'drag-drop entree_items',

		                data: { draggable: true, url: mitem_path(food) } %>

		<% end %>

</div>

<div class="col-sm-2 col-md-2 vegetables">

	<div class='meal_item_title'>Vegetables</div>

	<% Mitem.vegetables.each do |food| %>

		<%= content_tag :div,

		                food.name,

		                class: 'drag-drop vegetable_items',

		                data: { draggable: true, url: mitem_path(food) } %>

		<% end %>

</div>

<div class="col-sm-2 col-md-2 fruits">

	<div class='meal_item_title'>Fruits</div>

	<% Mitem.fruits.each do |food| %>

		<%= content_tag :div,

		                food.name,

		                class: 'drag-drop fruit_items',

		                data: { draggable: true, url: mitem_path(food) } %>

		<% end %>

</div>

<div class="col-sm-2 col-md-2 drinks">

	<div class='meal_item_title'>Drinks</div>

	<% Mitem.drinks.each do |food| %>

		<%= content_tag :div,

		                food.name,

		                class: 'drag-drop drink_items',

		                data: { draggable: true, url: mitem_path(food) } %>

		<% end %>

</div>

Add a few Console.log lines to determine where it’s dying? – H

Ok. Changed to Document.ready and got the drag and drops firing…

Also changed the data URl to be a data ID which is the ID of the meal item.

The part I am unsure of is assigning the mitem array that gets passed back in the form…

I know this should be simple and as you can tell I am a total newbie with this… :slight_smile:

John

I'm not sure how your form is constructed at the moment. But if you were passing the result of the dragged items back to a meals_controller, you could assign the array of values to `menu_item_ids` and the child menu items would be created and assigned. You'll have to ensure that you whitelist that attribute in a special way:

def meal_params
  params.permit(:foo, :bar, :baz, menu_item_ids: [])
end

You'll want to put this (and any other array-shaped parameters) at the end of the list of permitted params, and you have to cast it to an array as I've done here.

If you are sending the form to the menus_controller, then you've got a doubly-nested assignment going on, and you'll probably want to use accepts_nested_parameters_for in your model relationship between menu and meal. I would do that if I was building N number of meals within one menu form. If you're trying to get this going first, I would just save each meal individually first, and then, once it's working at that level, try to further complicate it by nesting forms.

Walter

I understand that part Walter. I have done many nested forms. However, this is the first one with a drag and drop feature.

The part I am struggling with is how to populate the array that gets passed back through the drag and drop events.

I have the strong params setup and could easily just add a simple nested form, but I wanted to add the drag and drop as a feature so they could build the meals easily with an IPad.

John

You'll have to assign the values you get back (in JavaScript) to a properly-named form element in your form. Try making a hidden input in the form with the name set to meal[meal_item_ids]. Then locate that field in JavaScript in your callback function (which fires after the drop event) and set the value of that hidden field to the array of values you get in your callback.

Walter

Sounds like you need some Javascript.

You should be able to use Javascript to manipulate a hidden field that will contain an array of items, or their ids. You will need to get your hands dirty with Javascript, I see no other “DOM-oriented” way to do it.

if you’re not opposed to Jquery I’d just recommend jQuery UI’s draggable which implements many hooks that will be good for you

https://jqueryui.com/draggable/

-Jason

Thank you all… I got the drag and drop working but ran into many other complications from the drag and drop and decided it was too complicated for what I was trying to achieve…

Snap to positions, alignment of objects, and on and on. Looked cool but not worth the extra code… :slight_smile:

John