I've got a flashcard system that allows users to create and go through sets of flashcards. I'm currently refactoring it to make it more efficient, but I'm running into a similar problem that I was before. Rendering the buttons through Javscript causes the buttons to not be clickable anymore unless the Javascript is reloaded. Instead of having 500 Javascript pages running in the background, I changed it so I didn't need it anymore. However, my paths are passing the parameters of the current card to the controller. Since I'm no longer generating the buttons via JS, however, the buttons are passing the parameter as the original card at page load.
<%= link_to ">", flashcard_path(type: "next", old: #card.id, status: #is_read), remote: true, class: "btn" %>
How can I update the #card.id without re-rendering the entire button? I'm open to suggestions if I need to change a bunch of stuff, too. I'm sure I m doing this in a strange way- as that is how I tend to do things. :)
This is the javascript that re-renders the flashcard and the accompanying controller method.
$('#id_number').html("<%= #card_num %>");
$('#flashcard-title').html("<%= escape_javascript(#card.title) %>");
$('#flashcard-lines').html("<%= escape_javascript(render partial: 'flashcard_lines', :locals => { card: #card, is_read: #read }) %>");
$('#flashcard-body').html("<%= escape_javascript(#card.body) %>");
def flashcard
all = FlashCard.all
old_flashcard = FlashCard.find(params[:old].to_i)
old_index = FlashCard.all.index(old_flashcard)
case params[:type]
when "new"
#card = FlashCard.new
#card.save
#read = false
when "edit"
#card = old_flashcard
#read = false
when "next"
if old_index == all.length - 1
back = 0
else
back = old_index + 1
end
binding.pry
#card = all[back]
#read = true
when "back"
if old_index == 0
back = all.length - 1
else
back = old_index - 1
end
#card = all[back]
#read = true
when "save"
old_flashcard.save
#card = old_flashcard
#read = true
when "delete"
old_flashcard.destroy
#card = FlashCard.all.last
else
#card = FlashCard.find(0)
#read = true
end
if params[:status]
#read = params[:status]
end
#card_num = FlashCard.all.index(#card) + 1
respond_to do |format|
format.html
format.js
end
end
I am not sure if I am understanding your problem correctly, but maybe you could do something like this?
$('#current_btn_id').attr('old',new_id);
Related
Not sure if I am asking this question wrong, but I can't seem to find exactly the issue I am faced with.
I have a very basic rails 6 app for a task list. Tasks are either complete or incomplete and the change/update of this is to be done via javascript (the html side works just fine).
Here is my form partial _task.html.erb:
<%= form_with model: task, html: { class: "edit_task", id: "edit_task_#{task.id}" } do |f| %>
<%= f.check_box :complete %>
<%= f.submit "Update" %>
<%= f.label :complete, task.name %>
<%= link_to "(remove)", task, method: :delete, data: {confirm: "Are you sure?"}, remote: true %>
<% end %>
Here is the javascript to submit the form, tasks.js
function submit_update(){
const checkboxes = document.querySelectorAll('.edit_task input[type=checkbox]');
const submitbutton = document.querySelectorAll('.edit_task input[type=submit]');
submitbutton.forEach(button => button.remove());
checkboxes.forEach(checkbox => {
checkbox.addEventListener('click', () => checkbox.parentElement.submit());
});
}
window.addEventListener('turbolinks:load', event => {
submit_update();
document.addEventListener('task_added', event => submit_update());
});
This part works just fine, but once submitted and based on this section of the controller
def update
#task.update!(task_params)
respond_to do |format|
format.html { redirect_to root_url, notice: 'Task successfully updated' }
format.js
end
end
My understanding is together this should launch update.js.erb, which currently looks like
unction update_task() {
const current_task = document.querySelector('#edit_task_<%= #task.id %>');
const complete_tasks = document.querySelector('#complete_tasks');
const incomplete_tasks = document.querySelector('#incomplete_tasks');
<% if #task.complete? %>
complete_tasks.insertAdjacentHTML('beforeend',current_task.innerHTML);
<% else %>
incomplete_tasks.insertAdjacentHTML('beforeend',current_task.innerHTML);
<% end %>
}
update_task();
I have tried changing the above to a single line using an alert call and it still never gets called.
If someone could let me know why update.js.erb is not being called, it would be much appreciated :)
If any additional information is required, please let me know?
EDIT:
On further testing, I have found that if I submit the update via the click of the button, ie remove the submission via javascript, that the update.js.erb is actioned correctly.
So it would seem the focus needs to be on the tasks.js file and how that submits?
What is weird is that when that is included, after the submit the HTML format runs just fine, just not the js format??
After a number of days of trying to get the correct search words I have found the solution and happy to submit for others to find :)
The magic can be found here
The change for my example is as follows --
checkboxes.forEach(checkbox => {
checkbox.addEventListener('click', () => checkbox.parentElement.submit());
});
// becomes
checkboxes.forEach(checkbox => {
checkbox.addEventListener('click', event => {
checkbox.parentElement.dispatchEvent(new Event('submit', {bubbles: true}));
});
});
I have since edited the update.js.erb (does the same thing just a bit cleaner), but it also
needed an additional entry for newly added items to trigger having the entry change positions:
$ cat app/views/tasks/update.js.erb
function update_task() {
const current_task = document.querySelector('#edit_task_<%= #task.id %>');
const to_task = document.querySelector('#<%= #task.complete? ? "complete_tasks" : "incomplete_tasks" %>');
// Remove from previous list
current_task.remove();
// Append to the end of new list
to_task.insertAdjacentHTML('beforeend', '<%= j render(#task) %>');
// Advise tasks.js that new entry needs listener added
to_task.dispatchEvent(new Event('task_added', { bubbles: true }));
}
update_task();
Hope others find this useful :)
I got this menu on my website
%button.dropdown-button
.current-user{onclick:'showMenu()'}
%img.current-user-image{src:current_user.picture_url}
= current_user
%i.fa.fa-bars{'aria-hidden':true}
.dropdown-content
.menu-option{onclick:'showFilters()'}
Filter Transactions
%i.fa.fa-paper-plane{'aria-hidden':true}
.transaction-filters
.filter-option
My Transactions
%i.fa.fa-square-o
%a{href:'#'}
.menu-option
Balance History
%i.fa.fa-history{'aria-hidden':true}
%a{href:destroy_user_session_path}
.menu-option
Sign Out
%i.fa.fa-sign-out{'aria-hidden':true}
And I got this timeline with transactions
.timeline-container
- #transactions.each do |transaction|
.transaction-container
.transaction-header-container
.transaction-kudos-container
= "+#{transaction.amount}"
%span.currency
₭
.transaction-avatar-container
%div
= image_tag transaction.receiver_image, class:'avatar'
.transaction-body-container
.transaction-content
%span
= "#{transaction.sender.name}:"
%span.highlighted
= "+#{transaction.amount} ₭"
%span
to
%span.highlighted
= transaction.receiver_name_feed
%span
for
%span.highlighted
= transaction.activity_name_feed
%div
-#%button#like-button
-# Like
%span.post-time-stamp
= "#{distance_of_time_in_words(DateTime.now, transaction.created_at)} ago"
= paginate #transactions
They are both rendered on my index.html.haml
So when I click the div .filter-option.sent I want to change the code change from
- #transactions.each do |transaction|
to
- #all_transactions.each do |transaction|
to filter out the transactions of the current user without reloading the page.
These variables are defined in my controller
#transactions = Transaction.order('created_at desc').page(params[:page]).per(20)
#all_transactions = Transaction.all_for_user(current_user)
With in my model the method all_for_user
def self.all_for_user(user)
Transaction.where(sender: user).or(Transaction.where(receiver: user)).order('created_at desc').page.per(20)
end
I tried a lot of things with AJAX but nothing seems to please me. Somebody can help me?
So if you'd like to replace that #transactions list with AJAX you will need to do a few things..
1) Move the #transactions block into a partial that takes a local variable.
#transactions-wrapper
= render partial: 'transactions/list', locals: {transactions: #transactions}
2) Create a link that submits to a route, that hits a controller action as #js ('data-remote': true ) ( or write a javascript function that triggers $.ajax request: https://www.w3schools.com/jquery/ajax_ajax.asp
%a.transaction-filter{href: "/transactions/#{transaction_type}", 'data-remote': true}
or ex..
%a.study{href: "/transactions/recent", 'data-remote': true}
%a.study{href: "/transactions/past", 'data-remote': true}
3) define the route
get '/transactions/:type' => 'transactions#filter'
4) re-assign the variable based on the filter, and re-render that partial with the new data in a filter.js.erb file thats in the view directory -> app/views/transactions
def filter
#filtered_transactions = Transactions.where(type: params[:type] ).order('created_at DESC')
respond_to do |format|
format.html
format.js { render :action => 'filter.js.erb', :layout => false}
end
end
$("#transactions-wrapper").html("<%= j render(:partial => 'transactions/list'', :locals => { transactions: #filtered_transactions } ) %>");
alert('Transactions have been filtered, yo')
Let me know if that makes sense! Except please don't actually javascript alert at the end ;)
I've noticed how Akira Matsuda have added helper in Kaminari gem and made ti a lot simple for us to use Load more button.
I've read the document and figured simply adding
<%= link_to_next_page #items, 'Next Page' %>
should make the ajax load button work, but id didn't.
So I googled few other articles for some help and I wrote
few extra things...
Here are what I have right now.
I have these files
root.html.slim
ul#works
= render :partial => "works"
work.html.slim
- #works.each do | work |
= link_to work
li
span.thumb
= work_image_of work
h4 = link_to work.title, work, thumb:true
p.pull-left
small
= work.collaborators.count.to_s
| collaborators
= link_to_next_page #works, 'Next Page', id: 'view_more'
index.controller
def root
#works = Work.page(params[:page]).per(9)
render :layout => 'application'
end
also added in application.js
$('#works').append("<%= escape_javascript(render 'works', object: #works) %>");
$("#view_more").replaceWith("<%= escape_javascript(
link_to_next_page(#works, 'more', remote: true, id: 'view_more')
) %>");
and this in action helper,
def link_to_next_page(scope, name, options = {}, &block)
param_name = options.delete(:param_name) || Kaminari.config.param_name
link_to_unless scope.last_page?, name, {param_name => (scope.current_page + 1)}, options.merge(:rel => 'next') do
block.call if block
end
end
I have jquery included in my js files.
What am I missing?
Please help me out!
Thank you for your time!!
I have an AJAX call in my application.js which sends 3 pieces of data to my events_controller#check action:
//application.js
$(document).on('click', "#check-button", function(){
...
$.ajax({
url: '/events/check',
data: {checkList: checkList , selected_date: selectedDate , length: length },
}
);
});
my check action:
#events_controller.rb
def check
checkList = params[:checkList]
selected_date = params[:selected_date]
length = params[:length]
busy_array = Array.new #this needs to be rendered in the view
...
busy_array.push(user.id) #busy_array is a list of user ids from database
end
#routes.rb
resources :events do
get :check, on: :collection
end
The view:
<button id="check-button" type="button">Check</button>
<div class = "col-md-6" id="unavailable">
<h2>Unavailable on this day:</h2>
<ol id="unavailable-list">
<li>THIS LIST SHOULD BE POPULATED BY BUSY_ARRAY</li>
</ol>
</div>
Now I need to send back data from events_controller#check to the view, but I don't know how to send busy_array to be rendered in events\new.html.erb
Thanks for you help, I am a ruby/JS beginner
busy_array = Array.new #this needs to be rendered in the view
If that has to be available in the view, you need to define an #instance variable:
def check
checkList = params[:checkList]
selected_date = params[:selected_date]
length = params[:length]
#busy_array = Array.new
...
#busy_array.push(user.id) #busy_array is a list of user ids from database
end
Each time you call a controller#action, the data/variables within the action are bound by local scope.
Outputting data in the view requires the variable to be made available across the entire instance of the class (not just the action/function). Thus #busy_array.
Fix
#app/views/events/....
<%= button_to "Check", events_check_path %>
<div class = "col-md-6" id="unavailable">
<h2>Unavailable on this day:</h2>
<ol id="unavailable-list"> </ol>
</div>
#app/assets/javascripts/application.js
$(document).on('click', "#check-button", function(e){
...
$.get('/events/check', {checkList: checkList , selected_date: selectedDate , length: length});
});
#app/controllers/events_controller.rb
class EventsController < ApplicationController
def check
#busy_array = User.joins(:event).where(event: { ... }).pluck(:id) #-> return user ids where event.x = y
respond_to do |format|
format.js #-> invokes app/views/events/check.js.erb
format.html #-> invoked when HTML request sent
end
end
end
#app/views/events/check.js.erb
<% #busy_array.each do |id| %>
$("ol#unavailable-list").append("<%=j id %>")
<% end %>
I don't know how to send busy_array to be rendered in events\new.html.erb
The variable will be available in new if it's an #instance var:
#app/views/events/new.html.erb
<%= #busy_array %>
The view is part of the instance of your EventsController class, but outside the scope of the check method. When you send a request to Rails, it uses an instance of the Controller to compile a set of HTML to return to your browser.
For example, you could sum up the EventsController flow as such:
Request > Routing > EventsController.new(request).action > Returned HTML
If you set EventsController.new(request) manually, you'd have the following:
#event = EventsController.new request
#event.check #-> "busy_array" locally scoped to this function
#event.response #-> outputs view code.... #busy_array needs to be instance var
You'll understand better if you read up about MVC:
For a real AJAX request in Ruby on Rails, you need to send response from check action in JS format.
events_controller.rb
def check
checkList = params[:checkList]
selected_date = params[:selected_date]
length = params[:length]
#events = Event.where("checklist = ? and date = ?, checkList, selected_date).limit(length.to_i)
...
respond_to do |format|
format.js
end
end
Now, in your view, instead a check.html.erb, you will have check.js.erb. In this Javascript file, you can access to your # variables and render partials or wherever.
check.js.erb
$("#unavailable-list").append('<%= escape_javascript( render :partial => "events/event", :collection => #events ) %>');
_event.html.erb
<li><%= event.name %></li>
I change busy_array for events array because I don't know what's exactly busy_array but you can change it.
This is the flow for an AJAX request.
In my rails app I have a dropdown menu that a user can select an account to make a payment to:
//_payment.html.erb
<div class="field" id="payment-to-account">
<%= f.label 'Payment To:' %><br>
<%= f.collection_select :to_account_id, #liability_account_payment_list, :id, :account_details, {include_blank: 'Please Select'} %>
</div>
When the user selects a account I render a partial inside of this form based on their selection:
//_payment.html.erb
<%= form_for(#transaction,
as: :transaction,
url:
#transaction.id ? account_transaction_path(#account, #transaction) : account_transactions_path) do |f| %>
...
<% #f = f %>
<div id="to-account-form" style="display:none;"></div>
...
<script>
jQuery(function(){
$("#payment-to-account").change(function() {
var selected_item = $( "#payment-to-account option:selected" ).text();
var selected_item_index = $( "#payment-to-account option:selected" ).index();
//looks for account type in parentheses followed by space moneysign " $"
var regExp = /\(([^)]+)\)\s\$/;
var matches = regExp.exec(selected_item);
// array of account ids in order of list
var payment_account_ids = <%= raw #payment_list_ids %>;
switch (matches[1]) {
case 'Mortgage':
$('#to-account-form').html("<%= j render 'payment_to_mortgage', f: #f %>");
$('#to-account-form').slideDown(350);
break;
case 'PersonalLoan':
case 'CreditCard':
case 'StudentLoan':
case 'OtherLiability':
$('#to-account-form').html("<%= j render 'payment_to_all_other_liabilities', f: #f %>");
$('#to-account-form').slideDown(350);
break;
default:
$('#to-account-form').html("<br>" + "Contact support, an error has occurred");
$('#to-account-form').slideDown(350);
}
});
});
</script>
Right now it renders the correct partial based on the selection, but when that partial loads I need more information from the account model. I created a method called find_pay_to_account that take the input selected account id in the Accounts model that looks for the account based on the id.
When the user selects and account from the drop down, I'd like that method called on the partial that is loaded so I can show the user additional information about the account they are making a payment to before they submit the form. But I don't know how. I wanted to add something like this to my jQuery switch statement.
selected_account_id = payment_account_ids[selected_item_index-1]
#payment_to_account = Account.find_pay_to_account(selected_account_id)
Since rails preloads the partials in the background, making the following change to my partial render in the case statements still wont work:
From this
$('#to-account-form').html("<%= j render 'payment_to_mortgage', f: #f %>");
To this
$('#to-account-form').html("<%= j render 'payment_to_mortgage', f: #f, #payment_to_account: #payment_to_account %>");
I did some searching and found that with AJAX might be able to help:
Pragmatic Studio
Rails Cast
But i'm trying to access the model, not the controller and I'm trying to update a form partial. What is the correct way to do this?
Here are pics that show the user flow. An example of what I'm trying to update can be seen in the last pic. When the mortgage account is selected, it needs to show the minimum payment for the mortgage account. Right now it says zero because the partials rendering with all the information from BOA seed 0214.
If you want to access record information from your model inside of front-end javascript you will indeed want to setup a small api to query the database for that information. In ajax it would be something like this
function processDataFunction(data){
console.log(data)
}
$.ajax({
type: "GET",
dataType: "json",
url: "/some-path/:some_id",
success: processDataFunction(data){}
});
#config/routes.rb
Rails.application.routes.draw do
get "/some-path/:some_id", to: "some_controller#some_view", :defaults => { :format => :json }
end
#app/controllers/some_controller.rb
class SomeController < ApplicationController
def some_view
#some_records = SomeModel.find_by_id(params[:some_id])
respond_to do |format|
format.json { render json: #some_records }
end
end
end
To access the information in the rendered partial without making another controller action, I collected all data I might need in the original action. That way I could get the exact result I was looking for without changing my routes and doing ajax request.
To do this I added methods to the controller new action. You can see from my original question, all accounts I may need information for are in the variable that is in the dropdown menu:
#liability_account_payment_list
This is where the dropdown menu gets its information from
That variable is in the Transaction controller new action. So I created another variable storing an array on the line after the above variable:
#liability_accounts_payment_list_minimum_payments = #liability_account_payment_list.map {|account| account.minimum_payment.to_f + account.minimum_escrow_payment.to_f}
This new variable is an array of all the accounts minimum payments in the order they are listed in the dropdown menu the user will select from.
Then I changed the jQuery on the page to the following
//_payments.html.erb
<script>
jQuery(function(){
$("#payment-to-account").change(function() {
var selected_item = $( "#payment-to-account option:selected" ).text();
var selected_item_index = $( "#payment-to-account option:selected" ).index();
//looks for something in parentheses followed by space moneysign " $"
var regExp = /\(([^)]+)\)\s\$/;
var matches = regExp.exec(selected_item);
// array of minimum payments from accounts in list converted from ruby to js
var min_payments = <%= raw #liability_accounts_payment_list_minimum_payments %>;
// set the js variable to the appropriate minimum payment
var selected_account_min_payment = min_payments[selected_item_index-1];
switch (matches[1]) {
case 'Mortgage':
$('#to-account-form').html("<%= j render 'payment_to_mortgage', f: #f %>");
$("#min-payment-field-hidden").val(selected_account_min_payment);
$("#min-payment-field").html(selected_account_min_payment);
$('#to-account-form').slideDown(350);
break;
case 'PersonalLoan':
case 'CreditCard':
case 'StudentLoan':
case 'OtherLiability':
$('#to-account-form').html("<%= j render 'payment_to_all_other_liabilities', f: #f %>");
$("#min-payment-field-hidden").val(selected_account_min_payment);
$("#min-payment-field").html(selected_account_min_payment);
$('#to-account-form').slideDown(350);
break;
default:
$('#to-account-form').html("<br>" + "Contact support, an error has occurred");
$('#to-account-form').slideDown(350);
}
});
});
</script>
The lines that have min-payment-field-hidden are because setting two different divs with the same id does not work. One div is being used to set hidden_field, the other is showing the user what the value is.
//_payment.html.erb
<-- To make sure the appropriate minimum payment is submitted to controller -->
<%= f.hidden_field :amount, :id => "min-payment-field-hidden" %>
<div>
<%= f.label "Minimum Payment" %>
<div id="min-payment-field"></div>
</div>
If you look at my switch statement, you can see I set the above value with these lines:
$("#min-payment-field-hidden").val(selected_account_min_payment);
$("#min-payment-field").html(selected_account_min_payment);
Now the user can see the minimum payment for the specific account they choose from the dropdown.