Rails源码研究之ActionView:一,基本架构和ERB

先看源码再分析

1,action_view.rb

$:.unshift(File.dirname(__FILE__) + "/action_view/vendor")
require 'action_view/base'
require 'action_view/partials'

ActionView::Base.class_eval do
  include ActionView::Partials
end

ActionView::Base.load_helpers(File.dirname(__FILE__) + "/action_view/helpers/")

还是require base.rb/partials.rb,然后helpers目录下有许多helpers模块都include进来

2,base.rb

module ActionView
 
  class Base

    module CompiledTemplates
    end
    include CompiledTemplates

    class ObjectWrapper < Struct.new(:value)
    end

    def self.load_helpers(helper_dir)
      Dir.entries(helper_dir).sort.each do |helper_file|
        next unless helper_file =~ /^([a-z][a-z_]*_helper).rb$/
        require File.join(helper_dir, $1)
        helper_module_name = $1.camelize
        class_eval("include ActionView::Helpers::#{helper_module_name}") if Helpers.const_defined (helper_module_name)
      end
    end

    def render(options = {}, old_local_assigns = {}, &block)
      if options.is_a (String)
        render_file(options, true, old_local_assigns)
      elsif options == :update
        update_page(&block)
      elsif options.is_a (Hash)
        options[:locals] ||= {}
        options[:use_full_path] = options[:use_full_path].nil    true : options[:use_full_path]
        if options[:file]
          render_file(options[:file], options[:use_full_path], options[:locals])
        elsif options[:partial] && options[:collection]
          render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals])
        elsif options[:partial]
          render_partial(options[:partial], ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals])
        elsif options[:inline]
          render_template(options[:type] || :rhtml, options[:inline], nil, options[:locals] || {})
        end
      end
    end

    def render_file(template_path, use_full_path = true, local_assigns = {})
      @first_render ||= template_path
      if use_full_path
        template_path_without_extension, template_extension = path_and_extension(template_path)
        if template_extension
          template_file_name = full_template_path(template_path_without_extension, template_extension)
        else
          template_extension = pick_template_extension(template_path).to_s
          template_file_name = full_template_path(template_path, template_extension)
        end
      else
        template_file_name = template_path
        template_extension = template_path.split('.').last
      end
      template_source = nil
      begin
        render_template(template_extension, template_source, template_file_name, local_assigns)
      rescue Exception => e
        if TemplateError === e
          e.sub_template_of(template_file_name)
          raise e
        else
          raise TemplateError.new(@base_path, template_file_name, @assigns, template_source, e)
        end
      end
    end

    def render_template(template_extension, template, file_path = nil, local_assigns = {})
      if handler = @@template_handlers[template_extension]
        template ||= read_template_file(file_path, template_extension)
        delegate_render(handler, template, local_assigns)
      else
        compile_and_render_template(template_extension, template, file_path, local_assigns)
      end
    end

    def compile_and_render_template(extension, template = nil, file_path = nil, local_assigns = {})
      local_assigns = local_assigns.symbolize_keys if @@local_assigns_support_string_keys
      if compile_template (template, file_path, local_assigns)
        template ||= read_template_file(file_path, extension)
        compile_template(extension, template, file_path, local_assigns)
      end
      method_name = @@method_names[file_path || template]
      evaluate_assigns
      send(method_name, local_assigns) do |*name|
        instance_variable_get "@content_for_#{name.first || 'layout'}"
      end
    end

    private

      def read_template_file(template_path, extension)
        File.read(template_path)
      end

      def create_template_source(extension, template, render_symbol, locals)
        if template_requires_setup (extension)
          body = case extension.to_sym
            when :rxml
              "controller.response.content_type ||= 'application/xml'\n" +
              "xml = Builder::XmlMarkup.new(:indent => 2)\n" +
              template
            when :rjs
              "controller.response.content_type ||= 'text/javascript'\n" +
              "update_page do |page|\n#{template}\nend"
          end
        else
          body = ERB.new(template, nil, @@erb_trim_mode).src
        end
        @@template_args[render_symbol] ||= {}
        locals_keys = @@template_args[render_symbol].keys | locals
        @@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h }
        locals_code = ""
        locals_keys.each do |key|
          locals_code << "#{key} = local_assigns[:#{key}]\n"
        end
        "def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend"
      end

      def compile_template(extension, template, file_name, local_assigns)
        render_symbol = assign_method_name(extension, template, file_name)
        render_source = create_template_source(extension, template, render_symbol, local_assigns.keys)
        line_offset = @@template_args[render_symbol].size
        if extension
          case extension.to_sym
          when :rxml, :rjs
            line_offset += 2
          end
        end
        begin
          unless file_name.blank 
            CompiledTemplates.module_eval(render_source, file_name, -line_offset)
          else
            CompiledTemplates.module_eval(render_source, 'compiled-template', -line_offset)
          end
        rescue Exception => e
          if logger
            logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
            logger.debug "Function body: #{render_source}"
            logger.debug "Backtrace: #{e.backtrace.join("\n")}"
          end
          raise TemplateError.new(@base_path, file_name || template, @assigns, template, e)
        end
        @@compile_time[render_symbol] = Time.now
      end
  end
