せたがやえんじにあぶろぐ

Ruby | Rails | iPhone | Web

Rails3以降にremote_function, link_to_remoteを移植してみる

かつてRails2にはremote_functionや、link_to_remoteと言う神メソッドがあった。

Rails2のAjaxライブラリはPrototype.jsが採用されていた。

しかしRails3以降、Prototype.jsはバイバイして神メソッドは使用することが出来ず、

link_to 'タイトル', URL, :remote => true

という、オプションを渡すことでAjax通信をするように変更された。

DOMオブジェクトを書き換える際は、js.erbファイルをrenderし、さらにそのファイル内でコールバックしたdataを指定したIDに挿入という、プログラミング初心者の私には、血尿レベルの意味不明っぷりであった。

本当に使えないのか...?

jQuery用にマッピングしなおせばいいだけじゃないのか...?

私は立ち上がった。

マッピングしたる、マッピングしたるでぇ..!!

と思ったので、link_to_remoteのソースコードjQuery用に書き換えてみたよ。

多分不完全なので、適宜修正が必要かもしれないよ。

Prototype.jsではコールバックに色々利用できたけど、jQuery.ajaxは[success,complete,error]の3種類だけが利用できるみたいだよ。




こんな感じ?

= link_to_remote('title', { update: :catalogs, url: {action: :index, controller: :menu},
                        submit: :form,
                        success:  "alert('success')",
                        complete: "alert('complete')",
                        error:    "alert('error')",
                        position: :bottom }, href: "#", style: 'margin:100px')


下記ソースを、app/helpers当たりにぶちこんだよ。

#!/usr/bin/ruby
# -*- encoding: UTF-8 -*-
module HtmlTagHelper

####################################
#         link_to_remote           #
####################################

	JS_ESCAPE_MAP = {
			'\\'    => '\\\\',
			'</'    => '<\/',
			"\r\n"  => '\n',
			"\n"    => '\n',
			"\r"    => '\n',
			'"'     => '\\"',
			"'"     => "\\'"
	}

	CALLBACKS    = Set.new([ :complete, :error ] +
														 (100..599).to_a)

	if "ruby".encoding_aware?
		JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '&#x2028;'
	else
		JS_ESCAPE_MAP["\342\200\250"] = '&#x2028;'
	end

	def escape_javascript(javascript)
		if javascript
			result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
			javascript.html_safe? ? result.html_safe : result
		else
			''
		end
	end

	def link_to_remote(name, options = {}, html_options = {})
		link_to_function(name, remote_function(options), html_options || options.delete(:html))
	end

	def link_to_function(name, function, html_options={})
		onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
		href = html_options[:href] || '#'
		content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
	end

	def remote_function(options={})
		javascript_options = options_for_ajax(options)
		callbacks = build_callbacks(options)

		update = ''
		if options[:update] && options[:update].is_a?(Hash)
			update  = []
			update << "success:'#{options[:update][:success]}'" if options[:update][:success]
			update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure]
			update  = '{' + update.join(',') + '}'
		elsif options[:update]
			update << "'#{options[:update]}'"
		end
		function = "$.ajax({"
		function << "type: '#{ options[:method] ? options[:method] : 'GET' }',"
		function << "url:  '#{ options[:url] ? url_for(options[:url]) : '#' }',"
		function << "data : #{ javascript_options },"
		callbacks.each {|callback,code| function << "#{callback.to_s}: #{code}, "	} if callbacks.present?
		function << "success: function(data) {"
		function << options[:success] + '; ' if options[:success].present?
		function << case options[:position]
									when :top
										"$('##{options[:update]}').prepend(data); "
									when :bottom
										"$('##{options[:update]}').append(data); "
									when :before
										"$('##{options[:update]}').before(data); "
									when :after
										"$('##{options[:update]}').after(data); "
									else
										"$('##{options[:update]}').html(data); "
								end if options[:update]
		function << "} });"

		function = "#{options[:before]}; #{function}" if options[:before]
		function = "#{function}; #{options[:after]}"  if options[:after]
		function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
		function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]
		return function.html_safe
	end

	def options_for_ajax(options)
		js_options={}

		if options[:submit]
			js_options['parameters'] = "$('##{options[:submit]} input, ##{options[:submit]} textarea,  ##{options[:submit]} select, ##{options[:submit]}').serialize('')"
		elsif options[:with]
			js_options['parameters'] = options[:with]
		else
			js_options['parameters'] = "$('form').serialize(this)"
		end
		if protect_against_forgery? && !options[:form]
			if js_options['parameters']
				js_options['parameters'] << " + '&"
			else
				js_options['parameters'] = "'"
			end
			js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
		end
	end

	def build_callbacks(options)
		callbacks = {}
		options.each do |callback, code|
			if CALLBACKS.include?(callback)
				callbacks[callback] = "function(request){#{code}}"
			end
		end
		return callbacks
	end

	def method_option_to_s(method)
		(method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
	end

end