当前位置: 代码迷 >> Web前端 >> Kissy Suggest 自动提醒例子
  详细解决方案

Kissy Suggest 自动提醒例子

热度:939   发布时间:2012-11-09 10:18:48.0
Kissy Suggest 自动提示例子

由淘宝团队开发出来的Kissy Suggest 自动提示组件是什么好用的,下面就结合Kissy Suggest的使用(上一篇里有介绍)写一个简单的例子:


1 展示页面index.html:

?

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=GB18030" />
<title>Suggest Examples</title>
<link rel="stylesheet" href="reset-grids-min.css" type="text/css" />
<script type="text/javascript" src="yahoo-dom-event.js"></script>
<script type="text/javascript" src="suggest.js"></script>
<style type="text/css">
    #page { padding: 50px 50px 300px; width: 750px; margin: 0 auto; }
    h1, h2, h3 { margin: 1em 0 0.3em; }
    .section { margin-bottom: 50px; }
    .section ol { margin: 5px 20px; }
    .section ol li { list-style: decimal inside; margin: 5px 0; }
    .search-input { width: 300px; height: 20px; padding: 5px 2px 0 4px; }
    .search-submit { padding: 4px 10px; margin-left: 5px; }
    input.g-submit { padding: 2px 8px; margin-left: 5px; }
    html { overflow-y: scroll; }