end

我们看到create_template_source方法中,对rxml后缀的模板使用Builder::XmlMarkup解析,对rjs后缀的模板使用update_page语句解析,而对rhtml则使用erb来解析

而compile_template方法则是对render_source调用CompiledTemplates.module_eval,由于render_source都是一些Ruby语句,所以module_eval会解析这些语句

render方法则根据options为:file、:partial、:partial collection、:inline来分发render方法

3,erb.rb

class ERB
  class Compiler

    Scanner.default_scanner = TrimScanner

    class Scanner
      SplitRegexp = /(<%%)|(%%>)|(<%=)|(<%#)|(<%)|(%>)|(\n)/

      def scan; end
    end

    class TrimScanner < Scanner
      TrimSplitRegexp = /(<%%)|(%%>)|(<%=)|(<%#)|(<%)|(%>\n)|(%>)|(\n)/

      def scan(&block)
	@stag = nil
	if @percent
	  @src.each do |line|
	    percent_line(line, &block)
	  end
	else
	  @src.each do |line|
	    @scan_line.call(line, &block)
	  end
	end
	nil
      end

      def percent_line(line, &block)
	if @stag || line[0] !=  %
	  return @scan_line.call(line, &block)
	end
	line[0] = ''
	if line[0] ==  %
	  @scan_line.call(line, &block)
	else
          yield(PercentLine.new(line.chomp))
	end
      end

      def scan_line(line)
	line.split(SplitRegexp).each do |token|
	  next if token.empty 
	  yield(token)
	end
      end
    end

    def compile(s)
      out = Buffer.new(self)

      content = ''
      scanner = make_scanner(s)
      scanner.scan do |token|
	if scanner.stag.nil 
	  case token
          when PercentLine
	    out.push("#{@put_cmd} #{content.dump}") if content.size > 0
	    content = ''
            out.push(token.to_s)
            out.cr
	  when :cr
	    out.cr
	  when '<%', '<%=', '<%#'
	    scanner.stag = token
	    out.push("#{@put_cmd} #{content.dump}") if content.size > 0
	    content = ''
	  when "\n"
	    content << "\n"
	    out.push("#{@put_cmd} #{content.dump}")
	    out.cr
	    content = ''
	  when '<%%'
	    content << '<%'
	  else
	    content << token
	  end
	else
	  case token
	  when '%>'
	    case scanner.stag
	    when '<%'
	      if content[-1] ==  \n
		content.chop!
		out.push(content)
		out.cr
	      else
		out.push(content)
	      end
	    when '<%='
	      out.push("#{@insert_cmd}((#{content}).to_s)")
	    when '<%#'
	      # out.push("# #{content.dump}")
	    end
	    scanner.stag = nil
	    content = ''
	  when '%%>'
	    content << '%>'
	  else
	    content << token
	  end
	end
      end
      out.push("#{@put_cmd} #{content.dump}") if content.size > 0
      out.close
      out.script
    end

  end
end

class ERB

  def initialize(str, safe_level=nil, trim_mode=nil, eoutvar='_erbout')
    @safe_level = safe_level
    compiler = ERB::Compiler.new(trim_mode)
    set_eoutvar(compiler, eoutvar)
    @src = compiler.compile(str)
    @filename = nil
  end

  def result(b=TOPLEVEL_BINDING)
    if @safe_level
      th = Thread.start { 
	$SAFE = @safe_level
	eval(@src, b, (@filename || '(erb)'), 1)
      }
      return th.value
    else
      return eval(@src, b, (@filename || '(erb)'), 1)
    end
  end

end

class ERB
  module Util

    public

    def html_escape(s)
      s.to_s.gsub(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;")
    end
    alias h html_escape
    module_function :h
    module_function :html_escape

    def url_encode(s)
      s.to_s.gsub(/[^a-zA-Z0-9_\-.]/n){ sprintf("%%%02X", $&.unpack("C")[0]) }
    end
    alias u url_encode
    module_function :u
    module_function :url_encode
  end
end

我们看到ERB初始化时调用了compiler.compile,compile方法首先调用默认的TrimScanner对模板scan并针对不同的标签进行处理,最后将结果写入Buffer

最后ERB的result方法使用Kernel#eval来处理模板文件并返回处理后的结果

Util模块定义了html_escape和url_encode方法,alias为h和u

JS面向对象的简单应用实例

  现在进入了WEB2.0时代,AJAX的应用也将无处不在,紧随其后的ajax ToolKit也相继出现DOJO、YahooUI…等非常强大的面向对象的JavaScript的工具箱,

对于你方便阅读Source,最好能够了解一下JavaScript下如何使用OO进行编程的, 这有很大的用处,以下偶提供的小例讲述了JS OO编程的简单应用

<html>
	<head>
		<title></title>
		
		<script>
			function ClassTest1(){
       alert("Congratulation! this frist class build success!");
     }

   function aa(){
    var a=new ClassTest1();
  }


    function ClassTest2(var1){
      alert(var1);

    }

    var b=new ClassTest2("hello");

  //构建一个class并构造一个name属性
  function ClassTest3(name){
  	this.name = name;
  }
  
  /*ClassTest3的成员方法*/
  ClassTest3.prototype.sayHello = function () {
   alert("Hello " + this.name);
}
  function newClassTest3(){
  	//实例化类class3
  	var class3 = new ClassTest3("class3name");
  	class3.sayHello();
  	//创建一个age属性并对其付值 

  	class3.age = 1;
  	alert(class3.age);
  	/*创建一个address.home属性并对其付值(与许多
  	面对象的语言一样JS也可以像用‘.’一样来用‘[]’来引用来
  	属性,主要是为了避免带'.'的属性)*/
  	class3['address.home'] = "shijiazhuang";
  	alert(class3['address.home']);
  }

			</script>
			
			
			<script language="javascript" type="text/javascript">
//我们可以通过for in循环来遍历对象的属性。

var testObj = {

prop1 : "hello",
prop2 : "hello2",
prop3 : new Array("hello",1,2)
}

for(x in testObj) alert( x + "-" + testObj[ x ] )

//-->
</script>

<SCRIPT LANGUAGE="javascript">
<!--
var Circle = { x : 0, y : 1, radius: 2 } // another example

for(p in Circle) 
alert( p + "-" + Circle[ p ] )


</SCRIPT>

	</head>
<body>
		<input type="button" onclick="aa()"/>
		<input type="button" onclick="newClassTest3()"/>
</body>
</html>

JS控制textarea输入中英文字数

IE中的textarea控制不了汉字的输入长度,所以只能自己去写一个脚本取判断。

在web页面中:

<textarea rows="6" cols="40" id="memo" name="memo" class="txt"  onpropertychange="Javascript:zlpc(200);" onpaste="return false;" style='height: 101px;width: 535px;'></textarea>

 添加如下脚本:

<script type="text/javascript"> 
						var lastValue=""; 
						function zlpc(lSize) 
						{ 
							//debugger; 
							if (event.propertyName=="value") 
							if (event.srcElement.value.truelen()>lSize) 
							{ //长度超出时,取消输入 
								//event.returnValue=false; //这种取消事件的方法没有效果 
								//只好用恢复原值的方法 
								event.srcElement.value=lastValue; 
							} 
							else 
							lastValue=event.srcElement.value; 
						} 
						
						String.prototype.truelen=function() 
						{ 
							var i=0; 
							for (var j=0;j<this.length;j++) 
							{ 
							if (this.charCodeAt(j)>255) 
							i+=2; 
							else 
							i++; 
						} 
						return i; 
						} 
				</script>

 

图片上传时,本地显示预览功能

问的人很多,我就把最简单给大家。

  1. <script type=“text/javascript”>
  2. function showImg(){
  3.   document.getElementById(“IMG”).src=document.getElementById(“photo”).value;
  4. }
  5. </script>
  6. <img src=“” id=“IMG” />
  7. <input type=“file” name=“photo” id=“photo” size=“30” onchange=“showImg()” />

还有许多细节,大家自己完善吧。

程序设计语言概论课程相关杂记

笔记都记在笔记本上了,这边记点side-story一般的杂记吧。

到计算机系去旁听了马晓星老师上的程序设计语言概论。周二的第一次课的时候还不知道有这课所以没去。周五(2008-02-22)去跟了第二次课。

接下来准备要讲到Scheme了。到http://www.scheme.com抓了Petite Chez Scheme 7.4的档。

—————————————————————–

不知道有多少人关注过Euphoria这种语言。与许多脚本语言一样,它是一种very-high-level语言。不过它宣称自己非常简单而且非常快——官网上的资料称它的先进的解释器比传统的Python和Perl的解释器快30倍。速度就罢了,发展到某个阶段之后大家都会说自己很快的……

它更重要的一个特点应该是它最主要的数据结构,sequence。字串在Euphoria中不过是sequence的语法糖。Sequence本质上是一种线性的异质容器,可以通过下标来访问其中的元素;元素可以是atom或者sequence。下标只能作用于变量(而不能作用于表达式,于是date()[2]这种写法就不行)。

还没仔细看,不过乍一看这sequence跟Lisp里的list看起来好像。于是给我一种直观的感觉,Euphoria就像是命令式的Lisp一般;不过Euphoria貌似没S-expression用。接下来看Scheme相关的时候也顺带一起看看Euphoria来对比吧。

Euphoria中“类型”的意味与其它语言很不一样。由于atom只是数值,则由atom而构成的sequence以及包含有sequence的sequence到最后都肯定是数值。结果user-defined type就变成了数值范围的检查……

type hour(integer x)
    return x >= 0 and x <= 23
end type

hour h1, h2

h1 = 10      -- ok
h2 = 25      -- error! program aborts with a message

Euphoria支持结构化程序设计语言的三种经典结构:顺序,分支,循环。

常见的语法结构都在,if-then-else、for、while等。

Euphoria例子:(来自官方文档

sequence list, sorted_list

function merge_sort(sequence x)
-- put x into ascending order using a recursive merge sort
    integer n, mid
    sequence merged, a, b

    n = length(x)
    if n = 0 or n = 1 then
        return x  -- trivial case
    end if

    mid = floor(n/2)
    a = merge_sort(x[1..mid])       -- sort first half of x
    b = merge_sort(x[mid+1..n])     -- sort second half of x

    -- merge the two sorted halves into one
    merged = {}
    while length(a) > 0 and length(b) > 0 do
        if compare(a[1], b[1]) < 0 then
            merged = append(merged, a[1])
            a = a[2..length(a)]
        else
            merged = append(merged, b[1])
            b = b[2..length(b)]
        end if
    end while
    return merged & a & b  -- merged data plus leftovers
end function

procedure print_sorted_list()
-- generate sorted_list from list
    list = {9, 10, 3, 1, 4, 5, 8, 7, 6, 2}
    sorted_list = merge_sort(list)
      sorted_list
end procedure

print_sorted_list()     -- this command starts the program

注意:(2008-02-22)今天老师上课的时候提到了C-like语言里使用花括号来表示代码块的特征。这种语言设计有一个*不太好*的地方,那就是无论是什么语句块都同样以“}”结束,很容易让人对错括号。所以会有在“}”之后标注语句块类型的注释习惯,例如:

while ( !done ) {
    switch ( code ) {
    case OP_ADD:
        if ( otherCond ) {
            // ...
        } else {
            // ...
        } // if-else
        break;
    // ...
    default:
        // ...
        break;
    } // switch
} // while

要从语言级别根除这个问题,最直观的方式就是“强制”在块的结尾标明语句块的类型。于是很自然的就演变成了类似“end function”“end if”的形式。

习惯真是魔鬼。之前很长一段时间都没办法从C-like语法的惯性中摆脱出来。直觉上不喜欢Pascal也是因为它不是curly-brace系的语言。呵呵,真糟糕。

—————————————————————–

(2008-02-24 16:30)刚才在看Channel 9上的Lang.NET 2008相关报道。其中一篇,[url=Dan Ingalls and Allen Wirfs-Brock: On Smalltalk, Lively Kernel, Javascript and Programming the Internet]http://channel9.msdn.com/Showpost.aspx postid=380959[/url],Dan在讲到JavaScript时提到了对“clean language”的看法,说如果一种语言提供了太多冗余的构造去做同一件事,那么它就不够“clean”。我对这种说法的理解还是不够充分。如果说JavaScript加入了class支持而不抛弃prototype-based的设计,那或许是很冗余,但现在的ECMAScript 3应该还算干净吧(但一点也不“简单”)。

想想看,通过closure来模拟对象,这也是一种强大的能力。当需要一个只有一个公共方法,而又需要保持内部状态的对象时,直接定义一个函数去返回一个内部嵌套的函数,让后者所持有的闭包去保持状态就行。Consise, no 

—————————————————————–

(2008-02-28 23:00)突然在想,编程语言最大程度的灵活度莫非就是意味着无类型么。

任何数据结构都可以用表(list)来模拟。记录(record)或者结构(struct)都可以被表模拟。很多不支持记录/结构的语言都会建议使用数组来模拟这类语言构造,而这时候的“数组”也就是一个list。Lisp系的语言依靠表而获得了强大的语言能力(当然更重要的是表的应用方式——S-expression),上面的Euphoria看来也是如此。

不过,一种语言中对表的操作要是只支持CAR/CDR/CONS,而没有下标索引的话,感觉还是稍微缺点什么?

假如索引不限定使用非负整数,而是可以使用任意类型的数据(例如字符串)的话,那不就是泛型的关联数组了么?让我想到ECMAScript……

胡思乱想而已,总之先记下来。

—————————————————————–

最近动态语言越来越热门,其中有不少都支持3个C的language constructs:closure、coroutine、continuation。闭包(closure)只在遵循lexical scoping的语言中出现,与嵌套作用域相关。coroutine与continuation都与“保留当前运行状态”相关;前者是说函数能保留自身的执行状态,并能将控制交给(yield)别的函数;后者是说系统能保存当前的执行状态(也就意味着要保存整个运行时stack),并在以后的某个时候能够恢复到先前保存过的某个状态继续执行。(以上描述纯粹是我现在对这3个C的理解,未必正确)。

—————————————————————–

根据worse-is-better原理,我一直喜欢的一些feature看来不少都是”worse”的一类呢。诶,原本对ECMAScript 4那么热衷的我,最近越来越觉得它kitchen-sink得太过头了。正所谓“核心概念”太多,使得语言肿胀……

JavaScript书

权威指南

《javascript高级程序设计》

另外:

我认为前端开发提升必看的两本书:

第一本是《ppk谈javascript》淘宝UED团队翻译…

这本书设计javascript浏览器兼容性等内容

第二本是《pro javascript tenniques》中文版作者是jquery库的作者john

其中文版是《精通javascript》陈贤安译

http://download.chinaitlab.com/special/javadownload.htm

当用<s:iterator>标签遍历数据时,实现鼠标移动到某行某行变色效果

当用<s:iterator>标签遍历数据时,实现鼠标移动到某行某行变色效果

jsp代码如下:

<table width="90%" border="0" cellspacing="0" cellpadding="0">
								<input type="hidden" name="users.userno" value="<s:property value='#session.users.userno'/>"/>
								<tr >
									<td align="center" width="30%">邮件标题</td>
									<td align="center" width="30%">内容</td>
									<td align="center" width="30%">是否已读</td>
									<td align="center" width="30%">时间</td>
									<td align="center" width="30%">操作</td>
								</tr>
								<s:iterator value="emailReciveList" var="e">
									<tr>
									<td align="center" width="30%">${e.ETitle}</td>
									<td align="center" width="30%">${e.EContent}</td>
									<td align="center" width="30%">${e.ERead}</td>
									<td align="center" width="30%"><s:date format="yyyy-MM-dd" name="#e.ETotime"/></td>
									<td align="center" width="30%">
									<input type="button" value="删除" id="del"/>
									<input type="hidden" value="${e.EId}" id="eid" name="email.EId" /></td>
									</tr>
								</s:iterator>
								</table>

jquery代码如下:

//鼠标移动某行某行变色
$(document).ready(function(){
	//滑动变色
 	$("tr").mouseover(function (){
  		$(this).css("background","grey"); 
	 }).mouseout(function (){
  		$(this).css("background","transparent");
 });
	
})

效果如下图所示:

  • 大小: 29.9 KB

纯CSSLightbox效果webjx.com

<!DOCTYPE html PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN”>
<html>
    <head>
        <title>纯CSS Lightbox效果webjx.com</title>
        <style>
        .black_overlay{
            display: none;
            position: absolute;
            top: 0%;
            left: 0%;
            width: 100%;
            height: 100%;
            background-color: black;
            z-index:1001;
            -moz-opacity: 0.8;
            opacity:.80;
            filter: alpha(opacity=80);
        }
        .white_content {
            display: none;
            position: absolute;
            top: 25%;
            left: 25%;
            width: 50%;
            height: 50%;
            padding: 16px;
            border: 16px solid orange;
            background-color: white;
            z-index:1002;
            overflow: auto;
        }
    </style>
    </head>
    <body>
        <p>This is the main content. To display a lightbox click <a href = “javascript:void(0)” onclick = “document.getElementById(‘light’).style.display=’block’;document.getElementById(‘fade’).style.display=’block'”>here</a></p>
        <div id=”light” class=”white_content”>This is the lightbox content. <a href = “javascript:void(0)” onclick = “document.getElementById(‘light’).style.display=’none’;document.getElementById(‘fade’).style.display=’none'”>Close</a></div>
        <div id=”fade” class=”black_overlay”></div>
    </body>
</html>