I followed this tutorial to implement drag and drop. https://www.driftingruby.com/episodes/drag-and-drop-with-hotwire
I am getting an error on drop that says Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'dataset'). Any thoughts on why this is happening?
Project model:
acts_as_list scope: :project
_project.html.erb
<div id="projects" data-controller="position">
<%= content_tag :div, data: { sgid: project.to_sgid_param, id: "#{dom_id project}" }, class: 'table-row' do %> <div><%= project.name %> </div>
<div><%= project.description %> </div>
<div><%= project.status %> </div>
<div><%= project.user.email %> </div>
<div class="flex justify-end">
<%= link_to "View", project_path(project) , data: {turbo: false} %>
<%= button_to "Delete", project, method: :delete, form: {data: {turbo_confirm: "Are you sure?"}} %> </div>
</div>
<% end %>
</div>
position_controller.js
import { Controller } from "#hotwired/stimulus";
import Sortable from "sortablejs"
import { put, get, post, patch, destroy } from "#rails/request.js"
export default class extends Controller {
connect() {
this.sortable = Sortable.create(this.element, {
animation: 150,
onEnd: this.updatePosition.bind(this)
})
}
async updatePosition(event) {
const response = await put('/projects', {
body: JSON.stringify({
sgid: event.project.dataset.sgid,
position: event.newIndex + 1
})
})
if (response.ok) {
console.log(event.project.dataset.sgid)
}
}
}
well event doesn't have a method project. You may have meant event.target. Anyway it looks as if the target of the event is probably the #projects element (but you need to check this in dev tools and see where the event is being captured).
Let's suppose that the event is being captured by the #projects element, well that element has an empty dataList. So wherever the capture is occurring, you need to put data: {sgid: project.to_sgid_param} on that element.
Finally, you should be able to populate the ajax request with the value sgid: event.target.dataset.sgid.
Got a partial view successfully interacting with my coffee script. Collection_Select change triggers script & resulting value is correct, Controller does hit def new successfully.
Only question remaining is how to access the results of the coffee script in the controller.
Partial View:
<% #modName = locals[:moduleName] %>
<% #id = locals[:id] %>
<%= form_with url: admin_command_path() do |f| %>
<%= collection_select(:refcode, :Code, Command.where(FLD: #modName), :Code, :Definition, options ={prompt: true}) %>
<br /><br />
<button class="btn_new">
<%= link_to "Execute", new_admin_command_path(mod: #modName, id: #id) %>
</button>
<% end %>
Coffee Script:
get_SubModule = ->
$('#refcode_Code').change (e) ->
com_value = $('#refcode_Code').val()
console.log 'COFFEEE IS LIFE', com_value
str = $('#refcode_Code :selected').text()
data: {sub_mod_str: com_value}
return
return
So now what.
ActiveAdmin.register Command do
def new
[need to access sub_mod_str here however possible]
end
I think when you Change value you need call Ajax and request controller
get_SubModule = ->
$('#refcode_Code').change (e) ->
com_value = $('#refcode_Code').val()
console.log 'COFFEEE IS LIFE', com_value
str = $('#refcode_Code :selected').text()
data: {sub_mod_str: com_value}
$.ajax(
type: 'POST'
url: your_url
data: data
dataType: 'json'
success: (data) =>
console.log(data)
error: (er) =>
console.log(er)
)
return
return
Controller
def new
byebug // check params
end
I am trying to build a form that allows a user to make a booking for a martial arts class they wish to attend. I have created a form that dynamically changes based on the selections the user makes, when I change any of the select options the form updates and when I submit the form it redirects to a Stripe checkout. The problem I have is after submitting the the form and I click the browsers back button or the back button provided on the Stripe checkout page the select options that have been updated have reverted back to the default options rather than the updated options. Can anyone help me correct this behaviour and get the correct form elements to persist?
Here is the code I am using to do this:
The view I am rendering the form in:
<% content_for :banner_title, #page_data['bannerTitle'] %>
<% content_for :head do %>
<meta name="turbolinks-cache-control" content="no-cache">
<% end %>
<div class="content container py-5">
<div class="row">
<div class="col-12 col-md-7 mx-auto">
<%= render "forms/booking", options: #options %>
</div>
</div>
</div>
The form I am using:
<%= form_for #booking, class: 'booking clearfix' do |f| %>
<div class="form-group">
<%= f.label(:class_name, "Select a class you wish to attend: ") %>
<%= f.select(:class_name, options_for_select(options[:class_names], #booking.class_name), {}, class: 'form-control' ) %>
</div>
<div class="form-group">
<%= f.label(:date, "Select a date:") %>
<%= f.select(:date, options_for_select( options[:dates], #booking.date ), {}, class: 'form-control' ) %>
</div>
<div class="form-group">
<%= f.label(:time, "Select a time: ")%>
<%= f.select(:time, options_for_select(options[:times], #booking.time), {}, class: 'form-control') %>
</div>
<div class="form-group">
<%= f.label(:attendees, "How many attending: ") %>
<%= f.select(:attendees, options_for_select(options[:attendees], #booking.attendees), {}, class: 'form-control' )%>
</div>
<%= f.submit 'Place Booking', class: 'btn btn-primary btn-lg text-light float-right', id: 'create-booking' %>
<% end %>
<%= javascript_pack_tag 'booking_form' %>
<script src="https://js.stripe.com/v3/"></script>
The model for the form (I'm not using ActiveRecord, i dont know if this makes any difference?):
class Booking
include ActiveModel::Model
MAX_ATTENDEES = 10
attr_accessor :time, :class_data, :attendees, :date, :class_name
def initialize(args={})
#time = args['time']
#class_name = args['class_name']
#class_data = args['class_data']
#date = args['date']
#attendees = args['attendees']
end
def day
#date.split(',').first
end
def available_dates
days_index_array = class_data['times'].keys.map {|k| day_index(k) }
days_within( days_index_array )
end
def available_times
if !date
class_data['times'][class_data['times'].keys.first.downcase]
else
class_data['times'][day.downcase]
end
end
def total_cost
#class_data['cost'].to_i * #attendees.to_i
end
def attending_string
ActionController::Base.helpers.pluralize(attendees, 'person')
end
private
def days_within(days, timeframe=1.month)
start_date = Date.tomorrow
end_date = start_date + timeframe
(start_date..end_date).to_a.select {|k| days.include?(k.wday) }
end
def day_index(day)
DateTime::DAYNAMES.index(day.to_s.capitalize)
end
end
And the controller I am calling the new action in:
class BookingsController < ApplicationController
include BookingsHelper
before_action :set_class_data
skip_before_action :set_page_data, except: :new
def new
set_booking
# store values to be passed to the form helper method options_for_select. Each value must be an array populated with arrays with the format [value, text]
#options = {
class_names: #class_data.map {|c| [ c['name'], c['name'] ]},
dates: #booking.available_dates.map {|d| [d.strftime('%A, %d %B'), d.strftime('%A, %d %B')] },
times: #booking.available_times.map {|t| [t,t]},
attendees: Booking::MAX_ATTENDEES.times.map {|i| [i+1, i+1]}
}
end
def create
end
def booking_form_data
booking_form_data = set_booking_form_data(params)
update_session_booking(booking_form_data)
render json: booking_form_data
end
private
def set_booking
if session[:current_booking]
pp "session exists"
#booking = Booking.new(session[:current_booking])
else
pp "session does not exist"
#booking = Booking.new
session[:current_booking] = #booking.instance_values
end
set_booking_class_data
end
def set_booking_class_data
!#booking.class_name ? #booking.class_data = #class_data.first.except('information') : #booking.class_data = #class_data.find {|cd| cd['name'] == #booking.class_name}.except('information')
end
def booking_params
params.permit(:class_name, :date, :time, :attendees, :update_type)
end
def update_session_booking(booking_form_data)
if params[:update_type] == 'class_name'
session[:current_booking]['class_name'] = params[:class_name]
session[:current_booking]['date'] = booking_form_data[:date_options].first
session[:current_booking]['time'] = booking_form_data[:time_options].first
elsif params[:update_type] == 'date'
session[:current_booking]['date'] = params[:date]
session[:current_booking]['time'] = booking_form_data[:time_options].first
elsif params[:update_type] == 'time'
session[:current_booking]['time'] = params['time']
elsif params[:update_type] == 'attendees'
session[:current_booking]['attendees'] = params[:attendees]
elsif params[:update_type] == 'load'
session[:current_booking] = booking_params.except(:update_type)
end
pp "Session Booking: #{session[:current_booking]}"
end
def set_booking_form_data(params)
booking_form_data = {}
selected_class = #class_data.find {|cd| cd['name'] == params[:class_name] }
# when the class_name select is changed
if params[:update_type] == 'class_name'
booking_form_data[:date_options] = days_within( selected_class['times'].keys.map {|k| day_index(k) } ).map {|d| d.strftime('%A, %d %B') }
booking_form_data[:time_options] = selected_class['times'][booking_form_data[:date_options].first.split(',')[0].downcase]
# when date select is changed
elsif params[:update_type] == 'date'
booking_form_data[:time_options] = selected_class['times'][params[:date].split(',')[0].downcase]
end
booking_form_data
end
end
And the javascript I am using to update the form:
getBookingFormData = (bodyData={}, successCallback=()=>{}) => {
$.ajax({
url: '/booking_form_data',
method: 'POST',
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
data: bodyData,
success: successCallback
})
}
createOptions = (values) => {
let newOptions = [];
$.each(values, (index, value) => {
let newOption = $('<option></option>');
newOption.attr('value', value);
newOption.text(value);
newOptions.push(newOption)
})
return newOptions
}
appendOptions = (options, element) => {
$(element).empty();
$(element).append(options)
}
currentFormValues = () => {
return {
class_name: $('#booking_class_name').val(),
date: $('#booking_date').val(),
time: $('#booking_time').val(),
attendees: $('#booking_attendees').val()
}
}
$('select#booking_class_name').on('change', () => {
let bodyData = {
class_name: $('select#booking_class_name').val(),
update_type: 'class_name'
}
let successCallback = (res) => {
let dateOptions = createOptions(res.date_options);
let dateSelect = $('select#booking_date');
let timeOptions = createOptions(res.time_options);
let timeSelect = $('select#booking_time');
appendOptions(dateOptions, dateSelect);
appendOptions(timeOptions, timeSelect);
}
getBookingFormData(bodyData, successCallback)
});
$('select#booking_date').on('change', () => {
let bodyData = {
class_name: $('select#booking_class_name').val(),
date: $('select#booking_date').val(),
update_type: 'date'
};
let successCallback = (res) => {
let timeOptions = createOptions(res.time_options);
let timeSelect = $('select#booking_time');
appendOptions(timeOptions, timeSelect);
}
getBookingFormData(bodyData, successCallback)
});
$('select#booking_time').on('change', () => {
let bodyData = {
time: $('select#booking_time').val(),
update_type: 'time'
};
getBookingFormData(bodyData);
});
$('select#booking_attendees').on('change', () => {
let bodyData = {
attendees: $('select#booking_attendees').val(),
update_type: 'attendees'
};
getBookingFormData(bodyData);
});
$('#create-booking').on('click',(e) => {
e.preventDefault();
bookingDefault = false
const stripe = Stripe(process.env.STRIPE_PUBLIC);
let requestHeaders = new Headers({
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'),
'Content-Type': 'application/json'
})
fetch('/create_checkout_session', {
method: 'POST',
headers: requestHeaders,
body: JSON.stringify(currentFormValues())
})
.then((res) => { return res.json() })
.then((session) => { return stripe.redirectToCheckout({ sessionId: session.id }) })
.then((result) => {
if (result.error) { alert(result.error.message) }
})
.catch((error) => { console.error('Error: ', error) })
})
From what Ive read i think it may be a problem related to caching which makes me think this is an issue with turbolinks but I could be completely wrong. Ive tried adding meta tags that disable turbolinks or force it to reload the page but they did not seem to work.
Any input at all would be really appreciated as Ive been stuck on this for days. Let me know if you need any more information
This isn't so much Stripe-related as it is related you your form value management. If you want to keep these values around, you'll need to build that into your front application, somehow. There are lots of options for this:
Using local storage
Using query parameters, if not sensitive info
Using a cookie and a server session you can re-retrieve and hydrate the f.select options with a default value.
I've been trying to show all conversation messages without refreshing page just with an ajax request. With this code i'm able to send message with channel but i'm not able to receive the message.
I would like to pass dynamically ID from frontend. I know it's possible to make that with a data parameter added into the body tag (like this).
I would like make the same but when i click on a conversation that change body data "conversation-id" and subscribe to the new channel id. I hope it's possible to make that with rails 5/jquery(ajax).
Here's my code :
conversation.coffee
App.conversation = App.cable.subscriptions.create channel: "ConversationChannel", id: document.querySelector('#conversation').dataset.conversationId,
connected: ->
$('#new_message').submit (e) ->
$this = $(this)
id = $this.find('#message_conversation_id');
body = $this.find('#message_body')
App.conversation.write id.val(), body.val()
body.val('')
e.preventDefault()
return false
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
$('.box-msg').append(data.data)
write: (id, body) ->
#perform 'write', {id: id, body: body}
conversation_channel.rb
class ConversationChannel < ApplicationCable::Channel
def subscribed
id = "[HOW CAN I PASS DYNAMICALLY ID]"
if id #on stream seulement si id existe
stream_from "conversation_#{id}"
end
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def write(data)
msg = Message.create(
conversation_id: data["id"],
body: data['body'],
user_id: 41,
read: 0
)
if msg.save
html = ApplicationController.render(partial: 'messages/message', locals: { message: msg})
end
ActionCable.server.broadcast "conversation_#{data["id"]}", data: html
end
end
conversations/index.html.erb
<script>
$(document).ready(function() {
$('.card-conversation').click(function() {
$("#conversation-content").html('');
var id = $(this).data("conversation");
$("body").attr("data-conversation-id", id);
$("#message_conversation_id").val(id); //input id
$.ajax({
url: "/conversations/"+id+"/messages",
cache: false,
success: function(html){
$("#conversation-content").append(html);
}
});
$("#conversation-content").fadeIn();
});
});
</script>
<div class="row h-100 no-margin">
<div class="col-md-4 h-100 messages-list" style="padding-right: 2px !important;">
<% #conversations.first(3).each do |conversation| %>
<div class="card card-conversation" data-conversation="<%=conversation.id %>">
<p>see conversation</p>
</div>
<% end %>
</div>
<div class="col-md-8 conversation no-padding" id="conversation">
<div id="conversation-content"></div>
<%= simple_form_for Message.new, url: "#" do |f| %>
<%= f.input :body, label: false, placeholder: "Add msg" %>
<%= f.input :conversation_id, label: false %>
<%= f.submit 'Go' %>
<% end %>
</div>
</div>
I am trying to pass some parameters from my view via an AJAX call to a custom method fetch_info in my photos controller. My controller does not seem to be receiving the parameters. When I click on an image to initiate the AJAX call, I see the following in my terminal:
Processing by PhotosController#fetch_info as JSON
Parameters: {"id"=>"", "secret"=>""}
Completed 500 Internal Server Error in 267ms
FlickRaw::FailedResponse ('flickr.photos.getInfo' - Photo not found):
app/controllers/photos_controller.rb:38:in `fetch_info'
It looks like the fetch_info method is being called, but the parameters are empty. How should I be passing in my parameters through AJAX?
Here is my view. I also have my javascript in the view for the purpose of just getting this to work.
index.html.erb
<div class="body_container">
<div id="photos_container">
<% #photos_array.each do |p| %>
<%= link_to '#' do %>
<div class='each_photo_container', id='<%="#{p[:id]}"%>' >
<%= image_tag p[:s_url] %>
</div>
<% end %>
<!-- Load Modal onClick -->
<script type="text/javascript">
jQuery(function() {
$('#<%=p[:id]%>').click(function (e) {
//ajax call to fetch photo info
var fetch_id = '<%=p[:id]%>';
var fetch_secret = '<%=p[:secret]%>';
$.ajax({
type: 'GET',
url: '/photos/fetch_info',
dataType: 'json',
data: { 'id' : fetch_id.val(), 'secret' : fetch_secret.val() }
});
return false;
});
});
</script>
<% end %>
<div class="basic_modal">
</div>
</div>
</div>
Here is my photos_controller.rb:
def fetch_info
puts params[:id]
puts params[:secret]
info = flickr.photos.getInfo(:photo_id => params[:id], :secret=> params[:secret])
end
You can use this code:
$('##{p[:id]}').click(function (e) {
//ajax call to fetch photo info
var fetch_id = '#{p[:id]}';
var fetch_secret = '#{p[:secret]}';
$.ajax({
type: 'GET',
url: '/photos/fetch_info',
dataType: 'json',
data: { 'id' : fetch_id, 'secret' : fetch_secret }
});
return false;
})