Rails 5 - Reinitializing Javascript after Ajax refresh? - javascript

So I have gotten a better understanding of the issue I have involving Javascript. The problem lies within the fact that after the AJAX call to refresh the contents of a div, the contents within that div are unlinked or uninitialized for the Javascript. What I am trying to do is make my list of activities sortable for the user. I use a priority field to save the order of the activities so when the user refreshes or leaves the page, then the order is maintained.
The problem I am receiving is the following:
ActiveRecord::RecordNotFound (Couldn't find Activity with 'id'=item):
and as a result, the activity order will not be saved. This happens after the AJAX call and refresh and it works normally beforehand, saving the order and whatnot. I have tried some solutions, such as one and moving the Javascript over into the partial to no success.
I believe the error above is the result of the activity not linking correctly with the Javascript file which leads it to an id of "item" so does anyone know how to fix this issue or any advice on how to fix it? Thanks!
Main View:
<!-- home.html.erb -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap 101 Template</title>
</head>
<!-- the jScrollPane script -->
<!--IF THE USER IS LOGGED IN, DISPLAY THE FOLLOWING -->
<% if current_user %>
<!--
<style>
.navbar {
background-color:#2F4F4F
}
</style>
-->
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href='home'>TimeTracker</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li>Signed in as <%=current_user.email %></li>
<li>
<%= link_to 'Sign Out', sign_out_path, method: :delete %>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css">
<h1 align = "center">Activities</h1>
<div id = 'activity_container'>
<ul id="Categories" class="connectedSortable">
<% #categories.each do |cats| %>
<% if cats.user_id == current_user.id%>
<div class="panel-group">
<div class="panel text-left">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#collapse<%= cats.id%>">
<%= cats.c_name %>
</a>
</h4>
</div>
<div id="collapse<%= cats.id%>" class="panel-collapse collapse">
<ul id="category_activities" class=".connectedSortable">
<% cats.activities.each do |activity| %>
<li class="list-group-item" >
<% if activity.hidden == false || activity.hidden == nil%>
<label class="my_label"><%= activity.a_name %></label>
<!-- Delete Activity -->
<%= link_to destroy_act_path(activity.id), method: :delete, data: { confirm: "Are you sure?" } do %>
<i class="fa fa-trash-o" aria-hidden="true" title="Delete"></i>
<% end %>
<!-- Edit activity -->
<%= link_to edit_act_path(activity.id)do %>
<!--<button class="editActivity" style="border:none; padding:0; background-color: transparent">-->
<i class="fa fa-pencil fa-fw" aria-hidden="true" title="Edit" id="editActivity"></i>
<!--</button>-->
<% end %>
<!-- Hide activity -->
<%= link_to hide_act_path(activity.id), method: :post do %>
<i class="fa fa-eye" aria-hidden="true" title="Hide" id="item"></i>
<% end %>
<% end %>
<% end %>
</li>
</ul>
</div>
</div>
</div>
<% end %>
<% end %>
</ul>
<ul id="Activities" class="connectedSortable" >
<!-- List each activity in database -->
<% #activities.each do |activity| %>
<% if (activity.hidden == false || activity.hidden == nil) && activity.category_id == nil %>
<li class="list-group-item" id='<%=activity.id%>' style="list-style: none;">
<!-- Display activity name -->
<label class="my_label"><%= activity.a_name %></label>
<!-- Delete Activity -->
<%= link_to destroy_act_path(activity.id), method: :delete, data: { confirm: "Are you sure?" }, remote: true do %>
<i class="fa fa-trash-o" aria-hidden="true" title="Delete"></i>
<% end %>
<!-- Edit activity -->
<%= link_to edit_act_path(activity.id)do %>
<button class="editActivity" style="border:none; padding:0; background-color: transparent">
<i class="fa fa-pencil fa-fw" aria-hidden="true" title="Edit" id="editActivity"></i>
</button>
<% end %>
<!-- Hide activity -->
<%= link_to hide_act_path(activity.id), method: :post do %>
<i class="fa fa-eye" aria-hidden="true" title="Hide" id="item"></i>
<% end %>
</li> <!-- End of list item -->
<% end %> <!-- End of if statement -->
<% end %> <!-- End of activity loop -->
</ul>
</div>
<ul class="pager">
<li class="previous"><span aria-hidden="true">←</span>Previous </li>
<li class="next"> Next<span aria-hidden="true">→</span></li>
<!-- *****************NEW*********************** -->
<%= form_for #activity, :url => create_act_path, remote: true, data: {type: 'script'} do |a| %>
<%= a.text_field :a_name, id: 'a_name_field', placeholder: 'Activity Name'%>
<%= a.select :category_id, Category.all.collect { |c| [c.c_name, c.id] }, include_blank: "--No Category--" %>
<%= a.submit 'Create', id: 'submitButton', class: 'btn btn-primary'%>
<% end %>
<%= form_for #category, :url => create_cat_path, remote: true do |c| %>
<%= c.text_field :c_name, id: 'c_name_field', placeholder: 'Category Name'%>
<%= c.submit 'Create', id: 'submitButton', class: 'btn btn-primary'%>
<% end %>
<!-- Button for showing all hidden items -->
<%= link_to unhide_act_path, method: :post do %>
<button class="showHidden" >Show Hidden</button>
<% end %>
<!-- Button to sort -->
<button class="sortActivity">Sort</button>
<button class="doneSorting">Done Sorting</button>
<!-- ***************************************** -->
</ul>
<!-- IF THE USER IS NOT LOGGED IN, DISPLAY THE FOLLOWING -->
<% else %>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">TimeTracker</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li class="active">
</li>
</ul>
</div>
</div>
</nav>
<div class="loginContainer" align="center">
<h1 align="center">
<b>
Please log in or sign up
</b>
</h1>
<div class="container">
<br>
<%= button_to 'Login', sign_in_path, :method => 'get', class: 'btn' %>
<br>
<%= button_to 'Sign Up', sign_up_path, :method => 'get', class: 'btn' %>
</div>
<!--<div class="container" style="background-color:#D3D3D3">
<input type="checkbox" checked="checked"> Remember me
<span class="psw">Forgot <a align="center" href="#">password?</a></span>
</div> -->
</div>
<% end %>
<script type="text/javascript">
function changeImg(img) {
if (img.src == "<%=asset_path('_unfilledbubble.png')%>"){
img.src = "<%=asset_path('_filledbubble.png')%>";
}
else {
img.src = "<%=asset_path('_unfilledbubble.png')%>";
}
}
</script>
<script type="text/javascript">
$(document).ready(function() {
$(function () {
$('.scroll-pane').jScrollPane({showArrows: true});
});
});
</script>
<script>
$(document).ready(function(){
function callAll(){
set_positions = function(){
// loop through and give each task a data-pos
// attribute that holds its position in the DOM
$('.list-group-item').each(function(i){
$(this).attr("data-pos",i+1);
});
}
// ready = function(){
// call set_positions function
set_positions();
// ************NEW execept for #Activities
$('#Activities').sortable({
connectWith: ".connectedSortable"
});
$('#Activities').disableSelection();
// $('#Categories').sortable({
// connectWith: ".connectedSortable"
// });
// $('#Categories').disableSelection();
$('#category_activities').sortable({
connectWith: ".connectedSortable"
});
$('#category_activities').disableSelection();
// *******end of NEW
$('#Activities li').on('click','li',function (){
var myid = $(this).attr('id');
alert(myid);
});
// after the order changes
$('#Activities').sortable().bind('sortupdate', function(e, ui) {
// array to store new order
var updated_order = []
// set the updated positions
set_positions();
// populate the updated_order array with the new task positions
$('#Activities li').each(function(i){
updated_order.push({ id: $(this).attr('id'), position: i });
});
// send the updated order via ajax
$.ajax({
type: "PUT",
url: '/home/sort',
data: { order: updated_order }
});
});
}
$(document).ajaxComplete(callAll());
})
</script>
Partial View:
<!-- List each activity in database -->
<% #activities.each do |activity| %>
<% if (activity.hidden == false || activity.hidden == nil) && activity.category_id == nil %>
<li class="list-group-item" id="item" data-id="<%activity.id%>" style="list-style: none;">
<!-- Display activity name -->
<label class="my_label"><%= activity.a_name %></label>
<!-- Delete Activity -->
<%= link_to destroy_act_path(activity.id), method: :delete, data: { confirm: "Are you sure?" }, remote: true do %>
<i class="fa fa-trash-o" aria-hidden="true" title="Delete"></i>
<% end %>
<!-- Edit activity -->
<%= link_to edit_act_path(activity.id)do %>
<button class="editActivity" style="border:none; padding:0; background-color: transparent">
<i class="fa fa-pencil fa-fw" aria-hidden="true" title="Edit" id="editActivity"></i>
</button>
<% end %>
<!-- Hide activity -->
<%= link_to activity, method: :post, :controller => :activities, :action => :set_hidden_true, remote: true do %>
<button class="hideActivity" style="border:none; padding:0; background-color: transparent">
<i class="fa fa-eye" aria-hidden="true" title="Hide" id="item"></i>
</button>
<% end %>
</li> <!-- End of list item -->
<% end %> <!-- End of if statement -->
<% end %> <!-- End of activity loop -->
Create Activity JS File:
<!--create_activity.js.erb-->
$('#Activities').html("<%= j (render 'object') %>");
Home Controller:
class HomeController < ApplicationController
respond_to :html, :js
def new
end
def index
end
def home
#activities = Activity.all
#activity = Activity.new
#categories = Category.all
#category = Category.new
end
def create_activity
#activity = Activity.create(activity_params)
#activity.user_id = current_user.id
#activity.priority = #activity.id
#object = Category.all
#activities = Activity.all
#categories = Category.all
if #activity.save
flash[:success] = 'Activity created successfully'
else
flash[:notice] ='ERROR: Activity could not be create'
end
end
def create_category
#category = Category.new(category_params)
#category.user_id = current_user.id
##category.priority = #category.id
#object = Category.all
#activities = Activity.all
#categories = Category.all
##category.priority = #category.id
if #category.save!
flash[:success] = 'Category created successfully!'
else
flash[:error] = 'ERROR: Category was not saved!'
end
end
def destroy_activity
#activity = Activity.find(params[:id])
#activity.destroy
#object = Category.all
#activities = Activity.all
#categories = Category.all
end
def welcome
end
def hide_activity
#object = Category.all
#activities = Activity.all
#categories = Category.all
#activity = Activity.find(params[:id])
#activity.update_attribute(:hidden, true)
respond_to do |format|
format.html {redirect_to activities_url}
format.js
end
end
#NEW 4/15
def edit_activity
#activity = Activity.find(params[:id])
end
def update_activity
#activity = Activity.find(params[:id])
if #activity.update_attributes(activity_params)
flash[:success] = 'Activity updated successfully!'
else
flash[:notice] = 'Activity was not updated'
end
end
def unhide_all
#object = Category.all
#activities = Activity.all
#categories = Category.all
#activities = Activity.all
#activities.update_all(hidden: false)
# redirect_to root_path
end
def sort
params[:order].each do |key, value|
Activity.find(value[:id]).update_attribute(:priority, value[:position])
end
render :nothing => true
end
private
def activity_params
params.require(:activity).permit(:a_name, :category_id)
end
def category_params
params.require(:category).permit(:c_name)
end
end
UPDATE
I have tried the following and as a test, replaced my script in the html with the following:
$('#Activities').on('click','li',function (){
var myid = $(this).attr('id');
alert(myid);
});
Before the Ajax, when I click on an activity, it correctly gives me its id. However, after doing an Ajax call, then when I try clicking on an activity, I get the same error where it claims it cannot find an activity with "id=item"

You have a lot of options here, one option is to change the bind to the document or the body and not directly to the id of the element because this is created dinamically, for example:
$("body").on('click',"#Activities" ,function () {
var myid = $(this).attr('id');
alert(myid);
});
you can initialize the event on the ajax request again when the element is rendered that is not recommended
or use plugins like https://github.com/adampietrasiak/jquery.initialize that can reinit the plugins if you bind it to a class, id or another selector.

I've created an example where you will be able to create a Person from the index page using an AJAX request. After the Person is created a new list of people is send to the index page. To simulate your JavaScript I set the background-color of every element to blue using jQuery.
Let's start with the index.html.erb itself.
<!-- app/views/people/index.html.erb -->
<p id="notice"><%= notice %></p>
<h1>People</h1>
<table>
<thead>
<tr>
<th>First name</th>
<th>Last name</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody id="people_tbody">
<%= render 'people', people: #people %>
</tbody>
</table>
<%= render('form', person: #people.new) %>
Next the two partials, _people.html.erb:
<!-- app/views/people/_people.html.erb -->
<% people.each do |person| %>
<tr>
<td><%= person.first_name %></td>
<td><%= person.last_name %></td>
<td><%= link_to 'Show', person %></td>
<td><%= link_to 'Edit', edit_person_path(person) %></td>
<td><%= link_to 'Destroy', person, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
and _form.html.erb from which the AJAX request gets fired:
<!-- app/views/people/_form.html.erb -->
<%= form_for(person, remote: true) do |f| %> <!-- remote true, makes it an AJAX call -->
<% if person.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(person.errors.count, "error") %> prohibited this person from being saved:</h2>
<ul>
<% person.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :first_name %>
<%= f.text_field :first_name %>
</div>
<div class="field">
<%= f.label :last_name %>
<%= f.text_field :last_name %>
</div>
<div class="actions">
<%= f.submit %> <!-- when submit is clicked the AJAX request is fired to PeopleController#create -->
</div>
<% end %>
To simulate your JavaScript problem I have the following file:
// app/assets/javascripts/people.js
function init_people() {
$('#people_tbody tr').css('background-color', 'lightblue')
}
$(document).ready(init_people)
This gives all rows a light blue background.
As you can see I'm using AJAX to send the filled in form because the remote: true is set. If the form is submitted the request will arrive in PeopleController#create, which looks like this:
# app/controllers/people_controller.rb
class PeopleController < ApplicationController
before_action :set_person, only: [:show, :edit, :update, :destroy]
# GET /people
# GET /people.json
def index
#people = Person.all
end
# GET /people/1
# GET /people/1.json
def show
end
# GET /people/new
def new
#person = Person.new
end
# GET /people/1/edit
def edit
end
# POST /people
# POST /people.json
def create # AJAX request arrives here
#person = Person.new(person_params)
respond_to do |format|
if #person.save
format.html { redirect_to #person, notice: 'Person was successfully created.' }
format.json { render :show, status: :created, location: #person }
# I added the underlying line to handle the AJAX response.
format.js { render :index, status: :created, location: #person }
else
format.html { render :new }
format.json { render json: #person.errors, status: :unprocessable_entity }
# I added the underlying line to handle the AJAX response.
format.js { render nothing: true }
end
end
end
# PATCH/PUT /people/1
# PATCH/PUT /people/1.json
def update
respond_to do |format|
if #person.update(person_params)
format.html { redirect_to #person, notice: 'Person was successfully updated.' }
format.json { render :show, status: :ok, location: #person }
else
format.html { render :edit }
format.json { render json: #person.errors, status: :unprocessable_entity }
end
end
end
# DELETE /people/1
# DELETE /people/1.json
def destroy
#person.destroy
respond_to do |format|
format.html { redirect_to people_url, notice: 'Person was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_person
#person = Person.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def person_params
params.require(:person).permit(:first_name, :last_name)
end
end
As you can see I added two lines to handle the js format in PeopleController#create. If the render comes from format.js { ... } rails knows to pick the index.js.erb file, and not the index.html.erb file. If you don't want this behavior you can specify the specific file.
My index.js.erb looks like this:
// app/views/people/index.js.erb
$('#people_tbody').html("<%= j(render('people', people: Person.all)) %>")
This replaces the contents of the #people_tbody with the new set op people (similar to your example). But now I've the same problem as you. The JavaScript isn't triggered anymore, and the background of every row is just white. To trigger the JavaScript over the new loaded content I just need to call the init function. This means any logic you want to apply after the document is ready and after an AJAX request should be extracted to a separate function. I placed my logic in the function init_people().
This means the index.js.erb should look like this:
// app/views/people/index.js.erb
$('#people_tbody').html("<%= j(render('people', people: Person.all)) %>")
init_people()
Now, after the AJAX content is loaded, the init_people() function is triggered setting up the JavaScript for the new loaded content.

Related

Rails 6 Ajax page keeps refreshing when creating category

I'm new to RoR and i have this problem in Rails 6 where i am trying to add a category to a categories list with ajax so that when i submit my form, the page shouldn't refresh. But it keeps refreshing.
I followed this tutorial and split the list of into a _category.html.erb partial and created a create.js.erb file that appends to the list but i don't get the same results.
here is my index.html.erb:
<body>
<main>
<section class="hero is-link is-fullheight pt-3">
<div class="hero-body">
<div class="container is-align-self-flex-start">
<h1 class="title has-text-centered">categories</h1>
<!-- container for the list of categories -->
<div class="container" id='category'>
<%= render #category %>
</div>
<h2 class="is-size-4">add a category</h2>
<%= render 'form', category: #category %>
</div>
</div>
</section>
</main>
</body>
this is my category list partial:
<!-- Loop over categories and add delete btn-->
<% #categories.each do |category| %>
<div class="columns is-mobile card mb-4">
<div class="column is-align-self-center ">
<p class="is-size-5"><%= category.name %></p>
</div>
<div class="column is-narrow">
<button class="button is-danger"><%= link_to 'delete', category, method: :delete, data: { confirm: 'Er du sikker?' } %></button>
</div>
</div>
<% end %> <!-- end of loop -->
this is my form partial for the input and submit btn:
<%= form_with(model: category) do |form| %>
<% if category.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(category.errors.count, "error") %> prohibited this category from being saved:</h2>
<ul>
<% category.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field has-addons">
<%= form.text_field :name , ({:class => "input", })%>
<%= form.submit "add", ({:class => "button is-primary", })%>
</div>
<% end %>
this is my create method in my categories controller that i suspect is the culprit because when i remove the format.html and format.json i get
"ActionController::UnknownFormat in CategoriesController#create"
def create
#categories = Category.all
#category = Category.new(category_params)
respond_to do |format|
if #category.save
format.js
format.html { redirect_to action: "index", notice: "Category was successfully created." }
format.json { render :show, status: :created, location: #category }
else
format.html { render :"index", status: :unprocessable_entity }
format.json { render json: #category.errors, status: :unprocessable_entity }
end
end
end
this is the js file to append the new category to the list:
var body = document.getElementByID("category");
body.innerHTML = "<%= j render #categories %>" + body.innerHTML;
I have tried
This tutorial with jquery also but that also reloads on submit.
I have triple checked that i don't have "local:true" anywhere.
Changed tried #category instead of #categories in some places with no luck
I generated the project with a scaffold
Is there something that i'm missing?
Any guidance is greatly appreciated.
You are not using data-remote attribute for your form. This attribute means the form will submit the results via Ajax.

Rails 5 AJAX refusing to render partial

I created an AJAX function that allows you to click to refresh posts at the top of the stack. Unfortunately, the ajax function is receiving the wrong parameters and returning server error 500. I am simply trying to load the latest posts available (most recent) I can't catch where the error is coming from. What do I need to do to get the newest posts to load on the stack using AJAX GET in index.html.erb?
posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: %[show edit update destroy share like]
before_action :authenticate_user!
def index
following_ids = current_user.following_users.pluck(:id)
following_ids << current_user.id
#posts = Post.where(user_id: following_ids).order('created_at DESC').page(params[:page])
#post = Post.new
end
def load_more
following_ids = current_user.following_users.pluck(:id)
following_ids << current_user.id
#posts = Post.where(user_id: params[:following_ids]).order('created_at DESC').limit(20)
respond_to do |format|
format.html
format.js
end
end
index.html.erb
<div class="col-md-5">
<div class="post-feed-bg">
<div id="post-index-form" class="post-form-bottom-padding">
<%= render 'posts/form' %>
</div>
<!---LOAD MORE POSTS BUTTON-->
<%= link_to load_more_path(#posts), class: 'bttn-material-circle bttn-danger bttn-md load-more-posts', :remote => true do %>
<i class="fas fa-sync"></i>
<% end %>
<div class="post-textarea-bottom-border"></div>
<div class="text-center">
<%= image_tag 'post/loading.gif', style: 'display: none;', class: 'loading-gif' %>
</div>
<div class="post-feed-container" id="container_posts">
<% if #posts.present? %>
<%= render partial: "posts/posts", locals: {posts: #posts} %>
<% end %>
</div>
</div>
</div>
_posts.html.erb
<% #posts.each do |post| %>
<div class="post-container" data-id="<%= post.id %>">
<div class="media" style="padding-bottom: 2em;">
<%= user_avatar_post(post.user) %>
<div class="media-body post-user-name">
<h5><i class="fas fa-user"></i> <%= link_to post.user.user_full_name, user_path(post.user) %></h5>
<p><%= linkify_hashtags(post.body_text) %> </p>
</div>
</div>
<div class="post-container-charm-bar">
<ul>
<li class="votes" id="#post_<%= post.id %>">
<%= link_to like_post_path(post), style: 'text-decoration: none', class: 'like-btn', method: :put, remote: true do %>
<p id="thumb-id" class="thumbs-up" style="cursor: pointer;">b</p>
<% end %>
</li>
<li><strong class="likes-count"><%= number_with_delimiter(post.get_likes.size) %></strong></li>
<li><%= link_to '#', data: {toggle: "modal", target: "#commentmodal"} do %>
<%= link_to post_path(post, anchor: 'comments') do %>
<i class="far fa-comments post-charm-bar-icon-color fa-2x"></i>
<% end %>
<% end %>
</li>
<li><strong><%= post.comment_threads.size %></strong></li>
<li>
<%= link_to({controller: 'posts', action: 'share', id: post.id}, method: :post, remote: true, style: 'text-decoration: none;') do %>
<i class="far fa-share-square fa-2x "></i>
<% end %>
</li>
<li>
<%= link_to post, style: 'text-decoration: none;' do %>
<i class="fas fa-eye fa-2x"></i>
<% end %>
</li>
<li>
<%= link_to edit_post_path(post), style: 'text-decoration: none;' do %>
<i class="fas fa-pencil-alt fa-2x post-charm-bar-icon-color"></i>
<% end %>
</li>
<li>
<% if current_user == post.user %>
<%= link_to post_path(post), style: 'text-decoration: none;', method: :delete, remote: true do %>
<i class="fas fa-trash-alt fa-2x"></i>
<% end %>
<% end %>
</li>
</ul>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-md-12 post-image">
<% if post.photo.present? %>
<%= image_tag post.photo.feed_preview, class: 'd-flex align-self-start mr-3 img-fluid rounded', lazy: true %>
<% else %>
<% end %>
</div>
</div>
</div>
</div>
<% end %>
<script type="text/javascript">
$(document).ready(function () {
$("img").lazyload({
effect: "fadeIn",
skip_invisible: true,
threshold: 500
});
});
</script>
index.js.erb
// Load new records
$("#container_posts").prepend("<%= escape_javascript(render(#posts)) %>").hide().fadeIn(1000);
//Append new data
$("<%= j render partial: "posts/#{#post.post_type}", locals: {post: #post } %>").appendTo($("#container_posts"));
//Update pagination link
<% if #posts.last_page? %>
$('.pagination').html("All done");
<% else %>
$('.pagination').html("<%= j link_to_next_page(#posts, 'Next Page', :remote => true) %> ");
routes.rb
resources :posts, on: :collection do
member do
......
end
end
get "/load_more", to: "posts#load_more", as: 'load_more'
load_more_posts.js
$(document).on('turbolinks:load', function () {
$('a.load-more-posts').click(function (e) {
// prevent the default click action
e.preventDefault();
// hide more load more link
$('.load-more-posts').hide();
// show loading gif
$('.loading-gif').show();
// get the last id and save it in a variable 'last-id'
var last_id = $('.post-container').last().attr('data-id');
// make an ajax call passing along our last career insight id
$.ajax({
// make a get request to the server
type: "GET",
// get the url from the href attribute of our link
url: "/posts",
// send the last id to our rails app
data: {
id: last_id,
user_profile: "", // maybe nil in certain cases
_: ""
},
// the response will be a script
dataType: "script",
success: function (data, textStatus, jqXHR) {
if (jqXHR.status == "204") {
$('.loading-gif').hide();
$('.load-more-posts').show();
}
$('.loading-gif').hide();
$('.load-more-posts').show();
},
error: function (jqXHR, exception) {
}
})
});
});
post parameters
=> #<Post id: 59, body_text: "Fix this!", photo: nil, user_id:
2, created_at: "2018-11-02 19:13:09", updated_at: "2018-11-02 19:13:09", post_id: nil, post_id: nil, post_counter: 0, cached_votes_total: 0, cached_votes_score: 0, cached_votes_up: 0, cached_votes_do
wn: 0, cached_weighted_score: 0, cached_weighted_total: 0, cached_weighted_avera
ge: 0.0, hash_id: "DQxad5rRVvfb">
server stacktrace
Started GET "/load_more.%23%3CPost::ActiveRecord_Relation:0x0000000016f89718%3E" for 127.0.0.1 at 2018-11-03 00:32:58 -0400
Started GET "/posts?id=41&user_profile=&_=1541219410513" for 127.0.0.1 at 2018-11-03 00:32:58 -0400
Processing by PostsController#load_more as
Processing by PostsController#index as JS
Parameters: {"id"=>"41", "user_profile"=>"", "_"=>"1541219410513", "on"=>:collection}
User Load (3.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 5], ["LIMIT", 1]]
User Load (2.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 5], ["LIMIT", 1]]
DEPRECATION WARNING: Setting custom parent classes is deprecated and will be removed in future versions. (called from parent_class_name at C:/Ruby24-x64/lib/ruby/gems/2.4.0/bundler/gems/acts_as_follower-c5ac7b9601c4/lib/acts_as_follower/follower_lib.rb:10)
DEPRECATION WARNING: Setting custom parent classes is deprecated and will be removed in future versions. (called from parent_class_name at C:/Ruby24-x64/lib/ruby/gems/2.4.0/bundler/gems/acts_as_follower-c5ac7b9601c4/lib/acts_as_follower/follower_lib.rb:10)
(2.0ms) SELECT "users"."id" FROM "users" INNER JOIN "follows" ON "follows"."followable_id" = "users"."id" AND "follows"."followable_type" = $1 WHERE "follows"."blocked" = $2 AND "follows"."follower_id" = $3 AND "follows"."follower_type" = $4 AND "follows"."followable_type" = $5 [["followable_type", "User"], ["blocked", false], ["follower_id", 5], ["follower_type", "User"], ["followable_type", "User"]]
(58.0ms) SELECT "users"."id" FROM "users" INNER JOIN "follows" ON "follows"."followable_id" = "users"."id" AND "follows"."followable_type" = $1 WHERE "follows"."blocked" = $2 AND "follows"."follower_id" = $3 AND "follows"."follower_type" = $4 AND "follows"."followable_type" = $5 [["followable_type", "User"], ["blocked", false], ["follower_id", 5], ["follower_type", "User"], ["followable_type", "User"]]
Completed 406 Not Acceptable in 127ms (ActiveRecord: 5.0ms)
ActionController::UnknownFormat (ActionController::UnknownFormat):
SERVER Stacktrace Update 2
Rendered collection of posts/_post.html.erb [25 times] (3464.5ms)
Rendered posts/_post.html.erb (2329.4ms)
Rendered posts/index.js.erb (10273.4ms)
Completed 500 Internal Server Error in 109620ms (ActiveRecord: 24197.5ms)
ActionView::Template::Error (undefined method `user_profile' for nil:NilClass):
1: <div class="post-container" id="post_<%= post.id %>" data-id="<%= post.hash_id %>">
2: <div class="media" style="padding-bottom: 2em;">
3: <%= user_avatar_post(post.user) %>
4: <div class="media-body post-user-name">
5: <h5><i class="fas fa-user"></i> <%= link_to full_name(post.user), user_path(post.user) %></h5>
6: <p><%= linkify_hashtags(post.body_text) %> </p>
app/helpers/posts_helper.rb:3:in `user_avatar_post'
app/views/posts/_post.html.erb:3:in `_app_views_posts__post_html_erb__891594994_136890360'
app/views/posts/index.js.erb:6:in `_app_views_posts_index_js_erb__704681650_227473700'
Processing by ExceptionHandler::ExceptionsController#show as JS
Parameters: {"id"=>"41", "_"=>"1541262709872", "on"=>:collection}
Error during failsafe response: Could not render layout: undefined method `[]' for nil:NilClass
C:/Ruby24-x64/lib/ruby/gems/2.4.0/gems/actionview-5.2.0/lib/action_view/layouts.rb:418:in `rescue in _default_layout'
C:/Ruby24-x64/lib/ruby/gems/2.4.0/gems/actionview-5.2.0/lib/action_view/layouts.rb:415:in `_default_layout'
C:/Ruby24-x64/lib/ruby/gems/2.4.0/gems/actionview-5.2.0/lib/action_view/layouts.rb:392:in `block in _layout_for_option'
C:/Ruby24-x64/lib/ruby/gems/2.4.0/gems/actionview-5.2.0/lib/action_view/renderer/template_renderer.rb:96:in `resolve_layout'
_post.html.erb partial is not rendering with user and bypassing helper methods in posts_helper.rb
posts_helper.rb
module PostsHelper
def user_avatar_post(current_user)
if current_user.user_profile.avatar.feed_thumb.url.nil?
inline_svg 'user_dashboard/add-user-avatar.svg', size: '12% * 12%', class: 'd-flex align-self-start mr-3 purple-rounded rounded'
else
image_tag current_user.user_profile.avatar.feed_thumb.url, class: 'd-flex align-self-start mr-3 img-thumbnail rounded'
end
end
def full_name(user)
user.first_name + ' ' + user.last_name
end
end
Updated Post Controller
def load_more
if params[:last_post_id]
following_ids = current_user.following_users.pluck(:id)
following_ids << current_user.id
#posts = Post.where(user_id: params[:following_ids]).where('id < ?', params[:last_post_id]).order('created_at DESC').limit(20)
else
head :no_content
end
respond_to do |format|
format.html
format.js
end
end
rotute.erb
get "/load_more/:last_post_id", to: "posts#load_more", as: 'load_more'
index.html.erb
<!---LOAD MORE POST BUTTON-->
<div class="float-right" style="z-index: 1000;">
<%= link_to load_more_path(#posts.last.id), class: 'bttn-material-circle bttn-danger bttn-md load-more-posts', :remote => true do %>
<i class="fas fa-sync"></i>
<% end %>
</div>
I'm using friendly_id to hash ids for posts.
On your logs, you can see the load_more request has a really weird extension:
Started GET "/load_more.%23%3CPost::ActiveRecord_Relation:0x0000000016f89718%3E"
Rails think's that's the format and does not know how to render that format.
Your route is defined like this:
get "/load_more", to: "posts#load_more", as: 'load_more'
but you are using it like this
load_more_path(#posts)
and the route does not expect a parameter and it ends up using it as a format.
You should add something to the route so you know from what post "load more". I'll do something like:
get "/load_more/:last_post_id", to: "posts#load_more", as: 'load_more'
And use it like
load_more_path(#posts.last.id)
And on the controller you'll have access to params[:last_post_id] so you know you need to load more posts after that post id.
following_ids = current_user.following_users.pluck(:id)
following_ids << current_user.id
#posts = Post.where(user_id: params[:following_ids]).where('id < ?', params[:last_post_id]).order('created_at DESC').limit(20)

Dynamically generate copies partial form based on user selection in a nested form

I'm trying to create a form that will generate copies of a nested form depending on a user's selection. I have 3 models: Visualizations, which has_many Rows, which has_many Panes.
I want to be able to allow the user to click a small dropdown menu that will generate a row and then select 1, 2, or 3 panes. Depending on their selection, this will generate the form of their choice from a render.
here is an example where a user chose 3 panes (3 upload boxes for images) with just styled divs in place of the form
For starters. I know I would need to change up my controller a bit, but I'm not sure how to do that with a nested form since in every example I've seen so far, you need to predetermine how many times you would "build" a nested model form (not sure if that is the correct terminology). I'm not sure how to make that part dynamic.
Here is what I mean:
//visualization_controller.rb
def new
#visualization = Visualization.new
rows = #visualization.rows.build
panes = rows.panes.build
end
here is an example of the render that would be called if the usr wanted to generate a row with just 1 Pane:
<div class="medium-12 columns one_box_wrapper first" style="display:none">
<%= f.fields_for :rows, Row.new do |r| %>
<%= r.label :rowtitle %>
<%= r.text_field :rowtitle %>
<div class="nested" style="display:inline-block">
<%= r.fields_for :panes, Pane.new do |p| %>
<%= p.label :text %>
<%= p.text_field :text %>
<div class="mehv">
<img id="img_prev" width=300 height=300 src="#" alt="your image" class="img-thumbnail hidden"/> <br/>
<span class="meh">
Upload Pane Image<%= p.file_field :pane_photo, id: "pane_photo" %>
</span>
<%= p.hidden_field :pane_photo_cache %>
</div>
<% end %>
</ul>
</div>
<% end %>
</div>
Is this possible in rails? I feel like javascript would come into play but I don't think you are allowed to render server-side code through JS.
I feel a bit in over my head with this and any help would be greatly appreciated!
EDIT
I ended up using cocoon gem as it seems to have made my life a lot easier however.. I can't get the visualizations to all display on the index page! I'm not sure what the issue is.. The cover Title and Image show up but nothing else does!
//schema.rb
create_table "panes", force: :cascade do |t|
t.integer "row_id", null: false
t.text "text"
t.string "pane_photo"
end
create_table "rows", force: :cascade do |t|
t.integer "visualization_id", null: false
t.string "rowtitle"
end
create_table "visualizations", force: :cascade do |t|
t.string "title", null: false
t.string "cover_image"
end
//controller.rb
class VisualizationsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :authorize_user, except: [:index, :show]
helper_method :resource
def index
#visualizations = Visualization.all
#visualization = Visualization.new
end
def new
#visualization = Visualization.new
rows = #visualization.rows.build
panes = rows.panes.build
end
def create
#visualization = Visualization.new(visualization_params)
if #visualization.save
redirect_to edit_visualization_path(#visualization)
else
render :new
end
end
def edit
#visualizations = Visualization.all
#visualization = Visualization.find(params[:id])
end
protected
def visualization_params
params.require(:visualization).permit(:title, :cover_image, rows_attributes: [:visualization_id, :rowtitle, panes_attributes:[:text, :pane_photo]])
end
//index.html
<div class="visual_wrap text-center">
<ul class="no-bullet text-center">
<% #visualizations.each do |visualization| %>
<li class="text-center">
<div class="row visualization text-center">
<%= image_tag visualization.cover_image.to_s, id: "visualization_image", class: "output_image" %>
<p class="visualization_title"><%= visualization.title %></p>
<p>+</p>
</div>
<div class="placeholder">
<% visualization.rows.each do |row| %>
<%= row.rowtitle %>
<% row.panes.each do |p| %>
<%= p.text %>
<%= image_tag p.pane_photo.to_s, id: "pane_image", class: "output_image" %>
<% end %>
<% end %>
</div>
</li>
<% end %>
</ul>
</div>
//new.html.erb
<% if user_signed_in? %>
<% if current_user.admin? %>
<div class="try">
<div class="breadcrumb-wrapper">
<ul class="breadcrumbs">
<%= form_for #visualization, :html => {:multipart => true} do |f| %>
<li>Preview</li>
<li><div class="separating-bar">|</div>
<li><input type="submit" name="commit" value="Save" class="save button secondary"></li>
</ul>
</div>
<div class="cover_image text-center">
<div class="row pre_img_prev">
<div class="gray_bar"></div>
<div class="img_prev">
<img id="img_preview" src="#" alt="your image" class="img-thumbnail hidden text-center"/>
<input type="text" name="visualization[title]" id="visualization[title]" class="inputtext" />
<label for="visualization[title]" id="text_preview">+ Add Title</label>
<div class="trash-can_title">
<i class="fa fa-trash fa-3x" aria-hidden="true"></i>
</div>
</div>
<input type="file" name="visualization[cover_image]" id="visualization_cover_image" class="inputfile" />
<label for="visualization_cover_image">+ Add Cover Image</label>
<%= f.hidden_field :cover_image_cache %>
<div class="trash-can_cover">
<i class="fa fa-trash fa-3x" aria-hidden="true"></i>
</div>
</div>
</div>
<div class="row linkstest">
<div class="rows">
<%= f.fields_for :rows, Row.new do |task| %>
<%= render 'row_fields', f: task %>
<% end %>
<div class="links">
<%= link_to_add_association "Add Row", f, :rows %>
</div>
</div>
<% end %>
<% end %>
<% end %>
</div>
</div>
You don't need really need JS except to make it more "snappy". I think what you are looking for are Nested Forms.
Take a look at RaisCast it's a bit outdated but it's a good foundation.
You might want to look into Cocoon gem to help you create the new fields dynamically.
Rails nested_form inside nested_form
Update
If you don't want to use Javascript at all, you need to make an extra request to the controller to tell it to initialize (build) the rows and panes for you so that it renders the empty fields in the view.
For example:
def new
#visualization = Visualization.new
num_rows = params[:num_rows]
num_rows.each { #visualization.rows.build }
end
You would also need to have the number of panes in each row, so the parameter would have to be inside the row attributes, e.g. params[:visualization][:rows][0][:num_panes], and using that parameter you initialize the panes in the row. It's probably best to put that logic in a Factory service.
However if you create the empty fields using JS you can save yourself from making that extra request and all that logic. This is because Rails will automatically initialize (build) the rows and panes if you give it the right attributes, such as:
{
visualization: {
rows_attributes: {
# These are the attributes from an existing record
0: {
id: 39,
panes_attributes: {
0: {
id: 992
}
}
},
# This is a new set of attributes for a new record
# passed by the fields generated by cocoon where xxx
# and yyy are random numbers
xxxx: {
panes_attributes: {
yyyy: {
}
}
}
}
}
}
Then all you need to do in your controller is:
def new
# visualization_params are the permitted attributes
#visualization = Visualization.new(visualization_params)
end

Rails: Checkbox to change the value of a variable on click

I have a basic to do type app and I am trying to change the variable completed for an item to the current date/time when I click a checkbox. I found this post, which has led me to the following code structure:
My lists#show, on which items are created, checked off (updated), edited, and deleted:
<div class="container">
<div class="row" style="height: 70px"></div>
</div>
<div class="col-xs-10 col-xs-push-1 container">
<div class="md-well">
<h1 class="text-center"><%= #list.name %></h1>
<% if #items.count == 0 %>
<h4 class="text-center">You don't have any items on this list yet! Want to add some?<h4>
<% elsif #items.count == 1 %>
<h4 class="text-center">Only <%= #items.count %> Item To Go!</h4>
<% else %>
<h4 class="text-center">You Have <%= #items.count %> Items To Go!</h4>
<% end %>
<%= render 'items/form' %>
<% #items.each do |item|%>
<p>
<%= form_for [#list, item], class: "inline-block", id: 'item<%= item.id %>' do |f| %>
<%= f.check_box :completed, :onclick => "$this.parent.submit()" %>
<% end %>
<%= item.name %>
( <%= link_to "Delete", list_item_path(#list, item), method: :delete %> )
</p>
<% end %>
<div class="text-center">
<%= link_to "Back to My Lists", lists_path %>
</div> <!-- text-center -->
</div> <!-- well -->
</div> <!-- container -->
Here is my update method in my items_controller (which I believe has jurisdiction instead of the lists_controller even though it is the lists#show page because it is an item being updated:
def update
#list = List.friendly.find(params[:list_id])
#item = #list.items.find(params[:id])
#item.name = params[:item][:name]
#item.delegated_to = params[:item][:delegated_to]
#item.days_til_expire = params[:item][:days_til_expire]
#item.completed = params[:item][:completed]
#item.user = current_user
if #item.update_attributes(params[:item])
#item.completed = Time.now
end
if #item.save
flash[:notice] = "List was updated successfully."
redirect_to #list
else
flash.now[:alert] = "Error saving list. Please try again."
render :edit
end
end
Here is what is currently appearing:
And here are the two problems I'm having:
LESS IMPORTANT PROBLEM: The checkboxes should be displayed inline with the list items but aren't. My class inline-block simply makes links to this in the application.scss file:
.inline-block {
display: inline-block !important;
}
MORE IMPORTANT PROBLEM: Even after the checkbox is clicked, the value for completed remains nil as per my console:
[6] pry(main)> Item.where(name: "Feed Dexter")
Item Load (0.1ms) SELECT "items".* FROM "items" WHERE "items"."name" = ? [["name", "Feed Dexter"]]
=> [#]
Any ideas how to make this functional?

How to reset/clear form within my create.js.erb?

I can't get my form to reset correctly. It submits and it goes to the database but everything remains on the form.
Here is my code:
DogsController
def create
#dog = current_user.dogs.new(params[:dog])
if #dog.save
format.js
else
format.js
end
end
dog/create.js.erb
$('.add-dog-form').html('<%= escape_javascript(render(:partial => 'dogs/form', locals: { dog: #dog })) %>');
$('.add-dog-form > form')[0].reset(); // doesn't work
dog/new.js.erb
$('.add-dog-form').html('<%= escape_javascript(render(:partial => 'dogs/form', locals: { dog: #dog })) %>');
dogs/_form
<%= form_for(#dog, :remote => true) do |f| %>
<%= f.label :name, "Name" %>
<%= f.text_field :name %>
<%= f.submit "Add Dog" %>
<% end %>
I create dogs at the Pets view instead of the dogs.
PetsController
def add
#dog = Dog.new
#cat = Cat.new
#bird = Bird.new
end
pets/add.html.erb
I click on the tab which changes it to the Dog Tab using Twitter Bootstraps Tabbable Nav ability
<div class="tabbable">
<ul class="nav nav-tabs">
<li class="active">
Cat
</li>
<li>
Dog
</li>
<li>
Bird
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active add-cat-form" id="tab1">
<%= render "cats/form" %>
</div>
<div class="tab-pane add-dog-form" id="tab2">
<%= render "dogs/form" %>
</div>
<div class="tab-pane add-bird-form" id="tab3">
<%= render "birds/form" %>
</div>
</div>
</div>
How can I reset the form if it gets created?
Try giving the form an id:
<%= form_for(#dog, :remote => true, :html => { :id => 'dog_form' } ) do |f| %>
and $('#dog_form')[0].reset();.
Alternatively, you can do $(":input:not(input[type=submit])").val("");.
Well it seems my error was in my files themselves. I had created the js files as partials instead of regular pages. i.e. _create.js.erb should be create.js.erb. I also had to add respond_to do |format| in the controller create action. Now I got everything working correctly.

Categories

Resources