how to use ajax with json in ruby on rails - javascript

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)

Related

Pagination of search results with kaminari and ajax

I'm working on a Rails 5 application with mongoid and kaminari.
The application itself is gonna host several texts from 2 different research groups and provide different functionalities for each one. (One of them it's that 'batale' you'll see down there.)
One of the functionalities is search for a text or some parts of the text, but that's not the problem. I already can search and show the results successfully. The problem is with the pagination of these results.
My pagination works when showing all of the texts on index action, I have replicated the index.js.erb solution in kaminari's docs, but I can't figure out what js file rails will look for, since I have a different action on my controller. I make a get to a the first route (down here) and actually get the results from my controller, but as the route goes "/batale/texts/search'?params...'", I can't find out the actual js file that rails is going to look for. I've tried to create a js file on the locations that I thought it would look for (like /batale/texts/search.js or /batale/texts/search/search.js), but got no success.
Here is some of my files:
# in routes.rb
Rails.application.routes.draw do
devise_for :users, :controllers => {:registrations => "user/registrations"}
get '/batale/texts/search', to: "batale/texts#search"
# I tried this also
# get '/search_batale_texts', to: "batale/texts#search"
namespace :batale do
resources :texts
end
# and other routes...
end
---
# in batale/texts/index.html.erb
<%= render "batale/texts/search_texts" %>
<div id="pagination-itens">
<%= render partial: "batale/texts/list_texts" %>
</div>
---
# in batale/texts/_search_texts.html.erb
<%= form_tag batale_texts_search_path, remote: true, method: :get, id: 'batale_text-search-form' do %>
...all my regular labels and inputs...
<%= button_tag(type: :submit, class: "my-classes...") do %>
search
<% end %>
#nothing special
<%= end %>
---
#in /views/batale/texts/_list_texts.html.erb
<% #batale_texts.each do |batale_text| %>
actually displays the content and stuff
<% end %>
<div id="paginator" align="center"><%= paginate(#batale_texts, remote: true) %></div>
<script>
some little js just to use accordion on the texts
</script>
---
# in /views/batale/texts/index.js.erb
$("#pagination-itens").empty();
$("#pagination-itens").append("<%= escape_javascript render 'batale/texts/list_texts' %>");
$("#paginator").html("<%= escape_javascript(paginate(#batale_texts, remote: true)).to_s %>");
---
#assets/javascripts/batale/texts.js
var init_batale_text_search;
init_batale_text_search = function(){
$('#batale_text-search-form').on("ajax:before", function(event, data, status){
$('#pagination-itens').empty();
$('#batale_text-search-errors').empty();
show_spinner();
});
$('#batale_text-search-form').on("ajax:complete", function(event, data, status){
hide_spinner();
});
$('#batale_text-search-form').on('ajax:success', function(event, data, status){
$('#pagination-itens').html(data);
});
$('#batale_text-search-form').on('ajax:error', function(event, xhr, status, error){
hide_spinner();
$('#pagination-itens').html(" ");
$('#batale_text-search-errors').html("Something went wrong.");
});
}
$(document).ready(function(){
init_batale_text_search();
});
---
# in controllers/batale/texts_controller.rb
class Batale::TextsController < ApplicationController
before_action :set_batale_text, only: [:show, :edit, :update, :destroy]
def index
#batale_texts = Batale::Text.all.page(params[:page]).per(5)
end
def search
#batale_texts = Batale::Text.search(params).page(params[:page]).per(5)
if #batale_texts.count > 0
render partial: "batale/texts/list_texts"
else
render status: :not_found, nothing: true
end
end
#other regular actions...
end

How to display and link an certain attribute of an object

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>

How to send data from ruby controller to application javascript using AJAX?

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.

How do I dynamically populate a partial based on a link click?

So I have a partial called _node.html.erb. That is rendered in different views (and from different controllers) throughout my app. On one particular view, what happens is I generate a list of events that are linkable (and unique).
What I want to happen is, whenever someone clicks one of those links, it shows the partial and populates it with the correct info. The way I was initially trying to do it is via passing data-attributes from the link to some JS that does the updating of the partial. This quickly runs into a wall when we reach the node.comments, where the partial should load all of the comments associated with the node being rendered.
For example, here is some sample code of what my _node.html.erb may look like:
<!-- Card Meta -->
<div class="card__meta">
<aside>
<%= image_tag node.user.avatar.url, class: "card__author-avatar", alt: "" %>
</aside>
<section>
<h6 class="card__author-name"><%= node.user.name %></h6>
<time class="card__date"><%= node.created_at.strftime("%B %d, %Y") %></time>
<p class="card__description"><%= node.media.try(:description) %>
<textarea class="card-form__description" name="" id="" cols="30" rows="10">Aenean lacinia bibendum nulla sed consectetur.</textarea>
</section>
</div>
<!-- End Card Meta -->
<!-- Card Comments -->
<div class="card-comments-container">
<h4 class="card-comments__title"><%= pluralize(node.comments_count, "Comment") %></h4>
<a class="card-comments__button"><i class="icon-arrow-down"></i></a>
<div class="card-comments">
<%= render partial: "nodes/comment", collection: node.comments.includes(:user).order(created_at: :desc) %>
</div>
</div>
<!-- End Card Comments -->
Here is a sample of the link that a user can press, or rather the Rails code that generates the link:
<div class="item-wrapper">
<div class="item_comment">
<h5 class="item_comment-title"><%= event.action %> on <%= link_to event.eventable.node.name, "#", data: {toggle_card: "", card_id: event.eventable.node.id, card_title: event.eventable.node.media.title, card_description: event.eventable.node.media.description, card_time: event.eventable.node.media.created_at, card_comments: event.eventable.node.comments_count, card_favorites: event.eventable.node.cached_votes_total } %></h5>
<p class="item_comment-text"><%= event.eventable.message %></p>
</div>
</div>
This is an example of the type of jQuery I was using to do it originally:
$(document).ready(function() {
var overlay = $('.card-overlay'),
$card = overlay.find('.card'),
triggers = $('a[data-toggle-card]');
overlay.hide();
overlay.click(function(e) {
if( e.target != this ) {
} else {
overlay.hide();
}
});
triggers.click(function() {
var trigger = $(this);
var tr = trigger;
overlay.show();
var card = {
id: tr.data('card-id'),
title: tr.data('card-title'),
description: tr.data('card-description'),
time: tr.data('card-time'),
comments: tr.data('card-comments'),
favorites: tr.data('card-favorites'),
};
$card.attr('id', 'card-' + card.id);
$card.find('.card__content').attr('style', 'background-image: url(' + card.image + ')');
$card.find('.card__favorite-count').html('<i class="icon-heart"></i> ' + card.favorites);
$card.find('.card__comment-count').html('<i class="icon-speech-bubble-1"></i> ' + card.comments);
$card.find('.card__title').text(card.title);
$card.find('.card__date').text(card.time);
$card.find('.card__description').text(card.description);
$card.find('textarea').attr('id', 'card-input-field-' + card.id);
var player = videojs("example_video_" + card.id, {}, function() {});
player.src(
{
src: card.video.mp4, type: 'video/mp4'
},
{
src: card.video.webm, type: 'video/webm'
},
{
src: card.video.ogv, type: 'video/ogv'
}
);
player.load();
player.ready(function() {
console.log("READY")
});
return false;
});
});
How do I do this in a more "Railsy" way without relying on too much JS/jQuery?
Edit 1
After attempting Rich Peck's suggestion, I got this error:
05:46:31 web.1 | Unexpected error while processing request: JS, accepted HTTP methods are OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK, VERSION-CONTROL, REPORT, CHECKOUT, CHECKIN, UNCHECKOUT, MKWORKSPACE, UPDATE, LABEL, MERGE, BASELINE-CONTROL, MKACTIVITY, ORDERPATCH, ACL, SEARCH, and PATCH
To be clear, this is what I did:
Modified my routes to have this:
resources :events do
get :node, action: :node
end
Then created an action in my EventsController called node like this:
def node
#node = current_user.nodes.find(params: [node_id])
respond_to do |format|
format.js
end
end
Then I created a app/views/events/node.js.erb, that looks like so:
$overlay = $('.card-overlay');
$node = $('<%= j render "nodes/node", locals: {node: #node} %>');
$overlay.append($node);
Then I modified the link_to tag like so:
<%= link_to event.eventable.node.name, event_node_path(event.eventable.node), remote: true, method: :js %></h5>
Okay there are two problems here:
You're trying to load & populate a partial without any extra data (IE node.comments)
Secondly, you're heavily relying on JQuery to update various attributes on the page. Although there is nothing wrong with this in itself, it does become an issue if you're looking to change page layouts etc.
A more "railsy" way to do this would be to remotely load the node directly from the server, much as Alex Lynham recommended:
#config/routes.rb
resources :cards do
get :node_id, action: :node #-> url.com/cards/:card_id/:node_id
end
#app/controllers/cards_controller.rb
class CardsController < ApplicationController
def node
#card = Card.find params[:card_id]
#node = #card.nodes.find params[:node_id]
respond_to do |format|
format.js #-> app/views/cards/node.js.erb
end
end
end
#app/views/cards/node.js.erb
$node = $("<%=j render "cards/node", locals: {node: #node} %>")
$('body').append($node);
#app/views/cards/index.html.erb
<% #cards.each do |card| %>
<%= link_to card.name, card_node_path(#card), remote: true, format: :js %>
<% end %>
This will keep the code clear and concise.
You can put all your options directly in the _node.js.erb partial. Although it still means there are some changes you have to make, it will give you the ability to change your partial & implementation without all the JQuery specialization you have now.
You need to make an AJAX request to the server, then use a js.erb partial to return what you are looking for to the frontend, probably. That's the most 'Rails-y' way I can think of.
Personally, I'd ask for JSON and then use a bit of jQuery to wrangle the return values much as you have done in your example. By calling out to the backend is the only sane way you'll be able to get the associated records, the .comments bit, which is I believe where you're stuck, if I've understood your question correctly.
EDIT:
This is quite a good tutorial on how AJAX-y stuff can be accomplished with Rails, in case you've not had much cause to do so in the past.

How do insert a custom "not found message" on my search function using Javascript in Rails?

I've been able to implement a search function using AJAX in Rails but when a user types in a search query and doesn't find anything the result currently displays nothing. Instead of this, I want to display a message such as: 'Sorry nothing was found!', but I can't seem to get this to work.
This is my code from the index.js.erb file:
$('#products').append('<%= escape_javascript render(#products) %>');
$('.pagination').replaceWith('<%= escape_javascript paginate(#products) %>');
This is the code in my index.html.erb file:
<%= form_tag products_path, method: :get, authentication: false, id: 'search-form' do %>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search" %>
<% end %>
<div class="small-block-grid-2 medium-block-grid-3 large-block-grid-3" id="products">
<% #products.each do |product| %>
<%= render product %>
<% end %>
</div>
And finally this is the bit of relevant code in my products_controller.rb file:
respond_to :html, :js, :json
def index
#products = if params[:search].present?
Product.where("name LIKE ?", "%#{params[:search]}%")
else
Product.all
end
#products = Product.order(created_at: :desc).page(params[:page]).per(13)
respond_with #products
end
def search
#products = Product.where("name LIKE ?", "%#{params[:search]}%")
render #products
end
Sorry I should've included this in the question as well. Now I am able to get this message of "Sorry nothing was found!" message to display when a search query comes up with nothing, BUT the problem is that I have to press enter twice in order for it to display the message. What can I do to change my code here so when nothing is found the first time it displays: "Sorry nothing was found!"?
This is the code in my assets/javascripts/products.js file:
$(document).ready(function(){
$('#search-form').submit(function(event){
event.preventDefault();
var searchValue = $('#search').val();
$.get('/products/search?search='+searchValue)
.done(function(data){
console.log(data);
if ($("#products").children().length != 0){
$('#products').html(data);
}else{
$('#products').html("<h3>Sorry nothing was found!</h3>");
}
});
});
});
I believe that's all the necessary information needed to understand this question. If you need more clarification in order to understand what I'm doing, I'll be glad to provide it.
Thank you all for your input!

Categories