2010年8月22日星期日

blog迁移到appspot

blogger老早就不能访问了,一开始用gmail发邮件的方式写,后来麻烦了用flymemo记。

知识还是需要分享才会增长,重操旧业用micoblog在appspot上打了个blog:http://mzhoublog.appspot.com/

欢迎来踩踩

2010年7月15日星期四

10年7月新番推荐之一

妄想学生会 
想听h笑话,喜欢yy谈话。那么你不能不看这部新番。这部动画充满了下半身相关的玩笑。 

学院默示录 
生化危机的学院版,这是对这部作品最好的解释。画风很难得,值得一看。 

滑头鬼之孙 
等了很久的夏木友人帐第三季,结果等到了这部。虽然不是走和谐路线,不过也听不错,看起来很有感觉。 

这次新番虽然数量少了,但是质量普遍高了。这是个好趋势,即使还有很多后宫片,但是画质都不错!

2010年6月22日星期二

冒个泡

现在已经所有的学习心得移动到了flymemo.com,所以好久没写blog了,冒个泡。

2010年5月7日星期五

使用gtkpod让ubuntu 10.04完全支持iphone/itouch的读写

现在10.04能默认识别出iphone/itouch,但是rythmbox只是能读取但不能写入文件。我使用gtkpod来实现对iphone/itouch的完全支持,步骤如下:

sudo apt-get install gtkpod ifuse libmp4v2-0

安装完成后,

$ sudo mkdir /mnt/ipod
$ sudo chmod 777 /mnt/ipod
加载iphone到/mnt/ipod
$ ifuse /mnt/ipod 
这时gtkpod就会出现iphone的播放列表了。接下来的操作于ituns类似。gtkpod会在弹出iphone时同步信息。

注意使用完毕后卸载iphone:
$ fusermount -u /mnt/ipod

2010年4月17日星期六

[精品]jQuery用jsonp实现有限制的跨域请求

jQuery用jsonp实现有限制的跨域请求

浏览器一般意义上是禁止跨域请求的,这是从安全方面来考虑的。但是如果我们的业务是分布式的!这就会产生问题,这在云计算之风大起的今天是个重要问题。一下讲的是一种有限制的跨域请求实现。

jsonp基本原理:
插入<script>标签:这个script标签的scr是没有跨域限制的,所以我们可以插入这样一个标签如下:
<script type="text/javascript" src="http:跨域的请求/getList.js"/>

js里面的实际内容则是:
jsonpcallback("data");
这样一旦获取了js文件之后,jsonpcallback函数就会执行。而jsoncallback函数的真正实体事先已经定义好了。

jQuery实现jsonp:
浏览器端:
$.getJSON("http:/跨域的请求/getList.js?data=jsondata&jsoncallback=?",
        function(data){
          $.each(data.items, function(i,item){
            $("<img/>").attr("src", item.media.m).appendTo("#images");
            if ( i == 3 ) return false;
          });
});
服务器端(Java):
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
  throws ServletException, IOException {
String jsonData = getDataAsJson(req.getParameter("data"));
String output = req.getParameter("jsoncallback") + "(" + jsonData + ");";

resp.setContentType("text/javascript");
          
PrintWriter out = resp.getWriter();
out.println(output);
// prints: jsonp1232617941775({"symbol" : "IBM", "price" : "91.42"});
}

请求url中的jsoncallback=?就是指定了回调函数也就是getJSON的下一个参数,具体是什么名字则是由jQuery自动生成一个不会重复的函数名,例如:jsonp1232617941775

2010年4月7日星期三

[转]Javascript事件 --PPK

转自:http://www.zipeng.info/archives/ppk-on-js-events.html


今天我会说一些关于JavaScript事件的事情,我很希望自己能启发大家一些什么,不过大伙都是高手,不管怎么,我还是要说说 JavaScript事件。
大约一年前我打算对JavaScript的事件做一些深入的研究,并且我做了一张兼容性的表,大家可以通过下面这里来 访问

http://quirksmode.org/dom/events


那 么今天我会和大家谈论4件事情:
首先会是key事件(按键事件),因为在这里存在很多的困惑,很多是关于他们如何工作的困惑。
然 后我会谈谈change事件,他是我很喜欢的一个事件,不幸的是,它的工作情况有些乱。
第三,我会谈谈委派focus事 件,当然可能你已经知道了。委派的事件通常只会触发在鼠标或者是键盘事件上,但是我找到了一种办法使它也能够使用在focus上面。
最后我 会说说手机上面的事件,相当诡异的东西。。。

那么首先就来看看键盘事件吧,键盘事件有三个:keydown,keypress和keyup。大多数 的人都认为自己很清楚他们在会何时触发,而我在这里要告诉你,其实未必就如你所想的那样。
我们先来看看定义,、
keydown事件:当用 户按下键盘上面的一个键时会触发,用户一直按着这个键他就会持续触发。
keypress事件:有一点点的不同,用户按着一个字符键(原文是 character key)才触发,就是说用户按了一个能在屏幕上输出字符的按键keypress事件才会触发,比如我们往一个textarea里面输入字符。
keyup 事件:很简单,当我们释放一个按键时候会触发。
举一个例子,这样你能更明白些
比方说我按下了一个字母"o"键或是字母"i"键,那么 keydown和keypress就都会触发,而当我按下的是一个特殊的按键比如是shift键,那么kyedown会触发,而keypress不会。
keydown 和keypress的这种区别最初是由微软提出的,所有的ie都支持这种区分方式,一年前(08年吧)推出的safari3.1也适用这样的方式,不过这 只是说明了keydown和keypress存在方式的不同。相比较,firefox和opera会同时触发多个事件,因为此时事件既是一个 keydown事件又是一个keypress事件,所以我们应该会同时触发多个事件。这样很好,但是这样却无法解释为什么要同时存在两种事件、而不是一种 (keydown)。所以其实我还是很喜欢微软的这种区分的。
好吧,我们再来重温一次概念,keydown会在任何键按下时触 发,keypress只是在字符键按下时触发,以上是在ie和safari下的情况,另外,我目前没有google chrome,不过他和safari很相近,所以这个应该也适用于chrome浏览器。

