Slide 1

Slide 1 text

Ajax nested form & Ajax upload in Rails 何澤清 Tse-Ching Ho 2012/08/21

Slide 2

Slide 2 text

About • https://github.com/tsechingho • https://twitter.com/tsechingho • https://facebook.com/tsechingho

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Prerequisite Work

Slide 5

Slide 5 text

Gems supposed to be well understood • jquery-rails • anjlab-bootstrap-rails • simple_form • carrierwave • mini_magick / rmagick

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Twitter Bootstrap Modal

Slide 8

Slide 8 text

Handle Feedback of .ajaxSubmit() via Modal modal .feedback modal modal Error feedback modal modal Success feedback

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

creatures/index.html.erb

<%= t('.title') %>

<%= 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 %>

Slide 13

Slide 13 text

creatures/_table.html.erb <% creatures.each do |creature| %> <% end %> <%= Creature.human_attribute_name :popular_name %> <%= Creature.human_attribute_name :scientific_name %> <%= Creature.human_attribute_name :place_of_origin %> <%= Creature.human_attribute_name :characteristic %> <%= t('helpers.actions') %> <%= creature.popular_name %> <%= creature.scientific_name %> <%= creature.place_of_origin %> <%= creature.characteristic %> <%= link_to_show creature_path(creature) %> <%= link_to_edit_modal edit_creature_path(creature), '#creature-modal' %> <%= link_to_destroy creature_path(creature) %>

Slide 14

Slide 14 text

creatures/edit_modal.html.erb <%= simple_form_for @creature, remote: true, html: { data: { type: 'html' }, class: 'form-horizontal' } do |f| %> <% end %>

Slide 15

Slide 15 text

modal.js.coffee $ -> $.modal ||= {} $.modal.appendFeedback = (modal, data) -> $('
').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

Slide 16

Slide 16 text

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)

Slide 17

Slide 17 text

application.js //= require jquery //= require jquery-ui //= require jquery_ujs //= require bootstrap //= require chosen-jquery //= require modal //= require_tree .

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Ajax Nested Form

Slide 20

Slide 20 text

How To • Concepts • Save template in data attributes • DOM manipulation • https://github.com/nathanvda/cocoon • gem 'cocoon'

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

creatures/_form.html.erb <%= f.error_notification %>
<%= 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' } %>

<%= t('.creature_photos') %>

<%= render 'creature_photos/field_labels', creature_form: f %> <%= f.simple_fields_for :photos do |f2| %> <%= render 'creature_photos/fields', f: f2 %> <% end %>
<%= 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: { } } %>

Slide 24

Slide 24 text

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' } %>
<%= iconed_link_to_remove_association nil, f %>
<% end %>

Slide 25

Slide 25 text

application.js //= require jquery //= require jquery-ui //= require jquery_ujs //= require bootstrap //= require chosen-jquery //= require cocoon //= require modal //= require_tree .

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Ajax Upload It’s impossible since browsers forbid for security reason. It’s possible if we cheat browsers.

Slide 28

Slide 28 text

How To • Concepts • iFrame Transport • rack middleware to modify request header • https://github.com/leppert/remotipart • gem 'remotipart'

Slide 29

Slide 29 text

http://www.alfajango.com/blog/ajax-file-uploads-with-the-iframe-method/ iFrame Transport

Slide 30

Slide 30 text

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 .

Slide 31

Slide 31 text

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/

Slide 32

Slide 32 text

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/

Slide 33

Slide 33 text

THANKS