</style>
</head>
<body>
<div id="page">
    <div class="section">
    
    <!-- 
    	获取淘宝商品信息接口(以查询"a"为例子):
    	http://suggest.taobao.com/sug?code=utf-8&callback=?&q=a
     -->
    
        <h2>1. 淘宝首页的搜索提示:</h2>
        <form name="search" method="get" action="http://search1.taobao.com/browse/search_auction.htm">
            <input type="hidden" value="" name="sort"/>
            <input type="hidden" value="D9_5_1" name="f"/>
            <input type="hidden" value="" name="promote"/>
            <input type="hidden" value="2" name="isnew"/>
            <input type="hidden" value="b" name="atype"/>
            <input type="hidden" value="all" name="commend"/>
            <input type="hidden" value="auction" name="search_type"/>
            <input type="hidden" value="initiative" name="user_action"/>
            <input type="hidden" value="s1-e" name="ssid"/>

            <input name="q" id="q" class="search-input"/>

            <button type="submit" class="search-submit">淘我喜欢</button>
        </form>
        <script type="text/javascript">
            (function() {
                // 淘宝
                var dataUrl = 'http://suggest.taobao.com/sug';
                new KISSY.Suggest('q', dataUrl,
                    { autoFocus: true,
                      resultFormat: '约%result%个宝贝'
                    });
            })();
        </script>
    </div>



	<div class="section">
        <h2>1.1. 自动作测试用的搜索提示:</h2>
        <form name="search" method="get" action="http://search1.taobao.com/browse/search_auction.htm">

            <input name="myq" id="myq" class="search-input"/>

            <button type="submit" class="search-submit">搜我喜欢</button>
        </form>
        
        <script type="text/javascript">
            (function() {
                // 测试
                var dataUrl = 'AutoComplete';//http://localhost:8080/wwww/data.jsp
                new KISSY.Suggest('myq', dataUrl,
                    { autoFocus: true,
                      resultFormat: '约%result%个测试数据合适'
                    });
            })();
        </script>
    </div>


    <div class="section">
        <h2>2. 有啊首页的搜索提示:</h2>
        <form name="search2" method="get" action="http://youa.baidu.com/search/s" target="_blank">
            <input class="search-input" name="keyword" id="q2" />
            <button type="submit" class="search-submit">百度一下</button>
        </form>
        <style type="text/css">
            .youa-suggest-container { border-color: #5BA515 }
            .youa-suggest-container li { padding: 2px 0 3px; font-size: 14px; line-height: 20px }
            .youa-suggest-container li.selected { background-color: #DEEFC5 }
            .youa-suggest-container .suggest-result { font-size: 12px; color: #999 }
            .youa-suggest-container li.selected span { color: #240055 }
            .youa-suggest-container li.selected .suggest-result { color: #999 }
        </style>
        
        
        <script type="text/javascript">
            (function() {
                // 有啊
                var dataUrl = 'http://youa.baidu.com/suggest/se/s';
                window.suggestCallback = KISSY.Suggest.callback;

                var sug = new KISSY.Suggest('q2', dataUrl, {
                    containerClass: 'youa-suggest-container'
                });

                sug.subscribe('beforeDataRequest', function(query) {
                    this.dataScript.charset = 'GB18030';
                    this.queryParams = 'cmd=suggest&type=kwd&max_count=10&keyword=' + encodeURIComponent(query) + '&callback=suggestCallback';
                });

                // youa: suggestCallback({"err":"ok", "r":[{"key":"nike", "val":140000}, {"key":"nike鞋", "val":119000}, {"key":"nike板鞋", "val":44300}, {"key":"nike运动鞋", "val":115000}, {"key":"nike正品", "val":47400}, {"key":"nike包", "val":8300}, {"key":"nike篮球鞋", "val":18400}, {"key":"nike新款", "val":27600}, {"key":"nike 耐克", "val":140000}, {"key":"nike女鞋", "val":7850}], "num":10})
                // taobao: KISSY.Suggest.callback({"result": [["nike 正品", "2170000"], ["nike 专柜 正品", "834000"], ["nike 短袖", "242000"], ["nike 板 鞋", "989000"], ["nike 女鞋", "253000"], ["nike 运动鞋", "550000"], ["nike 包", "295000"], ["nike 鞋", "3160000"], ["nike 单肩包", "38500"], ["nike 09", "786000"]]})
                sug.subscribe('onDataReturn', function(data) {
                    data = data['r'] || [];
                    var result = [];

                    for (var i = 0, len = data.length; i < len; ++i) {
                        result.push([data[i]['key'], data[i]['val']]);
                    }

                    this.returnedData = result;
                });
            })();
        </script>
    </div>

    <div class="section" style="width: 380px">
        <h2>3. Google Search</h2>
        <form name="f" action="http://www.google.com/search">
            <table cellspacing="0" cellpadding="0">
                <tbody>
                <tr valign="top">
                    <td width="25%"></td>
                    <td nowrap="" align="center">
                        <input type="hidden" value="en" name="hl"/>
                        <input style="padding: 3px 2px" value="" title="Google Search" size="55" id="gq" name="q" maxlength="2048" autocomplete="off"/>
                        <br/>
                        <input type="submit" class="g-submit" onclick="this.checked=1" value="Google Search" name="btnG"/>
                        <input type="submit" class="g-submit" onclick="this.checked=1" value="I'm Feeling Lucky" name="btnI"/>
                    </td>
                </tr>
                </tbody>
            </table>
            <input type="hidden" name="aq" value="f"/><input type="hidden" name="oq" value="n"/><input type="hidden" name="aqi" value="g10"/>
        </form>
        <style type="text/css">
            .g-sug { border-color: #666 }
            .g-sug li { padding: 2px 0 3px }
            .g-sug li.selected { background-color: #D5E2FF }
            .g-sug li.selected span { color: #240055 }
        </style>
        <script type="text/javascript">
            (function() {
                // Google
                var dataUrl = 'http://clients1.google.com/complete/search';
                window.google = {};
                window.google.ac = {};
                window.google.ac.h = KISSY.Suggest.callback;

                var sug = new KISSY.Suggest('gq', dataUrl,
                {
                    resultFormat: '',
                    containerClass: 'g-sug'
                });

                sug.subscribe('beforeDataRequest', function(query) {
                    this.dataScript.charset = 'utf-8';
                    this.queryParams = 'hl=en&q=' + encodeURIComponent(query);
                });

                // google: window.google.ac.h(["ni",[["博网","73,248 结果","0z"],["博网首页","12,200,617 结果","1z"],["你是准备替党说话 还是准备替老百姓说话","136,545 结果","2z"],["nike","117,000,000 结果","3"],["nikon","127,000,000 结果","4"],["nissan","135,000,000 结果","5"],["nine west","40,000,000 结果","6"],["nike鞋","3,380,000 结果","7"],["倪萍 再婚","36,400 结果","8"],["牛年祝福语","582,000 结果","9"]]])
                // taobao: KISSY.Suggest.callback({"result": [["nike 正品", "2170000"], ["nike 专柜 正品", "834000"], ["nike 短袖", "242000"], ["nike 板 鞋", "989000"], ["nike 女鞋", "253000"], ["nike 运动鞋", "550000"], ["nike 包", "295000"], ["nike 鞋", "3160000"], ["nike 单肩包", "38500"], ["nike 09", "786000"]]})
                sug.subscribe('onDataReturn', function(data) {
                    this.returnedData = data[1] || [];
                });
            })();
        </script>
    </div>

    <div class="section">
        <h2>4. 来点广告:</h2>
        <form name="search3" method="get" action="http://search1.taobao.com/browse/search_auction.htm" target="_blank">
            <input class="search-input" name="q" id="q3"/>
            <button type="submit" class="search-submit">Search</button>
        </form>
        <script type="text/javascript">
            (function() {
                // 广告
                var dataUrl = 'http://suggest.taobao.com/sug';
                var sug = new KISSY.Suggest('q3', dataUrl,
                    { showCloseBtn: true
                    });

                sug.subscribe('beforeShow', function(container) {
                    var ad = document.createElement('div');
                    ad.innerHTML = '<a target="_blank" href="http://cn.rd.yahoo.com/SIG=15rc1i6ku/M=737626.13478244.13591643.12449672/D=cntaobao/S=2121060025:TBE1/Y=CN/EXP=1245385302/L=hyh1VsvR9en1pr0GSdrZFg.OeQAd4ko69jYABbb1/B=2wHzHsvR9bY-/J=1245378102375152/K=l5oQqDYguLTtP2x265XY0A/A=5762545/R=0/SIG=112e9jtv1/*http://xiaomihu.mall.taobao.com/"><img height="90" width="270" border="0" src="http://cn.yimg.com/a/amyy/737626_homepage_tbe1_270x90.jpg"/></a>';
                    container.appendChild(ad);
                });
            })();
        </script>
    </div>
</div>
</body>
</html>

?

2 在web.xml加入servlet配置信息,这里将用户的请求异步提交到servlet里处理,再返回json数据,从而实现自动提示功能:

?

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	
	<servlet>
		<description>This is the description of my J2EE component</description>
		<display-name>This is the display name of my J2EE component</display-name>
		<servlet-name>AutoComplete</servlet-name>
		<servlet-class>service.AutoComplete</servlet-class>
	</servlet>

	<servlet-mapping>
		<servlet-name>AutoComplete</servlet-name>
		<url-pattern>/AutoComplete</url-pattern>
	</servlet-mapping>
	
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>
?

3 加入servlet代代码AutoComplete.java:

package service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.Gson;

public class AutoComplete extends HttpServlet {

	public void destroy() {
		super.destroy(); // Just puts "destroy" string in log
		// Put your code here
	}

	
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		List<String[]> results = new ArrayList<String[]>();
		for(int i = 0 ; i < 10 ; i ++){
			String[] str ={"a" + i ,String.valueOf(1 + i)};
			results.add(str);
		}
		
		String result =new Gson().toJson(results);
//		renderJSON("KISSY.Suggest.callback({'result':"+result.replace("\"", "\'")+"})");  
		
		response.getWriter().write("KISSY.Suggest.callback({'result':"+result.replace("\"", "\'")+"})");  
		
		System.out.println("到了get方法");

//显示的数据格式一:String result = "[['a0','1'],['a1','2'],['a2','3'],['a3','4'],['a4','5'],['a5','6'],['a6','7'],['a7','8'],['a8','9'],['a9','10']]"
		//调用方法:response.getWriter().write("KISSY.Suggest.callback({'result':"+result+"})");
		
		//显示数据格式二:String result = "['什么是人','什么是神童','我要说点什么呢?']"
		//调用方法:response.getWriter().write("KISSY.Suggest.callback({'result':"+ result +"})");
		
	}

	
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		
         this.doGet(request, response);

		System.out.println("到了doPost方法");
	}

	
	public void init() throws ServletException {
		
	}

}
?

?

4 引入Kissy Suggest 依赖文件:

?

(1) suggest.js

?

?

/**
 * KISSY.Suggest 提示补全组件
 *
 * suggest.js
 * requires: yahoo-dom-event
 *
 * @author lifesinger@gmail.com
 */

var KISSY = window.KISSY || {};

(function(NS) {
    var Y = YAHOO.util, Dom = Y.Dom, Event = Y.Event, Lang = YAHOO.lang,
        head = document.getElementsByTagName("head")[0],
        ie = YAHOO.env.ua.ie, ie6 = (ie === 6),

        CALLBACK_STR = "KISSY.Suggest.callback", // 注意 KISSY 在这里是写死的
        STYLE_ID = "suggest-style", // 样式 style 元素的 id

        CONTAINER_CLASS = "suggest-container",
        KEY_EL_CLASS = "suggest-key", // 提示层中,key 元素的 class
        RESULT_EL_CLASS = "suggest-result", // 提示层中,result 元素的 class
        SELECTED_ITEM_CLASS = "selected", // 提示层中,选中项的 class
        BOTTOM_CLASS = "suggest-bottom",
        CLOSE_BTN_CLASS = "suggest-close-btn",
        SHIM_CLASS = "suggest-shim", // iframe shim 的 class

        BEFORE_DATA_REQUEST = "beforeDataRequest",
        ON_DATA_RETURN = "onDataReturn",
        BEFORE_SHOW = "beforeShow",
        ON_ITEM_SELECT = "onItemSelect",

        /**
         * Suggest的默认配置
         */
        defaultConfig = {
        /**
         * 用户附加给悬浮提示层的 class
         *
         * 提示层的默认结构如下:
         * <div class="suggest-container [container-class]">
         *     <ol>
         *         <li>
         *             <span class="suggest-key">...</span>
         *             <span class="suggest-result">...</span>
         *         </li>
         *     </ol>
         *     <div class="suggest-bottom">
         *         <a class="suggest-close-btn">...</a>
         *     </div>
         * </div>
         * @type String
         */
        containerClass: "",

        /**
         * 提示层的宽度
         * 注意:默认情况下,提示层的宽度和input输入框的宽度保持一致
         * 示范取值:"200px", "10%"等,必须带单位
         * @type String
         */
        containerWidth: "auto",

        /**
         * result的格式
         * @type String
         */
        resultFormat: "约%result%条结果",

        /**
         * 是否显示关闭按钮
         * @type Boolean
         */
        showCloseBtn: false,

        /**
         * 关闭按钮上的文字
         * @type String
         */
        closeBtnText: "关闭",

        /**
         * 是否需要iframe shim
         * @type Boolean
         */
        useShim: ie6,

        /**
         * 定时器的延时
         * @type Number
         */
        timerDelay: 200,

        /**
         * 初始化后,自动激活
         * @type Boolean
         */
        autoFocus: false,

        /**
         * 鼠标点击完成选择时,是否自动提交表单
         * @type Boolean
         */
        submitFormOnClickSelect: true
    };

    /**
     * 提示补全组件
     * @class Suggest
     * @requires YAHOO.util.Dom
     * @requires YAHOO.util.Event
     * @constructor
     * @param {String|HTMLElement} textInput
     * @param {String} dataSource
     * @param {Object} config
     */
    NS.Suggest = function(textInput, dataSource, config) {
        /**
         * 文本输入框
         * @type HTMLElement
         */
        this.textInput = Dom.get(textInput);

        /**
         * 获取数据的URL 或 JSON格式的静态数据
         * @type {String|Object}
         */
        this.dataSource = dataSource;

        /**
         * JSON静态数据源
         * @type Object 格式为 {"query1" : [["key1", "result1"], []], "query2" : [[], []]}
         */
        this.JSONDataSource = Lang.isObject(dataSource) ? dataSource : null;

        /**
         * 通过jsonp返回的数据
         * @type Object
         */
        this.returnedData = null;

        /**
         * 配置参数
         * @type Object
         */
        this.config = Lang.merge(defaultConfig, config || {});

        /**
         * 存放提示信息的容器
         * @type HTMLElement
         */
        this.container = null;

        /**
         * 输入框的值
         * @type String
         */
        this.query = "";

        /**
         * 获取数据时的参数
         * @type String
         */
        this.queryParams = "";

        /**
         * 内部定时器
         * @private
         * @type Object
         */
        this._timer = null;

        /**
         * 计时器是否处于运行状态
         * @private
         * @type Boolean
         */
        this._isRunning = false;

        /**
         * 获取数据的script元素
         * @type HTMLElement
         */
        this.dataScript = null;

        /**
         * 数据缓存
         * @private
         * @type Object
         */
        this._dataCache = {};

        /**
         * 最新script的时间戳
         * @type String
         */
        this._latestScriptTime = "";

        /**
         * script返回的数据是否已经过期
         * @type Boolean
         */
        this._scriptDataIsOut = false;

        /**
         * 是否处于键盘选择状态
         * @private
         * @type Boolean
         */
        this._onKeyboardSelecting = false;

        /**
         * 提示层的当前选中项
         * @type Boolean
         */
        this.selectedItem = null;

        // init
        this._init();
    };

    Lang.augmentObject(NS.Suggest.prototype, {
        /**
         * 初始化方法
         * @protected
         */
        _init: function() {
            // init DOM
            this._initTextInput();
            this._initContainer();
            if (this.config.useShim) this._initShim();
            this._initStyle();

            // create events
            this.createEvent(BEFORE_DATA_REQUEST);
            this.createEvent(ON_DATA_RETURN);
            this.createEvent(BEFORE_SHOW);
            this.createEvent(ON_ITEM_SELECT);

            // window resize event
            this._initResizeEvent();
        },

        /**
         * 初始化输入框
         * @protected
         */
        _initTextInput: function() {
            var instance = this;

            // turn off autocomplete
            this.textInput.setAttribute("autocomplete", "off");

            // focus
            Event.on(this.textInput, "focus", function() {
                instance.start();
            });

            // blur
            Event.on(this.textInput, "blur", function() {
                instance.stop();
                instance.hide();
            });

            // auto focus
            if (this.config.autoFocus) this.textInput.focus();

            // keydown
            // 注:截至目前,在Opera9.64中,输入法开启时,依旧不会触发任何键盘事件
            var pressingCount = 0; // 持续按住某键时,连续触发的keydown次数。注意Opera只会触发一次。
            Event.on(this.textInput, "keydown", function(ev) {
                var keyCode = ev.charCode || ev.keyCode;
                //console.log("keydown " + keyCode);

                switch (keyCode) {
                    case 27: // ESC键,隐藏提示层并还原初始输入
                        instance.hide();
                        instance.textInput.value = instance.query;
                        break;
                    case 13: // ENTER键
                        // 提交表单前,先隐藏提示层并停止计时器
                        instance.textInput.blur(); // 这一句还可以阻止掉浏览器的默认提交事件

                        // 如果是键盘选中某项后回车,触发onItemSelect事件
                        if (instance._onKeyboardSelecting) {
                            if (instance.textInput.value == instance._getSelectedItemKey()) { // 确保值匹配
                                instance.fireEvent(ON_ITEM_SELECT, instance.textInput.value);
                            }
                        }

                        // 提交表单
                        instance._submitForm();

                        break;
                    case 40: // DOWN键
                    case 38: // UP键
                        // 按住键不动时,延时处理
                        if (pressingCount++ == 0) {
                            if (instance._isRunning) instance.stop();
                            instance._onKeyboardSelecting = true;
                            instance.selectItem(keyCode == 40);

                        } else if (pressingCount == 3) {
                            pressingCount = 0;
                        }
                        break;
                }

                // 非 DOWN/UP 键时,开启计时器
                if (keyCode != 40 && keyCode != 38) {
                    if (!instance._isRunning) {
                        // 1. 当网速较慢,js还未下载完时,用户可能就已经开始输入
                        //    这时,focus事件已经不会触发,需要在keyup里触发定时器
                        // 2. 非DOWN/UP键时,需要激活定时器
                        instance.start();
                    }
                    instance._onKeyboardSelecting = false;
                }
            });

            // reset pressingCount
            Event.on(this.textInput, "keyup", function() {
                //console.log("keyup");
                pressingCount = 0;
            });
        },

        /**
         * 初始化提示层容器
         * @protected
         */
        _initContainer: function() {
            // create
            var container = document.createElement("div"),
                customContainerClass = this.config.containerClass;

            container.className = CONTAINER_CLASS;
            if(customContainerClass) {
                container.className += " " + customContainerClass;
            }
            container.style.position = "absolute";
            container.style.visibility = "hidden";
            this.container = container;

            this._setContainerRegion();
            this._initContainerEvent();

            // append
            document.body.insertBefore(container, document.body.firstChild);
        },

        /**
         * 设置容器的left, top, width
         * @protected
         */
        _setContainerRegion: function() {
            var r = Dom.getRegion(this.textInput);
            var left = r.left, w = r.right - left - 2;  // 减去border的2px

            // ie8兼容模式
            // document.documentMode:
            // 5 - Quirks Mode
            // 7 - IE7 Standards
            // 8 - IE8 Standards
            var docMode = document.documentMode;
            if (docMode === 7 && (ie === 7 || ie === 8)) {
                left -= 2;
            } else if (YAHOO.env.ua.gecko) { // firefox下左偏一像素 注:当 input 所在的父级容器有 margin: auto 时会出现
                left++;
            }

            this.container.style.left = left + "px";
            this.container.style.top = r.bottom + "px";

            if (this.config.containerWidth == "auto") {
                this.container.style.width = w + "px";
            } else {
                this.container.style.width = this.config.containerWidth;
            }
        },

        /**
         * 初始化容器事件
         * 子元素都不用设置事件,冒泡到这里统一处理
         * @protected
         */
        _initContainerEvent: function() {
            var instance = this;

            // 鼠标事件
            Event.on(this.container, "mousemove", function(ev) {
                //console.log("mouse move");
                var target = Event.getTarget(ev);

                if (target.nodeName != "LI") {
                    target = Dom.getAncestorByTagName(target, "li");
                }
                if (Dom.isAncestor(instance.container, target)) {
                    if (target != instance.selectedItem) {
                        // 移除老的
                        instance._removeSelectedItem();
                        // 设置新的
                        instance._setSelectedItem(target);
                    }
                }
            });

            var mouseDownItem = null;
            this.container.onmousedown = function(e) {
                e = e || window.event;
                // 鼠标按下处的item
                mouseDownItem = e.target || e.srcElement;

                // 鼠标按下时,让输入框不会失去焦点
                // 1. for IE
                instance.textInput.onbeforedeactivate = function() {
                    window.event.returnValue = false;
                    instance.textInput.onbeforedeactivate = null;
                };
                // 2. for W3C
                return false;
            };

            // mouseup事件
            Event.on(this.container, "mouseup", function(ev) {
                // 当mousedown在提示层,但mouseup在提示层外时,点击无效
                if (!instance._isInContainer(Event.getXY(ev))) return;
                var target = Event.getTarget(ev);
                // 在提示层A项处按下鼠标,移动到B处释放,不触发onItemSelect
                if (target != mouseDownItem) return;

                // 点击在关闭按钮上
                if (target.className == CLOSE_BTN_CLASS) {
                    instance.hide();
                    return;
                }

                // 可能点击在li的子元素上
                if (target.nodeName != "LI") {
                    target = Dom.getAncestorByTagName(target, "li");
                }
                // 必须点击在container内部的li上
                if (Dom.isAncestor(instance.container, target)) {
                    instance._updateInputFromSelectItem(target);

                    // 触发选中事件
                    //console.log("on item select");
                    instance.fireEvent(ON_ITEM_SELECT, instance.textInput.value);

                    // 提交表单前,先隐藏提示层并停止计时器
                    instance.textInput.blur();

                    // 提交表单
                    instance._submitForm();
                }
            });
        },

        /**
         * click选择 or enter后,提交表单
         */
        _submitForm: function() {
            // 注:对于键盘控制enter选择的情况,由html自身决定是否提交。否则会导致某些输入法下,用enter选择英文时也触发提交
            if (this.config.submitFormOnClickSelect) {
                var form = this.textInput.form;
                if (!form) return;

                // 通过js提交表单时,不会触发onsubmit事件
                // 需要js自己触发
                if (document.createEvent) { // w3c
                    var evObj = document.createEvent("MouseEvents");
                    evObj.initEvent("submit", true, false);
                    form.dispatchEvent(evObj);
                }
                else if (document.createEventObject) { // ie
                    form.fireEvent("onsubmit");
                }

                form.submit();
            }
        },

        /**
         * 判断p是否在提示层内
         * @param {Array} p [x, y]
         */
        _isInContainer: function(p) {
            var r = Dom.getRegion(this.container);
            return p[0] >= r.left && p[0] <= r.right && p[1] >= r.top && p[1] <= r.bottom;
        },

        /**
         * 给容器添加iframe shim层
         * @protected
         */
        _initShim: function() {
            var iframe = document.createElement("iframe");
            iframe.src = "about:blank";
            iframe.className = SHIM_CLASS;
            iframe.style.position = "absolute";
            iframe.style.visibility = "hidden";
            iframe.style.border = "none";
            this.container.shim = iframe;

            this._setShimRegion();
            document.body.insertBefore(iframe, document.body.firstChild);
        },

        /**
         * 设置shim的left, top, width
         * @protected
         */
        _setShimRegion: function() {
            var container = this.container, shim = container.shim;
            if (shim) {
                shim.style.left = (parseInt(container.style.left) - 2) + "px"; // 解决吞边线bug
                shim.style.top = container.style.top;
                shim.style.width = (parseInt(container.style.width) + 2) + "px";
            }
        },

        /**
         * 初始化样式
         * @protected
         */
        _initStyle: function() {
            var styleEl = Dom.get(STYLE_ID);
            if (styleEl) return; // 防止多个实例时重复添加

            var style = ".suggest-container{background:white;border:1px solid #999;z-index:99999}";
            style += ".suggest-shim{z-index:99998}";
            style += ".suggest-container li{color:#404040;padding:1px 0 2px;font-size:12px;line-height:18px;float:left;width:100%}";
            style += ".suggest-container li.selected{background-color:#39F;cursor:default}";
            style += ".suggest-key{float:left;text-align:left;padding-left:5px}";
            style += ".suggest-result{float:right;text-align:right;padding-right:5px;color:green}";
            style += ".suggest-container li.selected span{color:#FFF;cursor:default}";
            //style += ".suggest-container li.selected .suggest-result{color:green}";
            style += ".suggest-bottom{padding:0 5px 5px}";
            style += ".suggest-close-btn{float:right}";
            style += ".suggest-container li,.suggest-bottom{overflow:hidden;zoom:1;clear:both}";
            /* hacks */
            style += ".suggest-container{*margin-left:2px;_margin-left:-2px;_margin-top:-3px}";

            styleEl = document.createElement("style");
            styleEl.id = STYLE_ID;
            styleEl.type = "text/css";
            head.appendChild(styleEl); // 先添加到DOM树中,都在cssText里的hack会失效

            if (styleEl.styleSheet) { // IE
                styleEl.styleSheet.cssText = style;
            } else { // W3C
                styleEl.appendChild(document.createTextNode(style));
            }
        },

        /**
         * window.onresize时,调整提示层的位置
         * @protected
         */
        _initResizeEvent: function() {
            var instance = this, resizeTimer;

            Event.on(window, "resize", function() {
                if (resizeTimer) {
                    clearTimeout(resizeTimer);
                }

                resizeTimer = setTimeout(function() {
                    instance._setContainerRegion();
                    instance._setShimRegion();
                }, 50);
            });
        },

        /**
         * 启动计时器,开始监听用户输入
         */
        start: function() {
            NS.Suggest.focusInstance = this;

            var instance = this;
            instance._timer = setTimeout(function() {
                instance.updateData();
                instance._timer = setTimeout(arguments.callee, instance.config.timerDelay);
            }, instance.config.timerDelay);

            this._isRunning = true;
        },

        /**
         * 停止计时器
         */
        stop: function() {
            NS.Suggest.focusInstance = null;
            clearTimeout(this._timer);
            this._isRunning = false;
        },

        /**
         * 显示提示层
         */
        show: function() {
            if (this.isVisible()) return;
            var container = this.container, shim = container.shim;

            container.style.visibility = "";

            if (shim) {
                if (!shim.style.height) { // 第一次显示时,需要设定高度
                    var r = Dom.getRegion(container);
                    shim.style.height = (r.bottom - r.top - 2) + "px";
                }
                shim.style.visibility = "";
            }
        },

        /**
         * 隐藏提示层
         */
        hide: function() {
            if (!this.isVisible()) return;
            var container = this.container, shim = container.shim;
            //console.log("hide");

            if (shim) shim.style.visibility = "hidden";
            container.style.visibility = "hidden";
        },

        /**
         * 提示层是否显示
         */
        isVisible: function() {
            return this.container.style.visibility != "hidden";
        },

        /**
         * 更新提示层的数据
         */
        updateData: function() {
            if (!this._needUpdate()) return;
            //console.log("update data");

            this._updateQueryValueFromInput();
            var q = this.query;

            // 1. 输入为空时,隐藏提示层
            if (!Lang.trim(q).length) {
                this._fillContainer("");
                this.hide();
                return;
            }

            if (typeof this._dataCache[q] != "undefined") { // 2. 使用缓存数据
                //console.log("use cache");
                this.returnedData = "using cache";
                this._fillContainer(this._dataCache[q]);
                this._displayContainer();

            } else if (this.JSONDataSource) { // 3. 使用JSON静态数据源
                this.handleResponse(this.JSONDataSource[q]);

            } else { // 4. 请求服务器数据
                this.requestData();
            }
        },

        /**
         * 是否需要更新数据
         * @protected
         * @return Boolean
         */
        _needUpdate: function() {
            // 注意:加入空格也算有变化
            return this.textInput.value != this.query;
        },

        /**
         * 通过script元素加载数据
         */
        requestData: function() {
            //console.log("request data via script");
            if (!ie) this.dataScript = null; // IE不需要重新创建script元素

            if (!this.dataScript) {
                var script = document.createElement("script");
                script.type = "text/javascript";
                script.charset = "utf-8";

                // jQuery ajax.js line 275:
                // Use insertBefore instead of appendChild  to circumvent an IE6 bug.
                // This arises when a base node is used.
                head.insertBefore(script, head.firstChild);
                this.dataScript = script;

                if (!ie) {
                    var t = new Date().getTime();
                    this._latestScriptTime = t;
                    script.setAttribute("time", t);

                    Event.on(script, "load", function() {
                        //console.log("on load");
                        // 判断返回的数据是否已经过期
                        this._scriptDataIsOut = script.getAttribute("time") != this._latestScriptTime;
                    }, this, true);
                }
            }

            // 注意:没必要加时间戳,是否缓存由服务器返回的Header头控制
            this.queryParams = "q=" + encodeURIComponent(this.query) + "&code=utf-8&callback=" + CALLBACK_STR;
            this.fireEvent(BEFORE_DATA_REQUEST, this.query);
            this.dataScript.src = this.dataSource + "?" + this.queryParams;
        },

        /**
         * 处理获取的数据
         * @param {Object} data
         */
        handleResponse: function(data) {
            //console.log("handle response");
            if (this._scriptDataIsOut) return; // 抛弃过期数据,否则会导致bug:1. 缓存key值不对; 2. 过期数据导致的闪屏

            this.returnedData = data;
            this.fireEvent(ON_DATA_RETURN, data);

            // 格式化数据
            this.returnedData = this.formatData(this.returnedData);

            // 填充数据
            var content = "";
            var len = this.returnedData.length;
            if (len > 0) {
                var list = document.createElement("ol");
                for (var i = 0; i < len; ++i) {
                    var itemData = this.returnedData[i];
                    var li = this.formatItem(itemData["key"], itemData["result"]);
                    // 缓存key值到attribute上
                    li.setAttribute("key", itemData["key"]);
                    list.appendChild(li);
                }
                content = list;
            }
            this._fillContainer(content);

            // 有内容时才添加底部
            if (len > 0) this.appendBottom();

            // fire event
            if (Lang.trim(this.container.innerHTML)) {
                // 实际上是beforeCache,但从用户的角度看,是beforeShow
                this.fireEvent(BEFORE_SHOW, this.container);
            }

            // cache
            this._dataCache[this.query] = this.container.innerHTML;

            // 显示容器
            this._displayContainer();
        },

        /**
         * 格式化输入的数据对象为标准格式
         * @param {Object} data 格式可以有3种:
         *  1. {"result" : [["key1", "result1"], ["key2", "result2"], ...]}
         *  2. {"result" : ["key1", "key2", ...]}
         *  3. 1和2的组合
         *  4. 标准格式
         *  5. 上面1-4中,直接取o["result"]的值
         * @return Object 标准格式的数据:
         *  [{"key" : "key1", "result" : "result1"}, {"key" : "key2", "result" : "result2"}, ...]
         */
        formatData: function(data) {
            var arr = [];
            if (!data) return arr;
            if (Lang.isArray(data["result"])) data = data["result"];
            var len = data.length;
            if (!len) return arr;

            var item;
            for (var i = 0; i < len; ++i) {
                item = data[i];

                if (Lang.isString(item)) { // 只有key值时
                    arr[i] = {"key" : item};
                } else if (Lang.isArray(item) && item.length >= 2) { // ["key", "result"] 取数组前2个
                    arr[i] = {"key" : item[0], "result" : item[1]};
                } else {
                    arr[i] = item;
                }
            }
            return arr;
        },

        /**
         * 格式化输出项
         * @param {String} key 查询字符串
         * @param {Number} result 结果 可不设
         * @return {HTMLElement}
         */
        formatItem: function(key, result) {
            var li = document.createElement("li");
            var keyEl = document.createElement("span");
            keyEl.className = KEY_EL_CLASS;
            keyEl.appendChild(document.createTextNode(key));
            li.appendChild(keyEl);

            if (typeof result != "undefined") { // 可以没有
                var resultText = this.config.resultFormat.replace("%result%", result);
                if (Lang.trim(resultText)) { // 有值时才创建
                    var resultEl = document.createElement("span");
                    resultEl.className = RESULT_EL_CLASS;
                    resultEl.appendChild(document.createTextNode(resultText));
                    li.appendChild(resultEl);
                }
            }

            return li;
        },

        /**
         * 添加提示层底部
         */
        appendBottom: function() {
            var bottom = document.createElement("div");
            bottom.className = BOTTOM_CLASS;

            if (this.config.showCloseBtn) {
                var closeBtn = document.createElement("a");
                closeBtn.href = "javascript: void(0)";
                closeBtn.setAttribute("target", "_self"); // bug fix: 覆盖<base target="_blank" />,否则会弹出空白页面
                closeBtn.className = CLOSE_BTN_CLASS;
                closeBtn.appendChild(document.createTextNode(this.config.closeBtnText));

                // 没必要,点击时,输入框失去焦点,自动就关闭了
                /*
                 Event.on(closeBtn, "click", function(ev) {
                 Event.stopEvent(ev);
                 this.hidden();
                 }, this, true);
                 */

                bottom.appendChild(closeBtn);
            }

            // 仅当有内容时才添加
            if (Lang.trim(bottom.innerHTML)) {
                this.container.appendChild(bottom);
            }
        },

        /**
         * 填充提示层
         * @protected
         * @param {String|HTMLElement} content innerHTML or Child Node
         */
        _fillContainer: function(content) {
            if (content.nodeType == 1) {
                this.container.innerHTML = "";
                this.container.appendChild(content);
            } else {
                this.container.innerHTML = content;
            }

            // 一旦重新填充了,selectedItem就没了,需要重置
            this.selectedItem = null;
        },

        /**
         * 根据contanier的内容,显示或隐藏容器
         */
        _displayContainer: function() {
            if (Lang.trim(this.container.innerHTML)) {
                this.show();
            } else {
                this.hide();
            }
        },

        /**
         * 选中提示层中的上/下一个条
         * @param {Boolean} down true表示down,false表示up
         */
        selectItem: function(down) {
            //console.log("select item " + down);
            var items = this.container.getElementsByTagName("li");
            if (items.length == 0) return;

            // 有可能用ESC隐藏了,直接显示即可
            if (!this.isVisible()){
                this.show();
                return; // 保留原来的选中状态
            }
            var newSelectedItem;

            // 没有选中项时,选中第一/最后项
            if (!this.selectedItem) {
                newSelectedItem = items[down ? 0 : items.length - 1];
            } else {
                // 选中下/上一项
                newSelectedItem = Dom[down ? "getNextSibling" : "getPreviousSibling"](this.selectedItem);
                // 已经到了最后/前一项时,归位到输入框,并还原输入值
                if (!newSelectedItem) {
                    this.textInput.value = this.query;
                }
            }

            // 移除当前选中项
            this._removeSelectedItem();

            // 选中新项
            if (newSelectedItem) {
                this._setSelectedItem(newSelectedItem);
                this._updateInputFromSelectItem();
            }
        },

        /**
         * 移除选中项
         * @protected
         */
        _removeSelectedItem: function() {
            //console.log("remove selected item");
            Dom.removeClass(this.selectedItem, SELECTED_ITEM_CLASS);
            this.selectedItem = null;
        },

        /**
         * 设置当前选中项
         * @protected
         * @param {HTMLElement} item
         */
        _setSelectedItem: function(item) {
            //console.log("set selected item");
            Dom.addClass((item), SELECTED_ITEM_CLASS);
            this.selectedItem = (item);
        },

        /**
         * 获取提示层中选中项的key字符串
         * @protected
         */
        _getSelectedItemKey: function() {
            if (!this.selectedItem) return "";

            // getElementsByClassName比较损耗性能,改用缓存数据到attribute上方法
            //var keyEl = Dom.getElementsByClassName(KEY_EL_CLASS, "*", this.selectedItem)[0];
            //return keyEl.innerHTML;

            return this.selectedItem.getAttribute("key");
        },

        /**
         * 将textInput的值更新到this.query
         * @protected
         */
        _updateQueryValueFromInput: function() {
            this.query = this.textInput.value;
        },

        /**
         * 将选中项的值更新到textInput
         * @protected
         */
        _updateInputFromSelectItem: function() {
            this.textInput.value = this._getSelectedItemKey(this.selectedItem);
        }

    });

    Lang.augmentProto(NS.Suggest, Y.EventProvider);

    /**
     * 当前激活的实例
     * @static
     */
    NS.Suggest.focusInstance = null;

    /**
     * 从jsonp中获取数据
     * @method callback
     */
    NS.Suggest.callback = function(data) {
        if (!NS.Suggest.focusInstance) return;
        // 使得先运行script.onload事件,然后再执行callback函数
        setTimeout(function() {
            NS.Suggest.focusInstance.handleResponse(data);
        }, 0);
    };

})(KISSY);


/**
 * 小结:
 *
 * 整个组件代码,由两大部分组成:数据处理 + 事件处理
 *
 * 一、数据处理很core,但相对来说是简单的,由 requestData + handleResponse + formatData等辅助方法组成
 * 需要注意两点:
 *  a. IE中,改变script.src, 会自动取消掉之前的请求,并发送新请求。非IE中,必须新创建script才行。这是
 *     requestData方法中存在两种处理方式的原因。
 *  b. 当网速很慢,数据返回时,用户的输入可能已改变,已经有请求发送出去,需要抛弃过期数据。目前采用加时间戳
 *     的解决方案。更好的解决方案是,调整API,使得返回的数据中,带有query值。
 *
 * 二、事件处理看似简单,实际上有不少陷阱,分2部分:
 *  1. 输入框的focus/blur事件 + 键盘控制事件
 *  2. 提示层上的鼠标悬浮和点击事件
 * 需要注意以下几点:
 *  a. 因为点击提示层时,首先会触发输入框的blur事件,blur事件中调用hide方法,提示层一旦隐藏后,就捕获不到
 *     点击事件了。因此有了 this._mouseHovering 来排除这种情况,使得blur时不会触发hide,在提示层的点击
 *     事件中自行处理。(2009-06-18更新:采用mouseup来替代click事件,代码清晰简单了很多)
 *  b. 当鼠标移动到某项或通过上下键选中某项时,给this.selectedItem赋值;当提示层的数据重新填充时,重置
 *     this.selectedItem. 这种处理方式和google的一致,可以使得选中某项,隐藏,再次打开时,依旧选中原来
 *     的选中项。
 *  c. 在ie等浏览器中,输入框中输入ENTER键时,会自动提交表单。如果form.target="_blank", 自动提交和JS提交
 *     会打开两个提交页面。因此这里采取了在JS中不提交的策略,ENTER键是否提交表单,完全由HTML代码自身决定。这
 *     样也能使得组件很容易应用在不需要提交表单的场景中。(2009-06-18更新:可以通过blur()取消掉浏览器的默认
 *     Enter响应,这样能使得代码逻辑和mouseup的一致)
 *  d. onItemSelect 仅在鼠标点击选择某项 和 键盘选中某项回车 后触发。
 *  e. 当textInput会触发表单提交时,在enter keydown 和 keyup之间,就会触发提交。因此在keydown中捕捉事件。
 *     并且在keydown中能捕捉到持续DOWN/UP,在keyup中就不行了。
 *
 * 【得到的一些编程经验】:
 *  1. 职责单一原则。方法的职责要单一,比如hide方法和show方法,除了改变visibility, 就不要拥有其它功能。这
 *     看似简单,真要做到却并不容易。保持职责单一,保持简单的好处是,代码的整体逻辑更清晰,方法的可复用性也提
 *     高了。
 *  2. 小心事件处理。当事件之间有关联时,要仔细想清楚,设计好后再写代码。比如输入框的blur和提示层的click事件。
 *  3. 测试的重要性。目前是列出Test Cases,以后要尝试自动化。保证每次改动后,都不影响原有功能。
 *  4. 挑选正确的事件做正确的事,太重要了,能省去很多很多烦恼。
 *
 */

/**
 * 2009-08-05 更新: 将 class 从配置项中移动到常量,原因是:修改默认 className 的可能性很小,仅保留一个
 *                  containerClass 作为个性化样式的接口即可
 */

?

(2)?yahoo-dom-event.js

?

/*
 * Copyright (c) 2009, Yahoo! Inc. All rights reserved. Code licensed under the
 * BSD License: http://developer.yahoo.net/yui/license.txt Download by
 * http://www.codefans.net version: 2.7.0
 */
if (typeof YAHOO == "undefined" || !YAHOO) {
	var YAHOO = {};
}
YAHOO.namespace = function() {
	var A = arguments, E = null, C, B, D;
	for (C = 0; C < A.length; C = C + 1) {
		D = ("" + A[C]).split(".");
		E = YAHOO;
		for (B = (D[0] == "YAHOO") ? 1 : 0; B < D.length; B = B + 1) {
			E[D[B]] = E[D[B]] || {};
			E = E[D[B]];
		}
	}
	return E;
};
YAHOO.log = function(D, A, C) {
	var B = YAHOO.widget.Logger;
	if (B && B.log) {
		return B.log(D, A, C);
	} else {
		return false;
	}
};
YAHOO.register = function(A, E, D) {
	var I = YAHOO.env.modules, B, H, G, F, C;
	if (!I[A]) {
		I[A] = {
			versions : [],
			builds : []
		};
	}
	B = I[A];
	H = D.version;
	G = D.build;
	F = YAHOO.env.listeners;
	B.name = A;
	B.version = H;
	B.build = G;
	B.versions.push(H);
	B.builds.push(G);
	B.mainClass = E;
	for (C = 0; C < F.length; C = C + 1) {
		F[C](B);
	}
	if (E) {
		E.VERSION = H;
		E.BUILD = G;
	} else {
		YAHOO.log("mainClass is undefined for module " + A, "warn");
	}
};
YAHOO.env = YAHOO.env || {
	modules : [],
	listeners : []
};
YAHOO.env.getVersion = function(A) {
	return YAHOO.env.modules[A] || null;
};
YAHOO.env.ua = function() {
	var C = {
		ie : 0,
		opera : 0,
		gecko : 0,
		webkit : 0,
		mobile : null,
		air : 0,
		caja : 0
	}, B = navigator.userAgent, A;
	if ((/KHTML/).test(B)) {
		C.webkit = 1;
	}
	A = B.match(/AppleWebKit\/([^\s]*)/);
	if (A && A[1]) {
		C.webkit = parseFloat(A[1]);
		if (/ Mobile\//.test(B)) {
			C.mobile = "Apple";
		} else {
			A = B.match(/NokiaN[^\/]*/);
			if (A) {
				C.mobile = A[0];
			}
		}
		A = B.match(/AdobeAIR\/([^\s]*)/);
		if (A) {
			C.air = A[0];
		}
	}
	if (!C.webkit) {
		A = B.match(/Opera[\s\/]([^\s]*)/);
		if (A && A[1]) {
			C.opera = parseFloat(A[1]);
			A = B.match(/Opera Mini[^;]*/);
			if (A) {
				C.mobile = A[0];
			}
		} else {
			A = B.match(/MSIE\s([^;]*)/);
			if (A && A[1]) {
				C.ie = parseFloat(A[1]);
			} else {
				A = B.match(/Gecko\/([^\s]*)/);
				if (A) {
					C.gecko = 1;
					A = B.match(/rv:([^\s\)]*)/);
					if (A && A[1]) {
						C.gecko = parseFloat(A[1]);
					}
				}
			}
		}
	}
	A = B.match(/Caja\/([^\s]*)/);
	if (A && A[1]) {
		C.caja = parseFloat(A[1]);
	}
	return C;
}();
(function() {
	YAHOO.namespace("util", "widget", "example");
	if ("undefined" !== typeof YAHOO_config) {
		var B = YAHOO_config.listener, A = YAHOO.env.listeners, D = true, C;
		if (B) {
			for (C = 0; C < A.length; C = C + 1) {
				if (A[C] == B) {
					D = false;
					break;
				}
			}
			if (D) {
				A.push(B);
			}
		}
	}
})();
YAHOO.lang = YAHOO.lang || {};
(function() {
	var B = YAHOO.lang, F = "[object Array]", C = "[object Function]", A = Object.prototype, E = [
			"toString", "valueOf"], D = {
		isArray : function(G) {
			return A.toString.apply(G) === F;
		},
		isBoolean : function(G) {
			return typeof G === "boolean";
		},
		isFunction : function(G) {
			return A.toString.apply(G) === C;
		},
		isNull : function(G) {
			return G === null;
		},
		isNumber : function(G) {
			return typeof G === "number" && isFinite(G);
		},
		isObject : function(G) {
			return (G && (typeof G === "object" || B.isFunction(G))) || false;
		},
		isString : function(G) {
			return typeof G === "string";
		},
		isUndefined : function(G) {
			return typeof G === "undefined";
		},
		_IEEnumFix : (YAHOO.env.ua.ie) ? function(I, H) {
			var G, K, J;
			for (G = 0; G < E.length; G = G + 1) {
				K = E[G];
				J = H[K];
				if (B.isFunction(J) && J != A[K]) {
					I[K] = J;
				}
			}
		} : function() {
		},
		extend : function(J, K, I) {
			if (!K || !J) {
				throw new Error("extend failed, please check that "
						+ "all dependencies are included.");
			}
			var H = function() {
			}, G;
			H.prototype = K.prototype;
			J.prototype = new H();
			J.prototype.constructor = J;
			J.superclass = K.prototype;
			if (K.prototype.constructor == A.constructor) {
				K.prototype.constructor = K;
			}
			if (I) {
				for (G in I) {
					if (B.hasOwnProperty(I, G)) {
						J.prototype[G] = I[G];
					}
				}
				B._IEEnumFix(J.prototype, I);
			}
		},
		augmentObject : function(K, J) {
			if (!J || !K) {
				throw new Error("Absorb failed, verify dependencies.");
			}
			var G = arguments, I, L, H = G[2];
			if (H && H !== true) {
				for (I = 2; I < G.length; I = I + 1) {
					K[G[I]] = J[G[I]];
				}
			} else {
				for (L in J) {
					if (H || !(L in K)) {
						K[L] = J[L];
					}
				}
				B._IEEnumFix(K, J);
			}
		},
		augmentProto : function(J, I) {
			if (!I || !J) {
				throw new Error("Augment failed, verify dependencies.");
			}
			var G = [J.prototype, I.prototype], H;
			for (H = 2; H < arguments.length; H = H + 1) {
				G.push(arguments[H]);
			}
			B.augmentObject.apply(this, G);
		},
		dump : function(G, L) {
			var I, K, N = [], O = "{...}", H = "f(){...}", M = ", ", J = " => ";
			if (!B.isObject(G)) {
				return G + "";
			} else {
				if (G instanceof Date || ("nodeType" in G && "tagName" in G)) {
					return G;
				} else {
					if (B.isFunction(G)) {
						return H;
					}
				}
			}
			L = (B.isNumber(L)) ? L : 3;
			if (B.isArray(G)) {
				N.push("[");
				for (I = 0, K = G.length; I < K; I = I + 1) {
					if (B.isObject(G[I])) {
						N.push((L > 0) ? B.dump(G[I], L - 1) : O);
					} else {
						N.push(G[I]);
					}
					N.push(M);
				}
				if (N.length > 1) {
					N.pop();
				}
				N.push("]");
			} else {
				N.push("{");
				for (I in G) {
					if (B.hasOwnProperty(G, I)) {
						N.push(I + J);
						if (B.isObject(G[I])) {
							N.push((L > 0) ? B.dump(G[I], L - 1) : O);
						} else {
							N.push(G[I]);
						}
						N.push(M);
					}
				}
				if (N.length > 1) {
					N.pop();
				}
				N.push("}");
			}
			return N.join("");
		},
		substitute : function(V, H, O) {
			var L, K, J, R, S, U, Q = [], I, M = "dump", P = " ", G = "{", T = "}", N;
			for (;;) {
				L = V.lastIndexOf(G);
				if (L < 0) {
					break;
				}
				K = V.indexOf(T, L);
				if (L + 1 >= K) {
					break;
				}
				I = V.substring(L + 1, K);
				R = I;
				U = null;
				J = R.indexOf(P);
				if (J > -1) {
					U = R.substring(J + 1);
					R = R.substring(0, J);
				}
				S = H[R];
				if (O) {
					S = O(R, S, U);
				}
				if (B.isObject(S)) {
					if (B.isArray(S)) {
						S = B.dump(S, parseInt(U, 10));
					} else {
						U = U || "";
						N = U.indexOf(M);
						if (N > -1) {
							U = U.substring(4);
						}
						if (S.toString === A.toString || N > -1) {
							S = B.dump(S, parseInt(U, 10));
						} else {
							S = S.toString();
						}
					}
				} else {
					if (!B.isString(S) && !B.isNumber(S)) {
						S = "~-" + Q.length + "-~";
						Q[Q.length] = I;
					}
				}
				V = V.substring(0, L) + S + V.substring(K + 1);
			}
			for (L = Q.length - 1; L >= 0; L = L - 1) {
				V = V.replace(new RegExp("~-" + L + "-~"), "{" + Q[L] + "}",
						"g");
			}
			return V;
		},
		trim : function(G) {
			try {
				return G.replace(/^\s+|\s+$/g, "");
			} catch (H) {
				return G;
			}
		},
		merge : function() {
			var J = {}, H = arguments, G = H.length, I;
			for (I = 0; I < G; I = I + 1) {
				B.augmentObject(J, H[I], true);
			}
			return J;
		},
		later : function(N, H, O, J, K) {
			N = N || 0;
			H = H || {};
			var I = O, M = J, L, G;
			if (B.isString(O)) {
				I = H[O];
			}
			if (!I) {
				throw new TypeError("method undefined");
			}
			if (!B.isArray(M)) {
				M = [J];
			}
			L = function() {
				I.apply(H, M);
			};
			G = (K) ? setInterval(L, N) : setTimeout(L, N);
			return {
				interval : K,
				cancel : function() {
					if (this.interval) {
						clearInterval(G);
					} else {
						clearTimeout(G);
					}
				}
			};
		},
		isValue : function(G) {
			return (B.isObject(G) || B.isString(G) || B.isNumber(G) || B
					.isBoolean(G));
		}
	};
	B.hasOwnProperty = (A.hasOwnProperty) ? function(G, H) {
		return G && G.hasOwnProperty(H);
	} : function(G, H) {
		return !B.isUndefined(G[H]) && G.constructor.prototype[H] !== G[H];
	};
	D.augmentObject(B, D, true);
	YAHOO.util.Lang = B;
	B.augment = B.augmentProto;
	YAHOO.augment = B.augmentProto;
	YAHOO.extend = B.extend;
})();
YAHOO.register("yahoo", YAHOO, {
			version : "2.7.0",
			build : "1796"
		});
(function() {
	YAHOO.env._id_counter = YAHOO.env._id_counter || 0;
	var E = YAHOO.util, L = YAHOO.lang, m = YAHOO.env.ua, A = YAHOO.lang.trim, d = {}, h = {}, N = /^t(?:able|d|h)$/i, X = /color$/i, K = window.document, W = K.documentElement, e = "ownerDocument", n = "defaultView", v = "documentElement", t = "compatMode", b = "offsetLeft", P = "offsetTop", u = "offsetParent", Z = "parentNode", l = "nodeType", C = "tagName", O = "scrollLeft", i = "scrollTop", Q = "getBoundingClientRect", w = "getComputedStyle", a = "currentStyle", M = "CSS1Compat", c = "BackCompat", g = "class", F = "className", J = "", B = " ", s = "(?:^|\\s)", k = "(?= |$)", U = "g", p = "position", f = "fixed", V = "relative", j = "left", o = "top", r = "medium", q = "borderLeftWidth", R = "borderTopWidth", D = m.opera, I = m.webkit, H = m.gecko, T = m.ie;
	E.Dom = {
		CUSTOM_ATTRIBUTES : (!W.hasAttribute) ? {
			"for" : "htmlFor",
			"class" : F
		} : {
			"htmlFor" : "for",
			"className" : g
		},
		get : function(y) {
			var AA, Y, z, x, G;
			if (y) {
				if (y[l] || y.item) {
					return y;
				}
				if (typeof y === "string") {
					AA = y;
					y = K.getElementById(y);
					if (y && y.id === AA) {
						return y;
					} else {
						if (y && K.all) {
							y = null;
							Y = K.all[AA];
							for (x = 0, G = Y.length; x < G; ++x) {
								if (Y[x].id === AA) {
									return Y[x];
								}
							}
						}
					}
					return y;
				}
				if (y.DOM_EVENTS) {
					y = y.get("element");
				}
				if ("length" in y) {
					z = [];
					for (x = 0, G = y.length; x < G; ++x) {
						z[z.length] = E.Dom.get(y[x]);
					}
					return z;
				}
				return y;
			}
			return null;
		},
		getComputedStyle : function(G, Y) {
			if (window[w]) {
				return G[e][n][w](G, null)[Y];
			} else {
				if (G[a]) {
					return E.Dom.IE_ComputedStyle.get(G, Y);
				}
			}
		},
		getStyle : function(G, Y) {
			return E.Dom.batch(G, E.Dom._getStyle, Y);
		},
		_getStyle : function() {
			if (window[w]) {
				return function(G, y) {
					y = (y === "float") ? y = "cssFloat" : E.Dom._toCamel(y);
					var x = G.style[y], Y;
					if (!x) {
						Y = G[e][n][w](G, null);
						if (Y) {
							x = Y[y];
						}
					}
					return x;
				};
			} else {
				if (W[a]) {
					return function(G, y) {
						var x;
						switch (y) {
							case "opacity" :
								x = 100;
								try {
									x = G.filters["DXImageTransform.Microsoft.Alpha"].opacity;
								} catch (z) {
									try {
										x = G.filters("alpha").opacity;
									} catch (Y) {
									}
								}
								return x / 100;
							case "float" :
								y = "styleFloat";
							default :
								y = E.Dom._toCamel(y);
								x = G[a] ? G[a][y] : null;
								return (G.style[y] || x);
						}
					};
				}
			}
		}(),
		setStyle : function(G, Y, x) {
			E.Dom.batch(G, E.Dom._setStyle, {
						prop : Y,
						val : x
					});
		},
		_setStyle : function() {
			if (T) {
				return function(Y, G) {
					var x = E.Dom._toCamel(G.prop), y = G.val;
					if (Y) {
						switch (x) {
							case "opacity" :
								if (L.isString(Y.style.filter)) {
									Y.style.filter = "alpha(opacity=" + y * 100
											+ ")";
									if (!Y[a] || !Y[a].hasLayout) {
										Y.style.zoom = 1;
									}
								}
								break;
							case "float" :
								x = "styleFloat";
							default :
								Y.style[x] = y;
						}
					} else {
					}
				};
			} else {
				return function(Y, G) {
					var x = E.Dom._toCamel(G.prop), y = G.val;
					if (Y) {
						if (x == "float") {
							x = "cssFloat";
						}
						Y.style[x] = y;
					} else {
					}
				};
			}
		}(),
		getXY : function(G) {
			return E.Dom.batch(G, E.Dom._getXY);
		},
		_canPosition : function(G) {
			return (E.Dom._getStyle(G, "display") !== "none" && E.Dom._inDoc(G));
		},
		_getXY : function() {
			if (K[v][Q]) {
				return function(y) {
					var z, Y, AA, AF, AE, AD, AC, G, x, AB = Math.floor, AG = false;
					if (E.Dom._canPosition(y)) {
						AA = y[Q]();