Rails 5 - Ajax 刷新后重新初始化 Javascript?

Posted

技术标签:

【中文标题】Rails 5 - Ajax 刷新后重新初始化 Javascript?【英文标题】:Rails 5 - Reinitializing Javascript after Ajax refresh? 【发布时间】:2017-09-17 13:56:31 【问题描述】:

所以我对涉及 javascript 的问题有了更好的理解。问题在于,在 AJAX 调用刷新 div 的内容之后,该 div 中的内容未链接或未针对 Javascript 初始化。我想要做的是让我的活动列表可以为用户排序。我使用优先级字段来保存活动的顺序,因此当用户刷新或离开页面时,顺序保持不变。

我收到的问题如下:

ActiveRecord::RecordNotFound(找不到 'id'=item 的活动):

因此,活动订单将不会被保存。这发生在 AJAX 调用和刷新之后,它事先正常工作,保存订单和诸如此类的东西。我尝试了一些解决方案,例如 one 并将 Javascript 移到部分没有成功。

我相信上面的错误是由于活动没有正确链接到 Javascript 文件导致它的 id 为“item”所以有谁知道如何解决这个问题或关于如何解决它的任何建议?谢谢!

主视图:

<!-- 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><a href="#">Signed in as <%=current_user.email %></a></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"><a href="#"><span aria-hidden="true">&larr;</span>Previous </a></li>
      <li class="next"><a href="#"> Next<span aria-hidden="true">&rarr;</span></a></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>

局部视图:

 <!-- 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 -->

创建 Activity JS 文件:

<!--create_activity.js.erb-->
$('#Activities').html("<%= j (render 'object') %>");

家庭控制器:

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

更新

我尝试了following 并作为测试,将我在 html 中的脚本替换为以下内容:

    $('#Activities').on('click','li',function ()
        var myid = $(this).attr('id');
        alert(myid);
    );

在 Ajax 之前,当我点击一个活动时,它会正确地给我它的 ID。但是,在进行 Ajax 调用之后,当我尝试单击一个活动时,我得到了同样的错误,它声称找不到具有“id=item”的活动

【问题讨论】:

乍一看,我的猜测是$(document).ajaxComplete(callAll()); 修复了一些与活动相关的内容。这意味着$('#Activities').html("&lt;%= j (render 'object') %&gt;"); 后面应该跟callAll();。如果这触发了很多 JavaScript,您应该提取修复活动 JavaScript 相关内容所需的部分到它自己的函数,并在最后调用该函数。 (在你替换了$('#activities').html(...)之后)。 这个问题可以通过明确指定 ajax 请求的触发位置来进一步解决。所以我们知道要遵循什么路径。 @JohanWentholt 为迟到的回复道歉。截至目前,我试图简单地让创建函数使用 AJAX,这样用户就不需要刷新整个页面,但是在 AJAX 调用之后,这就是我的问题发生的地方。 【参考方案1】:

这里有很多选项,一个选项是更改绑定到文档或正文,而不是直接绑定到元素的 id,因为这是动态创建的,例如:

$("body").on('click',"#Activities" ,function () 
   var myid = $(this).attr('id');
   alert(myid);
);

你可以在渲染不推荐的元素时再次初始化ajax请求上的事件

或使用https://github.com/adampietrasiak/jquery.initialize 之类的插件,如果您将其绑定到类、id 或其他选择器,则可以重新初始化插件。

【讨论】:

【参考方案2】:

我创建了一个示例,您可以在其中使用 AJAX 请求从索引页面创建Person。在Person 创建后,people 的新列表将发送到索引页面。为了模拟您的 JavaScript,我使用 jQuery 将每个元素的背景颜色设置为蓝色。

让我们从index.html.erb 本身开始。

<!-- 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) %>

接下来是两个部分,_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 %>

_form.html.erb AJAX 请求被触发

<!-- 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 %>

为了模拟您的 JavaScript 问题,我有以下文件:

// app/assets/javascripts/people.js
function init_people() 
    $('#people_tbody tr').css('background-color', 'lightblue')


$(document).ready(init_people)

这会为所有行提供浅蓝色背景。

如您所见,我使用 AJAX 发送填写的表单,因为设置了 remote: true。如果提交表单,请求将到达PeopleController#create,如下所示:

# 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

如您所见,我添加了两行来处理PeopleController#create 中的js 格式。如果渲染来自format.js ... ,rails 知道选择index.js.erb 文件,而不是index.html.erb 文件。如果您不希望这种行为,您可以指定特定文件。

我的index.js.erb 看起来像这样:

// app/views/people/index.js.erb
$('#people_tbody').html("<%= j(render('people', people: Person.all)) %>")

这会将#people_tbody 的内容替换为新的集合操作人员(类似于您的示例)。但是现在我和你有同样的问题。 JavaScript 不再被触发,每一行的背景都是白色的。要在新加载的内容上触发 JavaScript,我只需要调用 init 函数。 这意味着您想要在文档准备好之后以及 AJAX 请求之后应用的任何逻辑都应该被提取到一个单独的函数中。我将我的逻辑放在函数 init_people() 中。

这意味着index.js.erb 应该如下所示:

// app/views/people/index.js.erb
$('#people_tbody').html("<%= j(render('people', people: Person.all)) %>")
init_people()

现在,在加载 AJAX 内容后,将触发 init_people() 函数为新加载的内容设置 JavaScript。

【讨论】:

以上是关于Rails 5 - Ajax 刷新后重新初始化 Javascript?的主要内容,如果未能解决你的问题,请参考以下文章

Rails 可以让 Ajax 保持开放多久

使用 AJAX 刷新 Rails 部分时的未定义方法

Rails 控制器方法在 Ajax 调用后不重新加载页面

如何使用CoffeeScript重新同步setTimeout()? Rails 5

创建类别时Rails 6 Ajax页面不断刷新

ajax搜索结果返回后初始化viewmodel并刷新