My existing sessions_controller use inline javascript exit to root_path as follow:
render inline: "<script>window.location.replace('#{url}')</script>"
It worked in Rails 4 but not in Rails 5.
Most document talking about calling javascript in view.
But this is controller.
From what I understand I should replace with
render :js => "myNewJavascriptFunction();"
Which I did tried create myNewJavascriptFunction() in another file eg:
views/sessions/create.js.erb it has error
undefined method `myNewJavascriptFunction' for
Help:
How or Should I put the javascript in views/sessions/create.js.erb
Can we put the javascript in assets to work with controller and how.
Here my existing codes:
In SessionsController
def create
auth = request.env["omniauth.auth"]
user = User.from_omniauth(auth)
session[:user_id] = user.id
if params.permit[:remember_me]
cookies.permanent[:auth_token] = user.auth_token
else
cookies[:auth_token] = user.auth_token
end
# render :js => refresh(root_path) #not work
# redirect_to root_path #not work with JQM
render inline: "<script>window.location.replace('#{url}')</script>" #not work with Rails 5
rescue
render inline: "<script>window.location.replace('#{url}')</script>" #not work with Rails 5
end
views/sessions/create.js.erb
function refresh(url='/') {
window.location.replace(\'#{url}\');
}
menu call
- if login?
%li
%a{"data-panel" => "main", :href => logout_path, "data-ajax"=>"false"} Sign Out
- else
%li
%a{:href=>new_session_path, "data-panel" => "main"} Sign In
%li
%a{:href=>new_identity_path, "data-panel" => "main"} Sign Up
%li
= link_to "Refresh", "#", :onclick=>"window.location.replace('/')"
- if !login?
= link_to image_tag( 'facebook-sign-in-button.png'), '/auth/facebook', rel: "external"
No need to have a views/sessions/create.js.erb
Just replace render inline: "<script>window.location.replace('#{url}')</script>" with render js: "window.location.replace('#{url}')"
Related
I have follow and unfollow buttons for users on my application. I don't want to do anything fancy, I just want to not have the page refresh every time follow or unfollow button is clicked.
My controller
relationships_controller.rb
def create
current_user.follow(#user)
respond_to do |format|
format.html { #handle HTML, i.e. full page reload }
format.js # handle ajax request
end
end
def destroy
current_user.unfollow(#user)
respond_to do |format|
format.html
format.js # this one handle the request comes from `remote: true` button
end
end
My view
tweets/index.html.erb
<% if current_user.id != tweet.user.id %>
<% if current_user.following?(tweet.user) %>
<%= button_to "Unfollow", relationships_path(user_id: tweet.user), remote: true, method: :delete, :class => "btn btn-primary" %>
<% else %>
<%= button_to "Follow", relationships_path(user_id: tweet.user), remote: true, :class => "btn btn-primary" %>
<% end %>
<br>
<% end %>
<hr/>
<% end %>
Relationships model
relationship.rb
class Relationship < ApplicationRecord
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
validates :follower_id, presence: true
validates :followed_id, presence: true
end
User model
User.rb
has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
has_many :passive_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
has_many :followers, through: :passive_relationships, source: :follower
def follow(user)
active_relationships.create(followed_id: user.id)
end
def unfollow(user)
active_relationships.find_by(followed_id: user.id).destroy
end
def following?(user)
following.include?(user)
end
Routes
routes.rb
resource :relationships, :only => [:create, :destroy]
Application.js
require("#rails/ujs").start()
require("turbolinks").start()
require("#rails/activestorage").start()
require("channels")
require("chartkick")
require("chart.js")
//= require jquery3
//= require popper
//= require bootstrap-sprockets
Inspected button element
<form class="button_to" method="post" action="/relationships?user_id=1" data-remote="true"><input class="btn btn-primary" type="submit" value="Follow"><input type="hidden" name="authenticity_token" value="hfwF8wXBcp/OM2P/pCYBnEBrjw22BDKWbw/dZFwwDsRpiIFq5jBKS/AoTMjkCZRrGum7UyW1kaL3h/4XEM2wIg=="></form>
With this when I click follow nothing now happens. I think I need a new js file in my views but unsure how to implement it.
I have looked at solutions but they are many and varied and seeking to do more than I want to do which is just a simple no refresh.
How is this best achieved? (can provide more code if needed)
EDIT: This code got jQuery working in Rails 6 in my ..webpack/enironment.js file
# app/config/webpack/environment.js
const {environment} = require('#rails/webpacker');
const webpack = require('webpack');
environment.plugins.append('Provide', new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery' # or if its not work specify path `'jquery/src/jquery'` which node_modules path for jquery
}));
module.exports = environment;
For #max
$(document).on('ajax:success', '.follow-btn', function(event){
let $el = $(this);
let method = this.dataset.method;
if (method === 'post') {
$('.follow-btn[href="'+this.href+'"]').each(function(el){ $(el).text('Unfollow'); });
this.dataset.method = 'delete';
} else if (method === 'delete') {
$('.follow-btn[href="'+this.href+'"]').each(function(el){ $(el).text('Follow'); });
this.dataset.method = 'post';
}
});
Instead of going down the js.erb rabbit hole you can just send a JSON request and write a simple event handler.
Lets start by adding a data-type="json" attribute to the buttons so they send a request for JSON instead of javascript:
<% unless current_user == tweet.user %>
<% if current_user.following?(tweet.user) %>
<%= link_to "Unfollow", relationships_path(user_id: tweet.user),
data: { remote: true, type: :json, method: :delete },
class: "follow-btn btn btn-primary"
<% else %>
<%= link_to "Follow", relationships_path(user_id: tweet.user),
data: { remote: true, type: :json, method: :post},
class: "follow-btn btn btn-primary"
%>
<% end %>
<% end %>
And then just write JSON responses for your controller.
def create
current_user.follow(#user)
respond_to do |format|
format.html
format.json { head :created }
end
end
def destroy
current_user.unfollow(#user)
respond_to do |format|
format.html
format.json { head :no_content }
end
end
As you can see its pretty damn simple, when creating a resource you return a 201 - Created and usually a location header or the entity in the body (a JSON payload describing what was created). When you update or destroy a record a 204 - No Content status code is sufficient.
If you test it now and look at the network tab in your browser inspector you will see that an AJAX request is sent but nothing happens in the view.
So lets write an event handler that toggles the button text and method after the request was sent. Since Rails UJS already created the AJAX handler for the button for us we can just hook into its events:
// put this in your application.js or anywhere in your pack
$(document).on('ajax:success', '.follow-btn', function(event){
let $el = $(this);
let method = this.dataset.method;
if (method === 'post') {
$el.text('Unfollow');
this.dataset.method = 'delete';
} else if (method === 'delete') {
$el.text('Follow');
this.dataset.method = 'post';
}
});
Why is this better than a js.erb template?
No server side involvement in updating the UI on the client. No spagetti-code views.
JavaScript is minified, not generated by ERB and easy to debug/reason about.
It can be changed to use optimistic create/delete to give instant feedback
I have a simple inventory system application I've been developing for a class project. One of the requirements is to have some form of Javascript and AJAX doing something in the app. It doesn't have to be anything large or super complicated, but it does have to be there. What my group decided to do was render a popup that displays information about an item when you click on the 'show' link, as it was similar to an example the professor did in class and it was somewhat useful for our app as well. However I cannot get it to work, it just bypasses the ajax and javascript stuff and goes straight to the show.html.haml page. Here is my code:
index.html.haml
%p#notice= notice
%h1 Items
%table
%thead
%tr
%th Name
%th Description
%th Quality
%th Price
%th Location
%th Options
%tbody
- #items.each do |item|
%tr
%td= item.name
%td= item.description
%td
= item.quality
%br/
%br/
= item.quality_desc
%td= item.price
%td= item.location
%td{:colspan => "3"}
= link_to 'Show', item, class: "items"
= link_to 'Edit', edit_item_path(item)
= link_to 'Destroy', item, method: :delete, data: { confirm: 'Are you sure?' }
%br/
= link_to 'New Item', new_item_path
_item.html.haml
This is the information that the popup is supposed to display
%h1 Items
%h2
= item.name
, #{item.category}
%br/
- item.images.each do |image|
= image_tag(image.small.url)
%br
Price: $#{item.price}
%br/
Description: #{item.description}
%br/
Quality: #{item.quality},
\#{item.quality_desc}
%br/
Location: #{item.location}
%br/
%br/
%br/
= link_to 'Edit', edit_item_path(#item)
= link_to 'Close', '', id: 'closeLink'
items_controller.rb
class ItemsController < ApplicationController
before_action :set_item, only: [:show, :edit, :update, :destroy]
# GET /items
# GET /items.json
def index
#items = Item.all
end
# GET /items/1
# GET /items/1.json
def show
render(:partial => 'item', :object => #item) if request.xhr?
end
# GET /items/new
def new
#item = Item.new
end
# GET /items/1/edit
def edit
end
# POST /items
# POST /items.json
def create
#item = Item.new(item_params)
respond_to do |format|
if #item.save
format.html { redirect_to #item, notice: 'Item was successfully created.' }
format.json { render :show, status: :created, location: #item }
else
format.html { render :new }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /items/1
# PATCH/PUT /items/1.json
def update
respond_to do |format|
if #item.update(item_params)
format.html { redirect_to #item, notice: 'Item was successfully updated.' }
format.json { render :show, status: :ok, location: #item }
else
format.html { render :edit }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
end
end
# DELETE /items/1
# DELETE /items/1.json
def destroy
#item.destroy
respond_to do |format|
format.html { redirect_to items_url, notice: 'Item was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_item
#item = Item.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def item_params
params.fetch(:item, {}).permit(:name, :description, :quality, :quality_desc, :price, :location, :category, { images: [] })
end
end
items.js
var ItemPopup = {
setup: function() {
// add hidden 'div' to end of page to display popup:
var popupDiv = $('<div id="itemInfo"></div>');
popupDiv.hide().appendTo($('body'));
$(document).on('click', '#items a', ItemPopup.getItemInfo);
},
getItemInfo: function() {
$.ajax({type: 'GET',
url: $(this).attr('href'),
timeout: 5000,
success: ItemPopup.showItemInfo,
error: function(xhrObj, textStatus, exception) {alert('Error!'); }
//'success' and 'error' functions will be passed 3 args
});
return(false);
},
showItemInfo: function(data, requestStatus, xhrObject) {
//center a floater 1/2 as wide and 1/4 as tall as screen
var oneFourth = Math.ceil($(window).width() / 4);
$('#itemInfo').css({'left': oneFourth, 'width': 2*oneFourth, 'top': 250}).html(data).show();
//make the Close link in the hidden element work
$('#closeLink').click(ItemPopup.hideItemInfo);
return(false); //prevent default link action
},
hideItemInfo: function() {
$('#itemInfo').hide();
return(false);
}
};
$(ItemPopup.setup);
application.js
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
// require turbolinks
// require_tree .
//= require items
application.html.haml
!!!
%html
%head
%meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
%title InventoryManager
= csrf_meta_tags
// = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
// = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
= stylesheet_link_tag 'application'
= javascript_include_tag "application"
%body
%nav.navbar.navbar-default
.container-fluid
/ Brand and toggle get grouped for better mobile display
.navbar-header
%button.navbar-toggle.collapsed{"aria-expanded" => "false", "data-target" => "#bs-example-navbar-collapse-1", "data-toggle" => "collapse", :type => "button"}
%span.sr-only Toggle navigation
%span.icon-bar
%span.icon-bar
%span.icon-bar
%a.navbar-brand{:href => "/default/index"} InventoryManager
/ Collect the nav links, forms, and other content for toggling
#bs-example-navbar-collapse-1.collapse.navbar-collapse
%ul.nav.navbar-nav
%li.active
%a{:href => "#"}
Items
%span.sr-only (current)
%ul.nav.navbar-nav.navbar-right
- if admin_signed_in?
%li= link_to "Logout", destroy_admin_session_path, :method => :delete
- elsif employee_signed_in?
%li= link_to "Logout", destroy_employee_session_path, :method => :delete
- else
%li
%a{:href => "/admins/sign_in"} Admin
%li
%a{:href => "/employees/sign_in"} Staff
/ /.navbar-collapse
/ /.container-fluid
= yield
If you need anymore of the code just ask. This is in Ruby on Rails 5
EDIT: So I fixed it by changing the selector in the event handler function so that it read '#items' and it grabbed the elements on my page with that id. However I got the code I was trying to use originally from a textbook, implying that what was there was supposed to work. Can someone explain to me why it didn't work initially?
EDIT2: Nevermind I figured it out.
So it turns out my trouble was this line right here:
$(document).on('click', '#items a', ItemPopup.getItemInfo);
I got this code from my class's textbook and modified it slightly to fit my application. As a result of this and a lack of knowledge of how the selectors worked, I thought this was selecting anchor tags with an id of 'items'. As it turns out, what it was actually doing is getting all the anchor tags inside an element with an id of 'items', in the case of the book, this particular element was a table, and the only links inside that table went to that applications show.html.haml page. That didn't quite work for my app since the index table has 3 different types of links on it, so I changed the line to read
$(document).on('click', '.items', ItemPopup.getItemInfo);
and changed the 'show' links on my index.html.haml to have a class of 'items'. This fixed my problem and it works perfectly now.
That being said if there is a solution to this sort of problem that is considered a better practice feel free to share it as an answer to this proble, as well, I'm all for learning something new.
Basically every time I create a post with AJAX, it won't render the new post because of this line:
<%= link_to "promote", vote_scribble_path(:scribble_id => scribble.id, :vote => true), remote: true, method: :post%>
THE LINK ABOVE ACTUALLY WORKS, it just prevents ajax render of newly created post.
here is the error that i get when trying to create new post with AJAX, it does actually create the post and it would render new post without the line above. After refresh of a page the new post with a working link appear.
Rendered scribbles/_scribbles.html.erb (18.9ms)
Rendered scribbles/create.js.erb (19.7ms)
Completed 500 Internal Server Error in 48ms
ActionController::RoutingError - No route matches {:action=>"vote", :controller=>"scribbles", :scribble_id=>155, :vote=>true}:
Here is the full code in action:
create.js.erb updates posts list after create
/*Replace the html of the div post_lists with the updated new one*/
$("#posts_list").html("<%= escape_javascript( render(:partial => "scribbles/scribbles") ) %>");
feedlist.html does render all of the posts
<div id="posts_list">
<%=render :partial => 'scribbles/scribbles', :locals => {:scribbles => #scribbles}%>
</div>
scribbles/_scribbles.html that link_to single line prevents ajax render. but it works.
<% #scribbles.each do |scribble| %>
<%= link_to "promote", vote_scribble_path(:scribble_id => scribble.id, :vote => true), remote: true, method: :post%>
<%end%>
even something like this gives me an error
<%= link_to "promote", vote_scribble_path()%>
vote_scribble_path(:scribble_id => scribble.id, :vote => true), remote: true, method: :post, :put,:get %> tried them all still same error
THE LINK WORKS IT JUST DOESN'T RENDER after AJAX CREATE, after page refresh everything is fine. any ideas why ?
resources :scribbles do
member do
post 'vote'
end
end
scribble_controller.rb
def vote
#scribble = Scribble.find(params[:scribble_id])
#vote = params[:vote]
if #vote == "true"
#v = :up
current_user.vote(#scribble, :direction => #v)
else
current_user.unvote_for(#scribble)
end
respond_to do |format|
format.js{}
end
end
You should be passing id instead of scribble_id in your link.
<%= link_to "promote", vote_scribble_path(:id => scribble.id, :vote => true), remote: true, method: :post%>
because as per your defined routes, the generated route for vote action would be:
vote_scribble POST /scribbles/:id/vote(.:format) scribbles#vote
which you can check by running rake routes.
As you can see vote_scribble_path is expecting an id placeholder and not a scribble_id which is why you receive the error as Rails cannot find a matching route in your application for scribbles#vote action with placeholder :scribble_id.
the relevant controller code:
def show_host
queue = TestQueue.find(params[:id])
#test_queues = TestQueue.where(:host => queue.host)
respond_to do |format|
format.html { render action: "show_host", :layout => nil}
format.json { render json: #test_queue }
end
end
view:
<iframe name="host_queues", scrolling="yes" width="82%" height="700px", align="right", frameborder="0", rel="stylesheet"></iframe>
<%= link_to host, { :controller => "test_queues", :action => "show_host", :id => id}, {:target => "host_queues"} %>
now the thing is, it works with replace the content of the iframe, but when i try to replace content in a div with class="host_queues", it doesnt work... works only with iframe ..
can any1 out a finger on the reason ? 10x ..
target only applies to frames and windows. You have to use Javascript to replace the contents of a div.
use remote: true option in link_to and than write javascript
OR
use form_tag with remote: true, in update option, give div id which you want to replace
you can chek below link
http://guides.rubyonrails.org/working_with_javascript_in_rails.html
I have form where user can subscribe for latest coupons.When i open same store link in multiple tab and subscribe, i get multiple subscription for single store causing duplication of data and sending same mail multiple times .So added validation on model.So the question is how do i get the error to be shown in view .When a user is already subscribed to a store.Since create is making a ajax request..I have pasted the codes below .Any help would be appreciated Thanks
_index.html.haml (views)
=form_for([#merchant,#coupon_subscription],:remote => true,:html => { :class => "store_subscribe" } ) do |f|
%h3 subscribe for #{#merchant.merchant_name} coupons
-if !logged_in?
=f.text_field :user ,:class => "input_text email"
=f.submit "Subscribe",:disable_with => "Subscribing...",:class => "store_subscribe subscribe_button"
subscription Controller
def create #creates subscription
return if !logged_in?
return if current_user.subscription_limit?
#merchant=Merchant.find_by_permalink(params[:merchant_id])
#coupon_subcription=CouponSubscription.new(:merchant_id => #merchant.id,:user => current_user)
#coupon_subcription.coupon_subscribe
respond_to do |format|
if #coupon_subcription.save!
format.html { redirect_to(:back, :notice => 'Success.') }
format.js
else
format.html { redirect_to(:back, :notice => #coupon_subcription.errors.full_messages || "Oops something went wrong")}
format.js
end
end
UserMailer.delay.coupon_subscription(current_user,#coupon_subcription)
end
Model
validate :validate_subscription ,:on => :create
private
def validate_subscription
#coupon_subscribed=CouponSubscription.find_by_user_id_and_merchant_id_and_active(self.user_id,self.merchant_id,true)
if #coupon_subscribed
self.errors.add(:base , 'You have already subscribed.')
end
end
create.js
<% if !logged_in? %>
$(".store_subscribe").remove()
$(".subscription_feedback").show().append("Login!")
<% else %>
$(".store_subscribe").remove()
$(".store_subscribe").show().append("<h3>Thanks for subscription .We have send you a mail </h3>")
<% end %>
In your create.js.erb file,
<% if #coupon_subscription.errors.present? %>
$("#id").html(#coupon_subscription.errors.full_messages);
<% end %>