So I have put together a comment section on my rails app from a couple tutorials online. Everything works well, except that when I post a comment, it doesn't get displayed unless I reload the page. I'm using the acts_as_commentable_with_threading gem.
Here's my comments controller:
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
commentable = commentable_type.constantize.find(commentable_id)
#comment = Comment.build_from(commentable, current_user.id, body)
if #comment.save
make_child_comment
redirect_back fallback_location: root_path, :notice => 'Comment was successfully added.'
else
render :action => "new"
end
end
private
def comment_params
params.require(:comment).permit(:body, :commentable_id, :commentable_type, :comment_id)
end
def commentable_type
comment_params[:commentable_type]
end
def commentable_id
comment_params[:commentable_id]
end
def comment_id
comment_params[:comment_id]
end
def body
comment_params[:body]
end
def make_child_comment
return "" if comment_id.blank?
parent_comment = Comment.find comment_id
#comment.move_to_child_of(parent_comment)
end
end
Here are my view files.
_form partial:
.comment-form
= simple_form_for #new_comment, :remote => true do |f|
= f.input :commentable_id, :as => :hidden, :value => #new_comment.commentable_id
= f.input :commentable_type, :as => :hidden, :value => #new_comment.commentable_type
.field.form-group
= f.input :body, :input_html => { :rows => "2" }, :label => false
.field.form-group
= f.button :submit, :class => "btn btn-primary", :disable_with => "Submitting…"
_reply.html.haml
- comments.each do |comment|
.comments-show
.comment
.media.mb-4
.d-flex.mr-3
.float-left.image
- if comment.user.profile_image.present?
.review-img
= image_tag attachment_url(comment.user, :profile_image, :fit, 50, 50, format: "jpg")
- else
%img.review-img{:alt => "user profile image", :src => "https://img.icons8.com/bubbles/100/000000/user.png"}/
.media-body
.small
%b
- if comment.user.username.present?
{comment.user.username}
- else
{comment.user.full_name}
.small.text-muted
{time_ago_in_words(comment.created_at)} ago
.small
{content_tag(:div, comment.body, style: "white-space: pre-wrap;")}
%br
.comment-nav
%a.comment-reply{:href => "#/", class: "btn btn-sm btn-link"} reply
.reply-form
= simple_form_for #new_comment do |f|
= f.hidden_field :commentable_id, value: #new_comment.commentable_id
= f.hidden_field :commentable_type, value: #new_comment.commentable_type
= f.hidden_field :comment_id, value: comment.id
.field.form-group
= f.text_area :body, class: 'form-control'
.field.form-group{:style => "margin-bottom: 60px"}
= submit_tag "Post Reply", class: 'btn btn-primary', style: "float: right;"
%div{:style => "margin-left: 100px;"}
= render partial: "comments/reply", locals: {comments: comment.children}
_template.html.haml
.card.my-2
.scrollable
.card-body
%b
#{pluralize(commentable.comment_threads.count, "Comment")}
%hr
= render :partial => 'comments/form', :locals => { new_comment: new_comment }
= render partial: 'comments/reply', locals: {comments: commentable.root_comments}
comments.js.coffee
$ ->
$('.comment-reply').click ->
$(this).closest('.comment').find('.reply-form').toggle()
return
jQuery ->
$(".comment-form")
.on "ajax:beforeSend", (evt, xhr, settings) ->
$(this).find('textarea')
.addClass('uneditable-input')
.attr('disabled', 'disabled');
.on "ajax:success", (evt, data, status, xhr) ->
$(this).find('textarea')
.removeClass('uneditable-input')
.removeAttr('disabled', 'disabled')
.val('');
$(xhr.responseText).hide().insertAfter($(this)).show('slow')
$(xhr.responseText).hide().insertAfter($(this))
appends a div containing your xhr.responseText with inline style "display: none" after and returns no jquery handle at all.
$(xhr.responseText).hide().insertAfter($(this)).length
> 0
so select the new div as sibling of form element and cast show('slow')
$(this).siblings('div').show('slow')
Related
I have 3 models
class Store < ActiveRecord::Base
has_many :storedescriptions
has_many :descriptions , through: :storedescriptions
def self.tokens(query)
stores = where("name like ?", "%#{query}%")
if stores.empty?
[{id: "<<<#{query}>>>", name: "New: \"#{query}\"" }]
else
stores
end
end
def self.ids_from_tokens(tokens)
tokens.gsub!(/<<<(.+?)>>>/) { create!(name: $1).id }
tokens.split(',')
end
end
Description Model
class Description < ActiveRecord::Base
has_many :storedescriptions
has_many :stores , through: :storedescriptions
end
and storedescription model
class Storedescription < ActiveRecord::Base
belongs_to :user
belongs_to :store
belongs_to :description
attr_reader :store_tokens
def store_tokens=(tokens)
Store.ids_from_tokens(tokens)
end
end
I have a form for storedescription
<%= simple_form_for(#storedescription) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :store_tokens,:label => "Add Store Name", input_html: { class: 'priceaddtokendesc form-control'} %>
<%= f.input :description_id, :as => "hidden",:input_html => { :value => item.id } %>
<%= f.input :price %>
<%= f.input :user_id , :as => "hidden",:input_html => { :value => current_user.id } %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
In script
<script type="text/javascript">
$(".priceaddtokendesc").tokenInput("/stores.json", {
crossDomain: false,
prePopulate: $(".priceaddtokendesc").data("pre"),
theme: "facebook",
allowFreeTagging: true,
resultsLimit: "10",
zindex: 9999,
propertyToSearch: "name",
allowCreation: true,
creationText: 'Add new element',
preventDuplicates: true
});
On create i would like to store Store_id => store_tokens, also i have tried adding through controller but it won'work.
def create
#storedescription = Storedescription.new(storedescription_params)
#storedescription.store_id = #storedescription.store_tokens
#storedescription.save
respond_with(#storedescription)
end
But every time i get null result.
What is the best way to implement this process?
Sorry for the long title. I don't know how I got stuck this much.
I wanted to have a button (actually a link_to styled as a button) for FOLLOW / UNFOLLOW on remote. That's a Follow model where records are stored for Corporation and User (a User can follow a Corporation). The follow/unfollow links are on the Corporation show page.
<% if user_signed_in? %>
<% if Follow.where(corporation_id: #corporation.id, user_id: current_user.id).first.nil? %>
<%= link_to 'FOLLOW', {:controller => "follows", :action => "create", :user_id => current_user.id, :corporation_id => #corporation.id}, remote: true, :method => "post", class: "btns follow", id: "follow1" %>
<% elsif %>
<%= link_to 'UNFOLLOW', {:controller => "follows", :action => "destroy", :corporation_id => #corporation.id, :user_id => current_user.id }, remote: true, :method => "delete", class: "btns unfollow", id: "unfollow" %>
<% end %>
<% end %>
These are the controller actions:
def create
#corporation_id = params[:corporation_id]
#follow = Follow.new(follow_params)
#follow.save
respond_to do |format|
format.js {render :file => "/corporations/create.js.erb" }
end
end
def destroy
#corporation_id = params[:corporation_id]
attending.destroy
#attending is a method where the follow is defined. This is ok.
respond_to do |format|
format.js {render :file => "/corporations/destroy.js.erb" }
end
end
I'm rendering create.js.erb in corporations, since the change has to happen there. If I leave it as format.js, it'll search in the follows folder which is empty.
The create.js.erb look like this:
$("#unfollow").click(function(){
$("unfollow").replaceWith("<%= escape_javascript(render :partial => "corporations/profile/follow", :locals => {corporation_id: #corporation_id}) %>");
});
Btw, I tried .html instead of replaceWith, but it's not that.
The destroy.js.erb is similar. And the _unfollow.html.erb partial is like this:
<% if !#corporation.nil? %>
<%= link_to 'UNFOLLOW', {:controller => "follows", :action => "destroy", :corporation_id => #corporation.id, :user_id => current_user.id }, :method => "delete", class: "btns unfollow", remote: true, id: "unfollow" %>
<% else %>
<%= Rails.logger.info("First here") %>
<%= Rails.logger.info(corporation_id) %>
<%= link_to 'UNFOLLOW', {:controller => "follows", :action => "destroy", :corporation_id => corporation_id.to_i, :user_id => current_user.id }, :method => "delete", class: "btns unfollow", remote: true, id: "unfollow" %>
<%= Rails.logger.info("Now here...") %>
<% end %>
Without the first condition it just fires up an error the corporation_id (it's same with locales[:corporation_id]) is not defined and similar.
I have no idea what to try now... All the tutorials on the net are quite simple but the remote action is just one action in the controller where it needs to change, this has to go to another controller then back, then again to Follow... I'd really appreciate the help.
So I'm using the acts_as_commentable_with_threading for a comment system similar to Reddit.
So on the show page of an item, I have a form_for that has (items/show.html.haml):
- if user_signed_in?
%h5 Have something to say?
= form_for([#item, #new_comment], remote: true) do |f|
.form-group
= f.text_area :body, class: "form-control", rows: "3"
= f.hidden_field :user_id, value: current_user.id
.form-group
= f.submit "Submit", class: "btn btn-sm"
With a controller that does (items_controller.rb):
def show
#item = Item.find(params[:id])
#comments = #item.comment_threads
if user_signed_in?
#new_comment = Comment.build_from(#item, current_user.id, "")
end
end
This will then have a create.js.erb that will append the newly made comment onto the page
$('#comments').append("<%= escape_javascript(render partial: 'comment', locals: { comment: #comment } ) %>");
if ("<%= #comment.body %>") {
$("#comment_body").val('')
}
This by itself, works. I render each comment, and then inside the partial that each comment is rendered from, if they have any children, I render the child comments too.
Such as...:
- if !#comments.empty?
= render partial: 'comments/comment', collection: #item.root_comments, as: :comment
However each comment is able to have a reply, and those replies can have replies of their own (again, like Reddit). So when I attempt to do the same thing with the child replies, it gives me a 500 error.
= form_for([#item, #new_comment], remote: true, html: { class: "comment-reply", id: "replyto_#{comment.id}" }) do |f|
.col-md-5
.form-group
= f.text_area :body, class: "form-control", rows: "3"
= f.hidden_field :user_id, value: current_user.id
= f.hidden_field :parent_id, value: comment.id
.form-group
= f.submit "Submit", class: "btn btn-sm"
%div{style: "clear:both;"}
So my question is, how do I make a form_for for the child comments, and then (probably) create a new js.erb so it won't "append", but rather, rerender the parent comment (so that would in turn render the newly made child comment).
I think I may need to make a new create, so like a create_child, but then what does the form_for turn into?
Figured it out myself.
Basically inside the comments_controller.rb I had to find out if the new reply had a parent_id.
def create
#item = Item.find(params[:item_id])
#all_comments = #item.comment_threads
if (params[:comment].has_key?(:parent_id))
#parent = Comment.find(params[:comment][:parent_id])
end
#comment = Comment.build_from(#item, current_user.id, params[:comment][:body])
if #comment.save
if #parent
#comment.move_to_child_of(#parent)
end
respond_to do |format|
format.js
end
else
flash.now[:error] = "Comment was not submitted."
redirect_to root_path
end
end
And then inside my create.js.erb I needed to find out if it also had a parent_id:
if ("<%= #comment.parent_id %>") {
$('.comment_<%= #comment.parent_id %>').append("<%= escape_javascript(render partial: 'comment', locals: { comment: #comment } ) %>");
$("#replyto_<%= #comment.parent_id %>").val('');
$("#replyto_<%= #comment.parent_id %>").toggle();
}
else {
$('#comments').append("<%= escape_javascript(render partial: 'comment', locals: { comment: #comment } ) %>");
if ("<%= #comment.body %>") {
$("#comment_body").val('');
}
}
This allows the child comments to be appended (via JavaScript) and for them to be put under the correct parent.
I have a nested form with checkboxes and text fields. I would like to be able to have the text fields only be enabled if the text box for that specific nested form is clicked/enabled. It is currently hard coded to enable/disable fields if the "custom" text box is set. How can I have javascript update these textbox attributes on the fly?
Form.erb now
<%= simple_nested_form_for #client do |f| %>
<%= f.fields_for :client_prices do |def_price_form| %>
<div class="controls controls-row">
<div class='span10'>
<% if def_price_form.object.custom == true %>
<%= def_price_form.input :custom, :wrapper_html => { :class => 'span1' } %>
<% end %>
<%= def_price_form.input :visit_type, :wrapper_html => { :class => 'span2' } %>
<%= def_price_form.input :price, :wrapper => :prepend, :wrapper_html => { :class => 'span2' }, :label => "Price" do %>
<%= content_tag :span, "$", :class => "add-on" %>
<%= def_price_form.input_field :price %>
<%= def_price_form.link_to_remove '<i class="icon-remove"></i>'.html_safe, :class => 'btn btn-danger', :wrapper_html => { :class => 'span3 pull-left' } %>
<%end%>
<% else %>
<%= def_price_form.input :custom, :hidden => false, :wrapper_html => { :class => 'span1' } %>
<%= def_price_form.input :visit_type, disabled: true, :wrapper_html => { :class => 'span2' } %>
<%= def_price_form.input :price, :wrapper => :prepend, :wrapper_html => { :class => 'span2' }, :label => "Price" do %>
<%= content_tag :span, "$", :class => "add-on" %>
<%= def_price_form.input_field :price, disabled: true %>
<%end%>
<%end%>
</div>
</div>
<% end %>
<%= f.link_to_add "Add a custom price", :client_prices, :class => 'btn btn-success' %>
<p> </p>
<div class="controls">
<%= f.button :submit, :class => 'btn btn-primary' %>
</div>
<% end %>
HTML generated by RoR here
http://jsfiddle.net/59AXJ/
This gets the attribute name from the checkbox that is clicked. Then finds inputs that have similar names, those are the inputs that we will toggle "disabled".
$("input[type='checkbox']").click(function () {
var thisCheckbox = $(this);
var id = $(this).attr("id");
var text = $("label[for=" + id + "]").text().toLowerCase();
var name = $(this).attr("name").replace("[" + text + "]", "");
$("input[name*='" + name + "']").each(function () {
var thisInput = $(this);
if (thisInput.attr("disabled")) {
thisInput.removeAttr("disabled");
} else {
thisInput.attr("disabled", "disabled");
thisCheckbox.removeAttr("disabled");
}
})
});
http://jsfiddle.net/Sbw65/ <-- test it out
As I know, it's impossible to make this on fly, but you can write some unique function on javascript, wich will connect some input with some checkbox by their css class. Something like this (code on CoffeeScript):
changeCheckbox: (class_value) =>
$('[type=checkbox] '+class_value)). on 'check', (e) ->
$input = $(e.currentTarget)
if !$input.prop("checked")
$("input "+class_value).prop('disabled', false)
else
$("input "+class_value).prop('disabled', true)
After that, you just need to add some class for connected checkbox and inputs.
i have followed the railscasts episode on nested forms(part 1 and 2) and having difficulty with adding fields using jquery, however when i click the remove fields link, the field gets removed.
Here is the code.
In my question model i have
class Question < ActiveRecord::Base
has_many :tags, :class_name => "Tag", :dependent => :destroy, :foreign_key => "question_id"
accepts_nested_attributes_for :tags, :reject_if => lambda { |a| a[:keyword].blank? }, :allow_destroy => true
In my tag model i have
class Tag < ActiveRecord::Base
attr_accessible :keyword, :question_id
belongs_to :question, :class_name => "Question", :foreign_key => 'question_id'
end
In my question form i have
<%= form_for #question, :url => { :controller => "questions", :action => "create" } do |f| %>
<%= f.label(:name, "Request Question:") %>
<%= f.text_field(:name, :size => 72, :maxlength => 120) %><br />
<%= f.fields_for :tags, :url => { :controller => "tags", :action => "create" } do |builder| %>
<%= render "tag_fields", :f => builder %>
<% end %>
<p><%= link_to_add_fields "Add new tag", f, :tags %></p>
<% end %>
In my tag_fields partial
<p class="fields">
<%= f.label(:keyword, "Keywords:") %>
<%= f.text_field(:keyword, :size => 20, :maxlength => 25) %>
<%= link_to_remove_fields "remove", f %>
</p>
In application_helper.rb
module ApplicationHelper
def link_to_remove_fields(name, f)
f.hidden_field(:_destroy) + link_to_function(name, "remove_fields(this)")
end
def link_to_add_fields(name, f, association)
new_object = f.object.class.reflect_on_association(association).klass.new
fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
render(association.to_s.singularize + "_fields", :f => builder)
end
link_to_function(name, h("add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")"))
end
end
Then finally in my application.js
function remove_fields(link) {
$(link).prev("input[type=hidden]").val("1");
$(link).closest(".fields").hide();
}
function add_fields(link, association, content) {
var new_id = new Date().getTime();
var regexp = new RegExp("new_" + association, "g")
$(link).parent().before(content.replace(regexp, new_id));
}
I have checked to see if files are included in page source. The jquery works because
other parts of my app are working. I do not get any error when i click add new tag.
I have looked at other solutions, but none work for me. I cannot seem to add a field.
Thanks for the help
I managed to figure this on out, but i am not sure if it is the best way.
In application_helper.rb i changed the following line from this
link_to_function(name, h("add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")"))
to this
link_to_function(name, "add_fields(this, '#{association}', '#{escape_javascript(fields)}')", :remote => true)
i am not 100% sure why it works, but i believe its got to do with rails 3 no longer having the link_to_function. Hope this help