OK,让我们来看一些实际问题,通常我们都会写一段 脚本来检测用户按过哪个按钮,然后利用这样的结果做一些有趣的交互应用。

键盘事件拥有两个属性,keyCode和CharCode,他们之 间有一些不一样之处。keyCode表示用户按下键的实际的编码,而charCode是指用户按下字符的编码。
因此,当我按下"a键",我会得到 keyCode 65和字母a的charCode 97(注意是小写的字母哦),而当我按下shift键,我会得到keyCode 16,而我不会得到任何的charCode,因为按shift并没输入任何的字符。就这样,很简单吧~~~
我们现在得到两个属性了,两个属性各自 包含了各种的信息,这真是好事~~~

但是接下来我们又遇到问题了,很复杂,无法解释但它就是这样的问题。keyCode属性。在 keyDown事件里面,事件包含了keyCode � 用户按下的按键的物理编码。在Onkeypress里,keyCode包含了字符编码,即表示字符的ASCII码。这样的形式适用于所有的浏览器 � 除 了火狐,它在onkeypress的时候keyCode返回数字0, 别问我为什么,它就这样。然后,火狐在Onkeydown中charCode返回数字0,在 onkeypress里,由charCode返回字符ASCII编码。而这个,只会在Firefox和Safari工作,因为他们是唯一支持 charCode的浏览器。(这一段翻译感谢信息学院的AN同学的纠正)

让我们更进一步看点更实际的东西,如果你想获取用户实际敲击的按 钮,用keyDown事件来获取事件对象,并获取keyCode,这在所有浏览器都行的通。另一方面,如果你想获取用户输入的字符, 那么就使用keypress来获取,然后获取charCode(火狐和safari)或是keyCode(其他浏览器)

然后,我 想有时候你也会有阻止某个按钮默认行为的想法,很常见的是方向键
设想你有一个适合于键盘的拖拽菜单,你希望用户用方向键来实现一个拖拽的操作,因 此你就要取消方向键的默认行为(使页面向上或是向下滚动),那么一般来说你就会在keyDown事件里面做这件事,因为如我所说的那样 ----keyDown会在一个按钮被按下时候触发,而keyPress只是在字符键按下时触发,但是很不幸,这样子在opera行不通(当然我得承认我 没有测试最新版的opera,也许现在已经修复了这个问题?待测试)。因此,我今天不会讨论在opera下面阻止方向键的方法,很坦诚的说,我还没有一个 答案。

这就是键盘事件的一些总结,不会很麻烦吧?我说了几种键盘事件,但是你得注意keydown和keypress的差别。

好 了好了,来说说change事件吧。照理论说,change事件应该是很好的一个事件,因为他应该是在表单有变化的时候触发。比如,你想在表单提交时检测 一些用户操作表单的事情,比如是检测和验证用户在各个域的输入。当然也有很多时候我们会动态创建一个表单或是表单域或是选框--这样的表单会和用户之间产 生很复杂的交互,这时候我们就要知道用户什么时候对表单作了修改,从理论上说,change事件是最合适不过的了。

但是通常,我们总是会被 迫去使用focus和blur事件或者是click事件去做检测表单变化的事,而不是用change事件。这是因为change事件不总是能很正常的运行 着。我们分三种情况来讨论
1、文本框的变化事件(text field)
2、选择框的变化事件(select box)
3、复选 框和单选框的变化事件

从文本框开始说吧,假设用户把焦点移动到了文本框上面(或用鼠标或用键盘的方式),之后又移去了焦点,比如说他进入了 下一个文本框的输入。在这样的情况下,change事件不触发,因为没有什么东西改变了---注意,第一个文本框的值并没有改变。但是我们对上述事情稍稍 做一些变化,如果我们说,用户点击了文本框,并输入了一些文本,然后再撤去焦点,这就触发了change事件,很好,他在所有浏览器中都如此工作。

然 而,当我们使用selectbox的时候问题就来了,选择框事件是你可以想到的最古老的浏览器事件,我想大约在95、96年我们在浏览器中就有了 selectbox了,他可能列出了漂亮的链接,并节省了页面的空间。我们已经使用选择框有很长一段时间了,你也可以用鼠标去做一个选择。首先你点击选择 框,它便列出了选项,当你再次单击里面的一个选项时,就触发了change事件。因为浏览器知道:"ok,用户已经改变了选择框里面的值了"。这也是用于 所有的浏览器。

但是,用户还可以用键盘来操作改变选择框(select)的值,这就为我们使用change事件带来了麻烦。首先用户会把焦 点移动到selectBox上面,然后利用方向键来选择他需要的选项。这还不是什么问题,但是这种情况,在IE和opera下面,每当用户按一次方向键 (切换选项),change事件就会触发一次。这明显是很大的一个问题,如果一个选项表有20个选项,那么用户从第一个选项移到最后一个就会触发19次 change事件,这可以说是ie和opera很严重的一个bug。
而在firefox和safari里面,他们所做的工作就是,允许用户在选项 之间用上下键来选择,然后会在用户blur这个选择框的时候触发change事件,通俗的说,你告诉浏览器,我已经选择好了,浏览器你可以用change 事件看看我是不是做了改变----像firefox和safari这样的工作方式很好很正确。不过,在IE下面做一些类似于选框导航的工作,当用户用键盘 来操作,使用change事件就很容易产生问题。这就是我们对于change事件的首个大问题。

