Add as favorite with no full page refresh - ruby-on-rails

I am working on a Hiragana flashcards app.
I spend nights and days to understand how don't refresh full page when I add a flashcard (hiragana) as a favorite.
Here is the favorite controller
class FavsController < ApplicationController
def index
#favs = Fav.where(user: current_user)
end
def create
#hiragana = Hiragana.find(params[:hiragana_id])
#fav = current_user.favs.new(hiragana: #hiragana)
if not #hiragana.favs.where(user: current_user).take
#fav.save
end
render json: #fav
end
def destroy
#fav = Fav.find(params[:id])
#fav.destroy
redirect_to :back
end
end
I render json in the create method and when I click on view I add only an hash
render view favorite
<% if current_user %>
<div class="hiragana-fav">
<% if hiragana.is_faved_by(current_user) %>
<%= link_to fav_path(hiragana.is_faved_by(current_user)), method: :delete do %>
<i class="fa fa-heart faved faved-on"></i>
<% end %>
<% else %>
<%= link_to hiragana_favs_path(hiragana), method: :post do %>
<i class="fa fa-heart faved faved-off"></i>
<% end %>
<% end %>
</div>
<% end %>
and it is located in hiragana render
<div class="row">
<ul class="list-inline text-center card-frame">
<li>
<div class="card">
<div class="front">
<% if current_user.try(:admin?) %>
<%= link_to hiragana_path(hiragana), class:'trash-hiragana', data: { confirm: 'Are you sure?' }, method: :delete do %>
<%= image_tag("delete-btn.png") %>
<% end %>
<% end %>
<span class="card-question img-popover" data-content="<h4 class='text-center letter-uppercase'><%= hiragana.bigletter.upcase %></h4><p class='text-center'><b><%= hiragana.midletter %></b> comme dans <b><%= hiragana.transcription %></b></p>">
<i class="fa fa-eye fa-lg"></i>
</span>
<div class="card-hiragana hiragana-<%=hiragana.bigletter.downcase.last%>">
<h1><b><%= hiragana.ideo1 %></b></h1>
</div>
<div class="card-katakana">
<p><%= hiragana.ideo2 %></p>
</div>
<%= render 'favs/favorites', hiragana: hiragana %>
</div>
<div class="back">
<div class="col-sm-3 col-xs-4 col-md-3 containerbackcards-<%=hiragana.bigletter.downcase.last%>">
<div class="backcard-hiragana">
<h1><b><%= hiragana.ideo1 %></b></h1>
</div>
<div class="card-bigletter">
<h4><%= hiragana.bigletter.upcase %></h4>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
When I add a card as favorite it gives me a hash like this :
{
id: 64,
user_id: 1,
hiragana_id: 4,
created_at: "2016-02-10T16:37:26.270Z",
updated_at: "2016-02-10T16:37:26.270Z"
}
I just want to have the heart grey to red as favorite, saved and not refresh the entire page. Your explainations are appreciated thank you.

In order to send the request to the controller without the page refreshing you need to use a combination of Ajax and JavaScript.
You use JavaScript to add a click listener to the .faved-on button and to trigger the Ajax request. You will also use JavaScript to prevent the default action occurring when you click the link ie. The Page Refresh
You then use Ajax to send the request to the controller and handle the response.
Your initial JavaScript code looks pretty correct, except you are missing the bit to stop the page from reloading.
See the e.preventDefault(); line below
$(document).ready(function() {
$('.faved-on').click(function(e) { //make sure to pass in the e (the event paramter)
e.preventDefault(); //this is the line you are missing
var fav = $('.faved-off')
//let your ajax handle the rest then
$.ajax({
type: "POST", url: "/hiraganas", dataType: "json",
success: function(data) {
console.log(data);
//change the color of your heart to red here
},
error: function(jqXHR) {
console.error(jqXHR.responseText);
}
});
})
})
I haven't tested your JavaScript but it looks pretty close to correct, I believe its the e.preventDefault(); bit you were missing

Related

How to perform POST operation in Rails using AJAX

