source

jQuery가 있는 레일에서 눈에 띄지 않는 동적 양식 필드

gigabyte 2023. 3. 20. 23:18
반응형

jQuery가 있는 레일에서 눈에 띄지 않는 동적 양식 필드

저는 Rails의 동적 폼 필드의 장애물을 극복하려고 시도하고 있습니다.이것은 이 프레임워크에서 그다지 우아하게 처리되지 않는 것 같습니다.프로젝트에도 jQuery를 사용하고 있습니다.jRails가 설치되어 있습니다만, 가능하면 눈에 띄지 않게 AJAX 코드를 쓰는 것이 좋습니다.

하기 때문에 하는 것이 폼빌더 컨텍스트에 의존하기 때문에 올바른 폼 ID를 생성하는 것이 문제입니다.하거나 기존 할 수 합니다.has_many정말 막막해요.

내가 지금까지 본 모든 예들은 어떤 식으로든 추악했다.Ryan Bates의 튜토리얼은 RJS를 필요로 하며, 마크업에서 꽤 보기 흉한 javascript를 발생시키며, 중첩된 속성 이전에 작성된 것으로 보인다.눈에 띄지 않는 jQuery에서 이 예제의 포크를 본 적이 있지만, 이 예제의 기능을 이해할 수 없기 때문에 프로젝트에서는 사용할 수 없었습니다.

누가 간단한 예를 들어줄 수 있나요?컨트롤러의 RESTful 규약을 준수하면서도 이것이 가능합니까?


Andy는 기존 레코드를 삭제하는 훌륭한 예를 올렸습니다.누구라도 올바른 속성을 가진 새 필드를 만드는 예를 제공할 수 있습니까?중첩된 형태로 어떻게 해야 할지 모르겠어요.

아무도 이에 대한 답을 제시하지 않았기 때문에 현상금이 걸려도 결국 나 스스로 해결할 수 있었다.이건 멍청한 짓이 아니었어!Rails 3.0에서는 이 작업이 더 쉬워졌으면 합니다.

Andy의 예는 서버에 양식을 제출하지 않고 레코드를 직접 삭제하는 좋은 방법입니다.이 경우 실제로 필요한 것은 중첩된 폼으로 업데이트하기 전에 필드를 동적으로 추가/제거하는 방법입니다.이것은 약간 다른 경우입니다.필드가 삭제될 때 양식이 제출될 때까지 필드가 실제로 삭제되지 않기 때문입니다.상황에 따라서는 둘 다 사용하게 될지도 모릅니다.

는 팀 라일리의 복잡한 형태에 기반을 두고 있습니다. 예를 들어 기허브를 기반으로 하고 있습니다.

먼저 모델을 설정하고 모델이 중첩된 속성을 지원하는지 확인합니다.

class Person < ActiveRecord::Base
  has_many :phone_numbers, :dependent => :destroy
  accepts_nested_attributes_for :phone_numbers, :reject_if => lambda { |p| p.values.all?(&:blank?) }, :allow_destroy => true
end

class PhoneNumber < ActiveRecord::Base
  belongs_to :person
end

PhoneNumber 폼필드의 부분 뷰를 만듭니다.

<div class="fields">
  <%= f.text_field :description %>
  <%= f.text_field :number %>
</div>

그런 다음 사용자 모델에 대한 기본 편집 보기를 작성합니다.

<% form_for @person, :builder => LabeledFormBuilder do |f| -%>
  <%= f.text_field :name %>
  <%= f.text_field :email %>
  <% f.fields_for :phone_numbers do |ph| -%>
    <%= render :partial => 'phone_number', :locals => { :f => ph } %>
  <% end -%>
  <%= f.submit "Save" %>
<% end -%>

이것은 javascript를 사용하여 복제할 수 있는 PhoneNumber 모델의 템플릿필드 세트를 작성함으로써 동작합니다. 이번에는 도우미 을 만들어 app/helpers/application_helper.rb경우: 우::

def new_child_fields_template(form_builder, association, options = {})
  options[:object] ||= form_builder.object.class.reflect_on_association(association).klass.new
  options[:partial] ||= association.to_s.singularize
  options[:form_builder_local] ||= :f

  content_tag(:div, :id => "#{association}_fields_template", :style => "display: none") do
    form_builder.fields_for(association, options[:object], :child_index => "new_#{association}") do |f|
      render(:partial => options[:partial], :locals => { options[:form_builder_local] => f })
    end
  end
