Pro Yearly is on sale from $80 to $50! »

ajax nested form and ajax upload in rails

ajax nested form and ajax upload in rails

explain and demonstrate how to implement ajax nested form and ajax upload in rails project

61b55110e7f363bff43bcab8789930fb?s=128

tsechingho

August 21, 2012
Tweet

Transcript

  1. Ajax nested form & Ajax upload in Rails 何澤清 Tse-Ching

    Ho 2012/08/21
  2. About • https://github.com/tsechingho • https://twitter.com/tsechingho • https://facebook.com/tsechingho

  3. Demo Code https://github.com/tsechingho/ajax-tutorial

  4. Prerequisite Work

  5. Gems supposed to be well understood • jquery-rails • anjlab-bootstrap-rails

    • simple_form • carrierwave • mini_magick / rmagick
  6. Ground rails project • Have two models associated with has_many

    • Have one model mounted with carrierwave’s uploader • Render 'form' in views of new and edit • Use respond_with for each action of controller • Layout with twitter bootstrap
  7. Twitter Bootstrap Modal

  8. Handle Feedback of .ajaxSubmit() via Modal modal .feedback modal modal

    Error feedback modal modal Success feedback
  9. Creature & CreaturePhoto class Creature < ActiveRecord::Base attr_accessible :characteristic, :place_of_origin,

    :popular_name, :scientific_name, :animal_handbook_ids validates :popular_name, presence: true has_many :animal_handbook_creatures has_many :animal_handbooks, through: :animal_handbook_creatures has_many :photos, class_name: 'CreaturePhoto' def name popular_name end end require 'file_size_validator' class CreaturePhoto < ActiveRecord::Base attr_accessible :content_type, :file_name, :file_size, :creature_id, :source, :source_cache, :remove_source validates :source, file_size: { maximum: 3.megabytes.to_i } mount_uploader :source, CreaturePhotoUploader, mount_on: :file_name delegate :url, :current_path, :size, :filename, to: :source belongs_to :creature before_save :update_attributes_with_source end
  10. creatures_controller.rb class CreaturesController < ApplicationController before_filter :load_creature, only: [:show, :edit,

    :update, :destroy] respond_to :html def edit render 'edit_modal', layout: false if request.xhr? end def update @creature.update_attributes params[:creature] if @creature.valid? flash[:notice] = 'Creature was successfully updated.' end respond_with @creature do |format| format.html { if @creature.valid? load_creatures render partial: 'table', locals: { creatures: @creatures } else render 'edit_modal', layout: false end } if request.xhr? end flash.discard :notice if request.xhr? end end
  11. twitter_bootstrap_helper.rb def iconed_link_to(text, url, options = {}) icon_class = options.delete(:icon_class)

    link_to url, options do content_tag(:i, nil, class: icon_class) << ' ' << text end end def link_to_edit(url, options = {}) icon_class = options.delete(:icon_class) || 'icon-edit' default_options = { title: t('helpers.edit'), class: 'btn', icon_class: icon_class } iconed_link_to nil, url, default_options.deep_merge(options) end def link_to_edit_modal(url, modal_id) default_options = { remote: true, data: { target: modal_id, toggle: 'modal', type: 'html' }, class: 'btn modal-open' } link_to_edit url, default_options end
  12. creatures/index.html.erb <article id="creature-list"> <header> <h1><%= t('.title') %></h1> </header> <%= render_list

    class: 'nav nav-tabs' do |li| li << [link_to_open_modal(t('helpers.new'), new_creature_path, '#creature- modal'), { class: 'action' }] end %> <%= render 'table', creatures: @creatures %> <nav role="pagination"> </nav> </article> <div class="modal hide fade" id="creature-modal"></div>
  13. creatures/_table.html.erb <table class="table table-striped table-bordered"> <tr> <th><%= Creature.human_attribute_name :popular_name %></th>

    <th><%= Creature.human_attribute_name :scientific_name %></th> <th><%= Creature.human_attribute_name :place_of_origin %></th> <th><%= Creature.human_attribute_name :characteristic %></th> <th><%= t('helpers.actions') %></th> </tr> <% creatures.each do |creature| %> <tr> <td><%= creature.popular_name %></td> <td><%= creature.scientific_name %></td> <td><%= creature.place_of_origin %></td> <td><%= creature.characteristic %></td> <td class="btn-group"> <%= link_to_show creature_path(creature) %> <%= link_to_edit_modal edit_creature_path(creature), '#creature-modal' %> <%= link_to_destroy creature_path(creature) %> </td> </tr> <% end %> </table>
  14. creatures/edit_modal.html.erb <%= simple_form_for @creature, remote: true, html: { data: {

    type: 'html' }, class: 'form-horizontal' } do |f| %> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal">×</button> <h3><%= t('.title') %></h3> </div> <div class="modal-body"> <%= render 'form', f: f %> </div> <div class="modal-footer"> <a href="#" class="btn" data-dismiss="modal"><%= t('helpers.close') %></a> <%= f.button :submit, name: nil, class: 'btn-primary' %> </div> <% end %>
  15. modal.js.coffee $ -> $.modal ||= {} $.modal.appendFeedback = (modal, data)

    -> $('<div>').addClass('feedback hide').html(data).appendTo(modal) $.modal.replaceFeedback = (modal) -> modal.html(modal.children('.feedback').html()) $.modal.enableChosen() $.modal.replaceTable = (table_id, modal = $(this)) -> feedback_table = modal.find('.table') table = $(table_id).find('.table') table.html(feedback_table.html()) modal.find('.feedback').remove().end() .modal('hide') table.effect('shake') return true
  16. creatures.js.coffee $ -> $('#creature-list') .on 'ajax:success', 'a.modal-open', (event, data, status,

    xhr) -> modal = $($(this).attr('data-target')) modal.html(data) $.modal.enableChosen() .on 'ajax:error', '.a.modal-open', (event, xhr, status, error) -> modal = $($(this).attr('data-target')) $.modal.showErrorModal(status, error, modal) $('#creature-modal') .on 'ajax:success', '.simple_form', (event, data, status, xhr) -> modal = $(this).parent() $.modal.appendFeedback(modal, data) if modal.find('.feedback .alert-error').size() > 0 $.modal.replaceFeedback(modal) return true table_id = '#creature-list' $.modal.replaceTable(table_id, modal) .on 'ajax:error', '.simple_form', (event, xhr, status, error) -> modal = $('#creature-modal') $.modal.showErrorModal(status, error, modal)
  17. application.js //= require jquery //= require jquery-ui //= require jquery_ujs

    //= require bootstrap //= require chosen-jquery //= require modal //= require_tree .
  18. Key Points • Use respond_with • Render 'modal' specific files

    • Render partial files • Via data attributes • Define rails ajax callbacks • Use namespace for javascript methods • Catch ajax callback in div container if data-type is :html
  19. Ajax Nested Form

  20. How To • Concepts • Save template in data attributes

    • DOM manipulation • https://github.com/nathanvda/cocoon • gem 'cocoon'
  21. creature.rb class Creature < ActiveRecord::Base attr_accessible :characteristic, :place_of_origin, :popular_name, :scientific_name,

    :animal_handbook_ids, :photos_attributes validates :popular_name, presence: true has_many :animal_handbook_creatures has_many :animal_handbooks, through: :animal_handbook_creatures has_many :photos, class_name: 'CreaturePhoto' accepts_nested_attributes_for :photos, allow_destroy: true, reject_if: proc { |obj| obj.blank? } def name popular_name end end
  22. twitter_bootstrap_helper.rb module TwitterBootstrapHelper def iconed_link_to_add_association(text, *args) args << {} if

    args.size < 2 icon_class = args.last.delete(:icon_class) || 'icon-plus' default_options = { title: t('helpers.add'), class: 'btn' } args.last.deep_merge! default_options link_to_add_association *args do content_tag(:i, nil, class: icon_class) << ' ' << text end end def iconed_link_to_remove_association(text, *args) args << {} if args.size < 2 icon_class = args.last.delete(:icon_class) || 'icon-remove' default_options = { title: t('helpers.remove'), class: 'btn' } args.last.deep_merge! default_options link_to_remove_association *args do content_tag(:i, nil, class: icon_class) << ' ' << text end end end
  23. creatures/_form.html.erb <%= f.error_notification %> <div class="form-inputs"> <%= f.input :popular_name %>

    <%= f.input :scientific_name %> <%= f.input :place_of_origin %> <%= f.input :characteristic, input_html: { size: '20x5' } %> <%= f.association :animal_handbooks, input_html: { class: 'chzn-select' } %> </div> <h3><%= t('.creature_photos') %></h3> <div class="form-inputs form-inline"> <%= render 'creature_photos/field_labels', creature_form: f %> <%= f.simple_fields_for :photos do |f2| %> <%= render 'creature_photos/fields', f: f2 %> <% end %> </div> <%= iconed_link_to_add_association t('helpers.add'), f, :photos, data: { :'association-insertion-node' => '.form-inputs.form-inline', :'association-insertion-method' => :append }, partial: 'creature_photos/fields', render_options: { locals: { } } %>
  24. creatures/_fields.html.erb <%= field_set_tag nil, class: 'creature-fields row-fluid nested-form-hidden-label nested-fields' do

    %> <%= f.input :popular_name, wrapper_html: { class: 'span2' }, input_html: { class: 'span12' } %> <%= f.input :scientific_name, wrapper_html: { class: 'span2' }, input_html: { class: 'span12' } %> <%= f.input :place_of_origin, wrapper_html: { class: 'span2' }, input_html: { class: 'span12' } %> <%= f.input :characteristic, as: :string, wrapper_html: { class: 'span4' }, input_html: { class: 'span12' } %> <div class="control-group actions span2"> <%= iconed_link_to_remove_association nil, f %> </div> <% end %>
  25. application.js //= require jquery //= require jquery-ui //= require jquery_ujs

    //= require bootstrap //= require chosen-jquery //= require cocoon //= require modal //= require_tree .
  26. Key Points • accepts_nested_attributes_for :photos, allow_destroy: true • attr_accessible :photos_attributes

    • Render partial file • Use link_to_add_association helper • Use link_to_remove_association helper • Add 'nested-fields' class to container tag of nested item • Require cocoon javascript
  27. Ajax Upload It’s impossible since browsers forbid for security reason.

    It’s possible if we cheat browsers.
  28. How To • Concepts • iFrame Transport • rack middleware

    to modify request header • https://github.com/leppert/remotipart • gem 'remotipart'
  29. http://www.alfajango.com/blog/ajax-file-uploads-with-the-iframe-method/ iFrame Transport

  30. application.js //= require jquery //= require jquery-ui //= require jquery_ujs

    //= require bootstrap //= require chosen-jquery //= require cocoon // Since XMLHttpRequest (AJAX) standard has no support for file uploads, // use iframe-transport method of remotipart gem for ajax file upload. //= require jquery.remotipart //= require modal //= require_tree .
  31. Other ways? • iFrame • https://github.com/blueimp/jQuery-File-Upload • Flash • http://www.uploadify.com

    • Form Data • http://hacks.mozilla.org/2010/07/firefox-4- formdata-and-the-new-file-url-object/
  32. References • http://www.alfajango.com/blog/rails-3-remote-links- and-forms/ • http://www.alfajango.com/blog/rails-3-remote-links- and-forms-data-type-with-jquery/ • http://railscasts.com/episodes/196-nested-model- form-revised

    • http://www.alfajango.com/blog/ajax-file-uploads-with- the-iframe-method/ • http://os.alfajango.com/remotipart/
  33. THANKS