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

Ajax nested form and ajax upload in rails

Embed Size (px)

DESCRIPTION

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

Citation preview

Page 1: Ajax nested form and ajax upload in rails

Ajax nested form & Ajax upload in Rails

何澤清 Tse-Ching Ho2012/08/21

Page 3: Ajax nested form and ajax upload in rails

Demo Codehttps://github.com/tsechingho/ajax-tutorial

Page 4: Ajax nested form and ajax upload in rails

Prerequisite Work

Page 6: Ajax nested form and ajax upload in rails

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

Page 7: Ajax nested form and ajax upload in rails

Twitter Bootstrap Modal

Page 8: Ajax nested form and ajax upload in rails

Handle Feedback of .ajaxSubmit() via Modal

modal

.feedback

modal modal

Errorfeedback

modal

modal

Success feedback

Page 9: Ajax nested form and ajax upload in rails

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 endend

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_sourceend

Page 10: Ajax nested form and ajax upload in rails

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

Page 11: Ajax nested form and ajax upload in rails

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 endend

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_optionsend

Page 12: Ajax nested form and ajax upload in rails

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>

Page 13: Ajax nested form and ajax upload in rails

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>

Page 14: Ajax nested form and ajax upload in rails

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

Page 15: Ajax nested form and ajax upload in rails

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

Page 16: Ajax nested form and ajax upload in rails

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)

Page 17: Ajax nested form and ajax upload in rails

application.js

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

Page 18: Ajax nested form and ajax upload in rails

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

Page 19: Ajax nested form and ajax upload in rails

Ajax Nested Form

Page 20: Ajax nested form and ajax upload in rails

How To

• Concepts

• Save template in data attributes

• DOM manipulation

• https://github.com/nathanvda/cocoon

• gem 'cocoon'

Page 21: Ajax nested form and ajax upload in rails

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

Page 22: Ajax nested form and ajax upload in rails

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

Page 23: Ajax nested form and ajax upload in rails

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: { } } %>

Page 24: Ajax nested form and ajax upload in rails

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

Page 25: Ajax nested form and ajax upload in rails

application.js

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

Page 26: Ajax nested form and ajax upload in rails

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

Page 27: Ajax nested form and ajax upload in rails

Ajax UploadIt’s impossible

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

Page 28: Ajax nested form and ajax upload in rails

How To

• Concepts

• iFrame Transport

• rack middleware to modify request header

• https://github.com/leppert/remotipart

• gem 'remotipart'

Page 29: Ajax nested form and ajax upload in rails

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

iFrame Transport

Page 30: Ajax nested form and ajax upload in rails

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 .

Page 32: Ajax nested form and ajax upload in rails

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/

Page 33: Ajax nested form and ajax upload in rails

THANKS