how to combine ruby code with javascript in rails 7 - javascript

I need to combine ruby code with javascript, to show something which depends on the server's response.
app/views/posts/index.html.erb
<h1 class="text-center m-4 text-success">Posts</h1>
<div id="posts" data-controller="like" data-like-url-value="<%= like_posts_path %>">
<div class="row">
<% #posts.each do |post| %>
<%= render post %>
<% end %>
</div>
</div>
<script>
if(some condition){
console.log("All Posts has been loaded successfully.")
}
</script>
app/controllers/posts_controller.rb
def index
#posts = Post.all.order(id: :desc)
if #posts.length > 0
session[:post] = true
end
end
I have to use the index action session variable in the index template, I don't have any idea about how to combine ruby code with javascript.

The condition is set and can be checked on the server and the view is rendered on the server. That means you can simple use ERB to check the condition and then add a <script> block when it is true. The script block will run on the client.
# in the controller
def index
#posts = Post.all.order(id: :desc)
#post_loaded = #posts.length > 0
end
# in the view
<h1 class="text-center m-4 text-success">Posts</h1>
<div id="posts" data-controller="like" data-like-url-value="<%= like_posts_path %>">
<div class="row">
<% #posts.each do |post| %>
<%= render post %>
<% end %>
</div>
</div>
<% if #post_loaded %>
<script>
console.log("All Posts has been loaded successfully.")
</script>
<% end %>

I have a solution for the above question, Which you have asked.
app/controllers/posts_controller.rb
def index
#posts = Post.all.order(id: :desc)
if #posts.length > 0
session[:post] = "some value"
end
end
app/views/posts/index.html.erb
<h1 class="text-center m-4 text-success">Posts</h1>
<div id="posts" data-controller="like" data-like-url-value="<%= like_posts_path %>">
<div class="row">
<% #posts.each do |post| %>
<%= render post %>
<% end %>
</div>
</div>
<script>
let post = "<%= session[:post] %>";
console.log(post);
</script>

You can also try this; if your application has a lot of code, it will become easier to handle that and improve code quality as well.
app/controllers/posts_controller.rb
def index
#posts = Post.all.order(id: :desc)
if #posts.length > 0
session[:post] = true
end
end
app/views/posts/index.html.erb
<%= javascript_include_tag "my_javascript" %>
<h1 class="text-center m-4 text-success">Posts</h1>
<div id="posts" data-controller="like" data-like-url-value="<%= like_posts_path %>">
<div class="row">
<% #posts.each do |post| %>
<%= render post %>
<% end %>
</div>
</div>
app/javascript/my_javascript.js
if(some condition){
console.log("All Posts has been loaded successfully.")
}

Related

HTML and ejs Tags Messed Up After Save

I am pretty new to coding and learning templating at the moment. I am having trouble with html and ejs tags auto formatting after save.
As you can see in the images below (sorry, cannot embed images on the post yet since I am new here), html and ejs tags get separated after saving the file and it looks ugly. How can I prevent them from separating?
Saving also auto-indents codes after ejs tags... I want to use format on save, but don't want the indentation.
'Files:Associations' setting already has '*.ejs - html'.
'Emmet: Include Languages' setting also has 'ejs - html'.
'Editor: Default Formatter' is set to None.
Extensions I have are 'EditorConfig for VS Code' and 'EJS language support'
Code Before Save:
<body>
<div class="container">
<h1>Browsing The <%= name %> subreddit</h1>
<h2><%= description %></h2>
<p><%= subscribers %> Total Subscribers</p>
<hr>
<% for(let post of posts) { %>
<article>
<p><%= post.title %> - <b><%=post.author %></b></p>
<% if(post.img) { %>
<img src="<%= post.img %>" alt="">
<% } %>
</article>
<% } %>
</div>
</body>
Code After Save:
(for example, the bold tag looks out of place and I also want p and b tags in one line along with the ejs tags)
<body>
<div class="container">
<h1>Browsing The <%= name %> subreddit</h1>
<h2>
<%= description %>
</h2>
<p>
<%= subscribers %> Total Subscribers
</p>
<hr>
<% for(let post of posts) { %>
<article>
<p>
<%= post.title %> - <b>
<%=post.author %>
</b>
</p>
<% if(post.img) { %>
<img src="<%= post.img %>" alt="">
<% } %>
</article>
<% } %>
</div>
</body>
Indentation on Save - After ejs Tags
<%- include('partials/head') %>
<%- include('partials/head') %>
<body>
<%- include('partials/navbar') %>
<h1>Your random number is: <%= num %>
</h1>
<% if (num % 2===0) { %>
<h2>That is an even number!</h2>
<% } else { %>
<h2>That is an odd number!</h2>
<% } %>
<h3>
<%= num % 2===0 ? 'Even' : 'Odd' %>
</h3>
</body>
</html>
BeforeSave
AfterSave
Files: Associations setting
Emmet: Include Languages