end

def add_child_link(name, association)
  link_to(name, "javascript:void(0)", :class => "add_child", :"data-association" => association)
end

def remove_child_link(name, f)
  f.hidden_field(:_destroy) + link_to(name, "javascript:void(0)", :class => "remove_child")
end

다음으로 편집 부분에는 다음 도우미 메서드를 추가합니다.

<% form_for @person, :builder => LabeledFormBuilder do |f| -%>
  <%= f.text_field :name %>
  <%= f.text_field :email %>
  <% f.fields_for :phone_numbers do |ph| -%>
    <%= render :partial => 'phone_number', :locals => { :f => ph } %>
  <% end -%>
  <p><%= add_child_link "New Phone Number", :phone_numbers %></p>
  <%= new_child_fields_template f, :phone_numbers %>
  <%= f.submit "Save" %>
<% end -%>

제 js 플 템 、 업 js 릿 되 되 다 다 다 다 템 템 。만, 「」는, 「」의 「공백템플릿입니다.:reject_if 모델의 절은 이러한 필드를 폐기하고 사용자가 작성한 필드만 남습니다.업데이트:이 디자인을 재검토했습니다.아래를 참조해 주세요.

페이지 로드와 폼 제출 이외에는 서버로의 통신이 이루어지지 않기 때문에, 실제로는 AJAX라고 할 수 없습니다만, 그 후는 솔직히 방법을 찾을 수 없었습니다.

각 추가 필드에 대한 서버 응답을 기다릴 필요가 없으므로 실제로 AJAX보다 더 나은 사용자 환경을 제공할 수 있습니다.

마지막으로 javascript로 마무리해야 합니다.다음 항목을 'public/javascripts/application.js' 파일에 추가합니다.

$(function() {
  $('form a.add_child').click(function() {
    var association = $(this).attr('data-association');
    var template = $('#' + association + '_fields_template').html();
    var regexp = new RegExp('new_' + association, 'g');
    var new_id = new Date().getTime();

    $(this).parent().before(template.replace(regexp, new_id));
    return false;
  });

  $('form a.remove_child').live('click', function() {
    var hidden_field = $(this).prev('input[type=hidden]')[0];
    if(hidden_field) {
      hidden_field.value = '1';
    }
    $(this).parents('.fields').hide();
    return false;
  });
});

이때쯤이면 베어본 다이내믹 폼이 완성됩니다!여기 javascript는 매우 간단하고 다른 프레임워크에서도 쉽게 할 수 있습니다. 것을 쉽게 할 수 .application.js예를 들어 프로토타입 + lowpro를 사용하는 코드입니다.되어 있지 번거롭게 쓸 입니다.phone_numbers=()를 사용할 수 있습니다.모든 게 잘 풀려요.만!!


몇 가지 테스트를 더 진행한 결과, 템플릿은 외부로 이동해야 한다는 결론을 내렸습니다.<form>[ ]이렇게 하다거기에 보관해 두면, 나머지 폼과 함께 서버로 되돌려져 버리기 때문에, 나중에 골칫거리가 됩니다.

레이아웃 하단에 다음과 같이 추가했습니다.

<div id="jstemplates">
  <%= yield :jstemplates %>
</div

했습니다.new_child_fields_template★★★★★★★★★★★★★★★★★★:

def new_child_fields_template(form_builder, association, options = {})
  options[:object] ||= form_builder.object.class.reflect_on_association(association).klass.new
  options[:partial] ||= association.to_s.singularize
  options[:form_builder_local] ||= :f

  content_for :jstemplates do
    content_tag(:div, :id => "#{association}_fields_template", :style => "display: none") do
      form_builder.fields_for(association, options[:object], :child_index => "new_#{association}") do |f|        
        render(:partial => options[:partial], :locals => { options[:form_builder_local] => f })        
      end
    end
  end
end

, 그럼 이제 요.:reject_if템플릿이 반송되는 것에 대해 걱정할 필요가 없습니다.

제 코멘트에 대한 당신의 답변으로 미루어 볼 때, 삭제는 눈에 띄지 않게 처리하는 것이 우선이라고 생각합니다.예를 들어 발판이 있는 제품을 사용하겠습니다만, 코드는 범용이므로 어플리케이션에서 사용하기 쉬울 것입니다.

먼저 루트에 새로운 옵션을 추가합니다.

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

