Ajax nested form and ajax upload in rails

7,701 views

Published on

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

Published in: Technology
0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
7,701
On SlideShare
0
From Embeds
0
Number of Embeds
40
Actions
Shares
0
Downloads
41
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

Ajax nested form and ajax upload in rails

  1. 1. Ajax nested form &Ajax upload in Rails 何澤清 Tse-Ching Ho 2012/08/21
  2. 2. About• https://github.com/tsechingho• https://twitter.com/tsechingho• https://facebook.com/tsechingho
  3. 3. Demo Codehttps://github.com/tsechingho/ajax-tutorial
  4. 4. Prerequisite Work
  5. 5. Gems supposed to be well understood• jquery-rails• anjlab-bootstrap-rails• simple_form• carrierwave• mini_magick / rmagick
  6. 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. 7. Twitter Bootstrap Modal
  8. 8. Handle Feedbackof .ajaxSubmit() via Modal modal modal modal modal Error feedback Success .feedback modal feedback
  9. 9. Creature & CreaturePhoto require file_size_validator class CreaturePhoto < ActiveRecord::Baseclass Creature < ActiveRecord::Base attr_accessible :content_type, :file_name, :file_size, attr_accessible :characteristic, :place_of_origin, :creature_id, :source, :source_cache, :remove_source:popular_name, :scientific_name, :animal_handbook_ids validates :source, validates :popular_name, presence: true file_size: { maximum: 3.megabytes.to_i has_many :animal_handbook_creatures } has_many :animal_handbooks,through: :animal_handbook_creatures mount_uploader :source, has_many :photos, class_name: CreaturePhoto CreaturePhotoUploader, mount_on: :file_name delegate :url, :current_path, :size, :filename, to: :source def name popular_name belongs_to :creature endend before_save :update_attributes_with_source end
  10. 10. creatures_controller.rbclass 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? endend
  11. 11. twitter_bootstrap_helper.rbdef 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 endenddef 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)enddef 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_optionsend
  12. 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. 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. 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. 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. 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. 17. application.js//= require jquery//= require jquery-ui//= require jquery_ujs//= require bootstrap//= require chosen-jquery//= require modal//= require_tree .
  18. 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. 19. Ajax Nested Form
  20. 20. How To• Concepts • Save template in data attributes • DOM manipulation• https://github.com/nathanvda/cocoon• gem cocoon
  21. 21. creature.rbclass 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 endend
  22. 22. twitter_bootstrap_helper.rbmodule 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 endend
  23. 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. 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. 25. application.js//= require jquery//= require jquery-ui//= require jquery_ujs//= require bootstrap//= require chosen-jquery//= require cocoon//= require modal//= require_tree .
  26. 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. 27. Ajax Upload It’s impossiblesince browsers forbid for security reason. It’s possible if we cheat browsers.
  28. 28. How To• Concepts • iFrame Transport • rack middleware to modify request header• https://github.com/leppert/remotipart• gem remotipart
  29. 29. iFrame Transporthttp://www.alfajango.com/blog/ajax-file-uploads-with-the-iframe-method/
  30. 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. 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. 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. 33. THANKS

×