Within a rails 4 app, I am using a link_to to send an upvote on posts via json.
Here is what I have in my posts controller:
def upvote
#post = Post.find(params[:id])
#post.liked_by current_user
respond_to do |format|
format.html {redirect_to :back }
format.json { render json: { count: #post.get_upvotes.size } }
end
end
Here is what I have in my view
<%= link_to like_post_path(post), method: :put, class: 'vote', remote: true, data: { type: :json } do %>
<%= image_tag('vote.png') %>
<%= content_tag :span, post.get_upvotes.size %>
<% end %>
<script>
$(document)
.on('ajax:send', '.vote', function () { $(this).addClass('loading'); })
.on('ajax:complete', '.vote', function () { $(this).removeClass('loading'); })
.on('ajax:error', '.vote', function(e, xhr, status, error) { console.log(status); console.log(error); })
.on('ajax:success', '.vote', function (e, data, status, xhr) {
$(this).find("span").html(data.count);
$(this).find("img").attr("src", '<%= asset_path 'voted.png' %>');
});
</script>
When I click on the link, the vote goes through as a JSON request, I see this in my log:
Processing by PostsController#upvote as JSON
But for some reason, my snipped of javascript is not working. Neither the counter or the icon update. How can I fix this? Does this have to do with turbolinks, does it have to do with where I am placing the javascript?
In Rails you can perform a similar task by having a JavaScript response. Add in your respond_to a format.js similar to format.html then have a view upvote.js.erb that looks like:
(function() {
var postId = "#post-<%= #post.id %>";
$(postId).find(".vote").find("span").text("<%= #post.get_upvotes.size %>");
$(postId).find(".vote").find("img").attr("src", "<%= asset_path "voted.png" %>");
})();
I changed your call to .html to .text since you're not actually setting any HTML inside the element, there is no reason to call .html.
This post also assumes there is some mechanism to identify the post the vote link belongs to (in the example the parent post element has an ID of "post-#" where # is the ID of the post object).
EDIT
Two changes I'd make if I were working on this project. First I would attach the voted.png path to the .vote element as a data attribute. data-voted-image-src="<%= asset_path "voted.png" %>". Next, I would never pass a number in the return as there is no reason to do so. When the vote is clicked you can handle everything on the front end by assuming the request is successful. Which saves all this potential nastiness. While I realize that changing from what you current have to adding the data attribute isn't a huge leap I just find it more semantic than having it in the JavaScript.
The click action on the vote link then becomes:
// Assume all posts have a class 'post'
// I'm also using 'one' because once they vote they don't need to anymore
$(".post").find(".vote").one("click", function(e) {
var count = parseInt($(this).find("span").text()),
votedSrc = $(this).data("voted-image-src");
$(this).find("img").attr("src", votedSrc);
$(this).find("span").text(count + 1);
});
Now no response from the server is necessary, and you can change your JSON response to {success: true} or something simple.
jQuery is the default rails javascript library. The default rails javascript library used to be prototype, so old tutorials/docs use it. This is what the ajax looks like with jQuery:
app/controllers/static_pages_controller.rb:
class StaticPagesController < ApplicationController
def show_link
end
def upvote
respond_to do |format|
format.json {render json: {"count" => "10"} }
end
end
end
app/views/static_pages/show_link.html:
<div>Here is an ajax link:</div>
<%= link_to(
"Click me",
'/static_pages/upvote',
'remote' => true, #Submit request with ajax, and put text/javascript on front of Accept header
data: { type: :json }) #Put application/json on front of Accept header
%>
<div>Upvotes:</div>
<div id="upvotes">3</div>
<script>
$(document).ready( function() {
$(this).ajaxSuccess( function(event, jqXHR, ajaxInfo, data) {
//var js_obj = JSON.parse(jqXHR.responseText);
//$('#upvotes').html(js_obj["count"]);
//Apparently, the fourth argument to the handler, data,
//already contains the js_obj created from parsing the
//json string contained in the response.
$('#upvotes').html(data["count"]);
});
});
</script>
config/routes.rb:
Test1::Application.routes.draw do
get 'static_pages/show_link'
get 'static_pages/upvote'
...
end
url to enter in browser:
http://localhost:3000/static_pages/show_link
See jquery docs here:
http://api.jquery.com/ajaxSuccess/
Response to comment:
You could also do the following in your controller:
def upvote
#upvotes = 2 #Set an #variable to the number of upvotes
respond_to do |format|
format.js {} #By default renders app/views/static_pages/upvote.js.erb
end
end
Then:
app/views/static_pages/upvote.js.erb:
$('#upvotes').html(<%= #upvotes %>)
Related
From file soul.html.erb , partial view called at the time of page load
<%= render 'contact_options_soul_d' %>
In file contact_options_soul_d.html.erb partial view looks like this -
<div id="contact-option">
<div id= 'un-authenticated' data-access-api-result = <%= #valid_ticket %> %></div>
</div>
On click of a button a Rest call happens in controller and partial view is called from partial_create.js.erb
def partial_create
respond_to do |format|
format.js
end
return
end
File partial_create.js.erb
$('#contact-option').html('<%= escape_javascript(render :partial => 'rhythm/contact_options_soul_d') %>')
My Question is - partial view is called , breakpoint hits and new value of #valid_ticket also gets updated, but view is not getting updated as webpage still looks same even after updation.(View remains same when checked in browser inspect)
Please help!!!
This is not an answer to exactly the way you're doing it, but you could always render it in the controller and return that on "respond_to", then on Ajax success use the returned value to substitute the html:
def partial_create
html = (render_to_string partial: 'rhythm/contact_options_soul_d', locals: { if_you_need: variables_in_partial })
respond_to do |format|
format.json { render json: { new_html: html, success: true } }
end
return
end
then on your JS code (this with jQuery):
function soul_searching(parameters) {
$.ajax({
url: "YOUR/POST/ROUTE",
type: "POST",
dataType: "json",
data: {
any_parameters: 'you_need_to_pass_to_controller'
},
success: function(data) {
$('#contact-option').html(data['new_html']);
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
//DO SOMETHING
}
});
}
You need to prepend partials with an underscore. For example:
_contact_options_soul_d.html.erb
Note: You only need to do this to the file name. You can still call the partial with
<%= render partial: 'contact_options_soul_d.html.erb' %>
Also make sure you're referencing the path if the partial is in a separate folder.
I'm using the react-rails gem and have two models: Message and User. User has_many :messages.
In my message.js.jsx, I'd like to show the User of that message. In regular erb, it'd just be <%= message.user.name %>. How would I do this in the message.js.jsx component?
You could rename your component to message.js.jsx.erb and use ERB in it, but it will only be compiled once when Rails starts up.
A more React-ish way to handle is to AJAX load the user data in componentDidMount (or a Store, if using Flux).
message.js.jsx
getInitialState: function() {
return { user: { name: '' } };
},
componentDidMount: function() {
$.getJSON('/users/'+ this.props.id +'.json', function(userData) {
if (this.isMounted()) {
this.setState({ user: userData })
}
});
},
You can create a Rails endpoint to return userData as JSON something like this:
users_controller.rb
def show
#user = User.find(params[:id])
respond_to do |format|
format.html # default html response
format.json { render json: #user.to_json(only: [:id, :name]) }
end
end
See Facebook's page on this for more details
I agree with Unixmonkey that is the react way. You can also do it a few more ways.
#user = JSON.parse user.to_json(include: [:messages], only: [:id, :name])
As well as using componentDidMount to hit a JSON endpoint using jbuilder which you can put a timeout on if you want to update dynamically.
componentDidMount: function() {
$.getJSON('/users/'+ this.props.id +'.json', function(user) {
if (this.isMounted()) {
this.setState({ user: user })
}
});
},
Your show.json.jbuilder under user views would look something like this:
json.id #user.id
json.name #user.name
json.messages #user.messages do |message|
json.id message.id
json.content message.content
json.created_at message.created_at
end
My class Contribution belongs_to User. I have a form for a new contribution which includes a search field for for the user to whom the contribution will belong -
in /contributions/new.html.erb -
<%= text_field_tag :search, params[:search], id: "search" %>
<%= link_to "Search", search_contributions_path, id: "search_submit" %>
in application.js.erb
$(document).on("click", "a#search_submit", function(){
$.ajax({
url: $(this).attr("href"),
data: {query: $("#search").val() },
success: function(data) {
var user = $.parseJSON(json);
}
});
});
and in contribution_controller.rb -
def search
#users = User.search(params[:search])
render :users => #users.to_json
end
My controller is trying to render a page - how do I make it return the result I want to the view?
you could write:
render json: { users: #users.to_json }
Wouldn't a respond_to fix that? Something like
respond_to :html, :xml, :json
depending on the formats you want to "respond to", obviously.
Here's the source http://apidock.com/rails/ActionController/MimeResponds/ClassMethods/respond_to
You must use the following in your controller:
respond_to :html, :xml, :json
And You can return json as the following:
respond_to do |format|
format.json { render json: #users.result() }
end
Or as the following:
render json: { :users => #users.result().to_json }
I think you used ransak for searching, so you need use #users.result().to_json instead of #users.to_json
The issue is not related to json, your issue is related to javascript change your ajax call to be:
$(document).on("click", "a#search_submit", function(e){
e.preventDefault();
$.ajax({
url: $(this).attr("href"),
data: {query: $("#search").val() },
success: function(data) {
var user = $.parseJSON(json);
}
});
});
I would like to change a Workorder.wostatus_id based on data in html.
In my index html, I have the wostatus.id stored like this:
<span id="woid">4</span>
I would like to update the workorder.wostatus_id = 4
This is in the workorders.js.coffee - but, it's not working:
$.ajax
type: 'POST'
url: 'http://localhost:5000/workorders'
data:
workorder:
wostatus_id: $("#woid").val()
Maybe I'm not getting to the right workorder record?
Even doing this didn't update the workorder.wostatus_id
$.ajax
type: 'POST'
url: "http://localhost:5000/workorders"
data:
workorder:
wostatus_id: '3'
This didn't work either:
$.ajax
type: 'POST'
url: "http://localhost:5000/workorder/17"
data:
wostatus_id: '7'
I'm missing something big time.
Does the ajax POST execute this code in the workorder controller????
# PUT /workorders/1
# PUT /workorders/1.json
def update
#workorder = Workorder.find(params[:id])
respond_to do |format|
if #workorder.update_attributes(params[:workorder])
format.html { redirect_to #workorder, notice: 'Workorder was successfully updated.' }
format.json { head :ok }
else
format.html { render action: "edit" }
format.json { render json: #workorder.errors, status: :unprocessable_entity }
end
end
UPDATE:
I added this to the workorder controller:
def changestatus
#workorder = Workorder.find(params[:id])
#workorder.update_attribute :wostatus_id, '4'
render nothing: true
end
I added this to the routes:
resources :workorders do
member { put :changestatus }
end
This is currently in the js.coffee:
$.ajax
type: 'PUT'
url: "http://localhost:5000/workorders/11/changestatus"
data:
wostatus_id: 4
(I'm hard coding things until I get the next step working.)
SO - this works, workorder 11 gets wostatus_id changed to 4.
But, now I'm having trouble getting the right information from the html.
The html contains 2 data fields I need - one for which workorder and the other is what the wostatus_id is.
Here is the html for the update url:
<div class="false" data-change-url="http://localhost:5000/workorders/16/changestatus">
I thought this would get that url - but, it doesn't work:
$(this).data('change-url')
If I understand correctly, then I think your sending a single value while your controller expects an array, and you're using different param names (wostatus_id on client, workorder on server).
Perhaps what you want is this:
$.ajax
type: 'POST'
url: $('#sort2').data('update-url')
data:
workorder: $('#sort2 span').map((i, el) ->
el.text()
) // Change the selector to the elements that holds the ID
Found out I didn't need any new controller code - I could just use update.
This is for jquery-ui sortable.
receive: (event, ui) ->
str_id = $(ui.item).attr('id')
woid = str_id.split('_')[1]
$.update "/workorders/" + woid,
workorder:
wostatus_id: $(this).data('wostatus-id')
Thanks for the help - you got me going in the right direction.
I'm trying to ajaxify my will_pagniate pagination in rails. I want to have the old page fade out and the new one fade in.
Here's the relevant part of my controller:
respond_to do |format|
format.html # new.html.erb
format.js {
render :update do |page|
page.replace 'page', :partial => 'cur_page'
end
}
format.xml { render :xml => #branch }
end
The aforementioned partial:
<div id="page">
<%= will_paginate %>
<div id="posts">
<%= render #posts %>
</div>
<%= will_paginate %>
</div>
And the relevant part of application.js:
document.observe("dom:loaded", function() {
// the element in which we will observe all clicks and capture
// ones originating from pagination links
var container = $(document.body)
if (container) {
var img = new Image
img.src = '/images/spinner.gif'
function createSpinner() {
return new Element('img', { src: img.src, 'class': 'spinner' })
}
container.observe('click', function(e) {
var el = e.element()
if (el.match('.pagination a')) {
el.up('.pagination').insert(createSpinner())
target = $('posts')
new Effect.fade(target, { duration: 0.3, afterFinish: function()
{
new Ajax.Request(el.href,
{
method: 'get',
onSuccess: function(){ new Effect.Appear(target, {duration:0.3})}
})
}})
e.stop()
}
})
}
})
The script seems to get killed on this line,
new Effect.fade(target, { duration: 0.3, afterFinish: function()
because I see the spinner.gif start, then no fading and the page is refreshed normally. I have got the ajax working before I tried to add Effect.Fade and Effect.Appear.
Is this the right way to go about this? Should I put the effects in the controller instead?
Here is what I did using jQuery and working well too :)
Put your will_paginate helper view call in a div
#tickets_pagination
= will_paginate #tickets
In application.js
$("#tickets_pagination .pagination a").live("click", function() {
$.get("/users/?"+this.href.split("?")[1], null, null, "script");
return false
});
The javascript above will convert the pagination links in #tickets_pagination to ajax links
In your controller as usual
def index
#tickets = Ticket.all.paginate({:page => params[:page], :per_page => 10 })
respond_to do |format|
format.js
format.html
end
end
Now finally in index.js.erb
$("#tickets_list_table").fadeOut('slow');
$("#tickets_list_table").html("<%= escape_javascript(render :partial =>'tickets/tickets_table', :locals => {:tickets => #tickets}) %>");
$("#tickets_list_table").fadeIn('slow');
Here tickets/ticket_table has a table that lists all tickets. The partial is rendered in a div #ticket_list_table
Hope this will work for you as well.
I tried putting more of the work into the javascript helpers:
respond_to do |format|
format.html # new.html.erb
format.js {
render :update do |page|
page.visual_effect :fade, 'posts', :afterFinsh => "function(){" +
page.replace 'page', :partial => 'cur_page' +
page.visual_effect(:appear, 'branches') + "}"
end
}
format.xml { render :xml => #branch }
end
Then removed this part of the javascript:
new Effect.fade(target, { duration: 0.3, afterFinish: function()
I get the effect I want, but all out of order. The request completes and the html is replaced, then the div fades out and then reappears!
Not very familiar with RoR, does it generate its own client-side JS that may possibly be battling your code?
If not, I would say the problem is somewhere in your own client-side code. For testing, get rid of the HREF attribute from the anchor tag and place the URL as a string literal in the Ajax request. If nothing happens, there is a problem with the Ajax request itself. If the page loads as expected, then the event in the original scenario is not being completely stopped.
Also, clean up your JS a bit just to be sure, line-ending semi-colons where needed.
You seem to mix up things a bit.
Either you write $('posts').fade or new Effect.fade('posts').
Secondly, i can't seem to find the afterFinish option in the documentation.
So i would suggest something along the following lines:
container.observe('click', function(e) {
var el = e.element()
if (el.match('.pagination a')) {
el.up('.pagination').insert(createSpinner())
target = $('posts')
new Effect.fade('posts', { duration: 0.3});
setTimeout("new Ajax.Request(el.href, { method: 'get',
onSuccess: function(){
new Effect.Appear('posts', {duration:0.3})
} })", 1000);
e.stop();
}
})
Hope this helps.