不幸的事继续发生,当我们在复选和单选框上 面使用change事件,情况会更糟,我以一个复选框为例,基本上他和单选的工作方式一样。当用户点击了复选框(我不仅仅指用鼠标点选), 一个点选操作可以通过鼠标或是键盘来达到。总之,这样的一个点击使得复选框的值发生了变化---true or false。按常理这时候change就发生了,因为用户确确实实的改变了东西,这样的一件事也的的确确会在firefox,safari,opera里 面发生,但是IE不会
IE到底怎么了?你点击了复选框而什么也没有发生。接下来呢,你用鼠标或是键盘把焦点移出这个复选框移到别 的复选框上面,原来的这个复选框的blur事件就触发了,同时change事件随之触发。这样的情况对于一些整理表格的操作实现来说是很糟糕的,比如通过 复选框来开启表单的隐藏选项,在IE下面你只能在blur一个复选框的时候开启隐藏选项,这样对于用户来说是很困惑的(因为会给用户带来一种滞后的感 觉)。这也是IE下面一个很严重的bug。

你可以看到我在ppt上面给W3C也划了一个叉号,因为据我所知w3c的事件标准只是规定了 blur,根据标准,一个change事件应该在一个表单域改变并被移去焦点时发生。现在我们可以总结一下,change在文本域里面工作的很不错,当我 们用键盘操作select框是它很有意义,而对于单选框和复选框或者是你用鼠标操作select框change事件使用的意义就不太大。(我作为翻 译认为这里ppk出现了口误,把鼠标和键盘操作selectbox的情况说反了,大家要看了欢迎纠正,另外ppk貌似也没有提到用鼠标scroll方式操 作select框的值的方式)

这就是我们目前遇到的一个问题,而我目前还没有找到一个很好的解决方式。幸运的是,我们可以用 click事件或是focus事件或是blur事件做一些弥补,虽然这不会花费太多的精力,但是我还是期望change事件能变得真正的实用。这样我们可 以比较统一的用一个事件去侦测一个表单里面的变化并做一些有意思的事。目前这还是我们能期待的。

进入我们今天的第三个话题吧,事件委派 (event delegation),我非常确信你(指yahoo的同学们)应该已经知道什么是事件委派,不过其他的人却不知道,我简单的介绍一下。基 本来说,事件委派是减少定义事件处理的一种方式,我们用下拉菜单为例。用传统的方式,我们可能要对这个下拉菜单添加很多的事件处理函数,当你的鼠标停留在 父级菜单上面,1级子菜单弹出,我们在移动鼠标到1级菜单上面,二级菜单又会出来。而当我们使用事件委派时情况就有所不同,事件委派利用了事件的冒泡,当 用户移动鼠标到一个链接(link)上面,触发了链接的mouseover事件,同时通过冒泡mouseover事件冒泡到了链接的上一级元素,直至冒泡 到整个文档上面,这个过程会触发链接(link)上级任何一个含有mouseover事件的元素的mouseover事件,比如会冒泡到li和ul的 mouseover事件,那我们为什么不把处理事件的函数放在链接的上层呢?这样可以节省众多链接的事件处理函数的绑定。

曾经我的一个客户 抱怨他的站点有点慢,尤其是在IE下面,于是我看了源代码,我发现他的下拉菜单有六七十甚至更多的链接,每一个链接绑定了自己的mouseover、 mousedown、mouseout事件,这就使得页面变得缓慢了。
因为每一个浏览器都会为单独的事件处理开辟一个单独的内存,更何况是 IE(拥有内存泄露的美名)。于是我告诉我的客户事件委派,然后。。。。(网页快起来了)

那么首先让我们来建立一个下拉菜单

<ul>
<li><a href="#">Multimedialize</a>
<ul>
<li><a href="#">Sound</a></li>
<li><a href="#">Java applets</a></li>
</ul></li>
<li><a href="#">Ajaxify</a>
<ul>
<li><a href="#">Web 2.0</a></li>
<li><a href="#">Web 3.0</a></li>
<li><a href="#">Web 4.0b</a></li>
</ul></li>
</ul>

我们首先把我们的mouseover和mouseout事件处理函数绑定到ul上面,这在所有浏览器中都行的通。

var dropdown = {
init: function (dropdown) {
dropdown.onmouseover = this.mouseOver;
dropdown.onmouseout = this.mouseOut;
}
}

不过如果一个用户不用鼠标,而是想用键盘操作打开这个下拉菜单,我们又该怎么做呢?这个问题有个我们带来了一个重要的主题,设备的独立 性(让网页支持多种设备的操作)。这也是我想向大家解说的,我们做的东西要兼容各种设备。
基本的规则是,所有带有"mouse"在名字里面 的事件,只是在你真正使用鼠标的时候工作。听起来很简单吧,不过这总会被很多人忽视。因此,我们需要用户使用键盘的时候能告诉我们用户何时是鼠标 移入何时又是鼠标移出的事件,那么目前看最合适不过的就是focus和blur事件。因此,如果事件通过鼠标mouseover或是键盘focus冒泡到 上面的元素,冒泡到达ul时我们就执行目标函数展开菜单。当鼠标mouseout或者是键盘blur冒泡上去时,我们就执行关闭菜单的函数。但是问题在 于,这不可行,因为focus和blur事件不会冒泡。

这就带给我们另一个问题,什么事件支持冒泡?什么事件又不支持 冒泡?我们先把事件分为两类:一类是设备事件(鼠标或是键盘按键事件),另一类是界面事件。