Scenario
I have created a simple application for learning Rails and Bootstrap. What this application does is it allows users to give nicknames to celebrities and like the nicknames as well. You can check it at http://celebnicknames.herokuapp.com
I have got a link_to button for posting likes for a celebrity in my home/index.html.erb as:
<%= link_to home_updatelike_path(id: nickname.id, search: #search ), method: :post, remote: true, class: "update_like btn btn-xs btn3d pull-right" do %>
<i class="glyphicon glyphicon-thumbs-up"></i>
<% end %>
The corresponding code in my home_controller.rb for the post method is:
def updatelike
#like = Like.new(:nickname_id => params[:id], :ip_address => request.remote_ip)
#like.save
respond_to do |format|
format.html { redirect_to root_path("utf8"=>"✓", "search"=>params[:search], "commit"=>"Search") }
format.json { head :no_content }
format.js { render :layout => false }
end
end
The code in my updatelike.js.erb is as:
$('.update_like').bind('ajax:success', function() {
});
Now, what I want to achieve is to submit a like without performing a page reload.
Problem:
Whenever I press the button, the database operation is done, but the change is not reflected in the page unless I reload it. Please guide me through this noob question and also provide links to resources which explain using AJAX with Rails.
Edit
Here is the entire home/index.html.erb
<div class="jumbotron col-md-6 col-md-offset-3">
<h1 class='text-center'>Nicknamer</h1>
</div>
<div class="container">
<div class="row">
<div class="col-sm-8 col-sm-offset-2 bottom50">
<%= form_tag(root_path, :method => "get", id: "search-form", type: "search") do %>
<div class = "form-group" >
<div class = "col-sm-9 " >
<%= text_field_tag :search, params[:search], placeholder: "Name", class: "form-control" %>
</div>
<div class = "col-sm-3" >
<%= submit_tag "Search", class: "btn btn-default btn-block btn-success"%>
</div>
</div>
<% end %>
</div>
</div>
<% #names.each do |name| %>
<div class="col-md-6 col-xs-8 col-md-offset-3 col-xs-offset-2 bottom50">
<!-- ===== vCard Navigation ===== -->
<div class="row w">
<div class="col-md-4">
<% if name.sex == true %>
<%= image_tag("mal.jpg", :alt => "", class: "img-responsive") %>
<% else %>
<%= image_tag("fem.jpg", :alt => "", class: "img-responsive") %>
<% end %>
</div><!-- col-md-4 -->
<!-- ===== vCard Content ===== -->
<div class="col-md-8">
<div class="row w1">
<div class="col-xs-10 col-xs-offset-1">
<h3><%= "#{name.name}" %></h3>
<hr>
<h5>Also known as</h5>
<% name.nicknames.each do |nickname| %>
<div class = "row w2 bottom10">
<% if nickname.name_id == name.id %>
<div class="col-xs-7">
<%= nickname.nickname %>
</div>
<div class="col-xs-3 text-right">
<%= pluralize(nickname.likes.count, 'like') %>
</div>
<div class="col-xs-1">
<%= link_to home_updatelike_path(id: nickname.id, search: #search ), method: :post, remote: true, class: "update_like btn btn-xs btn3d pull-right" do %>
<i class="glyphicon glyphicon-thumbs-up"></i>
<% end %>
</div>
<% end %><!-- if -->
</div><!-- row w2 -->
<% end %><!-- do -->
<div class = "row w3 bottom30">
<%= form_for #addnickname, as: :addnickname, url: {action: "addnickname"} do |f| %>
<div class = "form-group" >
<%= f.hidden_field :name_id, :value => name.id %>
<%= f.hidden_field :search, :value => #search %>
<div class = "col-xs-9">
<%= f.text_field :nickname , :required => true, class: "form-control" %>
</div>
<div class = "col-xs-3">
<%= f.submit "Add", class: "btn btn-default btn-info"%>
</div>
</div>
<% end %>
</div>
</div><!-- col-xs-10 -->
</div><!-- row w1 -->
</div><!-- col-md-8 -->
</div><!-- row w -->
</div><!-- col-lg-6 -->
<% end %>
</div><!-- /.container -->
and this is the entire home_controller.rb
class HomeController < ApplicationController
def index
#addnickname = Nickname.new
if params[:search]
#search = params[:search]
#names = Name.search(params[:search])
else
#names = Name.all
end
end
def addnickname
#addnickname = Nickname.new(:nickname => params[:addnickname][:nickname], :name_id => params[:addnickname][:name_id])
if #addnickname.save
redirect_to root_path("utf8"=>"✓", "search"=>params[:addnickname][:search], "commit"=>"Search")
else
render :new
end
end
def updatelike
#like = Like.new(:nickname_id => params[:id], :ip_address => request.remote_ip)
#like.save
respond_to do |format|
format.html { redirect_to root_path("utf8"=>"✓", "search"=>params[:search], "commit"=>"Search") }
format.json { head :no_content }
format.js { render :layout => false }
end
end
private
def addnickname_params
params.require(:addnickname).permit(:name_id, :nickname, :search)
end
end
Ok, so you need a way to identify the div with the number that needs to change:
<div class="col-xs-3 text-right">
<%= pluralize(nickname.likes.count, 'like') %>
</div>
The easiest way to do this would be to have an id attribute on the div that corresponds to the (unique) database id of the nickname. Rails has a helper method for this called div_for. So you could change the above code to this:
<%= div_for(nickname, class: "col-xs-3 text-right") do %>
<%= pluralize(nickname.likes.count, 'like') %>
<% end %>
That will generate a div with an id like nickname_1 (if this id of the nickname is 1).
Now in your javascript template you can easily target the div that needs to be updated:
$("nickname_#{#like.nickname.id}").html(#like.nickname.likes.count);
The ajax:success binding is unnecessary here since you are rendering a javascript template. In the template you can access instance variables set in the controller - just like you can from an HTML template. The above code is meant to replace the content of the div that contains the likes count with the new count.

Render a Rails view in a Bootstrap modal

I have hit a brick wall. I've been hacking at this problem for so long now, I'm not even sure how I got to where I am. All I can say is, I've tried all of the below without success:
How to add bootstrap modal with link_to so the link content open in modal ?
How to show twitter bootstrap modal via JS request in rails?
rails link_to :remote
http://apidock.com/rails/ActionView/Helpers/PrototypeHelper/link_to_remote
How do I render "new", "edit" and "delete" views within Bootstrap modals on the "index" view rather than linking to separate pages for each?
Here is my code as it stands now. For now, lets ignore "edit" and "delete" and just focus on "new". When I click the "New" button, a modal with the string "<%= j render "items/new" %>" appears (instead of the form that that ruby statement should render). What am I doing wrong?:
items_controller.rb:
class ItemsController < ApplicationController
def index
#items = Item.all
end
def new
respond_to do |format|
format.js {}
end
end
def create
#item = Item.new(item_params)
if #item.save
flash[:notice] = "'#{#item.name}' saved!"
redirect_to items_path
else
flash[:notice] = "Something went wrong :("
render "index"
end
end
def edit
#item = Item.find(params[:id])
respond_to do |format|
format.js {}
end
end
def update
#item = Item.find(item_params[:id])
if #item.update_attributes(item_params)
flash[:notice] = "Successfully updated #{#item.name}."
redirect_to items_path
else
flash[:notice] = "Oops"
# render "edit"
end
end
private
def item_params
params.require(:item).permit(:name, :bid, :uuid)
end
end
items/index.html.erb
<div class="row">
<div class="col-xs-12">
<%= link_to "New", new_item_path, remote: true, class: "btn btn-success pull-right new", data: { toggle: "modal", target: "#newModal" } %>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<table class="table table-hover items">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>UUID</th>
<th colspan="2">Links</th>
</tr>
</thead>
<tbody>
<% #items.each do |item| %>
<tr>
<td><%= item.id %></td>
<td><%= item.name %>
<!-- edit/remove icons -->
<span class="edit-remove">
<%= link_to edit_item_path(item.id), remote: true, data: { toggle: "modal", target: "#editModal" } do %>
<span class="glyphicon glyphicon-pencil text-muted"></span>
<% end %>
<a href="#">
<span class="glyphicon glyphicon-remove text-muted"></span>
</a>
</span>
</td>
<td><%= item.uuid %></td>
<td><%= link_to "XXX", "http://xxx" %></td>
<td><%= link_to "XXXX", "http://xxx", target: "_blank" %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
<!-- newModal skeleton -->
<div class="modal fade" id="newModal">
<div class="modal-dialog">
<div class="modal-content">
</div>
</div>
</div>
<!-- editModal skeleton -->
<div class="modal fade" id="editModal">
<div class="modal-dialog">
<div class="modal-content">
</div>
</div>
</div>
<!-- deleteModal skeleton -->
<div class="modal fade" id="deleteModal">
<div class="modal-dialog">
<div class="modal-content">
</div>
</div>
</div>
items/new.html.erb
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
<h4 class="modal-title">New Item</h4>
</div>
<div class="modal-body">
<%= form_for :item, url: { action: "create" } do |f| %>
<div class="form-group">
<%= f.label :name, "Name" %>
<%= f.text_field :name, { class: "form-control" } %>
</div>
<div class="form-group">
<%= f.label :bid, "BID" %>
<%= f.text_field :bid, { class: "form-control" } %>
</div>
<div class="form-group">
<%= f.label :uuid, "UUID" %>
<%= f.text_field :uuid, { class: "form-control" } %>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<%= submit_tag "Save", class: "btn btn-primary" %>
<% end %>
</div>
javascripts/items.js
$(document).on("page:change", function() {
$("#newModal .modal-content").html('<%= j render "items/new" %>');
});
In the case of new for instance, you want to render a javascript file. For this, you'll need to create items/new.js.erb.
Also, remove ", data: { toggle: "modal", target: "#newModal" }" from your link, we will do that in the javascript.
# new.js.erb
$("#newModal .modal-content").html('<%= j render "items/form" %>');
$("#newModal").modal(); // Or whatever the Bootstrap function is to render the modal
# items/_form.html.slim
# Here you'll put your form
You cannot use "render" on views directly, you should render partials and not views (this is why I asked you to put your form into a partial).
I threw this together and it puts a big '3' in my document 3 seconds after loading it:
<script>
setTimeout(function() {
$("#holder").html("<%= j render(:file => 'things/test.html.erb') %>");
}, 3000);
</script>
<div id="holder></div>
app/views/things/test.html.erb:
<h1><%= 1 + 2 %></h1>
That should get you going.

Rails will_paginate and tabs

I have a problem with working my tab along with will_pagination. When i press Tab 2, it will show the correct content, but when i press on the pagination link, example page 2. It will bring me back to my Tab 1 view. After this, when i press back Tab 2, it will appear page 2 content of Tab 2. The problem here is why does it bring my back to my Tab 1 view when i press on the pagination link in Tab 2.
My View Code
<div>
<ul class="nav nav-tabs" id="TabLength">
<% #biscuit.each_index do |i| %>
<% if i == 0 %>
<li class="active"><a data-toggle="tab" href="#<%= #biscuit[i] %>"><%= #biscuit[i] %></a></li>
<% else %>
<li><a data-toggle="tab" href="#<%= #biscuit[i] %>"><%= #biscuit[i] %></a></li>
<% end %>
<% end %>
</ul>
<div class="tab-content">
<% #biscuit.each_index do |i| %>
<% if i == 0 %>
<div class="tab-pane fade in active" id="<%= #biscuit[i] %>">
<div class="row" id="PictureSetting">
<% #testpage.each do |i| %>
<div class="col-md-4" id="ProductPic">
<%= image_tag(i.name , class:"img-thumbnail", size:"180x180") %>
</div>
<% end %>
</div>
<%= will_paginate #testpage, :param_name => 'user_page' %>
</div>
<% elsif i == 1 %>
<div class="tab-pane fade" id="<%= #biscuit[i] %>">
<div class="row" id="PictureSetting">
<% #memberpage.each do |i| %>
<div class="col-md-4" id="ProductPic">
<%= image_tag(i.name , class:"img-thumbnail", size:"180x180") %>
</div>
<% end %>
</div>
<%= will_paginate #memberpage, :param_name => 'choco_page' %>
</div>
<% else %>
<div class="tab-pane fade" id="<%= #biscuit[i] %>">
<div class="row" id="PictureSetting">
<h1>hello</h1>
</div>
</div>
<% end %>
<% end %>
</div>
</div>
Thanks
Based on answer from here, you can design the following code for the problem:
<script>
// open tab on click
$('#TabLength a').click(function (e) {
e.preventDefault();
$(this).tab('show');
// getting tab id
var id = $(e.target).attr("href").substr(1);
// change hash value for all pages
$('ul.pagination > li > a').each(function(i, pageAnchor) {
pageAnchor.hash = id;
});
});
// assign tab id to location hash
$("ul.nav-tabs > li > a").on("shown.bs.tab", function (e) {
var id = $(e.target).attr("href").substr(1);
window.location.hash = id;
});
// open initial hash
var hash = window.location.hash;
$('#TabLength a[href="' + hash + '"]').tab('show');
// UPDATE
$('ul.pagination > li > a').each(function(i, pageAnchor) {
pageAnchor.hash = hash;
});
</script>
It saves currently selected tab into location.hash. and selects it when you navigate to a new page.

Alternative to "link_to... do ... end" aproach

I have this piece of code.
<div class="tweet" id="tweet-<%= tweet.id %>">
<%= image_tag #user.avatar.url(:thumb) %>
<div class="tweet-content">
<strong><%= #user.first_name + " " + #user.last_name %></strong>
<span class="date">- <%= tweet.created_at.strftime("%e %B %Y") %></span>
<p><%= tweet.content %></p>
<div class="tweet-action">
<% #replies_count = tweet.replies_count %>
<% #like_class = "glyphicon glyphicon-star like-tweet"+tweet.liked(current_user.id) %>
<% #like_id = "tweet-like-"+tweet.id.to_s %>
<%= link_to "", liked_tweets_path(tweet_id: tweet.id), method: :post, remote: true, class: #like_class, id: #like_id %>
<% if tweet.liked_tweets.count > 1 %>
<span><%= tweet.liked_tweets.count %></span>
<% end %>
<!-- -->
Retweet
</div>
<%= render 'tweets/form', parent: tweet %>
</div>
<div class="replies">
<!-- Here goes ajax resposnse tweets#show_resplies -->
</div>
</div>
I want to achieve that when I click on ".tweet" I want to do this action tweets_show_replies_path(parent: tweet, active: false ). When I tried to wrap the whole thing in link_to .... do ... end it failed and the link didn't wrap the ".tweet" element.
Could you please tell me how to achieve the effect that I described above?
Use some javascript and an ajax request!
$(document).ready(function() {
$( ".tweet" ).click(function() {
$.ajax({
type: "POST",
format: "js",
url: <url for tweets_show_replies_path>,
data: {
parent: <%= tweet %>,
active: false
},
dataType: "script"
});
});
});
Then in your controller, do whatever you need to and render the partial.

passing dynamic variable to partial for rendering in a modal

I have the following Code on a partial that dynamically generates a list of associated steps to a pin. Both pins and steps have image(s). Users are able to add multiple steps to a pin and then this view dynamically generates a view of these steps. For each step there are associated image(s) which I'd like to pop-up as a modal. The challenge is I keep getting the following error:
"undefined local variable or method `step' for #<#:0x00000102f498d8>"
When I stub out rendering the partial, the modal appears basically blank but works. Any ideas How do I do this?
<div class="col-md-6">
<% (0..(Step.where("pin_id = ?", params[:id]).count)-1).each do |step| %>
<div class="col-md-12">
<div class="well">
<ul class="nav pull-right">
<% if current_user == #pin.user %>
<%= link_to edit_step_path((Step.where("pin_id = ?", params[:id]).fetch(step)), :pin_id => #pin.id) do %>
<span class="glyphicon glyphicon-edit"></span>
Edit
<% end %> |
<%= link_to Step.where("pin_id = ?", params[:id]).fetch(step), method: :delete, data: { confirm: 'Are you sure?' } do %>
<span class="glyphicon glyphicon-trash"></span>
Delete
<% end %> |
<%= link_to new_step_image_path(:step_id => Step.where("pin_id = ?", params[:id]).fetch(step), :pin_id => #pin.id) do %>
Add
<span class="glyphicon glyphicon-picture"></span>
<% end %>
<% end %>
<% if StepImage.where("step_id = ?", Step.where("pin_id = ?", params[:id]).pluck(:id).fetch(step)).count == 0 %>
<% else %>
| <a href="#StepImageModal" data-toggle="modal">
<span class="glyphicon glyphicon-picture"></span> (<%= StepImage.where("step_id = ?", Step.where("pin_id = ?", params[:id]).pluck(:id).fetch(step)).count %> ) </strong>
</a>
<% end %>
</ul>
<strong> Step <%= step+1 %>
<p>
<%= Step.where("pin_id = ?", params[:id]).pluck(:description).fetch(step) %>
</p>
</div>
</div>
<%end %>
</div>
<div class="modal fade" id="StepImageModal">
<div class="modal-header">
<a class="close" data-dismiss="modal">×</a>
<h3>Modal Header</h3>
</div>
<div class="modal-body">
<%= render :partial => 'pins/step_images',
:locals => { :step => step } %>
</div>
<div class="modal-footer">
Close
</div>
</div>
I don't get what you're trying to do here
Loop
As mentioned in the comments, you've got a loop which goes through your steps. However, outside the loop, you want to display the partial
The solution to your woes will either be to set an instance variable (not desired), or use another persistent data type. I'd do this:
#app/controllers/steps_controller.rb
def action
#step = {}
end
#app/views/steps/action.html.erb
<% (0..(Step.where("pin_id = ?", params[:id]).count)-1).each do |step| %>
<% #step[step.id.to_sym] = step.details %>
<% end %>
<%= render :partial => 'pins/step_images', :locals => { :step => #step } %>
unless
Some refactoring for you:
<% unless StepImage.where("step_id = ?", Step.where("pin_id = ?", params[:id]).pluck(:id).fetch(step)).count == 0 %>
| <%= link_to "#StepImageModal", data: { toggle: "modal"} do %>
<span class="glyphicon glyphicon-picture"></span>(<%= StepImage.where("step_id = ?", Step.where("pin_id = ?", params[:id]).pluck(:id).fetch(step)).count %> ) </strong>
<% end %>
<% end %>

Resources