이제 제품 보기에 삭제 보기를 추가합니다.

<% title "Delete Product" %>

<% form_for @product, :html => { :method => :delete } do |f| %>
  <h2>Are you sure you want to delete this Product?</h2>
  <p>
    <%= submit_tag "Delete" %>
    or <%= link_to "cancel", products_path %>
  </p>
<% end %>

이 보기는 JavaScript가 비활성화된 사용자만 볼 수 있습니다.

제품 컨트롤러에서 삭제 액션을 추가해야 합니다.

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

이제 인덱스 보기로 이동하여 삭제 링크를 다음과 같이 변경하십시오.

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

이 시점에서 앱을 실행하고 제품 목록을 보면 제품을 삭제할 수 있지만 JavaScript를 사용할 수 있는 사용자에게는 더 좋은 방법이 있습니다.삭제 링크에 추가된 클래스는 JavaScript에서 사용됩니다.

이것은 JavaScript의 큰 덩어리가 됩니다만, Ajax 콜을 실시하는 코드에 초점을 맞추는 것이 중요합니다.AjaxSend 핸들러의 코드와 'a.delete' 클릭 핸들러입니다.

(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("<div class='pop delpop'><p>Are you sure?</p><p><input type='button' value='Yes' /> or <a href='#' class='close'>Cancel</a></div>");
  $(".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);
};

필요한 CSS는 다음과 같습니다.

.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;
}

POST, PUT, DELETE를 만들 때 인증 토큰을 함께 보내야 합니다.기존 JS 태그 아래에 다음 행을 추가합니다(아마도 레이아웃에서).

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

거의 끝났다.응용 프로그램컨트롤러를 열고 다음 필터를 추가합니다.

before_filter :correct_safari_and_ie_accept_headers
after_filter :set_xhr_flash

그리고 대응하는 방법:

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

Ajax 콜인 경우 플래시 메시지를 폐기해야 합니다.그렇지 않으면 다음 일반 http 요청 시 "과거"에서 플래시 메시지가 표시됩니다.두 번째 필터는 웹킷과 IE 브라우저에도 필요합니다.이 2개의 필터를 모든 Rails 프로젝트에 추가합니다.

이제 파괴 행동만 남았다.

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

그게 다야.Rails를 사용한 눈에 띄지 않는 삭제.많은 작업이 입력된 것 같지만, 일단 시작하면 그렇게 나쁘지 않습니다.당신도 이 레일캐스트에 관심이 있을 거예요.

그런데, 레일이 조금 바뀌어서 더 이상 _delete, now use_destroy를 사용할 수 없습니다.

def remove_child_link(name, f)
  f.hidden_field(:_destroy) + link_to(name, "javascript:void(0)", :class => "remove_child")
end

또, 새로운 레코드의 html을 삭제하는 것만으로 간단하게 할 수 있었습니다.그래서 나는 이렇게 한다.

$(function() {
  $('form a.add_child').click(function() {
    var association = $(this).attr('data-association');
    var template = $('#' + association + '_fields_template').html();
    var regexp = new RegExp('new_' + association, 'g');
    var new_id = new Date().getTime();

    $(this).parent().before(template.replace(regexp, new_id));
    return false;
  });

  $('form a.remove_child').live('click', function() {
    var hidden_field = $(this).prev('input[type=hidden]')[0];
    if(hidden_field) {
      hidden_field.value = '1';
    }
    $(this).parents('.new_fields').remove();
    $(this).parents('.fields').hide();
    return false;
  });
});

참고로 라이언 베이츠 a는 아름답게 작동하는 보석을 가지고 있습니다: nested_form

Rails 3용 객체의 fields_를 동적으로 추가하기 위한 눈에 띄지 않는 jQuery 플러그인을 만들었습니다.js 파일을 다운로드하기만 하면 매우 사용하기 쉽습니다.설정은 거의 없습니다.관례대로만 하면 됩니다.

https://github.com/kbparagua/numerous.js

그것은 매우 유연하지는 않지만 그 일을 할 수 있을 것이다.

https://github.com/nathanvda/cocoon을 사용하여 동적으로 폼을 생성했습니다.어소시에이션을 우아하게 처리해, 문서는 매우 간단합니다.simple_form도 함께 사용할 수 있어 특히 도움이 되었습니다.

언급URL : https://stackoverflow.com/questions/1704142/unobtrusive-dynamic-form-fields-in-rails-with-jquery

반응형