rails源码解读之ActionView之画面标签
2011-05-09 17:01
204 查看
rails的画面标签虽说已经基本够用了,
但是总也会有实现起来不方便的地方,
这时候就需要用到自定义标签了。
为了防止盗链,先把原文地址贴出来
http://blog.csdn.net/zhao_hongsheng/archive/2011/05/09/6406996.aspx
想要自定义标签就得能看懂rails的源码。
(在网上搜索了一下,中文资料太少了,还是自己看吧)
写出来各位网友分享一下。
path: rails/actionpack/lib/action_view/helpers
主要从2标签入手form_tag,form_for
其他就好办了。
看源码,先读注释!!
在form_helper.rb开头看到
说明form_tag标签用到的方法基本都定义在form_tag_helper.rb文件中
form_for标签用到的方法都定义在form_helper.rb文件中。
先看个简单的form_tag_helper.rb
打开一看,所有用的方法都定义在里面了。
比如这个text_field_tag标签,
里面调用tag_helper.rb中的tag方法实现了标签的输出。
很简单嘛~,但是实际中大多用的还是form_for标签。
这个用点儿小难度,follow me ~
打开form_helpler.rb文件,找到form_for的定义
这句先把<form action="xxxx" method="xx" ....>等内容生成好
然后关键的调用了个fields_for方法,来生成form标签里面的内容
找到这个方法的定义,还是在这个文件里
注意最后2行,实例化了一个ActionView::Base.default_form_builder的对象,
这个对象就是我们经常用到的
中的 f 变量了,
那么这个f 到底是什么类的对象呢?
看form_helper.rb的最后
原来是ActionView::Helpers::FormBuilder类型的对象。
那么 <%= f.text_field .....%>就是调用的
FormBuilder类中的相应的text_field方法喽~
有代码为证,这个类里确实定义了这些方法
这里要注意的是
又是class_eval,ruby的黑魔法(black magic)
除了label check_box radio_button fields_for这四个方法
所有form_helper module里的实例方法都被重新定义了,
当然这四个方法随后也都被重写了,
之所以分开写只程序技巧上的问题,
但却增加了阅读的难度。
下面具体看这些方法是怎么被重写的。
@template.send,调用了@template这个对象的相应的方法,
可以看出其实FormBuilder中的这些方法只是做了一个代理,
终究还是调用了,FormHelper中定义的方法,
好处就是f.text_field的时候不用传@object这个参数了。
@template就是self,也就是Active::View::Base的实例对象,
没看懂?仔细看看FormBuilder的initialize方法,你行的!
现在问题简化了,只要去看FormHelper中关于标签的方法如何定义的就好啦,
拿text_field举例
调用了,InstanceTag类的to_input_field_tag方法,
to_input_field_tag中还是调用tag_helper.rb中(Tag类)的tag方法输出标签。
到这里就已经理清了整个标签输出的过程。
之后标签自定义什么的,随你了~
顺便提一下,标签输出的ActiveRecord值都是before_type_cast的
before_type_cast是什么不知道?那得补一下ActiveRecord相关知识了,
有空儿我再写上来。
也许还有的网友跟我有同样的疑问
select方法的定义在FormBuilder里怎么找不到?
用IDE在全局搜索下,发现在form_options_helper.rb下还有定义。。
完。。。
第一次写源码解读,若有错误,欢迎指正探讨~
但是总也会有实现起来不方便的地方,
这时候就需要用到自定义标签了。
为了防止盗链,先把原文地址贴出来
http://blog.csdn.net/zhao_hongsheng/archive/2011/05/09/6406996.aspx
想要自定义标签就得能看懂rails的源码。
(在网上搜索了一下,中文资料太少了,还是自己看吧)
写出来各位网友分享一下。
rails版本 | 2.3.8 |
---|
相关文件 |
---|
form_helper.rb form_tag_helper.rb form_options_helper.rb tag_helper.rb form_helper.rb |
其他就好办了。
看源码,先读注释!!
在form_helper.rb开头看到
# There are two types of form helpers: those that specifically work with model attributes and those that don't. # This helper deals with those that work with model attributes; to see an example of form helpers that don't work # with model attributes, check the ActionView::Helpers::FormTagHelper documentation.
说明form_tag标签用到的方法基本都定义在form_tag_helper.rb文件中
form_for标签用到的方法都定义在form_helper.rb文件中。
先看个简单的form_tag_helper.rb
打开一看,所有用的方法都定义在里面了。
比如这个text_field_tag标签,
里面调用tag_helper.rb中的tag方法实现了标签的输出。
def text_field_tag(name, value = nil, options = {}) tag :input, { "type" => "text", "name" => name, "id" => name, "value" => value }.update(options.stringify_keys) end
很简单嘛~,但是实际中大多用的还是form_for标签。
这个用点儿小难度,follow me ~
打开form_helpler.rb文件,找到form_for的定义
def form_for(record_or_name_or_array, *args, &proc) raise ArgumentError, "Missing block" unless block_given? options = args.extract_options! case record_or_name_or_array when String, Symbol object_name = record_or_name_or_array when Array object = record_or_name_or_array.last object_name = ActionController::RecordIdentifier.singular_class_name(object) apply_form_for_options!(record_or_name_or_array, options) args.unshift object else object = record_or_name_or_array object_name = ActionController::RecordIdentifier.singular_class_name(object) apply_form_for_options!([object], options) args.unshift object end concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding) fields_for(object_name, *(args << options), &proc) concat('</form>', proc.binding) end
这句先把<form action="xxxx" method="xx" ....>等内容生成好
concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
然后关键的调用了个fields_for方法,来生成form标签里面的内容
找到这个方法的定义,还是在这个文件里
def fields_for(record_or_name_or_array, *args, &block) raise ArgumentError, "Missing block" unless block_given? options = args.extract_options! case record_or_name_or_array when String, Symbol object_name = record_or_name_or_array object = args.first when Array object = record_or_name_or_array.last object_name = ActionController::RecordIdentifier.singular_class_name(object) apply_form_for_options!(record_or_name_or_array, options) else object = record_or_name_or_array object_name = ActionController::RecordIdentifier.singular_class_name(object) end builder = options[:builder] || ActionView::Base.default_form_builder yield builder.new(object_name, object, self, options, block) end
注意最后2行,实例化了一个ActionView::Base.default_form_builder的对象,
这个对象就是我们经常用到的
<% form_for :ad, :url=>{:action => 'ad_create', :id => @campaign} do |f| %> <% end %>
中的 f 变量了,
那么这个f 到底是什么类的对象呢?
看form_helper.rb的最后
class Base cattr_accessor :default_form_builder self.default_form_builder = ::ActionView::Helpers::FormBuilder end
原来是ActionView::Helpers::FormBuilder类型的对象。
那么 <%= f.text_field .....%>就是调用的
FormBuilder类中的相应的text_field方法喽~
有代码为证,这个类里确实定义了这些方法
class FormBuilder #:nodoc: # The methods which wrap a form helper call. class_inheritable_accessor :field_helpers self.field_helpers = (FormHelper.instance_methods - ['form_for']) attr_accessor :object_name, :object, :options def initialize(object_name, object, template, options, proc) @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc @default_options = @options ? @options.slice(:index) : {} end (field_helpers - %w(label check_box radio_button fields_for)).each do |selector| src = <<-end_src def #{selector}(method, options = {}) @template.send(#{selector.inspect}, @object_name, method, objectify_options(options)) end end_src class_eval src, __FILE__, __LINE__ end def fields_for(record_or_name_or_array, *args, &block) case record_or_name_or_array when String, Symbol name = "#{object_name}[#{record_or_name_or_array}]" when Array object = record_or_name_or_array.last name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" args.unshift(object) else object = record_or_name_or_array name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" args.unshift(object) end @template.fields_for(name, *args, &block) end def label(method, text = nil, options = {}) @template.label(@object_name, method, text, objectify_options(options)) end def check_box(method, options = {}, checked_value = "1", unchecked_value = "0") @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value) end def radio_button(method, tag_value, options = {}) @template.radio_button(@object_name, method, tag_value, objectify_options(options)) end def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError") @template.error_message_on(@object, method, prepend_text, append_text, css_class) end def error_messages(options = {}) @template.error_messages_for(@object_name, objectify_options(options)) end def submit(value = "Save changes", options = {}) @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit")) end private def objectify_options(options) @default_options.merge(options.merge(:object => @object)) end end end
这里要注意的是
(field_helpers - %w(label check_box radio_button fields_for)).each do |selector| src = <<-end_src def #{selector}(method, options = {}) @template.send(#{selector.inspect}, @object_name, method, objectify_options(options)) end end_src class_eval src, __FILE__, __LINE__ end
又是class_eval,ruby的黑魔法(black magic)
除了label check_box radio_button fields_for这四个方法
所有form_helper module里的实例方法都被重新定义了,
当然这四个方法随后也都被重写了,
之所以分开写只程序技巧上的问题,
但却增加了阅读的难度。
下面具体看这些方法是怎么被重写的。
def #{selector}(method, options = {}) @template.send(#{selector.inspect}, @object_name, method, objectify_options(options)) end
@template.send,调用了@template这个对象的相应的方法,
可以看出其实FormBuilder中的这些方法只是做了一个代理,
终究还是调用了,FormHelper中定义的方法,
好处就是f.text_field的时候不用传@object这个参数了。
@template就是self,也就是Active::View::Base的实例对象,
没看懂?仔细看看FormBuilder的initialize方法,你行的!
现在问题简化了,只要去看FormHelper中关于标签的方法如何定义的就好啦,
拿text_field举例
def file_field(object_name, method, options = {}) InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options) end
调用了,InstanceTag类的to_input_field_tag方法,
to_input_field_tag中还是调用tag_helper.rb中(Tag类)的tag方法输出标签。
def to_input_field_tag(field_type, options = {}) options = options.stringify_keys options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size") options = DEFAULT_FIELD_OPTIONS.merge(options) if field_type == "hidden" options.delete("size") end options["type"] = field_type options["value"] ||= value_before_type_cast(object) unless field_type == "file" options["value"] &&= html_escape(options["value"]) add_default_name_and_id(options) tag("input", options) end
到这里就已经理清了整个标签输出的过程。
之后标签自定义什么的,随你了~
顺便提一下,标签输出的ActiveRecord值都是before_type_cast的
before_type_cast是什么不知道?那得补一下ActiveRecord相关知识了,
有空儿我再写上来。
options["value"] ||= value_before_type_cast(object) unless field_type == "file"
也许还有的网友跟我有同样的疑问
select方法的定义在FormBuilder里怎么找不到?
用IDE在全局搜索下,发现在form_options_helper.rb下还有定义。。
class FormBuilder def select(method, choices, options = {}, html_options = {}) @template.select(@object_name, method, choices, options.merge(:object => @object), html_options) end def collection_select(method, collection, value_method, text_method, options = {}, html_options = {}) @template.collection_select(@object_name, method, collection, value_method, text_method, options.merge(:object => @object), html_options) end def country_select(method, priority_countries = nil, options = {}, html_options = {}) @template.country_select(@object_name, method, priority_countries, options.merge(:object => @object), html_options) end def time_zone_select(method, priority_zones = nil, options = {}, html_options = {}) @template.time_zone_select(@object_name, method, priority_zones, options.merge(:object => @object), html_options) end end
完。。。
第一次写源码解读,若有错误,欢迎指正探讨~
相关文章推荐
- AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking
- Rails Action View::Helpers 方法
- AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking
- oschina-app源码分析-提醒标签BadgeView使用逻辑流程
- ViewGroup的dispatchTouchEvent方法源码解读
- AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking
- AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking
- Android6.0源码解读之View点击事件分发机制
- mvc源码解读(20)-controller和view之查找view
- mvc源码解读(4)-ViewData&ViewBag&TempData&ViewModel
- mvc源码解读(12)-mvc四大过滤器之ActionFilter
- struct2源码解读(10)之执行action请求前篇
- Rails源代码分析(49):ActionView::Helpers::AssetTagHelper
- Android View源码解读:浅谈DecorView与ViewRootImpl
- Android view 事件分发源码解读
- ViewPager源码不完全解读
- struct2源码解读(5)之解析bean标签
- Bootstrap源码解读标签、徽章、缩略图和警示框(8)
- ViewPagerIndicator源码解读