I am trying to build a 4-choice quiz application using Ruby on Rails and JavaScript.
If you click one of the Choice Buttons at the Question View, a helper method "updateData(bool)" updates database and then you'll see the result without view transition. And if you click the Chart Button after answering, the view transitions to the Chart View.
However, the helper method "updateData" seems to be called not only by Choice Button but by Chart Button. It updates the database and increment the Question Number against my expectation.
According to the outputs from console.log, it is not likely that the Chart Button calls JavaScript function sendAnswer(ans).
I use Rails 5.0.1.
I would appreciate any advice. Thanks!
<head>
<script>
function sendAnswer(ans){
var questionID = getNumber();
var data = readCSV(questionID);
// True if answer number matches correct answer number
if(data[5] == ans ){
console.log("true")
<% updateData(1) %>
}else{
console.log("false")
<% updateData(0) %>
};
// Create Chart Button after answering
$("#bottom").append('<div id="showChart"></div>');
$("#showChart").html('<button id="showChartBtn" onclick="showChart()">Show Chart</button>');
};
</script>
<script>
function showChart(){
window.location.href = "/chart";
};
</script>
</head>
<body>
<% alphabets = ["A", "B", "C", "D"] %>
<!-- For loop to make A-D choices -->
<% for i in 0..3 %>
<button class="choice_btn" id="btn_<%= i %>" onclick="sendAnswer(<%= i %>);">
<%= alphabets[i] %>
</button>
<% end %>
<div id="bottom">
<!-- Chart Button comes here -->
</div>
</body>
# Return user according to remember token
def current_user
if (user_id = session[:user_id])
#current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(:remember, cookies[:remember_token])
log_in user
#current_user = user
end
end
end
# Increment the Question Number, and increment the Correct Answer Number if true
def updateData(bool)
current_user.questionNum += 1
current_user.correctNum += bool
current_user.save
end
In the html code above, embedded Ruby <% updateData %> is executed while generating HTML.
I should instead call Controller Action by Button Click Event, and then call helper method via Action.
Related
I am working on a faceted search in rails. It follows from my previous question: Rails: How to use facets with search results
What I am able to do is to use scopes to get all the facets with respect to the search results as shown in the figure:
What I am currently working on is use the facets texts as a link to add extra params to the search result we already got.
I have two questions in regard to this:
How to take existing search URL and add a link to add/merge params when a link is clicked. I am using params merge which is not working.
The code is as below.
<h3 style = "color:#7C064D; text-align: center;"><strong>CYLINDER</strong></h3>
<%- #listings_cylinder.each do |key, value| -%>
<h5 style = "color:#F00000; text-align: left;"><strong><%= link_to "#{key.upcase if key} (#{value})" , search_listings_path(params.merge(:cylinder => "#{key}")) %> </strong></h5>
<% end %>
How to use multiple filters at the same time. What I want is when a facet link is clicked, it should add a small filter link below the
sorting link in the pic. When a user wants to undo that facet filter
effect, he clicks the X on that small link and he gets to the search
query without the filter. The same what happens on hotels.com or
priceline.com as shown:
Note: I don't want to use solr/elaticsearch because it's not the best use case here. I want to learn it from scratch. I am already able to build facets texts with simple rails. With a little help, I will be able to do it.
Codes:
Scopes I am using:
scope :listing_search_cities, -> {group(:city).count}
scope :listing_search_body_type, -> {group(:bodytype).count}
scope :listing_search_states, -> {group(:state).count}
scope :listing_search_newused, -> {group(:NewUsed).count}
scope :listing_search_transmission, -> {group(:transmission).count}
scope :listing_search_cylinder, -> {group(:cylinder).count}
scope :listing_search_fuel, -> {group(:fuel).count}
scope :listing_search_drive, -> {group(:drive).count}
scope :listing_search_trim, -> {group(:trim).count}
scope :listing_search_color, -> {group(:color).count}
scope :listing_search_year, -> {group(:year).count}
scope :listing_search_interiorcolor, -> {group(:interiorcolor).count}
In the controller search action, I am using:
def search
#listings = Listing.search(params)
#listings_cities = #listings.listing_search_cities
#listings_bodytype = #listings.listing_search_body_type
#listings_states = #listings.listing_search_states
#listings_newused = #listings.listing_search_newused
#listings_cylinder = #listings.listing_search_cylinder
#listings_transmission = #listings.listing_search_transmission
#listings_drive = #listings.listing_search_drive
#listings_trim = #listings.listing_search_trim
#listings_fuel = #listings.listing_search_fuel
#listings_color = #listings.listing_search_color
#listings_interiorcolor = #listings.listing_search_interiorcolor
#listings_year = #listings.listing_search_year
end
In view, I am using iteration through the key values like this:
<%- #listings_cylinder.each do |key, value| -%>
<h5 style = "color:#F00000; text-align: left;"><strong><%= link_to "#{key.upcase if key} (#{value})" , search_listings_path(params.merge(:cylinder => "#{key}")) %> </strong></h5>
<% end %>
Not the solution, but some ideas (not tested):
Pass all filters to the search method in a hash in params (such as params[:filters]).
Define an instance variable with all the filters and load it in the search method in the controller.
Filter the listing based on filters.
def search
#current_filters = params[:filters]
#listings = Listing.all
#listings = #listings.where(:cylinder => #current_filters[:cylinder]) if #current_filters[:cylinder]
#listings = #listings.where(:fuel => #current_filters[:fuel]) if #current_filters[:fuel]
#etc
end
In the view:
<% #listings_cylinder.each do |key, value| %>
<h5><%= link_to "#{key.upcase if key} (#{value})", search_listings_path(filters: #current_filters.merge(:cylinder => "#{key}")) %>
</h5>
<% end %>
In the view, when showing current filters:
Applied filters:
<ul>
<% #current_filters.each do |key, value| %>
<li><%= link_to value, search_listings_path(filters: #current_filters.except(key)) %>
</li>
<% end %>
</ul>
I'm trying to display all the topics that are being discussed on my application, so I created a dropdown menu (btw if there is a better way of doing this please feel free to share it):
<div class="form-group">
<label for="topic_category">Category</label>
<select id="topic_category" name="topic[category]" class="form-control">
<option>Art</option>
<option>Business</option>
<option>Books</option>
<option>Charity</option>
<option>Coding</option>
<option>Cooking</option>
<option>Dance</option>
<option>Design</option>
<option>DIY</option>
<option>Engineering</option>
<option>Fashion</option> etc...
</select>
</div>
What I'm trying to do is to create links to each single category and also display how many topics are active through something like
----- Edit 1 -----
what I'm trying to achieve is something like this:
<div class="row">
<div class="col-md-6" style ="padding-top: 10px; border-right: 1px solid #ccc;">
<h4 style="text-align: center;">Topics</h4>
<link_to project.category(Art)> (project.category(art).count)
</div>
I know that is wrong but is the closest I get to explaining what I'm trying to achieve
---- Edit 2 -----
So I'm still trying to get it right, this is probably because I'm a newbie. So following your answers I implemented the code which looks something like this.
static_pages controller.
def home
if logged_in?
#user = current_user
#topics_count = Topic.group(:category).count
end
end
def categoryselection
category = params[:category]
topics = Topic.where(category: category)
render json: { success: true, Topics: topics }
end
def help
end
def about
end
def contact
end
end
home view
.......
<% if logged_in? %>
<div class="row">
<%= render 'shared/sidebar_right' %>
<div class="col-md-8" style = "align-content: right">
<%= render 'shared/feed' %>
</div>
</div>
<% else %>
.....
Sidebar_right view
......
<div id='topics'>
<% #topics_count.each do |topic, count| %>
<a class ='project-link' href='topic_path?category=<%= topic %>'> <%= topic %> (<%= count %>)</a>
<% end %>
......
<script type>
$('#topics').on('change', '.topic-link' function (e) {
e.preventDefault();
var category = $(e.currentTarget).val();
var queryUrl = $(e).href + '?category=' + category;
$.get(queryUrl, function(resp) {
if (resp.success) {redirect_to topic_path
// select the div or whatever node on the DOM you are displaying the result, and change it.
}
});
});
</script>
Topics Controller
class TopicsController < ApplicationController
.....
def index
end
def show
#user = current_user
#topic = Topic.find(params[:id])
end
.....
The error I'm getting is no routes match [GET] topic_path, and I've checked on my routes and it does exit it actually refers to "show", is this happening because the page I'm using is home rather than the topics page?
Thanks!
Based on your new edit
In the controller:
def index
#topics_count = Topic.group(:category).count
# this will give you something like this { 'Art' => 10, 'Business' => 15' }
end
on the html page:
<div id='topics'>
<% #topics_count.each do |topic, count| %>
<a class='topic-link' href='topic_show_url?category=<%= topic %>'> <%= topic %> (<%= count %>)</a>
<% end %>
</div>
You don't need to (and shouldn't) load all the topics because you are just showing the count, and at large scale, loading the entire table will likely crash your site.
This will take care of your second request, showing the topic the user picks when the link is clicked.
on the page, send ajax request when user select one of those options.
$('#topics').on('click', '.topic-link', function (e) {
e.preventDefault();
var category = $(e.currentTarget).val();
var queryUrl = $(e).href + '?category=' + category;
$.get(queryUrl, function(resp) {
if (resp.success) {
// select the div or whatever node on the DOM you are displaying the result, and change it.
}
});
});
in the controller
def you_define_this_method
category = params[:category]
topics = Topic.where(category: category)
render json: { success: true, topics: topics }
end
You can add a link inside your <option> tag... ie.
<option>Art</option>
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.
I am implemeting a facebook application in rails using facebooker plugin, therefore it is very important to use this architecture if i want to update multiple DOM in my page.
if my code works in a regular rails application it would work in my facebook application.
i am trying to use ajax to let the user know that the comment was sent, and update the comments bloc.
migration:
class CreateComments < ActiveRecord::Migration
def self.up
create_table :comments do |t|
t.string :body
t.timestamps
end
end
def self.down
drop_table :comments
end
end
controller:
class CommentsController < ApplicationController
def index
#comments=Comment.all
end
def create
#comment=Comment.create(params[:comment])
if request.xhr?
#comments=Comment.all
render :json=>{:ids_to_update=>[:all_comments,:form_message],
:all_comments=>render_to_string(:partial=>"comments" ),
:form_message=>"Your comment has been added." }
else
redirect_to comments_url
end
end
end
view:
<script>
function update_count(str,message_id) {
len=str.length;
if (len < 200) {
$(message_id).innerHTML="<span style='color: green'>"+
(200-len)+" remaining</span>";
} else {
$(message_id).innerHTML="<span style='color: red'>"+
"Comment too long. Only 200 characters allowed.</span>";
}
}
function update_multiple(json) {
for( var i=0; i<json["ids_to_update"].length; i++ ) {
id=json["ids_to_update"][i];
$(id).innerHTML=json[id];
}
}
</script>
<div id="all_comments" >
<%= render :partial=>"comments/comments" %>
</div>
Talk some trash: <br />
<% remote_form_for Comment.new,
:url=>comments_url,
:success=>"update_multiple(request)" do |f|%>
<%= f.text_area :body,
:onchange=>"update_count(this.getValue(),'remaining');" ,
:onkeyup=>"update_count(this.getValue(),'remaining');"
%> <br />
<%= f.submit 'Post'%>
<% end %>
<p id="remaining" > </p>
<p id="form_message" > </p>
<br><br>
<br>
if i try to do alert(json) in the first line of the update_multiple function , i got an [object Object].
if i try to do alert(json["ids_to_update"][0]) in the first line of the update_multiple function , there is no dialog box displayed.
however the comment got saved but nothing is updated.
questions:
1.how can javascript and rails know that i am dealing with json objects?deos ROR sent it a object format or a text format?
2.how can i see what is the returned json?do i have to parse it?how?
2.how can i debug this problem?
3.how can i get it to work?
You have to parse the returned JSON in your javascript using a JSON parser.
Here is the one I use: https://github.com/douglascrockford/JSON-js/blob/master/json2.js
So upon success you'll do something like:
var stuff = json.parse(returnedJSON)