How to trigger a JS code once when a new model is created in rails?

I'm trying to have an 'Achievement Unlocked' JS animation play on my show page when someone creates a new cocktail model. So it should only play once and never again on that particular page. I'm kinda stuck on how to do that without creating a separate table for 'Achievements' in my DB. Does anyone have a simple solution? Here's my controller (the NUM, #num, and #num2 are variables I created to figure out a solution, feel free to disregurad):
class CocktailsController < ApplicationController
NUM = Cocktail.count
def index
#cocktails = Cocktail.all
end
def show
#cocktail = Cocktail.find(params[:id])
# #dose = Dose.new
#num = NUM
end
def new
#cocktail = Cocktail.new
end
def create
#cocktail = Cocktail.new(cocktail_params)
#num = NUM
if #cocktail.photo.attached?
#num2 = #num + 2
#cocktail.save
redirect_to cocktail_path(#cocktail)
else
redirect_to new_cocktail_path,
warning: "Sorry, you can't create a cocktail without
uploading an image"
end
end
def edit
#cocktail = Cocktail.find(params[:id])
end
def update
#cocktail = Cocktail.find(params[:id])
#cocktail.update(cocktail_params)
redirect_to root_path
end
def destroy
#cocktail = Cocktail.find(params[:id])
#cocktail.destroy
# redirect_to root_path
end
private
def cocktail_params
params.require(:cocktail).permit(:name, :photo)
end
end
My HTML (The first div is the animation's html. My current code allows the animation to play more than once in all cocktail show pages that were created after the 7th cocktail) :
<% if #num < 7 %>
<div id="achievement" class="">
<div class="circle"></div>
<div class="copy">
<h4>Achievement Unlocked!</h4>
<p>Here are some internet points!</p>
</div>
</div>
<% end %>
<div class="banner" style="background-image: linear-gradient(rgba(0,0,0,0.4),rgba(0,0,0,0.4)), url( <% if #cocktail.photo.attached? %> <%= cl_image_path #cocktail.photo.key %> <% else %> https://raw.githubusercontent.com/lewagon/fullstack-images/master/uikit/lunch.jpg <% end %>)">
<div class="container">
<h1><%= #cocktail.name %></h1>
</div>
</div>
<br>
<div class="center">
<%= link_to "Add A New Dose", new_cocktail_dose_path(#cocktail), class: "btn btn-ghost" %>
</div>
<div class="cards">
<% #cocktail.doses.each do |dose| %>
<div class="card" >
<div class="card-body">
<h5 class="card-title"><%= dose.ingredient.name %></h5>
<p class="card-text"><%= dose.description %></p>
<%= link_to "Delete", cocktail_dose_path(#cocktail, dose), class: "btn btn-dark", method: :delete %>
</div>
</div>
<% end %>
</div>
<br>
<div class="center">
<%= link_to "Back", root_path, class: "btn btn-ghost" %>
</div>
Pass that info in params (the info that it is first or not) in params in the redirect after create and check in render view if params[:first_time].exists? and execute JS in that case.
You did not need to allow it in params as it was not something being "posted" from view to controller. NameError means you did not define it and that happened because you were sending #num2 = #num2 (key-value pair in params hash) and u had not defined num2's value. You could do num2: "I am num2" or anything and that would work :)

Ruby on Rails on rendering with Ajax

I am working on a simple website which has posts in forms of songs, now I have implemented like and dislike feature, but when I click on like/dislike it renders the numbers of likes/dislikes on all posts. And when I reload the page it returns them to normal. Now I would like it to change only the numbers for that particular post, without affecting the numbers on other posts?
My view for posts:
<div class="col-6">
<% for #s in #songs %>
<div class="card border-secondary mb-1">
<div class="card-header">
<div class="col-1">
<%= image_tag User.find(#s.user_id).user_image.url, :size=>"50x50" %>
</div>
<div class="col-11 text-muted">
<a href="/user/<%= User.find(#s.user_id).username %>" class="info-col">
<%= User.find(#s.user_id).username %>
</a>
- <%= #s.created_at.to_formatted_s(:short) %>
</div>
</div>
<div class="card-body">
<h4 class="card-title">
<%= #s.title %>
</h4>
<p class="card-text"><%= simple_format(#s.content) %></p>
</div>
<div class="card-footer text-muted">
<div class="col-12">
<div class="row">
<div class="col-3" id="song_like">
<%= render '/components/song_like' %>
</div>
<div class="col-3" id="song_dislike">
<%= render '/components/song_dislike' %>
</div>
<div class="col-4" id="song_comment">
<%= render '/components/song_comment' %>
</div>
</div>
</div>
</div>
</div>
<% end %>
</div>
The song_like partial has the following code:
<%= link_to like_song_path(#s.id, like: true), remote: true, method: :post do %>
<i class="fa fa-thumbs-o-up fa-lg" aria-hidden="true"></i> &nbsp <span class="songlikes">
<%= #s.thumbs_up_total %>
</span>
<% end %>
The song_dislike partial has the following code:
<%= link_to like_song_path(#s.id, like: false), method: :post, remote: true do %>
<i class="fa fa-thumbs-o-down fa-lg" aria-hidden="true"></i> &nbsp <span class="songdislikes">
<%= #s.thumbs_down_total %>
</span>
<% end %>
And my 'like' controller is like:
def like
#s = Song.find(params[:id])
#like = Like.create(like: params[:like], user: current_user, song: #s)
respond_to do |format|
if #like.valid?
format.html
format.js
else
format.html
format.js
end
end
end
This is how like.js.erb looks like:
$('.songlikes').html("<%= #s.thumbs_up_total %>");
$('.songdislikes').html("<%= #s.thumbs_down_total %>");
Here is the part of routes.rb file:
resources :songs do
member do
post 'like'
end
end
I am assuming there is some issue on rendering or jquery, but can't figure it out. Do You have any instructions?
EDIT: You should remove the redirect_to in your f.html?
This reloads the page - and the js will therefore not work.
An Ajax call is per definition not reloading the page.
If you remove those lines and try the following:
There's no new data in your jQuery.
You need a global attribute, so the view can find the data from your controller.
For example:
#like = ...
Then in your view you can add a local to be rendered:
$("#song_like").load("<%=j render partial: '/components/song_like', locals: {like: #like} %>");
But, you need to change your partial to be a partial. And change the variable to like so it will be rendered in the right way.
Hope it helps!
UPDATE
If you want to hit a specific song you can render it with each and then assign an id to your post's class
For example:
<% #posts.each do |post| %>
<div class="post-content">
... other stuff ...
<div class="likes like-post-<%= post.id %>">
.. number of likes
</div>
<%= link_to "Like", link_route_path(post) %>
</div>
<% end %>
# controller
# So you can use the id in your js view
#id = params[:id]
# view.js
$(".like-post-" + "<%= #id %>").html("<%= #s.thumbs_up_total %>");
Hope the idea helps.
change rendering like this:
<div class="col-3" id="song_like">
<%= render partial: '/components/song_like', locals: {s: #s, like: true} %>
</div>
then in partial:
<%= link_to like_song_path(s.id, like: like), method: :post, remote: true do %>
<i class="fa fa-thumbs-o-up fa-lg" aria-hidden="true"></i> &nbsp <%= s.thumbs_up_total %>
<% end %>
and in controller of like_song
should define
#s= Song.find(params[:id])
like = Like.create(like: params[:like], user: current_user, song: #song)
#like = !like.like # opposite to your current status
rest of the things good and then in like.js.erb
$('#songs_like').html("<%= escape_javascript(render partial: '/components/song_like', locals: { s: #s, like: #like } ) %>")

remove comment from view using AJAX rails

I have a directory app that has comments. I add and delete comments with remote: true and I want to fade them out in my view with Jquery.
How do I go about getting a reference to the comment I want to fade out in the view? I can delete the comment just fine, but I don't know how to fade the comment out in my view
Here is my controller:
class CommentsController < ApplicationController
def create
business = Business.find(params[:business_id])
new_comment = business.comments.create
new_comment.comment = params[:comment_text]
new_comment.save
respond_to do |format|
format.html {redirect_to business}
format.js
end
end
def destroy
business = Business.find(params[:business])
comment = Comment.find(params[:id])
comment.destroy!
params[:id] = business.id
respond_to do |format|
format.html {redirect_to business_url(id: params[:id])}
format.js
end
end
end
Comments are rendered in my business view like this:
<aside class="comment-section">
<h4 class="thin-grey-underline"><span class="glyphicon glyphicon-comment glyphicon-left-space"></span> Reviews </h4>
<% if !#business.comments.empty? %>
<% #business.comments.each do |comment| %>
<div id="comments">
<%= render partial: 'comment', locals: {comment: comment, business: #business} %>
</div>
<% end %>
<% else %>
<p> No reviews yet... </p>
<% end %>
<%= render partial: 'comments_form' %>
</aside>
my single comment partial:
<div class="row single-comment vertical-align">
<div class="col-sm-12 col-md-3">
<div class="profile-image" style="background: url('<%= business.profile_image_url(:thumb).to_s %>') no-repeat; background-position:center"></div>
</div>
<div class="col-sm-12 col-md-9">
<h5><%= comment.title %></h5>
<p><%= comment.comment %></p>
<%= button_to(business_comment_path(#business.id,comment.id), {method: :delete, remote: true, class:"btn btn-danger delete-comment-button pull-right", params:{id: comment.id, business: business.id}} ) do%>
<i class="glyphicon glyphicon-trash"></i>
<% end %>
</div>
</div>
the form partial to create a new comment:
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="comments-form">
<%= form_tag(business_comments_path(#business), html: { id: "comment-form" }, method: 'post', remote: true) do %>
<h5 class="thin-grey-underline"><span class="glyphicon glyphicon-pencil glyphicon-left-space"></span> Write a Review </h5>
<%= text_area_tag(:comment_text,nil, class: "comment-form-input")%>
<%= hidden_field_tag 'business_id', params[:id] %>
<%= submit_tag("Comment", class:"btn btn-primary") %>
<% end %>
</div>
</div>
</div>
If I have a list of comments, how do I delete a specific comment by clicking on the delete link and also have it removed from my view in a fadeout manner?
I have a destroy.js.erb file that gets called, but I don't know how to get a reference to fade out the specific comment.. not sure if I am explaining myself clearly here
You could try something like this:
In your comment partial:
<div id="comment_<%= comment.id %>" class="row single-comment vertical-align">
destroy.js.erb:
$('#comment_<%= #comment.id %>').remove();
Knockoutjs can do that The "if" binding
First step is to make sure that the thing you want to delete has a unique id, and that this id is easy to generate. I patched AR::Base with an instance method called dom_id for this, which is like so:
def dom_id
"#{self.class.name.underscore}-#{self.id}"
end
which means if i'm rendering out a list of Foo objects i can do something like
<%= render :partial => "foos/foo", :collection => #foos %>
and in the partial do this
<li id="<%= foo.dom_id %>">
....
</li>
and the id will be something like "foo-123".
Now, in my destroy.js.erb action, after deleting a foo object, i know that it's dom id on the page can be got with #foo.dom_id. To fade it out and delete it i would then do
page << "$('##{#foo.dom_id}').fadeOut('slow', function(){$(this).remove();});"
You can use helper dom_id method (docs):
Change <div id="comments"> to <div id="<%= dom_id comment %>"> or add id attribute to div.single-comment
In js response do $('#<%= dom_id #comment %>').fadeOut();
Another option is to bind to jQuery event like this:
$('body').on('ajax:success', '.delete-comment-button', function(event) {
$(event.original_target).closest('.single-comment').fadeOut();
})
Then you can just return status 2XX for the delete request

dynamic page caching in rails

How to add dynamic caching to time or I should use fragment caching?
I tried to follow RailsCasts but nothing works.
_feed.html.rb
<% cache feed do %>
<div class="feed">
<div id="time">
<%= render 'shared/time' unless #page_caching %>
</div>
<p class="pull-right grey"><%= time_ago_in_words(feed.posted_at) %></p>
<div class="clearfix">
<p class="grey"><%= image_tag feed.profile_pic, :class => 'pic', :size => '15x15' %> #<%= feed.name %></p>
</div>
<p class="content"><%= raw auto_link(feed.content, :username_include_symbol => true, :target => '_blank') %></p>
</div>
<% end %>
_time.html.rb
<p class="pull-right grey"><%= time_ago_in_words(feed.posted_at) %></p>
index.js.rb
$('#feeds').append('<%= j render(#feeds) %>');
<% if #feeds.next_page %>
$('.pagination').replaceWith('<%= j will_paginate(#feeds) %>');
<% else %>
$('.pagination').remove();
<% end %>
$('#time').prepend('<%= j render("shared/time") %>');
feeds_cotroller.rb
def index
#page_caching = true
#feeds = Feed.page(params[:page]).per_page(12)
end
using helpers that changes over time should not be used inside a cache block. The best solution to this is to use a js plugin that will insert the time string for you. google for timeago js plugin and you'll get a bunch of useful results. The first result there is in http://timeago.yarp.com/. Using that plugin, your code should look like the following (i've inserted a script which should insert the time string)
<% cache feed do %>
<div class="feed">
<div id="time">
<%= render 'shared/time' unless #page_caching %>
</div>
<p class="pull-right grey timeago" title="<%= feed.posted_at %>"></p>
<script>
jQuery(document).ready(function() { jQuery("p.timeago").timeago() })
</script>
<div class="clearfix">
<p class="grey">
<%= image_tag feed.profile_pic, :class => 'pic', :size => '15x15' %> #<%= feed.name %>
</p>
</div>
<p class="content"><%= raw auto_link(feed.content, :username_include_symbol => true, :target => '_blank') %></p>
</div>
<% end %>
This is a simple implementation. You probably want to move the js call to an asset file so you dont have to call it inside the cache. Anyway, this should give you an idea.

Categories