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>
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>
How can I refresh the page (in the background, seamlessly) when a user checks a checkbox so that once the checkbox is checked off, that div or task is then moved to another location because it's completed?
I have separated them like so
def home
if current_user
#todos = current_user.todos.where(completed: false)
end
end
def complete
if current_user
#todos = current_user.todos.where(completed: !false)
end
end
So that once the checkbox is checked it is moved, and this works - but the page has to be refreshed to see that the task has been moved.
In my todos controller where I mark the task complete it looks like this
def completed
if #todo.update_attribute(:completed, !#todo.completed)
flash[:success] = "Congratulations, it was successful."
redirect_to dashboard_path
else
flash.now[:error] = "ERROR: Please try again."
render :new
end
end
And my view is as so
<% #todos.each do |todo| %>
<div class="card hoverable">
<div class ="card-content mh-100">
<span class="card-title"><%= todo.title %></span>
<p><%= todo.item %></p>
</div>
<div class="card-action grey lighten-5">
<p style="margin: 0;">
<%= check_box_tag 'todo[completed]', todo.id, todo.completed, data: { remote: true, url: url_for(controller: :todos, action: :completed, id: todo), method: "POST" }, id: todo.id, :onclick => "Materialize.toast('Todo Completed, Grats!', 4000)" %>
<%= label_tag todo.id, "COMPLETE", :class => 'strikethrough' %>
</p>
</div>
</div>
<% end %>
But how can I refresh the page, or the div when the checkbox is checked so the task disappears seamlessly?
Give your div a unique ID based on the todo id value so that you can select it for removal:
<div class="card hoverable" id="todo_container_<%= todo.id %>">
Add a class to your check box so that you can specify a click handler:
check_box_tag 'todo[completed]', todo.id, todo.completed, class: 'todo_completed', data: { remote: true, url: url_for(controller: :todos, action: :completed, id: todo), method: "POST" }, id: todo.id, :onclick => "Materialize.toast('Todo Completed, Grats!', 4000)"
Then (assuming you are using jQuery) specify the click handler:
$(".todo_completed").click(function(){
// it looks like you've got all the info you need for your ajax call in the data attributes attached to the checkbox, so I think the call just looks like this:
$.ajax($(this).data());
});
Finally your controller needs to render a .js template instead of redirecting - just call it completed.js.erb and it should be rendered automatically. In that template put the javascript to remove the container div from the DOM:
// completed.js.erb
$("todo_container_<%= #todo.id %>").remove();
Use removeChild() with the div id to remove it, just put this in an onClick handler for the checkbox.
If desired:
Save the div when you delete it (from the return value of removeChild) in a variable, so if the checkbox is unchecked, you can use the div in createChild().
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.
I have a products model with a 'dept' attribute. the model includes a list of departments. In my view is a drop down list with all the department types. I am trying to get the page to refresh after a department is selected from the dropdown. I would prefer to have it updated via AJAX but at this point ill take a normal page referesh.
My Model
class Product < ActiveRecord::Base
attr_accessible :dept, :price, :title
DEPT_TYPES = ["Baby","Beauty"]
end
My controller:
class StoreController < ApplicationController
def index
#title= "Home"
#products = Product.order(:premium)
#baby , #beauty = [], []
#products.each do |product|
#baby << product if product.dept == 'Baby'
#beauty << product if product.dept == 'Beauty'
end
respond_to do |format|
format.html # index.html.erb
format.js # index.js.erb
format.json { render json: #products }
end
end
My View
<div class ="filter">
<select>
<option value="<%=#baby%>">baby</option>
<option value="<%=#beauty%>">beauty</option>
</select>
</div>
<div id="products_list">
<% #products.each do |product|%>
...
<%end%>
You can use simple ajax jQuery function:
$(".filter").change(function(){
var value = $(this).val()
$.ajax({
url: <%= stores_path(:json) %>,
type: 'GET',
data: value,
success: function(data){
$("#products_list").html(data.products)
}
})
})
You have two way to do it:
The first one is to send from the server for example JSON with all products. Or you can create ".js.erb" file in the views and handle the actions there.
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)