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

Ajax nested form and ajax upload in rails

Jan 15, 2015

Download

Technology

Tse-Ching Ho

explain and demonstrate how to implement ajax nested form and ajax upload in rails project
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
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