Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

tsechingho

August 21, 2012
Tweet

More Decks by tsechingho

Other Decks in Programming

Transcript

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

    • simple_form • carrierwave • mini_magick / rmagick
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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>
  7. 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>
  8. 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 %>
  9. 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
  10. 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)
  11. application.js //= require jquery //= require jquery-ui //= require jquery_ujs

    //= require bootstrap //= require chosen-jquery //= require modal //= require_tree .
  12. 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
  13. How To • Concepts • Save template in data attributes

    • DOM manipulation • https://github.com/nathanvda/cocoon • gem 'cocoon'
  14. 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
  15. 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
  16. 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: { } } %>
  17. 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 %>
  18. application.js //= require jquery //= require jquery-ui //= require jquery_ujs

    //= require bootstrap //= require chosen-jquery //= require cocoon //= require modal //= require_tree .
  19. 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
  20. How To • Concepts • iFrame Transport • rack middleware

    to modify request header • https://github.com/leppert/remotipart • gem 'remotipart'
  21. 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 .
  22. 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/