Unobtrusive dynamic form fields in Rails with jQuery

前端 未结 6 1077
情话喂你
情话喂你 2020-12-12 10:10

I\'m attempting to get over the hurdle of dynamic form fields in Rails -- this appears to be something the framework doesn\'t handle very gracefully. I\'m also using jQuery

6条回答
  •  醉话见心
    2020-12-12 10:29

    Based on your answer to my comment I think handling deletion unobtrusively is a good place to start. I'll use Product with scaffolding as an example, but the code will be generic so it should be easy to use in your application.

    First add a new option to your route:

    map.resources :products, :member => { :delete => :get }
    

    And now add a delete view to your Product views:

    <% title "Delete Product" %>
    
    <% form_for @product, :html => { :method => :delete } do |f| %>
      

    Are you sure you want to delete this Product?

    <%= submit_tag "Delete" %> or <%= link_to "cancel", products_path %>

    <% end %>

    This view will only be seen by users with JavaScript disabled.

    In the Products controller you'll need to add the delete action.

    def delete
      Product.find(params[:id])
    end
    

    Now go to your index view and change the Destroy link to this:

    <%= link_to "Delete", delete_product_path(product), :class => 'delete' %>
    

    If you run the app at this point and view the list of products you'll be able to delete a product, but we can do better for JavaScript enabled users. The class added to the delete link will be used in our JavaScript.

    This will be a rather large chunk of JavaScript, but it's important to focus on the code that deals with making the ajax call - the code in the ajaxSend handler and the 'a.delete' click handler.

    (function() {
      var originalRemoveMethod = jQuery.fn.remove;
      jQuery.fn.remove = function() {
        if(this.hasClass("infomenu") || this.hasClass("pop")) {
          $(".selected").removeClass("selected");
        }
        originalRemoveMethod.apply(this, arguments);
      }
    })();
    
    function isPost(requestType) {
      return requestType.toLowerCase() == 'post';
    }
    
    $(document).ajaxSend(function(event, xhr, settings) {
      if (isPost(settings.type)) {
        settings.data = (settings.data ? settings.data + "&" : "") + "authenticity_token=" + encodeURIComponent( AUTH_TOKEN );
      }
      xhr.setRequestHeader("Accept", "text/javascript, application/javascript");     
    });
    
    function closePop(fn) {
      var arglength = arguments.length;
      if($(".pop").length == 0) { return false; }
      $(".pop").slideFadeToggle(function() { 
        if(arglength) { fn.call(); }
        $(this).remove();
      });    
      return true;
    }
    
    $('a.delete').live('click', function(event) {
      if(event.button != 0) { return true; }
    
      var link = $(this);
      link.addClass("selected").parent().append("

    Are you sure?

    or Cancel

    "); $(".delpop").slideFadeToggle(); $(".delpop input").click(function() { $(".pop").slideFadeToggle(function() { $.post(link.attr('href').substring(0, link.attr('href').indexOf('/delete')), { _method: "delete" }, function(response) { link.parents("tr").fadeOut(function() { $(this).remove(); }); }); $(this).remove(); }); }); return false; }); $(".close").live('click', function() { return !closePop(); }); $.fn.slideFadeToggle = function(easing, callback) { return this.animate({opacity: 'toggle', height: 'toggle'}, "fast", easing, callback); };

    Here's the CSS you'll need too:

    .pop {
      background-color:#FFFFFF;
      border:1px solid #999999;
      cursor:default;
      display: none;
      position:absolute;
      text-align:left;
      z-index:500;
      padding: 25px 25px 20px;
      margin: 0;
      -webkit-border-radius: 8px; 
      -moz-border-radius: 8px; 
    }
    
    a.selected {
      background-color:#1F75CC;
      color:white;
      z-index:100;
    }
    

    We need to send along the auth token when we make POST, PUT or DELETE. Add this line under your existing JS tag (probably in your layout):

    <%= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery? -%>
    

    Almost done. Open up your Application controller and add these filters:

    before_filter :correct_safari_and_ie_accept_headers
    after_filter :set_xhr_flash
    

    And the corresponding methods:

    protected
      def set_xhr_flash
        flash.discard if request.xhr?
      end
    
      def correct_safari_and_ie_accept_headers
        ajax_request_types = ['text/javascript', 'application/json', 'text/xml']
        request.accepts.sort!{ |x, y| ajax_request_types.include?(y.to_s) ? 1 : -1 } if request.xhr?
      end
    

    We need to discard flash messages if it's an ajax call - otherwise you'll see flash messages from the "past" on your next regular http request. The second filter is also required for webkit and IE browsers - I add these 2 filters to all of my Rails projects.

    All that's left is the destroy action:

    def destroy
      @product.destroy
      flash[:notice] = "Successfully destroyed product."
    
      respond_to do |format|
        format.html { redirect_to redirect_to products_url }
        format.js  { render :nothing => true }
      end
    end
    

    And there you have it. Unobtrusive deleting with Rails. It seems like a lot of work all typed out, but it's really not that bad once you get going. You might be interested in this Railscast too.

提交回复
热议问题