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
Related
I have 2 models: Team and Quest. When creating a new team, I have a drop-down of all the quests. When a quest is selected, I want to display information about the quest.
My understanding is that everything in the form is on the client side and AJAX is required to pass the selected quest to the server side. My code is based on this Stack Overflow answer.
Here is how I constructed my form:
app/views/teams_form.html.erb
<%= form_for(#team) do |f| %>
<fieldset>
<ol>
<li>
<%= f.label :name %>
<%= f.text_field :name %>
</li>
<li>
<%= f.label :quest_id %>
<%= f.select :quest_id,
options_from_collection_for_select(#quests, :id, :name),
{}, {remote: true, url: '/teams/new', method: 'get'} %>
</li>
<% if #x != nil && #x.id != nil %>
<li><%= #x.id %></li>
<% end %>
</ol>
<p>
<%= f.submit %>
</p>
</fieldset>
<% end %>
app/controllers/team_controller.rb
def new
#team = Team.new
#quests = Quest.all
respond_to do |format|
if params[:quest_id] != nil
#x = Quest.find(params[:quest_id])
end
format.html #new.html.erb
format.json
format.js
end
end
My goal was to pass the :quest_id parameter from the form to the #x variable and use that in the form.
This has produced nothing. I'm not getting the parameter in the controller and I'm not sure what I'm missing.
As per the description shared it seems like the you are unable to get the value of the selected item from the dropdown.
Below mentioned code is used for selecting value from dropdown also you can inspect the same using developer tools of the browser.
quest = $("#quest_id").val();
Note: Assuming the selector id is quest_id, change it according to your form.
Now, you can call the ajax using the below mentioned code.
$.ajax({
type: "GET",
url: "/teams/new",
data:{ quest_id: quest },
dataType: "json",
success:function(data){
# logic for using ajax result
}
Hope it helps!!
Finally got this working, wanted to post if anyone sees this and is having the same problem:
I went with a separate AJAX request since that was being suggested
app/views/teams_form.html.erb
<script>
$(document).ready(function() {
$('#team_quest_id').change(function() {
$.ajax({
url: "/teams/new",
data: {quest_id: $("#team_quest_id option:selected").val()},
dataType: "script",
method: "get",
success: function(r){}
});
});
});
</script>
I moved the location of the parameter assignment
app/controllers/team_controller.rb
def new
#team = Team.new
#quests = Quest.all
if params[:quest_id] != nil
#x = Quest.find(params[:quest_id])
end
respond_to do |format|
format.html #new.html.erb
format.json
format.js
end
end
And most importantly - I created a js file to render my form
app/views/new.js.erb
$('#new_team').html("<%= j (render 'form') %>");
This video was extremely helpful
The code in your question is almost correct, you forgot to nest the attributes in data.
<% # app/views/teams_form.html.erb %>
<%= f.select :quest_id,
options_from_collection_for_select(#quests, :id, :name),
{}, {remote: true, url: '/teams/new', method: 'get'} %>
<% # should be: %>
<%= f.select :quest_id,
options_from_collection_for_select(#quests, :id, :name),
{}, {data: {remote: true, url: '/teams/new', method: 'get'}} %>
<% # or even better, use the path helper instead of the hard coded path %>
<%= f.select :quest_id,
options_from_collection_for_select(#quests, :id, :name),
{}, {data: {remote: true, url: new_team_path, method: :get}} %>
Having set the attributes correctly, we still need to fix the form further. On page request the browser will request the form, but #x will never be set. Since ERB will not be send to the client we'll need to add a handle to find our quest container element back.
<% # app/views/teams_form.html.erb %>
<% if #x != nil && #x.id != nil %>
<li><%= #x.id %></li>
<% end %>
<% # should be something like %>
<li id="quest-info-container"></li>
Now in the controller split of the HTML request from the JS request.
# app/controllers/teams_controller.rb
def new
respond_to do |format|
format.html do
#team = Team.new
#quests = Quest.all
end
format.js do
#quest = Quest.find(params.dig(:team, :quest_id))
end
end
end
You could simplify the above by sending the select data-path to another url that handles the quest preview.
Now we need to render the preview in our container we need 2 files for this, first of how the resulting structure should look. Keep in mind that this will be rendered inside the container.
<% # app/views/teams/_quest_preview.html.erb %>
<% # Here comes what you want to display about the quest. You can give this %>
<% # file another name if you like. You have #quest to your disposal here. %>
<%= #quest.id %> <strong><%= #quest.name %></strong>
Now we only need a JavaScript file that loads the above structure into our created handle.
<% # app/views/teams/new.js.erb %>
handle = document.getElementById('quest-info-container');
handle.innerHTML = '<%= j render('quest_preview') %>';
The j is an alias for escape_javascript. If the partial is not in the same directory use <%= j render('other_dir/some_partial') %> instead.
I been diggins some days on gems for country and state/province selection. Some are great (like country-state-select) but not for my needs.
The almost mandatory country_select gem is good but lacks states. Nevertheless is based on this very cool gem called countries. Gem countries really puts together a lot of good info, lets say Mozambique and its subdivisions, and so on, for over 300 countries, and includes i18n.
Having countries gem in the app, all is needed is some javascript to interact with it. Calls are made with this:
country = ISO3166::Country.new('US')
Here is the form:
<%= simple_form_for(#order) do |f| %>
<div><%= f.input :country %></div>
<div><%= f.input :state %></div>
<%= f.button :submit, t('.submit') %>
<% end %>
<script type="text/javascript">
states_drop_down = $("#order_state");
$("#order_country").change(function() {
// How to make this work?
<% unless params[:country].nil? %>
states_drop_down.clearAttributes();
<% ISO3166::Country.new(params[:country]).states.each do |state| %>
states_drop_down.setAttribute(<%= state[:name] %>, <%= state[:alpha2] %>); // How to log to check if new attributes are present?
<% end %>
states_drop_down.reload(); // How to reload the state simple_form input?
<% end %>
});
The goal is the known one, to populate state selector with correct country every time the country dropdown changes. Any help? Thanks.
I found a solution, even though is not using gem countries anymore. Data is seeded to database and pulled from there. Found the data here.
Then all is needed is few steps:
// 1. new action at controller, in my case was orders. This receives the calls and renders the views.
def update_states
country = Country.find(params[:nation_id])
#states = country.states
respond_to do |format|
format.js
end
end
// 2. new get at routes to find the new action
get "orders/update_states", as: "update_states"
// 3. simple_form with collections. Note uses nation to avoid the simple_form country selector error.
<%= simple_form_for(#order) do |f| %>
<div><%= f.input :nation, collection: #countries %></div>
<div><%= f.input :state, collection: #states %></div>
<%= f.button :submit, t('.submit') %>
<% end %>
// 4. new doc at app/views/states/_state.html.erb to render inside the dropdown selector.
<option value="<%= state.id %>"><%= state.name %></option>
// 5. new lines at app/assets/javascripts/orders.js.coffee to listen to nation or country selector.
$ ->
$(document).on 'change', '#order_nation', (evt) ->
$.ajax 'update_states',
type: 'GET'
dataType: 'script'
data: {
nation_id: $("#order_nation option:selected").val()
}
error: (jqXHR, textStatus, errorThrown) ->
console.log("AJAX Error: #{textStatus}")
success: (data, textStatus, jqXHR) ->
console.log("State selection is working!")
// 6. and app/views/orders/update_cities.js.cofee, the piece that close the circle. This actually renders the info and views.
$("#order_state").empty()
.append("<%= escape_javascript(render(:partial => #states)) %>")
Must thanks Kernel Garden, I found the javascript I was looking here.
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!
I faced the same problem with this guy
I change rjs to js.erb just like him. And we all use <%= button_to 'Add to Cart',line_items_path(:product_id => product) ,:remote=>true %> to send an AJAX request to the controller. format.js to fine and execute create.js.erb. But the cart did not add anything.
log result :
Rendered line_items/_line_item.html.erb (4.3ms)
Rendered carts/_cart.html.erb (8.0ms)
Rendered line_items/create.js.erb (8.8ms)
That's the index.html.erb we send the AJAX request
<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>
<h1>Your Pragmatic Catalog</h1>
<% #products.each do |product| %>
<div class="entry">
<%= link_to image_tag(product.image_url), line_items_path(:product_id => product), html_options = {:method => :post} %>
<h3><%= product.title %></h3>
<%=sanitize product.description %>
<div class="price_line">
<span class="price"><%= number_to_currency(product.price,:precision=>3) %></span>
<%= button_to 'Add to Cart',line_items_path(:product_id => product) ,:remote=>true %>
</div>
</div>
<% end %>
That's the line_items controller function to handle the request
# POST /line_items
# POST /line_items.json
def create
# for exercise only
session[:counter] = nil
#cart = current_cart
product = Product.find(params[:product_id])
#line_item = #cart.add_product(product.id)
respond_to do |format|
if #line_item.save
format.html { redirect_to store_index_path }
format.js
format.json { render json: #line_item, status: :created, location: #line_item }
else
format.html { render action: "new" }
format.json { render json: #line_item.errors, status: :unprocessable_entity }
end
end
end
create.js.erb
$('#cart').html("<%= escape_javascript(render(#cart)) %>");
I fixed the problem.
Thanks to the great article which tell me the ability of firebug to see the source of response from AJAX request.
And JSLint helps me checkout the javascript syntax.
And finally I would like to thanks the Firebug which is such a great tool.
The problem is that the javascript is not being executed if there is any syntax error.
In my problem:
I should use single-quoted instead of double-qouted to wrap the render results. The render results comes out with many HTML with "", and "" which wrap them will cause syntax error in javascript. (double-qouted in double-qouated is not allowed)
So I simply change $('#cart').html("<%= escape_javascript(render(#cart)) %>"); to
$('#cart').html('<%= escape_javascript(render(#cart))%>');
I hope this answer will help the others who also suffer from this nightmare staff.
Help me increase the question rate if this is possible :)
Let's use j() helper method: $('#cart').html("<%=j render(#cart) %>");
I currently have a comment model that posts under a micropost and both are displayed on the same page. The issue is that both are displayed on the same page and both are paginated and I am trying to go for the facebook approach to microposting. Here is the issue below:
The links for both pagination turns into this href="/users/2?page=2" rather than href="/users/2/micropost?page=2" or href="/users/2/comment?page=2". I am unsure how to go about solving this problem. Here are some of my code. All suggestions are much appreciated!
Micropost Render HTML
<table class="microposts">
<% if microposts.any? %>
<%= render microposts %>
<%= will_paginate microposts, :page_links => false %>
<% else %>
<div class="EmptyContainer"><span class='Empty'>Add a thread!</span></div>
<% end %>
</table>
Comment Section HTML
<div id='CommentContainer-<%= micropost.id%>' class='CommentContainer Condensed2'>
<div class='Comment'>
<%= render :partial => "comments/form", :locals => { :micropost => micropost } %>
</div>
<div id='comments'>
<% comments = micropost.comments.paginate(:per_page => 5, :page => params[:page]) %>
<%= render comments %>
<%= will_paginate comments, :class =>"pagination" %>
</div>
</div>
User Controller for the Show Page
def show
#user = User.find(params[:id])
#comment = Comment.find(params[:id])
#micropost = Micropost.new
#comment = Comment.new
#comment = #micropost.comments.build(params[:comment])
#comments = #micropost.comments.paginate(:page => params[:page], :per_page => 5)
#microposts = #user.microposts.order('created_at DESC').paginate(:per_page => 10, :page => params[:page])
respond_to do |format|
format.html
format.js
end
end
Problem lies within will_paginate way of creating urls for each page (it doesn't have anything to do with jQuery).
By design, will_paginate try its best to guess what's the base url for the page user is on (internally it's using controller/action to do that). That base url is then combined with any extra params passed to will_paginate helper using :params and succesive page numbers.
For now (will_paginate 3.0.3), in order to overwrite this default behavior, you need to write your custom LinkRenderer class. Below there's example of such class - it makes use of new, extra option :base_link_url that can be passed to will_paginate view helper. Passed string is then used as a base when creating pagination links. If :base_link_url option is not passed, it will fallback to default behavior.
Put following class somewhere rails can find it on load (/lib for example, provided you've added /lib to your autoload paths in application.rb):
# custom_link_renderer.rb
class CustomLinkRenderer < WillPaginate::ActionView::LinkRenderer
def prepare(collection, options, template)
#base_link_url = options.delete :base_link_url
#base_link_url_has_qs = #base_link_url.index('?') != nil if #base_link_url
super
end
protected
def url(page)
if #base_link_url.blank?
super
else
#base_url_params ||= begin
merge_optional_params(default_url_params)
end
url_params = #base_url_params.dup
add_current_page_param(url_params, page)
query_s = []
url_params.each_pair {|key,val| query_s.push("#{key}=#{val}")}
if query_s.size > 0
#base_link_url+(#base_link_url_has_qs ? '&' : '?')+query_s.join('&')
else
#base_link_url
end
end
end
end
Usage:
# in your view
will_paginate collection, :renderer => CustomLinkRenderer, :base_link_url => '/anything/you/want'
And now back to your case. By this time you probably see the solution - you can have two will_paginate widgets on one page with different base urls by passing different :base_link_url options for those two.