鼠标和键盘事件是在使用输入设备的时候 触发的,我还在做一个更好的定义,不过基本是这个意思,基本上用户所作的就是点击鼠标按键、点击键盘按键、移动鼠标或其他的一些事,总之是和一个具体的外 接设备相关的,这些事件的例子如,mouseout,mouseover,click,keydown,keypress,顺便说一声click是唯一既 迟滞鼠标触发又支持键盘触发的事件:你用鼠标点击会触发click,用键盘tab移动焦点到目标然后回车也会触发click。click是很少的能够工作 于多种设备下的事件。因此,一个click事件是很安全的。其他的一些设备事件总是只支持某一个设备,最后要说的,就是以上提及的这些事件支持冒泡,直到 document级。

界面事件,在一个常规事件触发时执行,而不关注它是如何被触发的(翻译的有点生涩,欢迎提意见)
界面事件的一 个比较好的例子是,表单的submit事件,当一个表单被提交他就会触发,而表单submit事件并不关心用户是用什么方式来提交表单的,可以使用户用鼠 标点击了提交按钮提交表单,可以是用户输入文本按了回车提交表单,只要是提交了表单,submit事件就会触发。这类事件还有很多,又如 onload,onupload,change,focus,blur,对了这里有我们苦苦追寻的focus和blur事件。因为我们希望我们的下拉菜单 能够兼容多种设备。

大体来说,界面事件不支持冒泡,对我们来说这真是一个不好的消息,因为如果我们想让键盘用户也能用我们的 下拉菜单,我们就要用到focus和blur。但是focus和blur并不能冒泡上ul元素,我们就只能老老实实的把事件处理绑定在每一个li上面了。 当然还有一个不算好消息的好消息,我们可以在事件捕获中得到focus和blur,事件捕获的工作是和事件冒泡相反的,但是它在IE下不被支持。。。不过 我们一会儿会讨论IE。

我们说事件冒泡,我们点击了a元素,a的click执行,然后事件冒泡到了li,之后是ul,之后是 document。事件捕获则是,在我们点击了a之后,事件从document一级级的下来,经过ul、li、然后才到达a并终止捕获。你会觉得冒泡和捕 获这两个东西用哪个都差不多,不过当我向dean Edwards问了这个问题后,他说:"不不不,你应该在大多数情况下去使用事件捕获"。

当 你要捕获一个focus事件,被绑定在目标元素的祖先元素上的事件就会被执行,这个可以在所有浏览器下使用,当然除了IE,因为它不支持事件捕获。因此基 本来说,如果我们把焦点移上a元素上并同时使用事件冒泡,只有a链接的unfocus事件会被触发。但是我们反过来使用事件捕获,那么这个unfocus 事件就会顺着document->ul->li的顺序执行下去。

为什么事件捕获和事件冒泡会有差别?我也说不出。这只是我测试 出来的,我开始在opera里面测试,开始我以为他只是opera特有的,但是我在firefox下面也测试,哦?火狐也有这个特性,然后又测试了 safari,呵呵,safari也有同样的表现,最后,我发现它几乎是一个跨浏览器的特性。这样让我觉得事件冒泡和事件捕获具有同样的作用,因为在我之 前的感觉中,事件是否能被捕获并不最重要,但是能否冒泡就不一样了(能否被冒泡意义重大)。不管怎么说,目前的这种状况(应该是说冒泡和捕获的混乱的支持 情况)意义不是很大,但他确实能解决很实际的问题,因为现在我们可以对focus和blur进行事件委派了。

我们要做的很简单,我们给根元 素添加上addEventListener,并给这个函数的最后一个参数设为true,使其工作在捕获阶段。

var dropdown = {
init: function (dropdown) {
dropdown.onmouseover = this.mouseOver;
dropdown.onmouseout = this.mouseOut;
dropdown.addEventListener('focus',this.mouseOver,true);
dropdown.addEventListener('blur',this.mouseOut,true);
}
}

这样,一旦下拉菜单有focus事件发生,那么ul上面的事件捕获就会触发,绑定在上面的this.mouseOver函数就会被执 行。别问why,他就是这样工作的。

当然,因为IE不支持事件捕获,我们还要添加一点点东西,做一些修改,IE虽不支持事件捕获但是他有自 己的解决方案,这是IE所特有的。

