I have an index page with different software in a table.
I want to display additional information (in a modal) when we click on a tr.
Everything works but I have the information of a single software that appears in my modal and is the same for each tr.
I would like to display the information of each software in the corresponding modals.
My script :
$(".clickable").click(function(e) {
if (!$(e.target).hasClass('no-click')) {
$('#exampleModal').modal('show');
}
});
My view :
<% #nonpremium.each do |software| %>
<table>
<tr class="clickable">
<td class="hey1">
<%= link_to software_path(software), class: "no-click" do %>
<%= image_tag software.logo.url(:medium), class:"no-click"%>
<% end %>
</td>
<td class="hey3">
<h6><%= software.slogan %></h6>
<p><%= software.largeslogan %></p>
</td>
</tr>
</table>
<div class="modal fade bd-example-modal-lg" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<%= link_to software.software_name, software_path(software), class:"no-click" %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<% end %>
I tried something like this in my script, but it does not work ..
$(".clickable").click(function(e) {
if (!$(e.target).hasClass('no-click')) {
$('#exampleModal-<%= #software.id %>').modal('show');
}
});
Thx for you help
EDIT :
Controller/pages
class PagesController < ApplicationController
before_action :click, only: :index
def home
#softwares = Software.all.order(:cached_votes_up => :desc )
#premium = Software.includes(:user).where(users: { subscribed: true }).order("RANDOM()").limit(2)
#nonpremium = #softwares - #premium
end
def search
#softwares = Software.ransack(name_cont: params[:q]).result(distinct: true)
#categories = Category.ransack(name_cont: params[:q]).result(distinct: true)
respond_to do |format|
format.html {}
format.json {
#softwares = #softwares.limit(5)
#categories = #categories.limit(5)
}
end
end
end
EDIT 2 :
I have the desired result by putting in my table the information that I want to recover, then I put a "display: none".
<style>
td.test {
display:none;
}
</style>
<td class="test">
<span><%= software.software_description %></span>
<span><%= get_video_iframe(software.youtube_id) %></span>
</td>
Then I get the information from my table in my script:
$(".clickable").click(function(e) {
if (!$(e.target).hasClass('no-click')) {
var description = this.childNodes[3].innerHTML;
var name = this.childNodes[5].innerHTML;
document.getElementById("myModalName").innerHTML = name;
document.getElementById("myModalDesc").innerHTML = description;
$('#exampleModal').modal('show');
}
});
For then displayed in my modal:
...
<div class="modal-body" id="myModalName">
Name
</div>
<div class="modal-body" id="myModalDesc">
Description
</div>
...
There is probably better to do, but being a beginner is how I achieve the desired result.
However I would like to post videos in my modals.
Am I not going to overload my home page by hiding youtube videos with my display: none?
You won't be able to use erb in your script (unless this is in a script tag within your view, in which case your code should work) - better using a data attribute. For example, if you update your tr to the following:
<%= content_tag :tr, class: "clickable", data: { software_id: #software.id } do %>
# the rest of your code within the tr
<% end %>
# Equivalent of using:
# <tr class="clickable" data-software_id="<%= #software.id %>">
This attaches the relevant software_id to the tr in the DOM. You can then use the following in your script, accessing this new attribute:
$(".clickable").click(function(e) {
if (!$(e.target).hasClass('no-click')) {
$('#exampleModal-' + $(e.target).data('software_id')).modal('show');
}
});
And everything should work as desired.
Let me know how you get on or if you have any questions. Hope this helps!
Edit based on your comment:
That error you're seeing will come because #software is nil and you are, therefore, attempting to call id on nil.
It's a common error, and means to need to ensure #software is correctly set in your controller. If you post your controller code, I might be able to help with this.
Alternatively, you can 'safely' try the method, using #software&.id with newer versions of Ruby / Rails (or #software.try(:id) on older versions). However, that's not likely to be helpful here, more of a side note :)
Edit 2:
So, in your controller, you're not actually assigning the singular #software, rather the plural #softwares:
#softwares = Software.all.order(:cached_votes_up => :desc )
#premium = Software.includes(:user).where(users: { subscribed: true }).order("RANDOM()").limit(2)
#nonpremium = #softwares - #premium
Then, in your view, you're looping through #nonpremium using the local variable software. So, you can either:
assign #software in the controller if it should always use the same data in the modal
go back to the previous option, assigning a data attribute to the tr, which is what I'd recommend. Using that should work, although you'll need to alter the code to use software without the # to address the correct variable.
I.E.
<%= content_tag :tr, class: "clickable", data: { software_id: software.id } do %>
# the rest of your code within the tr
<% end %>
This ensures the script addresses the click based on the element clicked, and pulls the id directly from there, which is within the scope of your software loop.
That do it for ya?
Related
I have a messaging system in my Rails 5 project where the Message model has an isread field to indicate whether the recipient has read the message yet or not.
I'm using a bootstrap modal to view the message and would like the message's isread field to change to true when the modal is closed.
Could someone explain how to do this from the button_tag or make the button tag call method in the controller to do it?
Something like:
message.isread = true
message.save!
to execute when the "Close" button is pressed from my view:
<div id="modal1<%= index %>" class="modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title"><%= message.subject %></h4>
</div>
<div class="modal-body">
<p><%= message.content %></p>
</div>
<div class="modal-footer">
<%= button_tag "Close", :class => "btn btn-default", "data-dismiss" => "modal" %>
</div>
</div>
</div>
</div>
Thank you!
You can define a new action in your controller which update the attribute isread to true and use the button_to helper
#routes
resources :messages do
post :is_read, on: :member
end
#messages controller
def is_read
#message = Message.find(params[:id])
#message.update(isread: true)
redirect_to ...
end
#view
<%= button_to "Close", is_read_message_path %>
If you want a button click to call a method in your controller you will need to first capture the action of the button click in javascript
$(".btn-default").click(function(){
});
Then you want to do an Ajax call to the controller method
$.ajax({
url: "/message/update_is_read",
type: "POST",
data: {is_read: isRead},
success: function(resp) {
console.log(resp);
},
error: function(resp) {
console.log(resp);
},
});
then in your controller catch it with
def update_is_read
is_read = params[:is_read]
end
Make sure you make add the path to your routes
post '/messages/update_is_read', to: 'messages#update_is_read', as: '/messages/update_is_read'
You can modify the controller code to save.
I have the following partial (_card_brand.html.erb), which looks like this:
<div class="payment-card">
<i class="fa fa-cc-<%= brand.downcase %> payment-icon-big text-success"></i>
</div>
That renders HTML that looks like this:
<div class="payment-card">
<i class="fa fa-cc-visa payment-icon-big text-success"></i>
</div>
The above is rendered with this:
<%= render partial: "subscriptions/card_brand", locals: { brand: current_user.card_brand } %>
What I want to do is change the class text-success, to be either: text-warning, text-primary, text-danger, etc. depending on if the card has brand: visa, amex, mastercard, discovery, etc.
So:
Visa = Success
AMEX = Warning
Mastercard = Primary
Discovery = Danger
Any other cards would be other classes.
How do I elegantly represent that in my view that renders the partial?
You may create a helper and use it so that it will be easy to add new classes as well.
application_helper.rb
CARD_CLASS = {
'visa' => 'success',
'amex' => 'warning',
'mastercard' => 'primary',
'discovery' => 'danger'
}
def payment_class(type)
CARD_CLASS[type.downcase]
end
_card_brand.html.erb
<div class="payment-card">
<i class="fa fa-cc-<%= brand.downcase %> payment-icon-big text-text-<%= payment_class(brand.downcase) %>"></i>
</div>
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
This is the view hello.html.erb using zurb foundation and tabulous:
<html>
<body>
<div class="contain-to-grid">
<nav class="top-bar" data-topbar>
<ul class="title-area">
<li class="name">
<h1>Hello World!</h1>
</li>
<li class="toggle-topbar menu-icon"><span>Menu</span>
</li>
</ul>
<ul class="center">
<li style="padding-top:55px;">
<%= form_tag("/salutation/hello", method: "post") do %>
<div class="large-6 columns" style="padding-left:250px;border:0px;
margin-right:0px;padding-right:0px;">
<%= text_field_tag(:find_stuff) %>
</div>
<div>
<%= submit_tag "Search", class: "large-1 small-3 columns",
style:"color:white;border:none;background-color:red;height:28px;margin:0px;"%>
</div>
<% end %>
Login|Register
</li>
</ul>
</nav>
<%= tabs %>
<%= subtabs %>
</div>
<div class="row">
<div class="six columns">
<div class="panel" style="min-height:800px">
<p><%= #test %></p>
<p><%= #test %></p>
</div>
</div>
</div>
</body>
</html>
This is the controller salutation.rb:
class SalutationController < ApplicationController
def new
#test = ''
end
def hello
#message = 'Hello, World!'
#test = params[:find_stuff]
end
end
This is very simple code for reading the text in the search box, and writing to the view. I have a
file tabulous.rb, which adds menu tabs to the screen. The code for tabulous.rb is:
tabulous.setup do
tabs do
news_tab do
text { 'News' }
link_path { news_index_path }
visible_when { true }
enabled_when { true }
active_when { in_action('any').of_controller('news') }
end
homes_tab do
text { 'Homes' }
link_path { homes_path }
visible_when { true }
enabled_when { true }
active_when { in_action('any').of_controller('homes') }
end
end
customize do
end
use_css_scaffolding do
background_color '#ccc'
text_color '#444'
active_tab_color '#fff'
hover_tab_color '#ddd'
inactive_tab_color '#aaa'
inactive_text_color '#888'
end
end
When I tried these tabs without the zurb foundation styling the form displays the menu tabs
fine, but when I embed this form in the view there is no display, and the form just displays the main top bar with the search box. Why this happens I can't figure out - is
this because of the styling by zurb foundation, that breaks it, or any other reason?
Why use tabulous.rb? Foundation already supports tabs out of the box. You can refer to the documentation for the markup required to create them. It'll be easier that way.
As an aside, I would really recommend against adding inline CSS in your markup. There should be a separation of concerns between your markup (for content), and your CSS (for style). Incidentally, this is the same reason that tabulous might not be a great choice for tabs. It's not Ruby's job to create structure and style.
He
I have two models in my rails application (Post, Picture) that are associated as follows:
#Post model
has_many :pictures, :dependent => :destroy
accepts_nested_attributes_for :pictures, :allow_destroy => true
#Picture model
belongs_to :post
In my post edit view I have two forms, so I can edit the post content as well as add pictures to the post. I use the jquery file upload plugin together with carrierwave to handle the upload process. This looks quite similar to this setup here http://tinyurl.com/aun7bl5
When I go to the post edit view the jquery file upload always shows me all pictures, because it uses the index action of the picture controller which fetches all images and renders them to json so jquery file upload can handle them. The index action looks like this.
def index
#pictures = Picture.all
render :json => #pictures.collect { |p| p.to_jq_upload }.to_json
end
The post param (:id) is available to the post controller when I edit a post. I can see it with the logger. But it is not available to the index action which is nested inside of the post edit form.
Now my question is, how I can provide the index action in the post controller with the id of the post I like to edit so that I can do there something like this to filter the pictures it gets:
def index
#pictures = Picture.where(:post_id => params[:id])
render :json => #pictures.collect { |p| p.to_jq_upload }.to_json
end
EDIT:
#Post#edit view
<div class=post-well>
<div class="page-header">
<h1>Reisebericht editieren</h2>
</div>
<%= simple_form_for #post do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :title, :label => "Titel", :input_html => { :class => 'new-post-inputfields' } %>
<%= f.input :body, :label => "Artikel", :input_html => { :class => 'new-post-inputfields' } %>
<%= f.hidden_field :picture_ids, :input_html => { :id => 'post_picture_ids' } %>
<%= f.button :submit, :label => "Speichern" %>
</div>
<% end %>
<h4>Bilder verwalten</h4>
<%= simple_form_for Picture.new, :html => { :multipart => true, :id => "fileupload" } do |f| %>
<!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload -->
<div class="row fileupload-buttonbar">
<div class="span7">
<!-- The fileinput-button span is used to style the file input field as button -->
<span class="btn btn-success fileinput-button">
<i class="icon-plus icon-white"></i>
<span>Hinzufügen</span>
<%= f.file_field :path, multiple: true, name: "picture[path]" %>
</span>
<button type="submit" class="btn btn-primary start">
<i class="icon-upload icon-white"></i>
<span>Upload</span>
</button>
<button type="reset" class="btn btn-warning cancel">
<i class="icon-ban-circle icon-white"></i>
<span>Abbrechen</span>
</button>
<button type="button" class="btn btn-danger delete">
<i class="icon-trash icon-white"></i>
<span>Delete</span>
</button>
<input type="checkbox" class="toggle">
</div>
<div class="span5">
<!-- The global progress bar -->
<div class="progress progress-success progress-striped active fade">
<div class="bar" style="width:0%;"></div>
</div>
</div>
</div>
<!-- The loading indicator is shown during image processing -->
<div class="fileupload-loading"></div>
<br>
<!-- The table listing the files available for upload/download -->
<table class="table table-striped"><tbody class="files" data-toggle="modal-gallery" data-target="#modal-gallery"></tbody>
</table>
<% end %>
</div>
<script>
var fileUploadErrors = {
maxFileSize: 'File is too big',
minFileSize: 'File is too small',
acceptFileTypes: 'Filetype not allowed',
maxNumberOfFiles: 'Max number of files exceeded',
uploadedBytes: 'Uploaded bytes exceed file size',
emptyResult: 'Empty file upload result'
};
</script>
<!-- The template to display files available for upload -->
<script id="template-upload" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
<tr class="template-upload fade">
<td class="preview"><span class="fade"></span></td>
<td class="name"><span>{%=file.name%}</span></td>
<td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
{% if (file.error) { %}
<td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td>
{% } else if (o.files.valid && !i) { %}
<td>
<div class="progress progress-success progress-striped active"><div class="bar" style="width:0%;"></div></div>
</td>
<td class="start">{% if (!o.options.autoUpload) { %}
<button class="btn btn-primary">
<i class="icon-upload icon-white"></i>
<span>{%=locale.fileupload.start%}</span>
</button>
{% } %}</td>
{% } else { %}
<td colspan="2"></td>
{% } %}
<td class="cancel">{% if (!i) { %}
<button class="btn btn-warning">
<i class="icon-ban-circle icon-white"></i>
<span>{%=locale.fileupload.cancel%}</span>
</button>
{% } %}</td>
</tr>
{% } %}
</script>
<!-- The template to display files available for download -->
<script id="template-download" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
<tr class="template-download fade">
{% if (file.error) { %}
<td></td>
<td class="name"><span>{%=file.name%}</span></td>
<td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
<td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td>
{% } else { %}
<td class="preview">{% if (file.thumbnail_url) { %}
<img src="{%=file.thumbnail_url%}">
{% } %}</td>
<td class="name">
{%=file.name%}
</td>
<td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
<td colspan="2"></td>
{% } %}
<td class="delete">
<button class="btn btn-danger" data-type="{%=file.delete_type%}" data-url="{%=file.delete_url%}">
<i class="icon-trash icon-white"></i>
<span>{%=locale.fileupload.destroy%}</span>
</button>
<input type="checkbox" name="delete" value="1">
</td>
</tr>
{% } %}
</script>
And the javascript:
$(function () {
// Initialize the jQuery File Upload widget:
$('#fileupload').fileupload({
completed: function(e, data) {
console.log(data.result[0].picture_id);
$("#post_picture_ids").val(function(i,val) {
return val + (val ? ', ' : '') + data.result[0].picture_id;
});
}
});
// Load existing files:
$.getJSON($('#fileupload').prop('action'), function (files) {
var fu = $('#fileupload').data('fileupload'),
template;
fu._adjustMaxNumberOfFiles(-files.length);
template = fu._renderDownload(files)
.appendTo($('#fileupload .files'));
// Force reflow:
fu._reflow = fu._transition && template.length &&
template[0].offsetWidth;
template.addClass('in');
$('#loading').remove();
});
});
Any help with this would be appreciated.
EDIT2: For one solution see below under #SybariteManoj answer. Another solution is to use:
$.getJSON($('#fileupload').prop('action') + '/' + $('#current_post_id').val(), function (files) {
in the beginning of the get function and then add a route for the pictures controller as follows:
get 'pictures/:id', to: 'pictures#index'
The index action in the pictures controller will then filter for the id parameter in this solution and looks like this:
def index
#pictures = Picture.where(:post_id => params[:id])
render :json => #pictures.collect { |p| p.to_jq_upload }.to_json
end
I think I prefer the full solution of #SybariteManoj so there is no need for a route and the index action loks like this now.
def index
#pictures = Picture.where(:post_id => params[:post_id])
render :json => #pictures.collect { |p| p.to_jq_upload }.to_json
end
I think I got the culprit. In your javascript, $.getJSON($('#fileupload').prop('action') this is passing the value of the action attribute of the image upload form.
Try adding this line somewhere in your edit view file
<%= hidden_field_tag :current_post_id, #post.id, :id => 'current_post_id' %>
and replace this line
$.getJSON($('#fileupload').prop('action'), function (files) {
with
$.getJSON($('#fileupload').prop('action') + '?post_id=' + $('#current_post_id').val(), function (files) {
I haven't tested it but I am quite sure this should solve your issue.
Since you are editing the post, the post params[:id] is available to the post controller's update action and not to others which is the default action call after editing the form in rails.
If you want the params[:id] in the index action then you need to either redirect to the index action after update action is called or you need to put the logic of showing the selected pictures in the update action only.
You can also create a custom action method to handle the process of showing the pictures that belongs to the post.
I suppose that your Picture has a foreign_key named post_id and you can simply use this in your index action to get only pictures which belong to the Post.
Try something like this :
def index
#pictures = #post.pictures
render :json => #pictures.collect { |p| p.to_jq_upload }.to_json
end
EDIT
Since your pictures belong_to your post, you also need to modify the new and create actions so that you create a picture for your post.
One way to do that is to create a method find_post in your Picture controller, and make a before_filter callback like this :
class PicturesController < ApplicationController
before_filter :find_post, :only => [:index, :new, :create]
def find_post
#post = Post.find(params[:post_id]) unless params[:post_id].nil?
end
def new
#picture = #post.pictures.new
end
## Same thing for the create action
end
And in your view, do the same when you create you form :
<%= simple_form_for #post.pictures.new
Hope this helps.