var dropdown = {
init: function (dropdown) {
dropdown.onmouseover = this.mouseOver;
dropdown.onmouseout = this.mouseOut;
if (dropdown.addEventListener) {
dropdown.addEventListener('focus',this.mouseOver,true);
dropdown.addEventListener('blur',this.mouseOut,true);
}
dropdown.onfocusin = this.mouseOver;
dropdown.onfocusout = this.mouseOut;
}

IE带有两个类似于focus和blur的事件,但唯一的区别就在于他们支持冒泡,他们是focusin和focusout,我们给原 来的代码添加上两行,dropdown.onfocusin, and dropdown.onfocusout。这样我们的事件委派就工作在了所有的浏览器中。

2010年3月25日星期四

[精品]Spring MVC的深入学习一


一 SessionAttribute

         Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中,以便下一个请求属对应的 ModelMap 的属性列表中还能访问到这些属性。这一功能是通过类定义处标注 @SessionAttributes 注解来实现的。

使用方法:
 @Controller @RequestMapping("/bbtForum.do") @SessionAttributes("currUser") //①将ModelMap中属性名为currUser的属性 //放到Session属性列表中,以便这个属性可以跨请求访问 public class BbtForumController { …     @RequestMapping(params = "method=listBoardTopic")     public String listBoardTopic(@RequestParam("id")int topicId, User user, ModelMap model) {         bbtForumService.getBoardTopics(topicId);         System.out.println("topicId:" + topicId);         System.out.println("user:" + user);         model.addAttribute("currUser",user); //②向ModelMap中添加一个属性         return "listTopic";     } }

二 ModelAttribute

         上面讲述了如何往ModelMap中放置属性以及如何使ModelMap中的属性拥有Session域的作用范围。除了在JSP视图页面中通过传统的方法访问ModelMap中的属性外,Spring提供了一个@ModelAttribute的注解可以将ModelMap中的属性绑定到请求处理方法的入参中,他还可以与jsp标签<form:form modelAttribute="entity"></form:form>一起使用。
         总结它的作用:
  • 将多个参数绑定到一个Bean中;
  • 自动将Bean值add到Model中,相当于进行了Model.addAttribute("key",bean);这样可以在jsp中使用EL表达式或是传统的jsp表达式访问bean;
  • 与jsp标签<form:form modelAttribute="entity"></form:form>一起使用,直接将bean的各个域对应到form标签的子标签(如<form:input path="field"/>,只需要将域的值设置为path值)直接显示在页面上。
  • 在方法之上使用,使方法的返回值被add到Model中,这个方法会被所有此类中的请求方法被调用时使用。所以所有的请求的model中都会有这个Attribute存在。

使用方法:
 @Controller @RequestMapping("/bbtForum.do") @SessionAttributes("currUser") //①让ModelMap的currUser属性拥有session级作用域 public class BbtForumController {  @Autowired private BbtForumService bbtForumService;     @RequestMapping(params = "method=listBoardTopic")     public String listBoardTopic(@RequestParam("id")int topicId, User user, ModelMap model) {         bbtForumService.getBoardTopics(topicId);         System.out.println("topicId:" + topicId);         System.out.println("user:" + user);         model.addAttribute("currUser",user); //②向ModelMap中添加一个属性         return "listTopic";     }
    @RequestMapping(params = "method=listAllBoard")    //③将ModelMap中的 public String listAllBoard(@ModelAttribute("currUser") User user) {  //currUser属性绑定到user入参中。         bbtForumService.getAllBoard();         System.out.println("user:"+user);         return "listBoard";     } }

在 ② 处,我们向 ModelMap 中添加一个名为 currUser 的属性,而 ① 外的注解使这个 currUser 属性拥有了 session 级的作用域。所以,我们可以在 ③ 处通过 @ModelAttribute 注解将 ModelMap 中的 currUser 属性绑定以请求处理方法的 user 入参中。

所以当我们先调用以下 URL 请求: http://localhost/bbtForum.do?method=listBoardTopic&id=1&userName=tom&dept.deptId=12

以执行listBoardTopic()请求处理方法,然后再访问以下URL: http://localhost/sample/bbtForum.do?method=listAllBoard

你将可以看到 listAllBoard() 的 user 入参已经成功绑定到 listBoardTopic() 中注册的 session 级的 currUser 属性上了。


在方法级上使用ModelAttribute:

 @Controller @RequestMapping("/bbtForum.do") public class BbtForumController {      @Autowired     private BbtForumService bbtForumService;      @ModelAttribute("items")//<——①向模型对象中添加一个名为items的属性     public List<String> populateItems() {         List<String> lists = new ArrayList<String>();         lists.add("item1");         lists.add("item2");         return lists;     }      @RequestMapping(params = "method=listAllBoard")     public String listAllBoard(@ModelAttribute("currUser")User user, ModelMap model) {         bbtForumService.getAllBoard();         //<——②在此访问模型中的items属性         System.out.println("model.items:" + ((List<String>)model.get("items")).size());         return "listBoard";     } }

三 注册自己的属性编辑器
 
  • 注册框架级的自定义属性编辑器
 <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">     <property name="webBindingInitializer">         <bean class="com.baobaotao.web.MyBindingInitializer"/>     </property> </bean>

 
 MyBindingInitializer是继承自
 
 WebBindingInitializer的类,如下:
 
 
 
 
 
 
 public class MyBindingInitializer implements WebBindingInitializer {     public void initBinder(WebDataBinder binder, WebRequest request) {         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");         dateFormat.setLenient(false);         binder.registerCustomEditor(Date.class,              new CustomDateEditor(dateFormat, false));         binder.registerCustomEditor(String.class, new StringTrimmerEditor(false));     } }
  • 注册 Controller 级的自定义属性编辑器
如果希望某个属性编辑器仅作用于特定的 Controller,可以在 Controller 中定义一个标注 @InitBinder 注解的方法,可以在该方法中向 Controller 了注册若干个属性编辑器,来看下面的代码:

 @Controller public class MyFormController {     @InitBinder     public void initBinder(WebDataBinder binder) { //必须有WebDataBinder类型的参数         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");         dateFormat.setLenient(false);         binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));     }     … }

[note]Spring MVC接收数组或List参数

客户端:
方法:POST
参数:columnList  Health
          columnList  Sport
          拥有相同的name即可。 

服务器端:

Servlet方式:request.getParameterValues("columnList");可以得到String数组

SpringMVC注解方式:@RequestParameter("columnList") String[] list;           //数组形式
                                 @RequestParameter("columnList") List<String> list;   //List形式
                                 @ModelAttribute MyModel model;                             //模型中带有数组或者List

public class MyModel {
      List<String> list;
      或者String[] list;

      ......
}

[note][转]外部引入CSS 的两种方式link和@import的区别


本质上,这两种方式都是为了加载CSS文件,但还是存在着细微的差别。
  • 老祖宗的差别。link属于XHTML标签,而@import完全是CSS提供的一种方式。link标签除了可以加载CSS外,还可以做很多其它的事情,比如定义RSS,定义rel连接属性等,@import就只能加载CSS了。
  • 加载顺序的差别。当一个页面被加载的时候(就是被浏览者浏览的时候),link引用的CSS会同时被加载,而@import引用的CSS 会等到页面全部被下载完再被加载。所以有时候浏览@import加载CSS的页面时开始会没有样式,然后突然样式会出现,网速慢的时候还挺明显。
  • 兼容性的差别。由于@import是CSS2.1提出的所以老的浏览器不支持,@import只有在IE5以上的才能识别,而link标签无此问题。
  • 使用dom控制样式时的差别。当使用javascript控制dom去改变样式的时候,只能使用link标签,因为@import不是dom可以控制的。
所以,无特殊情况推荐使用link来引入样式,尽量避免使用@import

2010年3月23日星期二

[note][转]javascript 中Object.prototype的几个方法

转自:http://playgoogle.com/?p=198


hasOwnProperty(property)

判断对象是否有某个特定的属性。必须用字符串指定该属性。(例如,o.hasOwnProperty(“name”)),返回布尔值。此方法无法检查该对象的原型链中是否具有该属性;该属性必须是对象本身的一个成员。

如下代码:

var s =”";

alert(s.hasOwnProperty(“split”)); èreturn false

alert(String.prototype.hasOwnProperty(“split”));èreturn true

isPrototypeOf(object)

判断该对象是否为另一个对象的原型。

obj1.isPrototypeOf(obj2);

obj1是一个对象的实例;obj2是另一个将要检查其原型链的对象。原型链可以用来在同一个对象类型的不同实例之间共享功能。如果obj2的原型链中包含obj1,那么isPrototypeOf 方法返回 true。如果obj2不是一个对象或者obj1没有出现在obj2中的原型链中,isPrototypeOf 方法将返回 false

propertyIsEnumerable()

判断给定的属性是否可以用 for…in 语句进行枚举,返回布尔值。

toString()

返回对象的原始字符串表示。对于 Object 对象,ECMA-262 没有定义这个值,所以不同的 ECMAScript 实现具有不同的值。

toLocaleString()

Object中和toString()方法的实现方式一样,但在其他类中有特定的实现,如在DatetoLocaleString() 方法可根据本地时间把 Date 对象转换为字符串,并返回结果。

valueOf()

返回最适合该对象的原始值。对于许多对象,该方法返回的值都与 toString() 的返回值相同。

以上6个方法都是Object.prototype上定义的,ECMAScript 中的所有对象都由Object继承而来,所以在ECMAScript上的所有对象都具有以几个方法。

2010年3月19日星期五

Broadcom Corporation BCM4312 802.11b/g
Intel(R) Pentium(R) Dual  CPU  T2370  @ 1.73GHz
8600M GT
bootsect /nt60 SYS /mbr /force

2010年3月17日星期三

[note]display:inline及与float:left用途详细区分与特征说明

      首先我们要明确,display:inline;与float:left;正确含义。display:inline;(内联)《CSS权威指南》中文字显示:任何不是块级元素的可见元素都是内联元素。其表现的特性是“行布局”形式,这里的“行布局”的意思就是说其表现形式始终以行进行显示。比如,我们设定一个内联元素border-bottom:1px solid #000;时其表现是以每行进行重复,每一行下方都会有一条黑色的细线。如果是块级元素那么所显示的的黑线只会在块的下方出现。

  当然这看起来不像是display:inline;与float:left;的区别所在,但是当理解了float:left;的特性那么我们就清楚到底是怎么回事了。float:left;(左浮动)他使得指定元素脱离普通的文档流而产生的特别的布局特性。并且FLOAT必需应用在块级元素之上,也就是说浮动并不应用于内联标签。或者换句话来说当应用了FLOAT那么这个元素将被指定为块级元素。

  那么我们很清楚了,内联(display:inline;)元素不能设置宽高,因为内联属于行布局,其特性是在一行里进行布局,所以不能被设定宽高。

2010年3月10日星期三

[note]Spring MultipartFile类解决文件上传问题

使用MultipartFile类解决文件的上传十分方便,代码如下:

        @RequestMapping(value = "/member/spaceBackground.do", method = RequestMethod.POST)
public void processSetupBackground(Model model, HttpServletResponse resp, HttpServletRequest req, @RequestParam("fileupload") MultipartFile fileupload) {
try {
String logoName = "logonai.gif";
String filePath = ProjectEnv.getWebappPath() + "/images/";
FileTools.input2File(filePath, fileupload.getInputStream(), logoName, true);  //将上传的文件用input流的方式传到文件logonai.gif中
PrintWriter out = resp.getWriter();
out.print("true");
out.close();
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
}
}

[note]真正的JSON格式

以前我写JSON时是这样写的:{name:'zmm',career:'programmer'}

今天发现有两个错误:

第一:JSON官方标准里面说明了不用单引号而必须用双引号。
第二:JSON官方标准里面String必须用双引号包裹,而key使用String表示。

下面是正确的JSON:
{"name":"zmm","career":"programmer"}

如果用Java那么需要放到String里面,如下:

String json = "{\"name\":\"zmm\",\"career\":\"programmer\"}";

使用转义符" \ "。

2010年3月4日星期四

[note]jQuery选择符转义

jQuery的选择符中如果有特殊字符如"#"、"."。那么需要进行转义,很容易只要在特殊字符前加上"\\"即可,如".\\.show",查找的是类名为".show"的节点。

2010年3月2日星期二

[note][转]使用Iterator 或for-each注意:java.util.ConcurrentModificationException

原帖:http://www.blogjava.net/fingki/archive/2010/03/02/314268.html

在使用Iterator处理Collection时,注意java.util.ConcurrentModificationException。
1.如果你仅仅是对collection进行遍历查询,那么不必担心什么。
2.但如果你在遍历过程中要对collection进行删除,那么你就要注意了。
For example:
private void testDel() {  
  1.     List<String> list = new ArrayList<String>();  
  2.     for (int i = 0; i < 10; i++) {  
  3.         String str = "td" + i;  
  4.         list.add(str);  
  5.     }  
  6.   
  7.     for (Iterator it = list.iterator(); it.hasNext();) {  
  8.         String str = (String) it.next();  
  9.         if (str.equals("td5")) {  
  10.             // list.remove(str);  // 删除方法一 
  11.             it.remove();  // 删除方法二 
  12.         }  
  13.     }  

上面的代码运行没有问题,但如果你用“方法一”替代“方法二”,则会出现java.util.ConcurrentModificationException。
(用for-each遍历也会出个类似问题)
具体原因是可以看一下先看看List中的remove方法源码:
  1. public boolean remove(Object o) {  
  2.     if (o == null) {  
  3.         for (int index = 0; index < size; index++)  
  4.             if (elementData[index] == null) {  
  5.                 fastRemove(index);  
  6.                 return true;  
  7.             }  
  8.     } else {  
  9.         for (int index = 0; index < size; index++)  
  10.             if (o.equals(elementData[index])) {  
  11.                 fastRemove(index);  
  12.                 return true;  
  13.             }  
  14.     }  
  15.     return false;  
  16. }  
  17.   
  18. private void fastRemove(int index) {  
  19.     modCount++; // 特别注意这里,这里只增加了modCount的值  
  20.     int numMoved = size - index - 1;  
  21.     if (numMoved > 0)  
  22.         System.arraycopy(elementData, index + 1, elementData, index,  
  23.                 numMoved);  
  24.     elementData[--size] = null; // Let gc do its work  

接着看。删除后得到下一个元素的代码,it.next():  it为AbstractList的内部类Iterator的一个实例。
  1. public E next() {  
  2.     checkForComodification();  
  3.     try {  
  4.         E next = get(cursor);  
  5.         lastRet = cursor++;  
  6.         return next;  
  7.     } catch (IndexOutOfBoundsException e) {  
  8.         checkForComodification();  
  9.         throw new NoSuchElementException();  
  10.     }  
  11. }  
  12.   
  13. final void checkForComodification() {  //注意这个方法
  14.     if (modCount != expectedModCount)  //检查这两个值是否相同
  15.         throw new ConcurrentModificationException();  

最后看Iterator的remove()方法的源代码:
  1. public void remove() {  
  2.     if (lastRet == -1)  
  3.         throw new IllegalStateException();  
  4.     checkForComodification();  
  5.     try {  
  6.         AbstractList.this.remove(lastRet);  
  7.         if (lastRet < cursor)  
  8.             cursor--;  
  9.         lastRet = -1;  
  10.         expectedModCount = modCount; // 设置expectedModCount  
  11.     } catch (IndexOutOfBoundsException e) {  
  12.         throw new ConcurrentModificationException();  
  13.     }  
  14. }  
  15.   
  16. final void checkForComodification() {  
  17.     if (modCount != expectedModCount)  
  18.         throw new ConcurrentModificationException();  

这下就明白了,list的remove方法只修改了modCount值,而iterator的remove能同步modCount和expectedModCount.

2010年2月28日星期日

2010年2月27日星期六

[note]js执行函数的方法以及获取windows的特殊方法

代码:

function mm(){
   this.say=function(){
       alert("sss");
   }
}

m= new mm();
m['say']();
console.log([].sort.call());
console.log([]["sort"]["call"]());

 [].sort.call()就是window,因为sort函数,是对数组的排序。call函数如果没有参数就用全局对象。如果使用sort方法的对象没有可排列的数组那么返回对象本身。

2010年2月26日星期五

[转][精品]浏览器缓存机制

Cache-Control

Cache-Control 是最重要的规则。这个字段用于指定所有缓存机制在整个请求/响应链中必须服从的指令。这些指令指定用于阻止缓存对请求或响应造成不利干扰的行为。这些指令通常覆盖默认缓存算法。缓存指令是单向的,即请求中存在一个指令并不意味着响应中将存在同一个指令。

cache-control 定义是:Cache-Control = "Cache-Control" ":" cache-directive。表 1 展示了适用的值。


表 1. 常用 cache-directive 值
Cache-directive 说明
public 所有内容都将被缓存
private 内容只缓存到私有缓存中
no-cache 所有内容都不会被缓存
no-store 所有内容都不会被缓存到缓存或 Internet 临时文件中
must-revalidation/proxy-revalidation 如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证
max-age=xxx (xxx is numeric) 缓存的内容将在 xxx 秒后失效

表 2 表明在不同的情形下,浏览器是将请求重新发送到服务器还是使用缓存的内容。


表 2. 对 cache-directive 值的浏览器响应
Cache-directive 打开一个新的浏览器窗口 在原窗口中单击 Enter 按钮 刷新 单击 Back 按钮
public 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面 浏览器重新发送请求到服务器 浏览器呈现来自缓存的页面
private 浏览器重新发送请求到服务器 第一次,浏览器重新发送请求到服务器;此后,浏览器呈现来自缓存的页面 浏览器重新发送请求到服务器 浏览器呈现来自缓存的页面
no-cache/no-store 浏览器重新发送请求到服务器 浏览器重新发送请求到服务器 浏览器重新发送请求到服务器 浏览器重新发送请求到服务器
must-revalidation/proxy-revalidation 浏览器重新发送请求到服务器 第一次,浏览器重新发送请求到服务器;此后,浏览器呈现来自缓存的页面 浏览器重新发送请求到服务器 浏览器呈现来自缓存的页面
max-age=xxx (xxx is numeric) 在 xxx 秒后,浏览器重新发送请求到服务器 在 xxx 秒后,浏览器重新发送请求到服务器 浏览器重新发送请求到服务器 在 xxx 秒后,浏览器重新发送请求到服务器

Cache-Control 是关于浏览器缓存的最重要的设置,因为它覆盖其他设置,比如 Expires 和 Last-Modified。另外,由于浏览器的行为基本相同,这个属性是处理跨浏览器缓存问题的最有效的方法。

失效

Expires 头部字段提供一个日期和时间,响应在该日期和时间后被认为失效。失效的缓存条目通常不会被缓存(无论是代理缓存还是用户代理缓存)返回,除非首先通过原始服务器(或者拥有该实体的最新副本的中介缓存)验证。(注意:cache-control max-age 和 s-maxage 将覆盖 Expires 头部。)

Expires 字段接收以下格式的值:“Expires: Sun, 08 Nov 2009 03:37:26 GMT”。如果查看内容时的日期在给定的日期之前,则认为该内容没有失效并从缓存中提取出来。反之,则认为该内容失效,缓存将采取一些措施。表 3-6 表明针对不同用户操作的不同浏览器的行为。


表 3. 当用户打开一个新的浏览器窗口时的失效操作 
Firefox 3.5 IE 8 Chrome 3 Safari 4
内容没有失效 浏览器呈现来自缓存的页面 浏览器重新发送请求到服务器。返回代码是 200 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面
内容失效 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200

表 4. 当用户在原始浏览器窗口中单击 Enter 按钮时的失效操作
Firefox 3.5 IE 8 Chrome 3 Safari 4
内容没有失效 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面 浏览器重新发送请求到服务器。返回代码是 304 浏览器重新发送请求到服务器。返回代码是 304
内容失效 浏览器重新发送请求到服务器。返回代码是 200 浏览器呈现来自缓存的页面 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200

表 5. 当用户按 F5 键刷新页面时的失效操作
Firefox 3.5 IE 8 Chrome 3 Safari 4
内容没有失效 浏览器重新发送请求到服务器。返回代码是 304 浏览器重新发送请求到服务器。返回代码是 304 浏览器重新发送请求到服务器。返回代码是 304 浏览器重新发送请求到服务器。返回代码是 304
内容失效 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200

表 6. 当用户单击 Back 或 Forward 按钮时的失效操作
Firefox 3.5 IE 8 Chrome 3 Safari 4
内容没有失效 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面
内容失效 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面 浏览器重新发送请求到服务器。返回代码是 200

注意:所有浏览器都假定为使用默认设置运行。

Last-Modified/E-Tag

Last-Modified 实体头部字段值通常用作一个缓存验证器。简单来说,如果实体值在 Last-Modified 值之后没有被更改,则认为该缓存条目有效。ETag 响应头部字段值是一个实体标记,它提供一个 “不透明” 的缓存验证器。这可能在以下几种情况下提供更可靠的验证:不方便存储修改日期;HTTP 日期值的 one-second 解决方案不够用;或者原始服务器希望避免由于使用修改日期而导致的某些冲突。

不同的浏览器有不同的配置行为。表 7-10 表明针对不同用户操作的不同浏览器的行为。


表 7. 当用户打开一个新的浏览器窗口时的 Last-Modified E-Tag 操作 
Firefox 3.5 IE 8 Chrome 3 Safari 4
内容自上次访问以来没有被修改 浏览器重新发送请求到服务器。返回代码是 304 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 304 浏览器重新发送请求到服务器。返回代码是 304
内容自上次访问以来已经被修改 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200

表 8. 当用户在原始浏览器窗口中单击 Enter 按钮时的 Last-Modified E-Tag 操作
Firefox 3.5 IE 8 Chrome 3 Safari 4
内容自上次访问以来没有被修改 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面 浏览器重新发送请求到服务器。返回代码是 304 浏览器重新发送请求到服务器。返回代码是 304
内容自上次访问以来已经被修改 浏览器重新发送请求到服务器。返回代码是 200 浏览器呈现来自缓存的页面 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200

表 9. 当用户按 F5 键刷新页面时的 Last-Modified E-Tag 操作
Firefox 3.5 IE 8 Chrome 3 Safari 4
内容自上次访问以来没有被修改 浏览器重新发送请求到服务器。返回代码是 304 浏览器重新发送请求到服务器。返回代码是 304 浏览器重新发送请求到服务器。返回代码是 304 浏览器重新发送请求到服务器。返回代码是 304
内容自上次访问以来已经被修改 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200

表 10. 没有缓存设置且用户单击 Back 或 Forward 按钮
Firefox 3.5 IE 8 Chrome 3 Safari 4
内容自上次访问以来没有被修改 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面
内容自上次访问以来已经被修改 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面 浏览器呈现来自缓存的页面 浏览器重新发送请求到服务器。返回代码是 200

注意:所有浏览器都假定使用默认设置运行。

不进行任何缓存相关设置

如果您不定义任何缓存相关设置,则不同的浏览器有不同的行为。有时,同一个浏览器在相同的情形下每次运行时的行为都是不同的。情况可能很复杂。另外,有些不该缓存的内容如果被缓存,将会导致安全问题。

不同的浏览器有不同的行为。表 11 展示了不同的浏览器行为。


表 11. 没有缓存设置且用户打开一个新的浏览器窗口
Firefox 3.5 IE 8 Chrome 3 Safari 4
打开一个新页面 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200
在原始窗口中单击 Enter 按钮 浏览器重新发送请求到服务器。返回代码是 200 浏览器呈现来自缓存的页面。 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200
按 F5 键刷新 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200
单击 Back 或 Forward 按钮 浏览器呈现来自缓存的页面。 浏览器呈现来自缓存的页面。 浏览器重新发送请求到服务器。返回代码是 200 浏览器重新发送请求到服务器。返回代码是 200

注意:所有浏览器都假定使用默认设置运行。