sin's profile有话好好说BlogLists Tools Help

Blog


    6/29/2006

    分析xloadtree, 用ajax实现的动态目录树

    此文来自:nickey
     
    这两天,为了实现ajax的树形目录,找了很多代码. 最后确定用xloadtree.

    在xloadtree的主页上, 有项目xtree 和xloadtree  一个是固定显示的树,一个是动态加载的树.

    在他的隐藏目录里,有一个xtree2  地址为http://webfx.eae.net/dhtml/xtree2b/  这个是正在测试的版本, 融合了xtree和xloadtree.  更新了代码, 但还是beta版.

    xloadtree 可以在各种浏览器下通用. 兼容性效果非常好.

    主要的三个文件:  xtree2.js, xloadtree2.js, xtree2.css 
    xloadtree2.js 主要是对ajax方式的封装, 实现异步调用. 显示树的时候,调用xtree2.js里面的代码. xtree2.css 控制树的显示式样.  在查看他的文档的时候,主要看xtree2的文档,这里面才有对树怎样进行操作. xloadtree只是个调用包装.

    xtree2.js :
    输出html代码的时候, 调用的是toHtml() 方法. 在这个方法中,其中最主要的是getRowHtml() 来得到每一行的输出.  方法中用 getExpandIconHtml() 来控制展开后图标的代码输出, getIconHtml()控制未展开的图标输出.  getLabelHtml() 控制文本的输出, 如果不想要图标,就把图标的输出代码屏蔽掉就OK.


    关于在页面输出后,看不到源代码的调试方法:

    <br/>
    <INPUT type="button" name="ad" onclick="charge();" value="change">
    <br/>
    <TEXTAREA rows="10" cols="30" id="ttt" name="ttt"></TEXTAREA>
    <br/>
    <INPUT type="button" name="findnode" onclick="findnode();" value="findnode">
    <br/>
    <INPUT type="button" name="openpath" onclick="openpath();" value="openpath">
    <script>
        
    function charge(){
            document.getElementById('ttt').value
    =tree.getSelected().toHtml();
        }
        
        
    function findnode(){
            tree.findChildByText(document.getElementById('ttt').value,
    0);
        }
        
        
    function openpath(){
            tree.openPath(webFXTreeHandler.htmlToText(document.getElementById('ttt').value),true);
            tree.getSelected().expand();
        }
    </script>

    在页面中放置如上代码.  注意,先要获得树的句柄.  比较创建树的时候,用如下代码:

    var tree = new WebFXTree("<%=rootname%>");

    在展开的树点,选择后, 点击change 按钮,可以得到这个节点对应的html代码.  然后对应html可以找到相应的CSS. 这个就不多写了.

    xloadtree在调用子节点树的时候,和服务器之间传输的是xml.
    DTD的定义如下
    <!ELEMENT tree (tree*)>
    <!ATTLIST tree
                text        CDATA    #REQUIRED
                src            CDATA    #IMPLIED
                action        CDATA    #IMPLIED
                icon        CDATA    #IMPLIED
                openIcon    CDATA    #IMPLIED
                target        CDATA    #IMPLIED
    >

    其中 text为显示文本,  src为下级目录目录的地址. action为点击的链接. icon为图标,openIcon为节点打开后的图标. target为目标,和a标签的target用法一样.


    因为xml 文件不能传送< > 这种符号, 可以采用&lt; &gt; 传送.   因为项目里需要在树上显示HTML代码,所以在js里面做了修改.  修改的部分比较多. 还没有测试有没有意外情况. 这个以后再说.


    还有, 在使用过程中,有一个BUG. 是设置tabindex的. 会出错.

    this.setTabIndex(this.tabIndex); 

    将xtree2.js文件中的这一行屏蔽掉就行了. 当然,先要确定你不需要使用tab键定位. :)


    2006- 4- 22 增加:
      在要打开指定目录树的时候 如果树里面包含html代码, 可以用如下语句
    <script>try{top.tree.tree.openPath(webFXTreeHandler.htmlToText("/北京新东方学校/<font color=red><b>留学考试</b></font>/fff"),true);}catch(err){}</script>


    2006-5-19
    前天在同事应用的时候,出现的问题是加载过程中如果树结点为中文,就加载不上. 后来找到问题的原因是在用servlet输出xml 的时候,是用的steam的方式, 后来改成字符输出方式才正确. 如下.

                PrintWriter out=response.getWriter();
                out.write(outputXML.toString());
                out.flush();
                out.close();


    AJAX 框架、类库推荐(1)

     

    http://developer.yahoo.net/yui/


    Yahoo UI库是一套工具和控件,用javascript编写,可以通过DOM,HTML和AJAX等技术建立高度互动的web应用程序。这个UI库使客户端脚本的编写变得方便,增强了开发者对浏览器基础构件元素的操作(时间,页内http请求和DOM)。Yahoo UI库可以仅仅通过几行代码和css文件控制着视觉,互动用户界面元素。所有的Yahoo UI库组件都在BSD协议下对所有用户开放使用。

     

     

     

    http://prototype.conio.net/ 


    非常优秀的javascript和ajax框架。很多应用都基于这个框架。这里是中文文档(本地)[英文原版]。
    1.3.1版本解读 http://3rgb.com/b/?act=viewthread&threadid=407(本地)
    另外一个解读 http://www.hibernate.org.cn/viewtopic.php?p=95454 (本地)
    1.4.0版本最新解读 http://www.sergiopereira.com/articles/prototype.js.html 

     

     

    http://openrico.org/


    开源的胖客户端javascript库。提供完全的ajax支持,拖放操作管理,以及令人惊异的动态效果。

     

     

     http://www.activewidgets.com/


    可滚动的javascript表格组件。在一个表格里显示所有数据,可缩放调整的列和客户端数据排列。

     

     

     

     http://www.nextapp.com/提供两个框架。


    Echo2 Web Framework 是开发web胖客户端应用的平台。通过内置的ajax渲染引擎提供性能、兼容、和增强的用户体验。使用Mozilla开放授权(也可以是LGPL),可以在开源和商业产品使用。EchoStudio 2.0 是基于Eclipse 3.1的开发环境。使用商业授权。

     

     

    http://www.modernmethod.com/sajax/


    Sajax是一个开源的ajax工具。可以在PHP, Perl或Python中使用。它能够帮你完成99%的工作!

     

     

     http://qooxdoo.oss.schlund.de/


    qooxdoo 是一个强大的开源 javascript 工具包。qooxdoo 能帮你完成胖客户端应用的设计-比以往任何时候都简单。

     

     

     http://zk1.sourceforge.net/


    开发网络应用的难度正在增加,为了解决这个问题,ZK被设计用来让web应用同时拥有丰富的用户体验和简单的编码模型。ZK包括基于AJAX的事件驱动引擎,自动完成互动,一套丰富的XUL和XHTML组件来增强可用性,和一套标记语言来简化UI设计。
    通过ZK,你可以用丰富的XUL组件来表现应用程序,通过监听用户事件触发来操作这些组件,就像桌面应用程序一样。用户获得桌面体验,而你的开发也像桌面应用程序一样简单。

     

     

    http://www.amowa.net/buffalo/


    唯一一个国产的ajax框架。目前来看功能非常强悍,最大的特点是可以和java无缝结合。

     

     

    6/20/2006

    刚看到一篇文章,里面夹杂着大量的ENGLISH,感觉不错。什么时候我说话也能这样就OK了。

      公司与客户签订的是一个长期的IT外包大合同,包括软件,硬件,服务等等。从另外一个角度来说,包括开发,维护,BI等等。这么大的合同客户与外包商是如何实施与执行的?如何保证客户的Business一如既往地运行,并且还更好地受益于IT的外包呢?我试着从我的team角度,以及从我所能得到的信息尽量来理解这个大的合同是如何实施与执行的。尽管这只是对这个IT外包大合同管中窥豹,但我希望我的文字也能让更多的人认识一个外包案例,也希望能和此案例有兴趣的做IT外包的方方面面的专家和公司探讨。

      我的team为客户公司各种信息系统提供技术支持,是大IT外包合同下的一个维护项目,负责客户business中的Europe和Asia区。客户的US business不在我team的support scope内,但有时候需要与US的一些team打交道。这里US port就不予仔细考虑。

      由于客户把IT外包给我们,客户方就不用招聘很多的IT人员,大部分的工作依照外包合同都由外包服务提供者来完成(我们公司并不是客户唯一的外包商,某些方面的服务还外包给了其他专业公司,如HelpDesk的工作和服务器维护就外包给了HP——HP的HelpDesk非常不错,而且服务器就是HP服务器^_^)。因此,绝大多数客户方的IT人员都是外包合同的执行参与者与项目管理者。客户对自己的IT部门人员所进行的培训除了必要的business培训,就是leadership的培训。

      客户是一家全球500强企业,Business非常多,客户的IT工作量也非常大。那么客户如何与外包商合作、沟通、实施这么大的外包合同呢?这就有必要提到外包商的customer relationship management team。 这个team和客户的management team密切合作,交换意见,共同研究合同的实施与改进,并致力于与客户关系的建立。因为合同签订的出发点就是双方都能从合同中受益,双方都有责任和义务帮助对方提高Business和收益,要不然,这个长期的合同可能没多久就会被Close。

      customer relationship management team经常会和客户一起评估当前的business以及IT的服务情况,做一些必要的变化。这不,去年Europe刚被merge into Asia的管理team,才一年的时间,由于这一年客户Business的变化,Europe的Business变的重要起来,今年就又要 seperate出去,成立独立的management team,方便更好地support Business。

      客户和BRM(Business relationship manager)一起评估project的大小,需要多少resource,然后我们(外包商)就开始project team的招兵买马,作为客户的Contract员工组成客户的IT部门。所谓Contract员工就是人是我们招的,和客户签聘用合同,我们为客户工作,每个resource每小时多少钱客户付给我们公司,我们来进行人员管理,客户只关注项目上的事。(我们有时候叫自己公司为人贩子公司,从另一方面来公司做的是目前时髦的人力猎头工作,只不过人都先招过来了,推荐合适的供客户挑选。呵呵)

      客户把整个IT部门分成很多team,各team负责不同的工作,彼此间又相互支持。除了与程序直接打交道的各种Business team外,其中IT部门的HelpDesk team、Database workgroup、CTO team、Common application team和Ops team与我们team联系最密切。前面几个team一看就知道他们做什么的,我稍微解释一下Common application team和Ops team做什么。

      Common application team顾名思义就是负责一些Common applications的,比如客户要求所有程序都是SSO(Single Sign On) enable的,这就需要有个系统能实现SSO的需求,保证程序的访问安全,并统一利用公司的员工信息库,保证信息的一致,防止各系统“各自为政”,减少重复工作。客户是使用的Site Minder作为SSO服务器,那么SSO的配置与维护就是一个Common的工作,这就是Common application team要做的事。当然,还有很多这样的Common application。

      Ops team一般负责系统的CCM(Change Control Management)中的deploy。如果系统做了改动,在客户同意执行change后,Ops team就会执行这次change。
     
    除了以上这些team,IT部门还有Data Warehouse team,ERP team等其他team,虽然与他们也打交道,但由于不是经常性的,这里就不专门介绍。

      好了,那么我们到底如何来support Business的呢?

      所有的team如何执行自己的职责在project的小合同中都有说明。其中如何处理客户的issue与需求,哪些事情是哪个team的scope都在 escalation chart中明确定义了的,然后让HelpDesk拿着这些escalation chart来处理。HelpDesk是在做IT系统维护时各个IT support team与Business之间唯一的联系点(SPOC - Single point of Contact)。当然,这是理想状态,也有Business用户直接联系我们的。

      HelpDesk接到用户escalate的issue后,先自己做初步的分析,如果HelpDesk能自己解决,问题就不会再往下传递了;如果是 Business方面的问题,用户把问题发到用户的队列中,并跟踪问题什么时候关闭;如果是我们系统技术方面的问题或Bug,HelpDesk就按照前面定义的escalation chart,把问题dispatch到我们team的队列中;如果是其他team的范围也同样的process。因为所有的程序在交给我们team(或其他team)之前就定义了程序的信息,专门有team来维护所有程序的信息。根据客户对所有Business的评估,把相应的程序分为不同的重要程度,这样所有的问题也都根据对Business的影响程度定义了几种不同的紧急程度,再定义了每种紧急程度的问题需要在规定的时间内解决。用户定义了一些 metrics用来度量我们服务的质量,并规定了可需要达到的值。这些都在project的合同中定义,双方签字同意。这就是传说中的SLA (Service Level Agreement)^_^。相应地,我们也在我们公司(外包商)的project Plan中阐述客户的metrics,让所有member都知道并执行,保证meet客户的SLA。

      当问题被dispatch到我们的队列中后,我们就需要在SLA规定的时间内解决问题。每周,专门的Monitor team会从工具中run出各个team这周服务的report,查看是否meet SLA。甚至那些对Business非常重要的系统每天就有report,然后report给management team。这也是客户对我们考核的一个重要指标。

      由于我们中国的team要support客户Europe和Asia的系统,欧洲客户与中国有6个小时时差,这要求我们的team有灵活的上班安排,来 meet客户的Business时间。我们把team分成2部分,一部分support欧洲程序,一部分support亚洲程序。由于我们公司在中国只是一个分公司,印度还有team,印度和中国也有2个小时时差,所以我们安排中国team上 下午班,这样下班不用太晚;印度team同样上 下午班,但他们和欧洲只有4个小时时差,这样就可以更好地meet客户的Business时间。

      当然有US的程序,上班时间又有些不同。在其他时间team的member提供On Call support。所谓On Call Support就是说在这段时间内,如果我们support的系统有问题,用户或HelpDesk可以Call我们的工作Cell Phone,我们就可以马上登陆到内部工作环境,开始移动办公。

     

    8/10/2005

    Portlet API参考实现的秘密

    概要 在Stefan Hepper和Stephan Hesmer的portlet系列文章的第二部分中,作者把着笔点从Portlet API的基础概要介绍转移到了Portlet API的参考实现(RI reference implementation也就是Pluto)的细节描述。作者还提供了一系列portlet的实例来说明怎样扩展Portlet API的标准函数。企业portal提供商使用可插的用户接口组件(portlets)向信息系统提供表示层。不幸的是,以前的提供商都只定义了自己的portlet API,在整个行业之中互不相容。为了标准化整个行业进程,Java团体发布了Java规范要求(JSR)168:Portlet规范。这篇系列文章的第一部分介绍了JSP 168的细节。第二部分重点放在portlet API的参考实现(RI)上,也就是Pluto。此外还提供了一个portlet的实例,读者可以通过这个实例来学习。文章第一节描述了RI的体系结构,包括portlet容器的可拆卸性的概念和怎样在其他项目中重用portlet容器。第二节介绍了RI的安装和使用,以及怎样快速配置portlet。其中文章还包括一个逐步深入的实例。注意:你可以通过文章之后的资源链接下载原代码 Pluto的体系结构 让我们先来看一下Pluto的体系结构和一些基本的概念。我们先简要的说明portal的参考实现和portlet容器在整个portal体系结构中的位置。接下来我们在细节方面研究Pluto的体系结构。最后,我们看一下在portlet容器里很有趣的:portlet 展开。 关于portal Pluto一般用来演示Portlet API如何工作以及向开发者提供一个测试portlets的实例平台。然而,如果没有驱动来运行和测试portlet容器有点麻烦。Pluto的简单portal组件只是架构于portlet容器,它只满足了JSR 168的基本要求。(相比之下,Apache的开源项目Jetspeed就要专业的多。Jetspeed将着重中在了portal本身而非portlet容器之上,并且更多的考虑了其他团体的需求。)图一描述了portal的基本体系结构。Portal的网络应用程序处理客户端请求,从用户的当前页面得到portlets,之后调用portlet容器以获得每个portlet的内容。portal使用Portlet 容器的 Invoker API来访问 portlet容器,从 portal看来,portlet 容器的主要接口是支持基于请求的方法调用 portlets。容器用户要想获得portal的相关信息则必须实现portlet容器的Provider SPI (Service Provider Interface)的callback接口。最终,portlet容器通过portlet API调用所有portlet。 图一:Pluto中的一个简单的portal的结构 Portlet容器 Portlet容器是portlet的运行环境,也是每个portal的组成核心。它需要有关portal本身的信息,且它必须重用自身的公共代码。因此,portlet容器和其他portal组件是完全分离的。这就是说,你可以将独立的portlet容器嵌入任意的portal,只要你满足portlet容器的条件,比方说实现所有的SPI。 Portlet容器的 Invoker API,或者叫入口点,扮演了portlet容器的主调用接口的角色。Portlet容器的Invoker API将portlet容器的生存周期(init,destroy)和基于请求的调用方法(initPage(),performTitle(),portletService()等等)结合了起来。因为portlet容器最后调用portlet的方法名有点类似portlet API的主portlet接口,不同的是是否必须要传递portlet定义符。正是因为这个附加的portlet定义符,portlet容器才能正确的调用portlet。除了要用API访问portlet容器之外,portal还必须扩展portlet容器定义的SPI。因此,RI引入了容器服务:在容器注册过的可拆卸组件提供基础功能并且可扩充。RI包含如下一些容器内的自建服务(前四个必须在运行portlet容器时实现,最后一个是可选的): 信息提供器:给portlet容器提供portal和portal框架的信息。通过这个接口来获得信息和存储portal信息。这些信息包括导航栏里的URL、portlet上下文、portlet模式和窗口状态控制。 工厂管理器:定义了怎么怎样通过工厂方法来获得一个具体实现。(一个标准的portal应该已经存在一个实现。) 日志服务:定义了一个日志工具(一个标准的portal应该已经存在一个实现)。 配置服务:定义了怎么样获得配置参数(一个标准的portal应该已经存在一个实现)。 属性管理器(可选):属性管理器接口的实现允许处理JSR168规范中定义的属性。严格的说,portlet对象模型也是SPI中的一部分,只是它在SPI中占有一个特殊的地位。Portlet对象模型处理所有的potlet对象,他由一个交织在一起的接口集合组成。因此,不能把他和容器服务分开来考虑。 图二:portlet容器结构 Portlet的部署 portlet 容器 架构在servlet容器之上并且增强了它的功能。为了实现它,portlet 容器将原始servlet 加入每一个portlet应用程序的war文件中,这一点我们在图三3中有所描述。部署portlet组件时,先取得原始的war文件,然后向其中加入一个新的或者修改原有的web.xml,并且加入一个servlet作为一个调用点来包装每个portlet。之后, portlet的部署器(?这个原文是Then the portlet deployment passes the modified war file to the application server deployment)会传递一个修改过的war文件到应用服务器,将其部署到应用服务器系统。在portlet的调用过程中,portlet容器调用添加进去的servlet,作为部署portlet的war文件的入口点。 图三:RI中portlet的部署 Pluto和WSRP标准 正像第一部分所描述的那样,JSR 168与远程portlet网络服务(the Web Services for Remote Portlets (WSRP))标准紧密结合。几乎同时形成的这两种标准发布了开源实现,实现了在各自的规范中描述必要的功能。作为共有的目标,两种标准努力能够在一起更好的合作。现在,portlet容器可以很好的运行WSRP portlet。 Pluto可以在一个portal中运行多个portlet容器。从而Pluto的portlet容器可以被初始化多次。更重要的是,可以用不同的方式来初始化它。每一个portlet容器可以使用SPI的不同实现。 RI的安装 你会发现Pluto的安装过程非常简单。执行install命令,build目录/build下的install.bat或者install.sh。接下来安装程序会提示你指定Tomcat的安装目录。(注意:在MS windows下文件分隔符不是反斜杠。)在这之后,安装进程会创建RI和所有portlet,安装portlet到指定的Tomcat目录。安装完成后请查看文档以确定完成了所有必要的手工设置工作。现在可以启动Tomcat,通过http://localhost:8080/pluto/portal来访问RI了。就是这么简单! 怎样部署portlet 在Pluto中部署portlet和它的安装一样的简单。只要记住你必须首先安装了Pluto,它正确的设置了prepareRun.properties。这是部署过程所必须的。在命令提示符下转到build目录,输入命令deployPortlet.bat , 用portlet war文件做参数,比如: deployPortlet.bat C:\pluto\portlets\bookmark_04\driver\bookmark_04.war Portlet实例 我们来看一个portlet的例子,Bookmark。它充分利用了Portlet API并且阐明了我们学到的概念。我们以一个简单的例子开始,我们在每一节一步步扩展这个Bookmark portlet,最后我们将几乎用到所有的portlet API,把它做成一个高级的portlet。 Bookmark portlet:版本一第一个Bookmark portlet用到了Portlet API中如下的一些特性: Portlet API 接口The Portlet API interface  Java服务器页面(jsp)JavaServer Pages (JSP) pages  Portlet API标签库The Portlet API tag libraries  部署描述符Deployment descriptors 第一个Bookmark portlet的两个JSP页面分别显示和编辑模式。每个JSP页面只是简单的显示了portlet的当前portlet模式和windwos状态。为了显示这些信息,我们用到了Portlet API标签库(只是部分程序代码,请下载全部代码,不然很难理解:译者注): public void doView (RenderRequest request, RenderResponse response) throws PortletException, IOException { response.setContentType("text/html"); String jspName = getPortletConfig().getInitParameter("jspView"); PortletRequestDispatcher rd = getPortletContext().getRequestDispatcher(jspName); rd.include(request,response); } 接下来的代码是例子中的一个简单的JSP 页面(即view.jsp:译者注): Hello,
    I am the bookmark portlet.

    Current Portlet Mode:
    Current Window State:

    Bookmark portlet:版本二 第二个Bookmark portlet进一步深入了Portlet API 的概念。除了第一例子所使用到的Portlet API 特性,它增加了: 动作处理Action handling  Portlet 参数 Portlet preferences  验证参数 A preferences validator  在部署描述符中预定义参数 Predefined preferences in the deployment descriptor 在第二个Bookmark例子里,两个新的JSP页面替代了版本一中的。首先,edit.jsp允许通过portlet动作添加和删除书签。在这个JSP页面中输入的书签将作为portlet参数存放。其次,view.jsp 以超链接显示出作为portlet参数存放的书签。 Bookmark portlet:版本三 新增用到的特性: 地区性部署描述符 Localizable deployment descriptor  资源包ResourceBundles 现在部署描述符和JSP页面从资源包里(ResourceBundles)获得可显示的字符集,他们都可以支持英文和德文了。 Bookmark portlet:版本四 最终的这个portlet例子通过portlet API传递递交参量(render parameters)示范了导航的概念(the navigational state concept )。在版本四里有七个书签,但默认一页只显示四个,如图四所示。通过点击next和back的超链接,用户可以导航到向前或者向后的五个书签。初始点将被初始化为递交参量,使得用户可以使用浏览器的刷新、后退和前进按钮。 Bookmark portlet版本四的界面 Portlet复习 象你所看到的那样,portlet规范的参考实现包括两个部分:portal和portlet容器。Portal作为一个简单的运行portlet容器的测试驱动。Portlet容器作为一个能迅速使用到其他portal(比如jetspeed)里的普通组件。 这个portlet实例用到了许多portlet API里的很重要的概念。你可以用所有portlet API和servlet API的特性来扩展这个实例。比方说你可以用一个servlet在新窗口中输出其他有用的信息,如一个打印预览。还可以通过Http会话与portlet进行交互。实际上,因为portlet是一个强大的技术,能用他实现的功能是无穷无尽的。

    使用Eclipse plus Pluto开发你的第一个与JSR168兼容的Portlet

    使用Eclipse plus Pluto开发你的第一个与JSR168兼容的Portlet
    By Terry.li
    SpiritSeekerS@sqatester.com[/pre]

    本文将介绍开发基于Portlet Specification v1.0 (JSR168) 的Portlet应用.  
    我们使用Pluto作为Portlet Container和Portal. 同时可以将开发的Portlet应用迁移到任何支持JSR168的Portlet Container上.

    ·    为什么要发布Portlet Specification? 什么是JSR168? 
    由于越来越多的公司开发了各自的Portal组件和基于其的Portal产品(如Bea, IBM, Oracle, Sun, Sybase, Novell, SAP, Jetspeed, Vignette 等.这种互不兼容的接口实现不断带给程序提供商各种问题和麻烦, 为了解决这种问题, JCP发布了JSP168 (Java Specification Request), Portlet Specification, 用以提供不同Portal和Portlets之间的互用性

    ·    什么是Portal?
    Portal是基于WEB的应用程序, 它将不同资源进行整合并展现给用户
    通常其有如下三个特点:

    a.    Personalization (个性化) 
    b.    Single sign on (单点登陆)
    c.    Content aggregation (内容聚合) 

    其中Content aggregation指的是将不同来源的信息整合到一个页面中用来让用户更方便的进行使用. 
    比如, 如果某客户需要进行一次商业采购,以往需要访问不同的供应商的主页得到相关信息, 但如果使用Portal将所有不同的货物供应商的商品catalog页面都整合到一个Catalog Portal页面中, 那么所有的商品信息都可以更快的进行浏览,筛选和定货, 加快了客户的商业运作效率.

    ·    什么是Portlet?
    Portlet是一种基于WEB组件的JAVA技术, 由Portlet Container进行管理. 处理请求并动态返回页面, 可以做为Portal 的可拔插的用户界面组件.
     
    ·    什么是Portlet Container? Portlet Container用来管理Portlet的生命周期并且提供其运行所需要的必要环境. 并且为Portlet Preferences提供持久性(Persistent)存取服务.但是其不支持内容的Aggregation. Aggregation由Portal组件提供.

    注: Portlet Preferences是Portlet的一个新特性,提供类似数据库的功能.但是不是用来取代数据库. 只是用来存取简单的Portlet参数配置.
            
    ·    什么是WSRP?
    WSRP 是 OASIS Web Service for Remote Portlets的缩写. WSRP主要用来简化Portal对于各种资源或者程序的整合的复杂性, 可以避免编程带来的整合的复杂性. 并且Portal的管理员可以从大量的服务中选择需要的用以整和到Portal中. 


    ·    Portlet and Servlet

    摘自(Portlet Specs v1.0)

    相同点:
    • Portlets are Java technology based web components
    • Portlets are managed by a specialized container
    • Portlets generate dynamic content
    • Portlets lifecycle is managed by a container
    • Portlets interact with web client via a request/response paradigm

    不同点:
    • Portlets only generate markup fragments, not complete documents. The Portal aggregates portlet markup fragments into a complete portal page
    • Portlets are not directly bound to a URL 
    • Web clients interact with portlets through a portal system
    • Portlets have a more refined request handling, action requests and render requests
    • Portlets have predefined portlet modes and window states that indicate the function the portlet is performing and the amount of real state in the portal page
    • Portlets can exist many times in a portal page

    Portlet特有:
    • Portlets have means for accessing and storing persistent configuration and customization data
    • Portlets have access to user profile information
    • Portlets have URL rewriting functions for creating hyperlinks within their content, which allow portal server agnostic creation of links and actions in page fragments
    • Portlets can store transient data in the portlet session in two different scopes: the application-wide scope and the portlet private scope

     Servlet特有:
    • Setting the character set encoding of the response
    • Setting HTTP headers on the response
    • The URL of the client request to the portal
      
    ·    什么是Pluto ?
            Pluto 是 Apache开发下的一个Open Source项目, 是基于Portlet Specs的一个 Portlet Container 的实现. 它也提供了Sample Portal实现. 但是功能相对简单:

          例如,
          1) 没有复杂的Layout实现.
          2) 不是multi-user enabled, 比如, 不同User之间的Portlet Preferences互相是可以share的.   

         请记住Pluto只是一个Portlet Container的实现, 不是一个Portal的实现. 如果你需要功能更为强大的Portal,可以使用JetSpeed , 它同样也是Apache的一个Open Source Project. 请参考文章末尾的资源部分.

    ·    概念 
          如图: Figure 1.1
    a.    Decorations and controls (修饰部分及 控制部分)
    b.    Portlet fragment (Portlet 片段)
    c.    Portlet window (Portlet 窗口)
    d.    Portlet page (Portlet 页面)

     
     
    Figure 1.1

    ·    开发工具( Eclipse2.1, Pluto-plugin, Jakarta-tomcat-4.1.29)
    下载地址:
    Eclipse
    http://www.eclipse.org/downloads/index.php

    Pluto-plugin
    http://prdownloads.sourceforge.net/plutoeclipse/org.eclipsefan.pluto.ui_1.0.0.zip?download

    Tomcat4.1
    http://mirrors.midco.net/pub/apache.org/jakarta/tomcat-4/v4.1.29/bin/jakarta-tomcat-4.1.29.zip

    JDK1.4
    http://Java.sun.com

    ·    配置开发环境
           Step1 :  解压eclipse-SDK-2.1.1-win32.zip
         Step2 :  解压org.eclipsefan.pluto.ui_1.0.0.zip , 并将其拷入eclipse\plugins目录
    下, 如下: eclipse\plugins\org.eclipsefan.pluto.ui_1.0.0\
    \Icon
    \Lib
    \Source
    \Webapp

            Step3:  解压tomat4.1到一目录. 并且配置好Tomcat服务器.    
            Congratulations, It’s done!

    a. 使用Portlet Wizard 来创建Portal
    1) 选择File > New > Project… > Portal > Pluto Portal application, 单          击Next

    2) 指定Project名称 , 这里使用pluto, 然后单击Next.




    3) 指定Tomcat路径, 单击Finish.  之后eclipse会将Pluto安装到tomcat上,并且自动配置好环境.



    b. 创建Portlets Application


    1) 创建Pluto portlet application 

            2) 输入project名称,这里用portlets


          c. 最后生成的工程如图 

    d. 通过portlet.xml 生成/更新 web.xml 


    e. 部署 Portlets

    1) 编译 portlets 工程.
    2) 在 [your tomcat installation directory]/webapps下, 新建目录portlets
    3) 将portlets工程下的web-root目录下所有文件, 拷入以上新建的portlets目录中.
    Tip: 如果在每次修改后你厌倦了Ctrl+C 和 Ctrl+V, 你可以在Portlets工程目录下自己编一个Bat文件, 然后你可以在eclipse里的Package Explorer中看到它, 双击就可以了. 其中deploy.bat文件就是实现其功能的.

    4) Start tomcat.
    5) 在IE 中输入: http://localhost: 8080/pluto/portal, 你应该能看到portal 页面.


    ·    Sample Portlet
    在Portlets工程文件创建过程中, 自动生成许多文件, 包括一个简单的Portlet, 我们来观察一下生成的sample portlet.


    1) 与Servlet非常类似, Portlet扩展自GenericPortlet
        
        import javax.portlet.*;

    public class SimplePortlet extends GenericPortlet

    2) 其三个方法对应了Portlet 标题栏中的三个联接 (View, Edit, Help)

    public void doView(…)

    public void doEdit(…)

    public void doHelp(…)

     
    Figure 1.9

    3) 三个方法分别调用了三个JSP文件, 用以生成Portlet fragment, 同样也可以调用Servlet产生Portlet fragment. 与Servlet应用类似,也可以使用
    getInitParameter(String s)方法,得到配置文件中Portlet的初始值. 只不过Servlet使用web.xml,而Portlet 使用portlet.xml文件.


    portlet.xml

               <init-param>
                <name>jspView</name>
                <value>/jsp/view.jsp</value>
    </init-param>


    SimplePortlet.java

    String jspName = 
    getPortletConfig().getInitParameter("jspView");


    ·    Portlet Tag library

    1)defineObjects Tag

    如果打开其中的一个JSP文件,你会发现,和Servlet一样,JSP中可以使用Portlet的一些variables.例如:

    renderResponse
    renderRequest
    portletConfig

    但是必须声名:
    <portlet:defineObjects/>
    其中不可以定义任何属性或者包含任何内容.

    2) actionURL Tag
    属性    值类型    对应值
    windowState    String    minimized, normal, maximized
    portletMode    String    view, edit, help
    var    String    任何值
    secure    String    true,false

    <portlet:actionURL windowState=”normal” portletMode=”edit”>
    <portlet:param name=”action” value=”login”/>
    </portlet:actionURL>

    创建一个action URL , 当访问它时将使portlet window变为normal 装态, 模式变为 edit.

    3) renderURL Tag
    属性    值类型    对应值
    windowState    String    minimized,normal,maximized
    portletMode    String    view, edit, help
    var    String    任何值
    secure    String    true,false

    <portlet:renderURL portletMode=”view” windowState=”maximized”>
    <portlet:param name=”number” value=”1”/> 
    <portlet:param name=”page” value=”2”/>
    </portlet:renderURL>

    创建一个render URL , 当访问它时将使portlet window变为maximized装态, 模式变为 view.
     
    4) namespace Tag
    为目前的Portlet产生一个唯一的Value. 防止和其他的Portlet 或者Portal页面上的value产生冲突.
    <A HREF=”javascript: <portlet:namespace/>doFoo()”>Foo</A>

    5) param Tag
    属性    值类型
    name    String
    <portlet:param name=”myParam” value=”someValue”/>
    注: param Tag不可以有body content.


    总结
    Pluto目前是v1.0, 有部分Portlet规范没有完全实现, 例如, renderResponse.setTitle (), 同时你可以从Apache的CVS下载最新的Pluto代码, 希望这编文章可以帮大家搭建一个简单的Portlet开发环境 , 熟悉Portlet相关的知识.


    资源:
    ·    Pluto 
    http://jakarta.apache.org/pluto

    ·    Pluto Mail List
    http://news.gmane.org/gmane.comp.jakarta.pluto.user

    ·    WSRP Spec1.0
    http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=wsrp

    ·    Apache的WSRP实现
    http://ws.apache.org/wsrp4j/

    ·    Apache’s Portal, JetSpeed:
    http://jakarta.apache.org/jetspeed/site/index.html

    ·    JSR 168: 
    http://www.jcp.org/en/jsr/detail?id=168
    ·    "Portlet 规范介绍" By Stefan Hepper 和 Stephan Hesmer 
    o    Part 1: Get your feet wet with the specification's underlying terms and concepts (August 2003) 
    o    Part 2: The Portlet API's reference implementation reveals its secrets (September 2003) 
    8/8/2005

    几种开源Portal的简单介绍分析

    主要包括:Pluto,Liferay,eXo,Jetspeed四种开源Portal的介绍和分析,有助于对具体项目选择合适的开发平台。

    (1)Pluto

    2003年10月JSR168规范1.0正式公布后,Jakarta Apache就开始实施Pluto计划(冥王星计划),最终开发出该规范的一个参考实现(Reference Implementation),即Pluto。Pluto的1.0.1-rc2版与2004年12月发布。
    Pluto实现基于 JSR168的一个 Portlet Container,相当于为开发者提供了一个运行portlets的工作平台。Pluto本身也提供了一个简单的Portal模块,该模块仅仅是为了满足Portlet容器和JSR 168的需要而写的,因而显得非常简单,提供的实用的Portlet也非常少。从某种意义上说Pluto更像是一个Portlet Container,作为一个实用的Portal开发框架尚需要更强大的支持。但新版本的Pluto仍没有推出。对于Pluto的应用开发,Apache更推荐使用Jetspeed项目框架。
    尽管Pluto作为一个完整的Portal应用还非常欠缺。但不少有影响力的Portal项目使用Pluto作为Portlet Container。这些项目包括:Jetspeed 、Cocoon uPortal 、Jahia等。由此可见Pluto的重要性。从开发者和学习者的角度看,Pluto的意义还在于为开发者和学习者提供了一个深入了解Portlet Container的简洁的参考实例。

    (2)Liferay

    Liferay(支持JSR168)代表了完整的J2EE应用 ,最高版本是2005年1月推出的Professional 3.2.0。它的主要优点有:
          (1)使用第三放的开源项目,如Hibernate等。特别是前台界面部分使用了Struts技术;
          (2)支持包括中文在内的多种语言;
          (3)支持较多的先进技术,如Web Services、EJB, JMS, SOAP, XML等;
    Liferay的缺点是它缺乏一个简单清晰可拓展的架构设计,整个架构比较复杂且庞大;Struts1.1本身并不支持JSR168,所以Liferay在实现诸如上下文共享等问题上显得十分笨重且没有从根本上解决这些问题;portlet设计也显得比较凌乱。此外,如果你的门户系统准备应用于商业用途,你需要购买License。基于它进行二次开发比较困难。

    (3)eXo

    eXo(支持JSR168)基于JSF的Portal实现。最新版本是2004年10月发布的1.0RC1版。
    主要优点包括:
    (1) 由AOP(AspectJ)实现的内容管理系统,极大提高了内容管理性能;
    (2) 基于Pico Container的Portlet Container,Pico是一个著名的IoC3轻量级容器。同时也实现了上下文共享,二次开发的流程比较清晰;
    (3) 使用Struts框架技术;
    (4) 提供工作流技术服务(Workflow service)。
    (5) 提供了很多交流工具,通过XML可以为结构化的信息轻易地创建视图;
         由上可见eXo采用了诸多先进技术,但存在不少缺点。主要缺点:由于Portal Server本身的数据是使用xmldb来进行处理,保存到数据库的数据都是乱码而且它所有默认的平台字符集都是ISO-8859_1。缺乏中文的充分支持,对于中文门户的开发并没有优势;由于JSF是重量级的表现层框架,使得exo的二次开发工作量比较大;对于商业Portal应用开发需要购买License。总体开发难度较大。

    (4) JetSpeed


    JetSpeed是Apache组织的开源Portal项目。Jetspeed 目前有两个版本可供选择1.X和2.0版本。Jetspeed1.X出现得比较早,但第一个较为成熟的版本发布是1.4。此后1.X不断推出新版本。但Jetspeed1.X都不支持JSR168。当JSR168在2003年发布后,Apche开始开发Jetspeed2,提供对JSR168 的全面支持。
    下表是Jetspeed重要版本的发布日期:
    Jetspeed的重要版本                                         发布日期
    Jetspeed-1.4b2                                              2002年12月
    Jetspeed-1.5                                                2004年4月
    Jetspeed-2.0-M1                                             2004年12月
    Jetspeed-2.0-M2                                             2005年4月
    Jetspeed1.X基于Turbine框架开发,它提供了丰富的Portal技术功能。主要优点有:XML文件注册Portlet,便于管理;图形化的部署和卸载;对中文的良好支持等。值得注意的是IBM早期的WebSphere Portal Server就是在JetSpeed1.X上进行二次开发的。Jetspeed2.0在1.X基础上进行了较大的改动,功能更加丰富,完全遵循JSR标准。

     

     

    配置方法,不同的版本的配置可能有变化,如不成功,可以自行察看文档。


    Pulto在Tomcat下的安装配置方法:
    (1) 从http://portals.apache.org/pluto/mirrors.cgi下载最新的二进制版本安装文件
    (2) 执行%pluto%/bin/install,将它安装到Tomcat目录下即可
    (3) 在浏览器中输入:http://localhost:8080/pluto/portal,运行Pluto

    Liferay Professional在Tomcat下的安装配置方法:
    (1) 从http://www.liferay.com/downloads/index.jsp下载Liferay Professional安装文件;
    (2) 解压ZIP包到某个目录下
    (3) 若下载Tomcat版本的Liferay安装文件,则不需再装Tomcat,直接启动bin目录下的startup.bat文件。
    (4) 在浏览器中输入http://localhost/运行Liferay Professional。

    eXo Platform在Tomcat下的安装配置方法:
    (1) 从http://www.exoplatform.com/portal/faces/public/exo 下载eXo Platform的安装包。
    (2) 解压ZIP包到某个目录下
    (3) 若下载Tomcat版本的eXo安装文件,则不需再装Tomcat,直接启动bin目录下的startup.bat文件。
    (4)  在浏览器中输入http://localhost/ 运行eXo Platform.

    Jetspeed2在Tomcat下的安装配置方法:
    (1) 从http://www.apache.org/dist/portals/jetspeed-2/ 下载Jetspeed2 M1或M2版本安装包。
    (2) 若是安装包中已经捆绑Tomcat,解压即可。若安装包不含Tomcat,先安装Tomcat,然后将安装包解压到Tomcat目录,并覆盖相关文件即可。
    (3) 安装要注意的问题有:Jetspeed2M1版本由于Bug无法安装Tomcat5.5上(此BUG见http://issues.apache.org/jira/browse/JS2-187);Jetspeed2必须安装在Tomcat5.0.28以上版本上,否则低版本由于JAAS问题无法登陆;
    (4) 启动jetspeed-database目录下的start-database.bat,启动数据库;
    (5) 启动Tomcat;
    (6) 在浏览器中输入http://localhost:8080/jetspeed/portal 运行Jetspeed;


     

    7/18/2005

    mop客户端限量内测

    当前版本: iMop 1.0
    更新信息: 于2005年7月5日更新
    软件大小:
    1.3M

    最低系统要求

    计算机/处理器:Pentium 2 或更高配置
    操作系统:Win 98、Win Me、Win2000、Windows XP 或更高版本
    磁盘空间:有 15MB 可用空间用于安装
    内 存: 20MB RAM


    7/8/2005

    GoogleMaps探秘

    Go to Google Maps Home
    1、Ajax 出现的背景
    最近一段时间,关心 Web 开发的朋友在网络上会经常听到 Ajax 这个 buzzword。使用 Google 搜索,会搜索到无数自称是 Ajax 的例子。这些例子的作者得意洋洋地展示自己学会了 xmlhttp 之后写出的 hello world,自以为已经精通了 Ajax。
    那么 Ajax 究竟是什么?它是不是就是等同于 xmlhttp 呢?我们先来从源头说起。
    Ajax 这个词的发明者是 Jesse James Garrett,他在这篇文章中发明了 AJAX 这个词:
    Ajax: A New Approach to Web Applications
    点击查看
    这篇文章发表在 2005 年 2 月 18 号。可能今天的很多朋友还没有读过这篇文章,我来介绍一下其中的内容。
    Ajax 是 Asynchronous JavaScript + XML 的缩写,其中用到的主要的技术包括:
    基于 XHTML/CSS 标准的展现
    使用 DOM 的动态显示和交互
    使用 XML 和 XSLT 的数据交换和处理
    使用 XMLHttpRequest 的异步数据获取
    JavaScript 把所有的一切捆绑在一起
    Ajax 的交互模型和传统基于 HTML Form 的交互模型有着非常大的区别。
    作者通过以下这个图对于传统的交互模式和 Ajax 交互模式做了一个比较。
    我们看到,在传统的交互模式下,客户端并无表示逻辑的执行,由服务器端执行所有的表示逻辑,然后把 HTML/CSS 传给客户端,客户端仅仅做简单的展现。
    传统的交互模式最大的问题就是任何哪怕是微小的交互行为都需要到服务器端走一趟,这样所带来大量的延迟令用户感觉很不舒服,也降低了用户的工作效率。可能现在 Web 大量用户已经习惯了等待一个页面刷新的无聊时间,可是这不能成为我们开发者推托有责任改善交互行为的借口。假设在用户使用 B/S 应用之前还使用过 C/S 应用,在 C/S 应用中难道会有这样可笑的事情发生吗?
    解决之道是什么?我们再看一下 Ajax 的交互模式。
    在客户端多出来了一个 Ajax engine,而且服务器传给客户端的已经不再是 HTML/CSS,而是纯的 XML 数据,客户端通过 XMLHttp 向服务器端发送请求。所有的表示逻辑在客户端通过 JS 脚本来执行,然后通过修改 DOM 来完成展现。
    由于有了位于客户端这个中间层,可以把原先必须在服务器端完成的很多交互工作放在了客户端完成,而客户端的 Ajax engine 的响应是即时的,因此用户的交互体验得到了极大的改善。我们可以通过下面这张图来比较两种交互模式下的时间分配。我们可以看出,传统的基于 HTML Form 的交互模式下用户的大量时间都浪费在了无聊的等待之上。这种新的交互模式的最大优点就是改善了用户的体验。此外还有很多其它方面的优点,例如不需要刷新页面、减少了服务器的处理负担、减少了交换的数据量等等。
    那么 Ajax 是不是一种神奇的新技术呢?完全不是,从 Ajax 所用到的上述核心技术来看,任何一种技术都是已经成熟了多年的技术,据我所知,最晚在 2001 年,所有这些技术都已经成熟并且进入了实用阶段。因此,我仅仅是把 Ajax 看作是传统的基于 XHTML/CSS/JS 开发的复兴。实际上我以前所在的公司已经完全采用这种架构做 Web 开发有 3 年之久。我们已经完全摒弃了 HTML Form。所以当我看到这篇文章的时候想到,如果我们早发明一个词,那这种新的 Web 开发模式可能就不叫 Ajax 了。不过这也证明了我们在几年前做出的决定还是有先见之明的。
    虽然在这个词出现很多年以前就已经有大量的开发人员采用这种开发模式,不过现在既然出现了这个词,起到了规范术语的作用。去年在 JavaEye 活动上做 XMLHttp 开发的讲座时我感觉自己是相当另类的一个人,因为那时候还没有多少 J2EE 开发人员会对 JS 感兴趣。现在 Ajax 越来越进入 J2EE Web 开发的主流领域,而且会长期保持它的生命力。
    5 月底 JavaEye 的活动上听熊节的演讲,什么也没记住,只记住一个词:古已有之。其实 Ajax 也是古已有之。Ajax 也不过就是新瓶装旧酒,但是它把以前由美工单独使用的这些技术(至今仍然有大量的 J2EE 开发人员认为写 JS 完全是美工的任务)完美地结合在了一起,产生出了巨大的价值。Ajax 是一种新的交互模式或者开发模式,而不是一个现成的框架。基于 Ajax 思想开发的应用都可以称作是 Ajax 应用。Ajax 对于这些传统的 J2EE 开发人员不怎么关心的客户端技术进行重新包装,对于企业应用产生了巨大的价值。我们知道表示层开发始终是具有重大意义的,占用了开发项目几乎一半的工作量。而用户很多时候就是看你的界面和交互,所以这部分是不能马虎的。架构你可以稍微马虎一点,基于 Spring/Hibernate 差也不会差很多。但是界面和交互是用户立刻能感受到的东西,是不能马虎的。
    目前 Google 为 Ajax 技术做出了巨大的投入,Google 这两年推出的一些应用如 Gmail、Google Groups、Google Suggest、Google Maps 等等都采用了 Ajax 的技术。其中最为醒目和最复杂的应用就是 Google Map。下面我将 Google Maps 作为 Ajax 应用的一个典型为大家做一些介绍。
     
    2、Ajax 之实例应用——Google Maps
    在做这个演讲之前,我参考网上的一些资料对 Google Maps 做了一些 hack 的工作。目前已经可以做到除了地图图片要从 Google 请求外,其余所有的数据都在本地运行。
    但是因为 Google Maps 前台的代码量比较大,并且是经过混淆的。所以整理这些代码花费了比较多的功夫。目前这个工作尚未全部完成。因此以下讲述的内容仅仅是根据我目前的成果。
    Google Maps 使用了那些技术?
    Google Maps 所使用的技术,基本上就是上面 Ajax 所提到的这些技术。
    1. standards-based presentation using XHTML and CSS;
    2. dynamic display and interaction using the Document Object Model;
    3. data interchange and manipulation using XML and XSLT;
    4. asynchronous data retrieval using XMLHttpRequest;
    5. and JavaScript binding everything together.
    Google Maps 的主页在
    http://maps.google.com
    Google Maps 每一张地图的请求方式是:
    http://maps.google.com/maps?ll=49.29,-123.12&spn=0.017998,0.027586&z=3&hl=en
    我们看到每一张地图有 4 个参数,按照值来算实际上是 6 个。
    ll:地图中心的精度和纬度
    spn:地图的范围(跨度,分横向和纵向)
    z:地图的缩放级别
    hl:地图采用的语言
    Google Maps 每一张大的地图实际上都是很多张小的 gif 贴图。普通地图每一张小图片的大小为 128x128 像素,卫星地图每一张小图片的大小为 256x256 像素。每一张小图片都有自己独立的 URL,其格式为:
    http://mt.google.com/mt?v=.1&x=50&y=20&zoom=4
    其中包括几个参数:
    v:当前版本号。.1 被推测为 0.1 版
    x:图片的 x 索引
    y:图片的 y 索引
    zoom:图片的缩放级别。
    每张地图需要哪些贴图使用固定的算法算出。然后自动像服务器请求这些贴图。当地图发生任何变化(例如:拖拽、移动、缩放等等)时,都会自动像服务器请求需要的图片。例如刚才我们看到了当地图发生移动时,图片会自动补全新的显示区域。所有的这些计算和处理,全部都是使用 JS 在浏览器端完成的。

    因为浏览器有图片的缓存功能,因此如果你经常查看某个相同地区的地图的话,久而久之,浏览器会缓存大量的图片,这样你使用 Google Maps 的性能就会越来越好了。

    Google Maps 还有搜索的功能,而且非常强大。我这里录制了一个简单的演示。
    Google Maps 还可以完成复杂的搜索,例如搜索 lax 这个地方的旅馆,结果为:
    点击地图右边旅馆条目,可以在地图上面出现旅馆的说明。这个说明是使用 XSLT 技术产生的。
    还可以搜索两点之间的路径。例如搜索 jfk to 350 5th ave, new york,得到的结果就是带有两点之间路径路径的地图。这里的路径在 IE 上是使用 VML 画的,而在其它浏览器上使用后台自动生成的透明 png 图片舆地图图片叠加产生。因为 IE 完全是在客户端完成,所以 IE 的性能会好一些。
    另外 Google Maps 的地图既可以加载在一个 DIV 中,也可以加载在一个 IFRame 中。熟悉 XMLHttp 的朋友知道,出现了 XMLHttp 后,需要通过隐藏的 IFrame 从服务器获得数据的场合已经非常少了。但是 IFrame 还有一些其它的用途。IFrame 比 XMLHttp 有优势的一个地方是 IFrame 可以和浏览器的历史记录结合起来,而 XMLHttp 做不到这一点。就是说如果数据来自于一个 IFrame,以后可以使用浏览器的back和forward功能这样可以带给用户更好的交互体验。比如刚才我们看到的用户搜索完 Boston,可能还想再看看 New York 的地图,他可以简单地使用浏览器的 back 按钮,而不需要重新输入 New York 再查一遍。他如果还想看 Boston 的地图,那么使用 forward 按钮就可以了。
    Google Maps 返回给前台的数据为 xml 格式,由前台的 JS 脚本解析后作相应的处理。
    这里有这样一个 xml 文件的例子。在这里面实际上只有 center 和 span 是必须的。客户端的脚本通过中心点位置和地图跨度自动像服务器请求相应的图片。
    <?xml version="1.0"?>
    <page>
    <center lat="49.29" lng="-123.12"/>
    <span lat="0.017998" lng="0.027586"/>
    </page>
    以上就是 Google Maps 主要的功能。我这里主要从客户端的角度来介绍,服务器端所提供的功能并未涉及。服务器端主要的作用是存储数据。按照目前 15 个缩放级别,每个级别都要保留大量的贴图文件,有人计算过,即使乡村、荒地的很多地方不需要地图,可以使用透明图片代替,北美地区总共的数据量大约在几十太的级别。Google 有世界上最强大的廉价 PC 服务器构成的 Linux 集群。存储这些数据并且提供良好的性能是完全没有问题的。

    其它的公司和个人是否可以使用 Google Maps 的技术?
    其它的公司和个人完全可以使用 Google Maps 的服务。目前 Google 尚未对这一服务进行收费。因此你可以从 Google 的服务器上请求图片,通过在你自己的服务器显示给客户。刚才我做的演示完全是在本地运行的,所有的 xml 数据和 js 脚本全部都在本地,仅仅是贴图来自于 Google 的服务器。Google 的 JS 脚本是有版权的,我这里仅仅是出于研究的目的,把脚本下载到本地,做了一些修改以便于研究。对于商业应用,可能不能这样做,可以在页面中直接使用 Google 服务器上的脚本。因此使用 Google Maps 的方式就是在你自己的服务器上面生成定制的地图数据,通过 XMLHttp 请求到客户端,使用 Google Maps 的脚本解析,然后从 Google 请求贴图数据。除了你自己的地图数据外,还可以从其它服务器上获得地图数据,例如从 Google 的服务器上获得数据。不过因为 XMLHttp 只能从本域获得数据,为了获得来自其它域的数据,需要在服务器端用 Servlet 实现一个代理,这个 Servlet 从其它域得到地图数据后返回给客户端。这样一个 Servlet 写起来是非常简单的,只需要几行代码。同时在客户端需要对 XMLHttp 对象做一个包装,使得新的对象可以请求来自不同域的数据。
    Google Maps 可以定制,Google 为定制 Maps 服务提供了很多的便利。定制的方法就是提供自己定义的地图数据文件,在刚才这个文件中,overlay 中就是开发者定制的内容。其中定制了两个点,当用户点这两个点的时候,会出现这两个地点的说明文字。说明文字的产生使用了 XSLT 技术。例如我制作一个全部 New York 市的中餐馆地图,我可以把每个餐馆的电话号码、联系人姓名等等信息加在上面。Google Maps 把这些接口提供出来允许开发者与他们与他们合作,开发出来面向不同领域和群体的地图服务来。通过出让一部分利润来扩大其影响力,是精明的商业行为。
    Google Maps 的突出优点:
    1、功能完善,具有常规地图服务所有的功能。
    2、性能优良。用户几乎从来不需要长期的等待。
    3、支持多种浏览器,目前 Google Maps 支持的浏览器包括以下这些:
    IE 5.5+ (Windows)
    Firefox 0.8+ (Windows, Mac, Linux)
    Safari 1.2.4+ (Mac)
    Netscape 7.1+ (Windows, Mac, Linux)
    Mozilla 1.4+ (Windows, Mac, Linux)
    Opera 7.5+ (Windows, Mac, Linux)
    已经囊括了目前所有主流的浏览器。
    Google Maps 通过自己开发的一套组件库封装了 IE 与其它浏览器的差别。主要的差别包括 XMLHttp、XMLDom 对象的创建语法,还有事件处理机制的不同。并且 Google Maps 尽量采用符合 Web 标准的技术,使得针对不同浏览器开发分支代码的情况变得最小。
    4、通过大量采用客户端的技术,放弃了传统的基于 HTML Form 的交互模式,因此使得用户获得了更好的交互体验。
    5、完全的组件化和面向对象开发。
    Google Maps 组件和面向对象设计的水平是非常高的。所有主要的的控件全部封装为对象,使用面向对象的方式进行操作。

    因为 XMLHttp 只能得到本域的数据文件,为了从其它地方获得数据文件,可以在服务器端实现一个代理。简单而言就是实现一个 Servlet,通过这个 Servlet 获得其它域的数据文件,然后返回给客户端。
    为此需要为 XMLHttp 对象做一个包装。我们来看看具体的包装技术。
    我对于 Google Maps 客户端脚本的整理工作目前还没有完全结束,因此今天具体的代码讲得比较少。主要是先从大的方面介绍了 Google Maps 的功能和采用的核心技术。下次有机会了我在给大家详细剖析一下 Google Maps 前台的代码。当然这需要大家对于 XHTML/CSS/JavaScript/XSLT/XMLHttp 等技术都有相当的了解才行。
    今天我首先给大家介绍了 Ajax 的由来和内涵。然后通过 Google Maps 作为实例让大家充分感受到了 Ajax 技术的优势。目前 Ajax 已经有越来越流行,并且进入了主流 J2EE 开发的领域。不光是小公司对 Ajax 感兴趣,大公司也越来越对 Ajax 产生了兴趣。前天我的一位在 Oracle 做开发的朋友说他们公司现在对 Ajax 也非常感兴趣,正在组织相关知识的学习。并且会考虑建造自己的 Ajax 组件库和开发工具。他们公司以前使用 Applet 做过很多应用,但是 Applet 显然已经是很落伍的技术,将来肯定会被 Java Web Start 所代替。而 JWS 也存在着一些问题。因此我认为 Ajax 对于他们应该来说是一个更好的选择。
    虽然我做过很多 JavaScript 开发,非常喜欢这门语言。但是无庸讳言,JavaScript 目前还存在着一些问题,妨碍了大规模的组件化开发。主要的问题目前我认为有以下两个:
    1、JavaScirpt 没有 Java 那样的 package 或者 C# 的 namespace 的概念,因此类和函数非常容易重名。这个问题不能仅仅通过制定命名规范来解决。
    2、JavaScirpt 中的继承不是真正的继承,仅仅是所有的子类对象共享一个父类对象,这个父类对象相当于一个 Singleton,因此必须是无状态的,不能保留自己的属性。这个问题使得 JavaScript 难以支持多层继承,无法构造大的继承体系。
    目前 ECMAScript4 正在开发,其中一个主要的目标就是为 JavaScript 提供真正的面向对象编程能力。这个标准推出,到主要的浏览器支持这个标准还有相当长的时间。在目前阶段解决这两个问题有一些临时的解决方案,由于时间原因我就不仔细讲了。好在目前客户端组件开发的规模比起服务器端还是要小得多,所以 JavaScript 目前的能力在大部分场合下都是够用了。
    由于目前各种主流浏览器都可以很好地支持 Web 标准。这里我说的 Web 标准指的是 XHTML/CSS/ECMAScript/DOM/XSLT 这些技术。XMLHttp 将来的一天也会挤进标准的行列,目前对 XMLHttp 标准化的工作正在进行中。现在基于 Web 标准做开发已经成为 Web 表示层开发的主流思想,彻底摒弃使用私有技术,只为某种浏览器做开发的时机已经成熟。我并不是一个唯标准论者,我从来都是从注重实用的开发者的角度来考虑问题的。我也在以前也曾经只为 IE 一种浏览器来做开发。但是今天,在我发现了基于标准开发并不会带来额外的成本(已经几乎不再需要写针对不同浏览器的代码分之),并且会带来向后兼容的巨大价值的时候,我毫不犹豫地拥抱了 Web 标准。我也推荐大家以后尽量采用符合标准的方式来做开发,其中遇到的具体困难可以直接和我联系。我一定会贡献出自己的经验的。关于什么是符合标准的开发方式,最佳实践,在以后的活动中我们可以继续来探讨。
    今天我要讲的内容就是这些,对于 Google Maps 感兴趣的朋友可以和我直接取得联系。我整理过的 Google Maps 的源代码在适当的时候也会公布出来。谢谢大家今天的参与!
     

    此文来自bea中国 计数器
    7/4/2005

    解决MSN7.X点击中文昵称好友字体变形

    MSN 7.X版本后,很多朋友抱怨在主窗口鼠标单击触发有中文名称好友时,好友名字的字体会变形(大概类似8号宋体),很难看。(如下图一):

    字体变形图1

    像6.X版本这样多好(下图是程序hack后的效果):

    字体正常图2

    恰好昨天我安装了最新版本7.0.0816,MESS的PATCH还没出炉,a-patch也存在几个问题,自己便手工hack起来,突然想起上面这个事情,试验了几次,找到了原因。现在给出解决方法:

    首先,关闭并退出MSN,用Resource Hacker打开msnmsgr.exe,进入UIFILE->936->1033,搜索并找到emoticontext [id=atom(buddyname)],大概这一段是这个样子:



    emoticontext
    [id=atom(buddyname)
    ] { layoutpos: left; fontweight: normal; contentalign: bottomleft | endellipsis; padding: rect( 0, 0, 0, 0 ); richeditstyle: PlainText | HideSelection | DisableDrag | ReadOnly; }


    在中间插入一行:
    foreground: windowtext;

    加在哪行都可以,比如你想让联系人昵称显示粗体还可以把fontweight的normal改为bold,如图示:
    ResHacker修改图三
    点击Compile,然后再Save!运行MSN 看看吧,再也不会变形了。MSN 7.X版本本方法都应该适用。Enjoy….

    计数器

    此文来自雨吁

    5/17/2005

    打造最COOL的MSN Messenger


    计数器 我的MSN状态发信给我:slim_cn@msn.com给我留言 展开查看

    能看出来这是播放器还是MSN Messenger吗?


    中间的歌词还会自动变的哦!




    怎么弄的呢?

    答案是用msn shell最新的v 4.1.0.161

    在shell的设置里面→个性化设置→滚动显示个性化信息→将显示个性化设置打上勾。在这个设置界面中只能输入5条。

    如果想多条,那就得到C:\Program Files\MSNShell\BIN修改psmloop.txt这个文件,这样想多少行都可以了。。

    好了,大家都试试吧。让朋友惊讶一下:)

    对了。还需要一个msn的皮肤,可以到http://ilove.msnshell.com/html/skin/399.html下载

    返回页首展开查看我是slim,欢迎光临啊!
    5/7/2005

    两妙招“强行”杀死病毒进程


      根据进程名查杀

      这种方法是通过WinXP系统下的taskkill命令来实现的,在使用该方法之前,首先需要打开系统的进程列表界面,找到病毒进程所对应的具体进程名。

      接着依次单击“开始→运行”命令,在弹出的系统运行框中,运行“cmd”命令;再在DOS命令行中输入“taskkill /im aaa”格式的字符串命令,单击回车键后,顽固的病毒进程“aaa”就被强行杀死了。比方说,要强行杀死“conime.exe”病毒进程,只要在命令提示符下执行“taskkill /im conime.exe”命令,要不了多久,系统就会自动返回如图所示的结果。

      根据进程号查杀

      上面的方法,只对部分病毒进程有效,遇到一些更“顽固”的病毒进程,可能就无济于事了。此时你可以通过Win2000以上系统的内置命令——ntsd,来强行杀死一切病毒进程,因为该命令除System进程、SMSS.EXE进程、CSRSS.EXE进程不能“对付”外,基本可以对付其它一切进程。但是在使用该命令杀死病毒进程之前,需要先查找到对应病毒进程的具体进程号。

      考虑到系统进程列表界面在默认状态下,是不显示具体进程号的,因此你可以首先打开系统任务管理器窗口,再单击“查看”菜单项下面的“选择列”命令,在弹出的设置框中,将“PID(进程标志符)”选项选中,单击“确定”按钮。返回到系统进程列表页面中后,你就能查看到对应病毒进程的具体PID了。

      接着打开系统运行对话框,在其中运行“cmd”命令,在命令提示符状态下输入“ntsd -c q -p PID”命令,就可以强行将指定PID的病毒进程杀死了。例如,发现某个病毒进程的PID为“444”,那么可以执行“ntsd -c q -p 444”命令,来杀死这个病毒进程。

    2005年5月
    1
    2
    3
    5
    6
    8
    9
    11
    12
    13
    14
    15
    16
    18
    20
    21
    22
    23
    24
    25
    27
    28
    29
    31
    2/19/2005

    【JavaScript】面向对象的JavaScript编程

        javascript对于做过Web程序的人不应该是陌生,初期是用来做一些简单的FORM验证,基本上是在玩弄一些技巧性的东西。IE 4.0引入了DHTML,同时为了对抗Netscape的javascript,提出了自己的脚本语言JScript,除了遵循EMAC的标准之外,同时增加了许多扩展,如下要提到的OOP编程就是其中的一个,为了命且概念,我以下提到的javascript都是Microsoft Internet Explorer 4.0以上实现的JScript,对于Netscape,我没有做过太多的程序,所以一些的区别也就看出来。


        javascript不是一个支持面向对象的语言,更加算不上一个开发平台,但是javascript提供了一个非常强大的基于prototype的面向对象调用功能,你可以在你自己需要的地方使用他们。因此,如何使用对象?本文尽可能从javascript面向对象实现原理出发,解析清楚它的工作模型。在了解这些模型之后,你可以在自己的脚本库中编写一些实现代码,然后在其他地方调用。

     

        javascript的语法和C++很接近,不过在类实现中没有使用关键字Class,实现继承的时候也没有采用传统的Public或者Implement等等所谓的关键字来标示类的实现。这样的情况下,可能有就有人会问,如何编写javascript的Class,如何实现继承。我开始也是百思不得其解,后来看了MSDN,才知道采用了prototype来实现,包括继承和重载,也可以通过这个关键字来实现。

     

        javascript的函数很奇怪,每个都是默认实现了Optional的,即参数都可以可选的,function a(var1,var2,var3),在调用的过程中a(),a(value1),a(value1,value2)等等的调用都是正确的,至少在即使编译部分可以完整通过,至于其它,只是和函数的实现逻辑比较相关了。

        以下就JS对于类的实现、继承、重载详细介绍其实现方式。

        1。实现

        Js类的实现就通过函数直接实现的,每个函数可以直接看成class,如下代码

        function ClassTest1(){

            ...//implement code

        }

        var a=new ClassTest1

        

        function ClassTest2(var1){

            ...//implement code

        }

        var b=new ClassTest("value")

        对于类的属性,可以通过两种方式实现

        1)this."<Property or Method"的方式实现,在类声明函数中直接给出函数的实现,如 this.Add=new function(strUserName,strPassword)这样的方式调用,编写的方式在Class Function中调用。

        2)通过ClassFunction.prototype.[FunctionName]=function(var1,var2...){//todo}这样的方式完成调用。

        这两种方式从目标来看是一致的,按照我个人的观点来看,区别的只是在于实现方式,通过this.propertyName的方式来创建,Jscript自动创建了property或者method的入口,不过从程序的角度而言,还是使用prototype的关键字实现比较灵活。

        

        另外javascript也可以和我们C++中那种嵌套声明的方法来声明,C++实现的方法如下

        Public Class ClassName:ParentClass{

            Public DataType FunctionName(){

     

            }

            Public Class ClassName{

                Public DataType FunctionName(){

                }

            }

        }

        在javascript当中,当然不存在class这样的关键字了,所以实现起来有点戏剧性,不过仍然为一个非常巧妙的实现。

        function className(){

            //Property Implement

            this.UserName="blue";

            //Method Implement

            this.Add=new function(){

     

            }

            //Sub Class Implement

            function SubClassName(){

                this.PropertyName="hi"            

            }

            //sub class method implement

            SubClassName.prototype.Change=function{

     

            }

        }

        //Main Class Method Implement

        className.prototype.Delete=function(){

     

        }

        如上的代码大致演示了javascript类中属性和方法的实现,另外有一点比较困惑,整个class中都是public的,没有关键字private之类的可以控制某些方法是否隐藏,那么在我们编写代码实现的规范中,我看国外一些程序员都是使用_functionName这样子为函数命的方法来区分,但是在调用过程中实际还可以调用的。

        实现了属性和方法,剩下的就是Event的实现了,我查找了许多资料,包括整个MSDN关于JScript的参考,都没有看到一个很好的模型关于事件实现的,后来参考了一些站点编写HTA(HTML Component,有空我会写一些相关的文章)的实现,借助于比较扭曲(我个人认为)的方法可以大致的实现基于事件驱动的功能。大致的思路是这样子的:

        1).将所有的事件定义成属性,只要简单的声明就可以

        2).在需要触发事件的代码中判断事件属性是否是一个函数,如果是函数,直接执行函数代码,如果是字符串,那么执行字符串函数,通过eval(str)来执行。

        3) .在类的实例当中注册事件函数。

        为了简单说明如上的思路,采用timer这样简单的例子来表述如上的所提到的内容,如果只是为了简单的实现timer的功能,javascript中setInterval函数就可以满足全部的要求,如下的代码只是用来说明Timer的工作原理。

    //Class For Timer
    function Timer(iInterval){
     //if not set the timer interval ,then defalut set to 500ms
     this.Interval=iInterval || 500;
     this._handleInterval;
     this.TimerEvent=null
     function Start(){
      if(this.Interval!=0){
       this._handleInterval=setInterval("TimerCallBack()",this.Interval);
      }
     }
     function Start(){
      clearInterval(this._handleInterval);
     }
     function TimerCallBack(){
      if (typeof this.TimerEvent=="function"){
       this.TimerEvent();
      }
      else if(this.TimerEvent!=null && this.TimerEvent.length>0){
       eval(this.TimerEvent);
      }
     }

    //Code for Instance
    var t=new Timer(3);

    //------------------------------------//

    //1.
    t.TimerEvent=function(){
    //todo
    }

    //2.
    t.TimerEvent="alert(\"hello\")";

    //3.

    t.TimerEvent=tTimerCall;

    //----------------------------------//
    t.Start();
    t.Stop();

    function tTimerCall(){

     

    }

     

        实际工作代码是在TimerCallBack()上面实现,事件触发作为属性的方式来实现,在应用实例中,代码提供了三种方法去调用事件,不过在事件的回调当中,我还没有想到如何可以带参数,只有才各自的实现当中访问各自需要的属性才能够实现全部的要求。

     

        2。继承。

        刚采用了大篇幅的文字去介绍如何实现javascript的各种实现,也就是从逻辑上完成了一个封装class的实现,从某种意义上来说,class的实现是真正脚本编程中使用最多的部分,不过如果只是要完成如上的功能,使用VBScript来编写更能更加清晰,毕竟VBscript提供了class关键字,同时提供了public 和private这两个关键字,可以清晰的将公共和私有对象分离,至于事件的实现,也可以采用类似javascript实现的思路,只是对于函数的引用需要采用GetRef这个函数,具体的用法可以参考scripting reference,MSDN里头也有详细的介绍,而javascript强大至于在于如下要说的了,虽然具体的东西可能不多。

        如上所言,我们已经完成了一个基本的类实现Timer,现在要做的是重新编写这个类,我们简单的只是想在这个类之中加入一个方法,提供当前的系统时间,方法的名称为getSystemDate,显然如果全部重新编写,那就失去了我这里说的意义了。先看看如下的实现。

        function NewTimer(iInterval){

            //call super

            this.base=Timer;

            this.base(iInterval);        

        }

        NewTimer.prototype=new Timer;

        NewTimer.prototype.getSystemDate=function(){

            var dt=new Date();

            return dt.getYear()+"-"+dt.getMonth()+"-"+dt.getDay();

        }

        

        上述代码实现了NewTimer类,从Timer继承,javascript没有使用“:”或者java的public那样类似的关键字,只是通过newclassname.prototype=new baseclass这样的方法来完成,同时NewTimer实现了getSystemDate的方法,在NewTimer的初始化函数中,我使用了this.base=Timer,是为了引用父类的实现,不过在对于父类其他实现函数的调用,到现在我没有找到一个确定的方法,是否通过this.base.start()那样来调用还是其他的,如果有谁比较清楚的,麻烦告诉我,另外在netscape的站点上,我查到有一个特殊的"__proto__"的属性好像是对于父类的直接引用,不过具体的我也没有尝试过,在msdn中也没有看到对于__proto__的支持。

        

        3。重载

        或许这个是OOP编程中比较复杂的地方了,在javascript的实现中有点无奈,也就是通过prototype的方式来完成的,不过因为我不清楚如何调用父类的实现函数,那么在重载中只能够重新编写所有的实现了,另外就是在实现中实例化一个父类,然后通过调用它来返回需要的东西。

        javascript中所有的对象都是从Object继承下来的,object提供了toString()的方法,也就是说如果调用alert(objInstance)这样的过程,实际上是调用了alert(objInstance.toString())的方法,如果没有编写实现,object默认的toString()都是"object object"这样子的,在许多地方需要重载这个函数的,比如Timer,如果我们希望var ins=new Timer(5);alert(ins)调用得到的是interval的值5,那么就需要重新编写toString()方法了

        Timer.prototype.toString=function(){ return this.Interval};

        以上代码实现之后alert(ins)得到的就是5了。

     

    2/16/2005

    用游戏串起程序员的基本功

      其实做一个程序员要学很多专业知识,有些知识既枯燥又繁杂,让人头疼,所以我们就用大家喜爱的小游戏来串起我们的课程。愿你学的愉快。

      这一篇我们将用大家熟知的一种游戏——麻将,来给大家示范一下数据结构这门课的用处。由于笔者的水平有限,文章不可能太深入,旨在抛砖引玉。同时有不正确的地方也恳请各位大虾给于指正。

      书归正传,我们先来分析一下麻将游戏的整个流程。

      首先,当玩家开局时要在内存中分出136个整型数组空间,来存放136张麻将牌。然后,用136个整型数来代表136张牌,并将个张牌随机分配到前面分出的136个整型数组空间。这样,整个麻将牌就洗好了。

      接着,我们可以用1到6的随机数代表榖子,并随着数的变化而显示不同点数的图片。以时间或次数来决定停止的时刻,最后得到的数字就是玩家所掷出的点数,之后就可以用这个数来确定发牌的位置。

      然后,就是发牌了。在这里我们可以在建立两个长度为13的整型数组空间,以分别存储两个玩家手中的牌。

      接下来就是最有意思的地方了,也就是玩牌的过程了。先比较对家打出的牌是否可以吃、碰、杠、胡,都没有,就从剩下的牌中一张张的分到每个该摸牌的玩家手中,并计算是否为胡牌,如果是,就提示玩家并让玩家决定是否胡牌;如果不是,就等玩家出牌后,根据是否是同一种牌并按大小顺序排列好。重复这一过程,直到有人胡牌,游戏结束。

      游戏的流程我们分析完了,怎么实现呢?这也是我们这篇教程的重点。一定好打起精神呀!

      我们先来看看游戏的第一个流程是怎样实现的。在文章的开始,我谈到了数组(就是具有同一数据类型的,并且位置连续的数据存储空间),用c语言我们可以这样实现。

    int pai[136];// 注意c语言中数组是从0~135

      那么怎么洗牌呢?通常的思路是用rand()来产生1~136之间的整型数,并按顺序放入我们刚才分配的用来存放麻将牌的数组空间中。为了不出现重复的数值,可以用产生的新数和前面已放入数组的数比较,如果重复就在从新分配,不重复就放入数组中。

      下面是实现这一过程的代码。

    Void xipai()
    {
     int temp;
     temp=(int ) 135*rand()/32565+1;
     for (int i=o;i<136;i++)//按顺序比较136个数组空间
     {
      if(i=o)
       pai[i]=temp; //第一个数直接放入数组
      else
       for(int j=o ;j<i;j++) //和前面放入数组的数比较
       {
        if( pai[j]==temp) //有重复的
         xipai(); //从新取数比较
        else
         pai[i]=temp; //没重复的,放入数组
       }//end for
     }//end for
    }//end xipai 

      在上面的代码中,我们使用了递归的方法,可以说它的效率是比较低的。一方面适应为递归本身对空间的要求,另一方面,就在于这个算法中,要进行多次的比较。举个较极端的例子,如果我们每次取得一个随机整型数都是在前面已经存放过的,那么就要一直从新取数并比较。当然这种可能性极少,但当数组存放有一半以上的数后,这种事情发生的可能性就会成指数的增长。

      这里我再介绍一种新的方法。前一种算法的弊处在于可能会有重复的数,而进行多次的取数和比较。我们换个思路,就是随机产生0到135的数组位置,按顺序把1~136的数按得到的数存放到相应的数组位置。有人可能会说,难道位置就不会有重复吗?别急,我们继续往下看。

      我们知道,c语言中对于一个int(整型)数,默认的初始化值是0,所以,我们只要在按顺序比较0到135个数组空间,那些值为0,就可以将数放入其中。这样就保证了不会出现重复的数,而且效率也比前一种方法要高。(大家可以思考一下为什么?我也会在后面给以解答)

      下面是这种算法的源代码:

    void xipai()
    {
     for (int i=1 ; i<=136; i++) //这是要放入的136个整型数
     {
      int a= (int)135 * rand()/32767+1; //随机产生位置
      if(pai[a]==0) //判断此位置是否为空
      {
       pai[a]=i; //空,放入其中
      }
      else //不空
      {
       for(int j=0;j<=135;j++) //比较所有数组空间
       {
        if(pai[j]==0) //找到空闲的位置
         pai[j]=i; //放入其中
        }//end for
      }// end if
     } //end for
    }//end xipai

      这种算法之所以比前一种效率高,原因就在于他即使每次得到的位置都不为空,也顶多搜索136遍,而前一种算法是最坏的可能是想不出的糟糕的。

      第一篇我们用数组实现了洗牌的步骤。当然还有其他的方法。这也就是编程的带给我们的乐趣——灵活,自由。只要你想不得到,没有你做不到的。

      今天我们就来实现第二步。掷色子,发牌。

      掷色子的问题,其实很简单,但涉及到动画制作的原理。所以我们先来补充些这方面的知识。

      我们知道,人的视觉都有一种现象,比如当你在黑暗中看灯泡时,突然灯泡熄灭了,但灯泡发光的影像还会在我们眼前停几秒钟,这就是我们熟知的“视觉暂留效果”。我们所看到的电视、电影,包括我们说熟知的flash,director等动画制作软件,都是依赖这个效果才实现的。当我们在高速状态下(一般是每秒24帧),按一定的顺序切换内容相近和连贯的一组图片时,我们看到的不是一张张单独的图片影像,而是一段连贯的动画。

      根据这个原理。我们掷色子的动画也就很容易实现了。只要我们快速的变换不同点数的色子6个面,就会让人觉得好象是真实的转动着的色子了。你不用担心,运行速度的问题,只要你的机子能看vcd,一般都没问题。先看看下面的代码框架:

    void zhisaizi()
    {
     int i;
     i= (int)5*rand()+1;
     switch(i)
      case 1:
       //载入第一张图片
      case 2:
       //载入第二张图片
      case 3:
      。。。。。。//省略中间过程
      case 6:
       //载入第六张图片
    }

      至于怎样载入和现实图像不是我们本篇的主要内容,大家可以学习《windows程序设计》这本书。

      对于发牌的问题,我想大家应该不会在有问题了。这里我只给出代码,其中有详细的注释,就不解释了。下面是代码:

    int apai[13],bpai[13];
    //apai,bpai分别代表两玩家手中的牌,如果你做的是四个人
    //的麻将,可你在自由扩展

    void fapai()
    {
     int a,b;
     int i;//i代表发牌的起始位置
     if((i/2)= =0)//是否为偶数
      apai[a++]=pai[i++]; //双数牌发给a玩家
     else
      bpai[b++]=pai[i++];//单数牌发给b玩家
    }

      下面是我们本篇要讲的重点:就是在玩家都拿到牌之后,我们需要按顺序摆放好牌。这里我们会更加深入的谈到数据结构方面的一些知识,希望大家用心。

      对于这个问题实质上也就是排序的问题。我们前面用136个整型数来代表136张牌,可以用1~136分别表示1~9万,1~9条,1~9饼,以及东南西北红中发财,也可以用1~39中的个位数分别对应着和他相等的1~9万,如3、13、23、33分别代表四张3万。其他以此类推。剩余的数字来表示东南西北红中发财。这个就可以由自己决定了。在此我们选择第一种方法来描述问题。

      那么就请怎样排序呢?最容易想到的方法是:取得一个数,和他前面的所有数比较,直到找到一个前面的数比它小,后面的数比他大的位置,并将其放入其中。再取得下一个数,继续上面的步骤。

      示例如下:

    8 7 4 3 6 1 //是要排序的数值
    7 8 4 3 6 1 //第一次,取得7,小于前面的8,交换位置
    7 4 8 3 6 1 //第二次,取得4,小于前面的8,交换位置
    4 7 8 3 6 1 //第三次,小于再前面的7,交换位置
    4 7 3 8 6 1 //第四次,取得3,小于前面的8,交换位置
    4 3 7 8 6 1 //第五次,小于再前面的7,交换位置
    3 4 7 8 6 1 //第六次,小于再前面的4,交换位置
    3 4 7 6 8 1 //第七次,取得6,小于前面的8,交换位置
    3 4 6 7 8 1 //第八次,小于再前面的7,交换位置
    3 4 6 7 1 8 //第九次,取得1,小于前面的8,交换位置
    3 4 6 1 7 8 //第十次,小于再前面的7,交换位置
    3 4 1 6 7 8 //第十一次,小于再前面的6,交换位置
    3 1 4 6 7 8 //第十二次,小于在前面的4,交换位置
    1 3 4 6 7 8 //第十三次,小于再前面的3,交换位置

      我们把这种方法叫做“插入排序法”。下面是源代码。

    void paixu ()// 用插入排序法
    {
     for (int i = 1; i <=13; i++)
     {
      int temp = apai[i];//取得一个数
      int j;
      for ( j = i; j > 0 && temp < apai[j - 1]; j--) //和他前面的每一个数进行比较,
       apai[j] = apai[j - 1]; //如果这个数小于她前面的数就交换
       apai[j] = temp;//插入到正确位置
     }//end for
    }//end 

      我们先来分析一下这种算法。可以看到,为了找到合适的插入位置,我们要用取得的数值与他前面的所有数值进行比较,并将进行多次的交换位置。尤其是当较小的数值放得越靠后时,交换到合适的位置所需的时间越长,如示例中的6和1,位置都靠后且相邻,但6放置到合适的位置只用了两步,而1放置到合适的位置却用了五步。

      有没有其他的方法呢?针对比较次数和交换次数较多,我们可以用另一种“折半插入排序法”。基本思路是:为了减少比较的次数,我们不用每个数都比较,而是先找到所取得的数值在数组中的位置,并找到它前面已排好顺序的数组的中间的一个数值,比较两数,如果取得的数值大,就与後面的数值的中间数值比较,一直下去,直到找到合适的位置;同理如果所取得的数值小,就与前面的数值的中间数值比较,一直下去,直到找到合适的位置。

      下面是示例:

    8 7 4 3 6 1 //是要排序的数值
    7 8 4 3 6 1 //第一次,取得7,小于前面的8,交换位置
    7 4 8 3 6 1 //第二次,取得4,小于前面的8,交换位置
    4 7 8 3 6 1 //第三次,小于再前面的7,交换位置
    3 4 7 8 6 1 //第四次,取得3,小于前面的数组(4 7 8)中的中间数值7比较,小于7,所以比较数组(4 7)中间数值4,大于4,并交换到合适位置
    3 4 6 7 8 1 //第五次,取得6,小于前面的数组(3 4 7 8)中的7,再与再靠前的数组(3 4 7)中间值4比较,大于4,交换位置
    1 3 4 7 8 6 //第六次,取得1,小于再前面的数组(3 4 6 7 8)中间值6,再与数组
    (3 4 6)中间值4比较,小于4,最后与数组(3 4)中间值3 比较,
    小于3,交换位置
     

      很明显这种方法的减少了比较的次数,但交换次数仍没减少。下面是其代码:

    void paixu()//用折半排序法
    {
     for (int i = 1; i < =13; i++)
     {
      int temp = apai[i]; //取得数值
      int low = 0, high = i - 1;//low,high,分别表示比较范围的上下限
       while (low <= high)//折半查找
      {
       int mid = (low + high) / 2; //取得中间位置
       if ( temp < apai[mid]) //如果小于中间位置的值
        high = mid - 1; //从新确定下限
       else //如果大于中间位置的值
        low = mid + 1; //从新确定上限
      }//end while
      for (int j = i - 1; j >= low; j--)
       apai[j + 1] = apai[j]; //记录后移
       apai[low] = temp; //插入到合适位置
     }//end for
    }//end


      前面我们学习了两种插入排序法,但当要排序的数组长度越长并且数值越不成顺序,比较和交换的次数就越多,效率越低。因此D.L.Shell在1959年提出了缩小增量排序法(又叫希尔排序法),基本思想是:取一个间隔,将长序列分成若干短的子序列,对每个子序列进行直插排序;然后逐渐缩小间隔,重复以上过程,直到间隔为1。可以看到这种算法,较好的克服了直接插入排序法的不足。

     

      下面是示例:

    8 7 4 3 6 1 //是要排序的数值,我们以一半的长度为间隔3
    3 7 4 8 6 1 //第一次,取得3,小于前面的8,交换位置
    3 6 4 8 7 1 //第二次,取得6,小于前面的7,交换位置
    3 6 1 8 7 4 //第三次,取得1,小于前面的4,交换位置
    1 6 3 4 7 8 //第四次,再缩小间隔,为2,取得1小于3,交换位置,取得7,大于前面的3,不变;取得8大于6,不变,取得4小于8,交换位置
    1 3 4 6 7 8 //第五次,再缩小间隔,为1,取得6,大于1,不变;取得3小于6,交换位置;取得4,小于6,交换位置;取得7,大于前面的6,不变;取得8 ,大于7,不变

      以下是代码:

    void paixu( ) //用希尔排序法,
    {
     int N=13;// N为前后纪录位置的增量
     for (int Z= N/2; Z; Z = Z/2)//每次缩小增量
      for (int i = Z; i < N; i++)//从增两大小开始比较
      {
       int temp = apai[i]; //将后一个备份
       for (int j = i; j >= Z && temp < a[j - Z]; j -= Z) //与他在同一个子序列的数一个个的较
       {
        a[j] = a[j -Z]; //如果小于,就交换
        }//end for
       a[j] = temp; //找到合适的插入点,放入其中
      }//end for
    }//end

     

      我们再来看最后一种关于数组的排序方法,就是快速排序法,它是目前最快的一种排序的方法.它的基本思想是:通过一趟排序将待排序的记录分割为独立的两部分,其中一部分记录的数值均比另一部分记录的数值小,然后继续分别对这两部分进行排序,直到整个序列有序为止.

      具体做法: 任取待排序列的某个记录(我们可以取第一个数)作为基准,按照该数值大小,将整个序列分成两个序列——左侧的所有记录的数值都比基准小(或者相等),右侧的都比基准大,基准则放在两个子序列之间,显然这时基准放在了最后应该放置的位置。分别对左右子序列重复上面的过程,直到最后所有的记录都放在相应的位置。

      示例如下:

    7 8 4 3 6 1 //是要排序的数值
    1 8 4 3 6 //第一次,取得7,作为基准,1为right值,7>1,交换位置
    1 4 3 6 8 //第二次, 8为left值,7<8,放到最后;
    1 4 3 6 8 //第三次,left取得4,小于7,放到前面,
    1 4 6 3 8 //第四次,right取6,小于7,放到前面
    1 4 6 3 8 //第五次,left=right=3,小于7,放到前面,
    1 4 6 3 7 8 //7放入合适位置,第一趟排序完成
    //后面,在以1为基准排序
    ……
    //直到成功

      代码如下:

    void paixu(int a[],int low,int high;)//用快速排序法
    {
     // low, high表示扫描的范围

     int pivot;//存放中心索引及其值的局部变量
     int scanup,scandown,mid;//用于扫描的索引
     if (high-low<=0) //如果数组中的元素少于两个,则返回
      return;
     else
      if(high-low==1) //如果有两个元素,对其进行比较
      {
       if(apai[high]<apai[low]) //如果后一个比前一个小,
        Swap(apai[low],apai[high]);//那么交换位置
        return;
      }//end if
     mid=(low+high)/2;//取得中心索引
     pivot=apai[mid];//将中间索引的值,赋给pivot
     Swap(apai[mid],apai[low]);//交换pivot及低端元素的值
     Scanup=low+1;
     Scandown=high;//初始化扫描索引scanup和scandown
     do{
      //从低端子表向上扫描,当scanup进入高端子表或遇到大于pivot的元素时结束.
      while(scanup<=scandown && apai[scanup]<=pivot)
       scanup++;
       //从高端子表向下扫描,当scandown遇到小于或等于pivot的元素时结束
      while(piovt<apai[scandown])
       scandown--;
       //如果两个索引还在各自的子表中,则表示两个元素错位,将两个元素换位
       if(scanup<scandown)
        Swap(apai[scanup],apai[scandown]);
     }while(scanup<scandown);
     //将pivot拷贝到scandown位置,分开两个子表
      apai[low]=apai[scandown];
      apai[scandown]=pivot;
     //如果低端子表(low至scandown-1)有2个或更多个元素,则进行递归调用
     if(low<scandown-1)
      paixu(apai,low,scandown-1);
      //如果高端子表(scandown+1至high) 有2个或更多个元素,则进行递归调用
     if(scandown+1<high)
      paixu(apai, scandown+1, high);
    }

      关于排序的问题已经够多了,就到这里吧,如果大家有兴趣,可以看已看这方面的书.下一节我们继续我们的游戏开发.


      对于麻将牌的玩法来说,重要的就是要有吃、碰、杠、胡等四种功能.那么在游戏中怎样编码实现呢?

      吃牌,就是比较对方打出的牌,和自家的牌是否可以连成一串.而碰牌就是比较对方打出的牌和自家的牌是否有2张相同的.如果有三张牌和对方打出的牌相同,就可以杠.胡牌则是至少要有一对相同的牌,除此之外,也可以有三张三张的相同的牌或连成串的牌.

      所以对于吃、碰、杠、胡,我们可以分解为,查找两张连续的牌,查找两张相同的牌,查找三张相同的牌,对于胡牌,还要确定有且只能有一对相同的牌,其他的可以是三种相同的牌,或三张连续的牌。可见在此查找是算法的关键。

      我们先来看一下有关查找的知识。

      第一种查找的方法叫“线性查找法”,是从数据中的第一个数值开始查找比较,如果找到就返回该值或该值所在的位置。

      示例如下:

      13 25 16 23 57 66 为一个整型数组。

      如果要在这样一个数组中找到57,

      步骤1:得到第一个数13,不相等,取得下一个数

      步骤2:得到第二个数25,不相等,取得下一个数

      步骤3:得到第三个数16,不相等,取得下一个数

      步骤4:得到第三个数23,不相等,取得下一个数

      步骤5:得到第三个数57,相等,返回所在位置5

      代码如下:

    int chazhao(int key)//用线性查找法 ,key表示要查找的数值

    {

    int i=0; //指示在数组中的位置

    while(i<14) // 直到结尾

    {

    if (key==apai[i]) //如果找到,

    return i; //返回所在位置

    i++; //没找到,继续向后搜索

    }

    return -1; //没找,返回失败

    }

      可以看到这种方法,不管数据是否有顺序,也不管数据的多少,都是按顺序挨个的搜索,直到找到或搜索完成,当数据很多时,尤其是要查找的数据排的比较靠后,就会很费时间。所以,对于已经排好顺序的数据,如果还用线性查找法来查找的话,一定是很浪费时间的。有什么好的方法呢?

      大家一定都查过英语词典吧,当你要查一个以S开头的单词时,谁都不会从头一页一页的向后翻,而是跳跃式的向后翻,假如我们先翻到了P开头的一页,我们一定会继续向后翻,而不必再查前面的页码。再翻一次,到达T开头的一页,我们就会又先前翻,找P和T之间的页.我们可以把这种查找的方法叫“折半查找法”。

      它的原理是:先用欲查找的数值和该组数据的中间位置上的数值比较,当小于中间值时,再向前查询,大于中间值时向后查询,继续取前面(或后面)一半数据的中间值进行比较,如果小于再向前查询,大于就向后查询,一直到找到或查询完毕为止。

      示例如下:

    5 7 12 25 34 37 43 46 58 //是要查找的数据段,其中要查找46,

      步骤一:中间值34<46,向后查找

      步骤二:得到后半部分的中间值43<46,再向后查找

      步骤三: 得到剩余部分的中间值46,返回

      可见,这种方法明显的减少了比较的次数.

      下面是源代码:

    void chaxun(int key)//用折半查询法,key表示要查询的数值

    {

    int left=o; //待查询数据段的左边界

    int right=12; //待查询数据段的右边界

    int mid; //待查询数据段的中间值

    while(left<=right) //只要没查询完

    {

    mid=(right+left)/2; //取得待查询数据段的中间值

    if(key<apai[mid]) //中间值大于待查询的数值

    right=mid-1; //再查找前半段

    else if (key>apai[mid]) //中间值小于待查询的数值

    left=mid+1; //再查找后半段

    else if(key= = apai[mid]) //中间值等于待查询的数值

    return mid; //返回位置

    }//end while

    return –1;

    }//end
     

      查询数据是数据结构中的又一重要知识,在实际应用中也很重要,如果有兴趣可再深入的研究.我们只能到此为止.

      回过头来,我们再来看一看,这样判断吃,碰,杠,胡.在此我们只以万牌为例.

      下面是数和牌对应关系:

    1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20

    21 22 23 24 25 26 27 28 29 30

    31 32 33 34 35 36 37 38 39 40

    一 二 三 四 五 六 七 八 九 (万) 东

      很明显同一张牌是在小于40的数据段中,并且个位相同的数.并且它还对应着牌上的数.

      所以判断是否为同一张牌的方法,我们可以这样写:

    tempa=apai[i]%10;
    tempb=apai[i+1]%10;
    if(tempa==tempb)
     shi tong yi zhang pai
    (注意这种牌和数得对应关系,在排序时,一定要以个位为准,而不是以实际的数值大小)

      判断是否可碰,则可以这样:

    tempa=apai[i]%10;
    tempb=apai[i+1]%10;
    tempc=apai[i+2]%10;
    if(tempa==tempb && tempb==tempc)
     

      判断是否可杠,可以这样:

    tempa=apai[i]%10;
    tempb=apai[i+1]%10;
    tempc=apai[i+2]%10;
    tempd=apai[i+3]%10;
    if(tempa==tempb && tempb==tempc && tempc==tempd)

      是否为顺,则可以这样判断:

    tempa=apai[i]%10;
    tempb=apai[i+1]%10;
    tempc=apai[i+2]%10;
    if(tempa==tempb+1 && tempb==tempc+1)

      很简单吧,相信你应该也会判断对方出的牌,自己是否可以碰,杠,吃了吧.在此我们就不多罗嗦了.

      胡牌稍麻烦些,要判断各种副牌的方式,这里我们一起看看一条龙的胡法.

      下面是源程序:

    void yitiaolong ()
    { int wan,bing,tiao;//wan,bing,tiao 分别代表三种牌
     int temp;
     for(int I=0;I<13;I++)
     {
      temp=apai[I];
      if(0<temp<40)
       wan++;
      if(40<temp<80)
       bing++;
      if(80<temp<120)
       tiao++;
      if(wan=8)//万牌一条龙
      for(int I=1;I<9;I++) //比较这8张牌
        if(apai[I]%10=api[I+1]%10)
         //是否後一张等于前一张,相等,说明已经有将牌了
        for(int a=1;a<40;a++)
         if (chaxun(a)!==-1) //找到所缺的牌
          if (I=duifangchupai)比较是否和对家大的牌相等
            ……..//胡了
           else
           //表示8张牌没有相同的,也就是又8个串
           //查找所缺的牌,
           //与对方出的牌比较,
           //相等 胡牌
            ……….
           if(wan=9)
            ……….
     }
     if(bing>=8)
      …..//饼牌一条龙
     if(tiao>=8)
      …… //条牌的一条龙
     }
    }

      由于篇幅所限而且文章的重点不在于此,这仅给出一个框架,聪明的读者,可以发挥自己的才智,将他补充完整.

      下面我们简单谈一下人工智能,毕竟我们在游戏中还需要一个对手.

      作为程序员,我们不可能做出一个具有高超智慧的对手,而且我们也不可能将游戏中种种可能的打牌方法都写入程序.那么就需要一种可操作的方法,使你的对手不至于太傻.

      对于给策略性的游戏添加人工智能主要有这么几步:

      (1) 要找出所有获胜的可能性

      (2) 建立获胜表,

      (3) 每一步的玩法给出一个恰当的评价分值,其中包括给对手制造的麻烦所得的分,和给自己带来的获胜机会所带来的分

      (4) 根据所给的分值,确定其中所有可能的玩法中,分值最高的一种玩法,也就是最好的一步走法.

      具体到麻将游戏下面是具体的分析:

      假设a1,a2,... an 是手上当前牌序列,其中包括吃 碰 杠后的牌,

      步骤1: 计算 玩家 离胡牌还有几步. 如果 玩家 已经听牌, 它离胡牌的步数是 1, 如果是一上一听, 步数是 2, 以此类推;

      步骤2: 如果步数是 m , 计算所有能使 玩家 到达胡牌的序列也就是所有胡牌的可能玩法 {b11,b12,....b1m}, {b21,b22,...b2m}, ....,{bt1,bt2,....,btm}

      步骤3: 对于每个一个 {bt1,bt2,...,btm}, 计算胡牌的点数, 以及到达胡牌时要打出去的牌: {ci1,ci2,....,cim}

      步骤4: 对于每一个 {bt1,bt2,...,btm} 计算胡牌的点数;

      步骤5: 选取收益最大的方案 {bji,bj2,....bjm}

      步骤6: 从{cj1,cj2,....cjm} 中按照跟熟原则打一张

      动动脑筋相信你会做出更智能化的得对手。


      游戏的细节部分快谈完了,我们的这篇文章也即将结束。但这部分所谈到得内容依然是数据结构方面的很主要的一部分内容。闲话少叙,回到我们的教程中。

      细心的读者可能发现了,这里我的所有例子都是基于数组这种结构来谈的。所以我们先来看一看数组的一些特点。从前面的例子中,我们可以看到,数组是一种不仅在存储地址上连续而且其中前后数据在逻辑上也连续的存储数据的结构。它的优点是,读取和修改其中的每一个数值都很方便。只要直接找到它的索引值就可以读取和修改它的数值。即使要想读取或修改数组中所有的数值,我们也只需要一个从头到尾的循环.但是对于数组的插入,删除,和排序却是一件很繁琐的事,比如我们在数组中间插入一个数值,就要将它所在的位置之后的所有数值后移一位,如果其后的数值很多,效率是极低的.示例如下:
    ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
    │2│3│4│5│6│7│8│9│ │ │ │
    └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘

     
      上面是待插入数值的数组.如果想在5的后面插入一个1,可以想到首先要找到5所在的位置,然后,从最后的9开始,向后移动一个位置,直到空出5后面的位置,最后将1插入其中.在这里我们只需移动四个数,但当数值越多,那么移动的数值也就会越多.

      除此之外,数组还有一个不足之处.那就是我们在不知到数据多少时,为存储下所有数据,必须建立很多空的数组,势必造成存储空间的浪费.

      这里我们再介绍一种数据结构——链表。我们先来看个例子。

    ┌─┬─┬─┬─┬─┬───┬─┬─┬─┬─┐
    │1│ │2│ │3│  4│ │5│ │ │
    └─┴─┴─┴─┴─┴───┴─┴─┴─┴─┘


      上面是存在内存中的数据,在存储位置上,它们都是不连续的,但我们可以不考虑他们的实际位置,而在逻辑上让他们形成一种前后相联的关系。怎么做呢?

      正如下图所示:我们只要加一个“链”就可以将内存中不连续的单元连接起来。用c语言我们可以这样做:
    Struct jiegouming //结构名称
    {
     int shu; //用来存储数据
     Struct list *next; //用来连接下一个
    }
    typedef struct jiegouming node;
    typedef Node *link;

    ┌─┬─┐
    │1│ │──┐
    └─┴─┘  │
    ┌─┬─┐  │
    │ │ │  │
    └─┴─┘  │
     ┌─────┘
     ↓
    ┌─┬─┐
    │2│ │──┐
    └─┴─┘  │
    ┌─┬─┐  │
    │ │ │  │
    └─┴─┘  │
     ┌─────┘
     ↓
    ┌─┬─┐
    │3│ │──┐
    └─┴─┘  │
    ┌─┬─┐  │
    │ │ │  │
    └─┴─┘  │
     ┌─────┘
     ↓
    ┌─┬─┐
    │4│ │
    └─┴─┘


      这样我们就可以用next这样的一个指针,将多个这样的结构联系起来了。而且还可以在需要的时候 ,动态的建立,不需要的时候,再释放掉,从而节省了,内存空间。

      我们来看一看链表的建立和释放。下面是源程序:

    link creatlist(link head)//建立一个有10个节点的链表,并初始化为0
    {
     link point;//声明两个接点
     link new;
     int num=10;//计数值设为10
     head = (link)malloc (sizeof(node));
     if(head==NULL)
      printf(” 内存建立失败”);
     else
     {
      head->next=NULL;
      printf(“建立成功”)
     }
     point = head;//将point 设为首节点
     while(num>0)
     {
      new = (link) malloc (sizeof(node));
      if(new==NULL)
       printf(“内存建立失败”);
      else
       new->shu=o;//将新建的节点中的数值设为0
       new->next=NULL;// 将新建的节点中的下一个节点设为NULL
       point->next=new;//将新建的节点连接到前一个节点上
       point=new;// 将新建节点设为当前节点
       num--; //计数值减一
     }//end while
    }//end

    void freelist(link head)//释放链表
    {
     link point; //声明一个节点
     while(head !=NULL)//当节点为空,说明已到链表最后
     {
      point=head;//保存节点头
      head=hesd->next; //向后移动倒下一个节点
      free(point); //删除头一个节点
     }//end while
    }//end

      对于链表如果不能实现查询,插入,删除等功能,那么它也就没有价值了,所以我们再来看看在链表中怎样实现这些功能.

      先来看看链表的查询.因为链表在逻辑上也是有序排列的,所以,要找一个数值,我们同样可以采用线性查找法挨个往下查找下去,直到找到所要查找的数值或者查找结束.

      下面是例程:

    int searchlist(int key,link head) //在链表head中查找key值
    {
     link point;//声明一个节点,指示当前位置
     int num=1;//记录节点所在位置的数值
     point = head; // 指向头一个节点
     while(point !=NULL)
     {
      if(point->shu==key) //如果节点中的数值和要查找的数值相等
       return num;// 返回这个数值在链表中的位置
      point = point->next; //如果节点中的数值和要查找的数值不相等,节点后移
      num++;//计数值加一
      }//end while
    }//end

      对于链表的插入方法,很灵活,不用象数组一样插入后,还要向后移动其后面的数值.下面是一个示例图:

    ┌─┬─┐
    │1│ │──┐──┐
    └─┴─┘  ┆  ↓  要插入的新节点
    ┌─┬─┐  ┆ ┌─┬─┐    
    │ │ │  ┆ │5│ │──┐ 
    └─┴─┘  ┆ └─┴─┘  │ 
     ┌─────┘────────┘ 
     ↓
    ┌─┬─┐
    │2│ │──┐
    └─┴─┘  │
    ┌─┬─┐  │
    │ │ │  │
    └─┴─┘  │
     ┌─────┘
     ↓
    ┌─┬─┐
    │3│ │──┐
    └─┴─┘  │
    ┌─┬─┐  │
    │ │ │  │
    └─┴─┘  │
     ┌─────┘
     ↓
    ┌─┬─┐
    │4│ │
    └─┴─┘

     

      可见,链表的插入,只要修改节点的指向就可以了.

      下面是源代码 :

    Link insertlist(link head,link new,int key)
    //在链表head中插入数值为key的节点new
    {
     link point;// 声明一个节点,来指示当前节点
     point = head; //当前节点指向头节点
     while(1)
     {
      if(point==NULL)//如果链表head中只有一个节点
      { //插入到首节点的前面
       new->next=head;
       head=new;//新节点作为首节点
       break;
      } //end if
      if(point->shu==key)//如果在链表head中找到key值
      { //插入到这个数值的后面
       new->next=point->next;
       point->next=new;
       break;
      } //end if
      point=point->next;//当前节点后移
     }//end while
     return head;
    }//end

      最后是怎样删除一个节点.方法和插入类似.请看示例图:

    Link deletelist(link head,int key)
    /在链表head中删除数值key
    {
     link point;//指示当前节点
     link back;//指示后一个节点
     point =head;//将头节点设为当前节点
     while(1)
     {
      if(point->next==NULL)//如果,没有节点
      {
       printf(“没有节点可供删除”);
       break;
      }
      if(head->shu ==key)//如果找到数值key所在的节点
      {
       head=point->next;//将当前节点的下一个
       free(point);//释放节点
       break;
      }
      back=point;//保存当前节点
      point=point->next;//当前节点后移
      if(point->shu==key)// 如果找到数值key所在的节点
      {
       back->next=point->next;
       //将当前节点所连接的节点连接到当前节点的前一个节点上
       free(point);//释放当前节点
       break;
      }//end if
     }end while
     return head;
    }//end 

      好了,就到这吧,相信大家已经体会到了数据结构着门课的重要性,我们的目的也就达到了.毕竟只是一篇基础教程.所以我们只讲到了数据结构中的一些基础性的知识.把它当作一篇入门的教程吧,有兴趣的读者可以学习一下相关的课程.一定会有所收获的.

    2/5/2005

    让搜索关键字以红色显示的函数


    <%
    '函数名:Highlight
    '参数strText是要被高亮显示的字符串或变量所在的字串或变量
    'strFind是要被高亮显示的字符串或变量,
    'strBefore被高亮显示的HTML代码前缀如:<font color=red>
    'strAfter被高亮显示的HTML代码的后缀
    Function Highlight(strText, strFind, strBefore, strAfter)
    Dim nPos
    Dim nLen
    Dim nLenAll

    nLen = Len(strFind)
    nLenAll = nLen + Len(strBefore) + Len(strAfter) + 1

    Highlight = strText

    If nLen > 0 And Len(Highlight) > 0 Then
    nPos = InStr(1, Highlight, strFind, 1)
    Do While nPos > 0
    Highlight = Left(Highlight, nPos - 1) & _
    strBefore & Mid(Highlight, nPos, nLen) & strAfter & _
    Mid(Highlight, nPos + nLen)

    nPos = InStr(nPos + nLenAll, Highlight, strFind, 1)
    Loop
    End If
    End Function
    %>
    调用方法:Response.Write Highlight("mysomeword", "someword", "<font color=red>", "</font>")
    显示成:mysomeword======================================
    查找某字段包含某关键字的代码:

    方法1:
    <script>
    var a="1:发电厂,qs0001:一期,jz0000:全厂设备,jz0001:1号机组,jz0002:2号机组,jz0003:3号机组,jz0004:4号机组,jz0005:5号机组,jz0006:6号机组";
    var b="发电厂";
    alert(a.indexOf(b));
    </script>

    方法2:
    <script language=vbscript>
    a="1:发电厂,qs0001:一期,jz0000:全厂设备,jz0001:1号机组,jz0002:2号机组,jz0003:3号机组,jz0004:4号机组,jz0005:5号机组,jz0006:6号机组,"
    a=split(a,",") 
    </script>

    数字日期转化为汉字日期格式 by 尤文

    <script>
      /*
      功能:YYYY-MM-DD 数字日期转化为汉字
      例:1984-3-7 -> 一九八四年三月七日
      调用:baodate2chinese("1984-3-7")
      */
      var chinese = ['零','一','二','三','四','五','六','七','八','九'];
      var len = ['十'];
      var ydm =['年','月','日'];
      function num2chinese(s)
      {

       //将单个数字转成中文.
        s=""+s;
        slen = s.length;
        var result="";
        for(var i=0;i<slen;i++)
        {
            result+=chinese[s.charAt(i)];
        }
         return result;
      }

      function n2c(s)
      {
        //对特殊情况进行处理.
        s=""+s;
        var result="";
        if(s.length==2)
        {
             if(s.charAt(0)=="1")
             {
                if(s.charAt(1)=="0")return len[0];
                return len[0]+chinese[s.charAt(1)];
              }
         if(s.charAt(1)=="0")return chinese[s.charAt(0)]+len[0];
            return chinese[s.charAt(0)]+len[0]+chinese[s.charAt(1)];
         }
         return num2chinese(s)
      }
      function baodate2chinese(s)
      {
         //验证输入的日期格式.并提取相关数字.
         var datePat = /^(\d{2}|\d{4})(\/|-)(\d{1,2})(\2)(\d{1,2})$/;
         var matchArray = s.match(datePat);
         var ok="";
         if (matchArray == null) return false;
         for(var i=1;i<matchArray.length;i=i+2)
         {
             ok+=n2c(matchArray[i]-0)+ydm[(i-1)/2];
         }
       return ok;
      }
      </script>
      YYYY-MM-DD:<input type=text name="mydate" value="1984-3-7">
      中文显示:<input type=text name="okdate">
      <input type=button onclick="document.all.okdate.value=baodate2chinese(document.all.mydate.value)"
    value="转换">

    根据内容控制IFrame的高度

    在包含的IFrame页面里:
    <TABLE WIDTH=75% BORDER=1 CELLSPACING=1 CELLPADDING=1>
     <TR>
      <TD id="zz">
       <iframe src="a.asp" height=100% scrolling=no></iframe></TD>
     </TR>
    </TABLE>


    在a.asp里加上这些代码:
    <SCRIPT LANGUAGE=javascript>
    <!--
    function document.onreadystatechange()
    {
       if (document.readyState=="complete")
       {
         var ptd = window.parent.document.getElementById("zz");
         ptd.style.height = document.body.scrollHeight;
       }
    }
    //-->
    </SCRIPT>

    当层遇到下拉框时挡不了select框

    当层遇到下拉框时总是挡不了select框?其实这是IE的BUG,其它的浏览器没有这个问题,对于这个问题论坛里不少提出,在这里提供我的几种方法,各有各的好处,有错,有好的意见者提出,谢谢.

    1.最直接的方法:隐藏下拉框.

    下面提供的是一个比较通用的一组函数:

    test.htm

    ------------

    <script>
    var HideElementTemp = new Array();
    //点击菜单时,调用此的函数,菜单对象
    function cal_hideElementAll(obj){
            cal_HideElement("IMG",obj);
            cal_HideElement("SELECT",obj);
            cal_HideElement("OBJECT",obj);
            cal_HideElement("IFRAME",obj);
    }
    function cal_HideElement(strElementTagName,obj){
    try{
        var showDivElement = obj;
        var calendarDiv = obj;
        var intDivLeft = cal_GetOffsetLeft(showDivElement);
        var intDivTop = cal_GetOffsetTop(showDivElement);//+showDivElement.offsetHeight;
        //HideElementTemp=new Array()
        for(i=0;i<window.document.all.tags(strElementTagName).length; i++){
     var objTemp = window.document.all.tags(strElementTagName)[i];
     if(!objTemp||!objTemp.offsetParent)
         continue;
     var intObjLeft=cal_GetOffsetLeft(objTemp);
     var intObjTop=cal_GetOffsetTop(objTemp);
     if(((intObjLeft+objTemp.clientWidth)>intDivLeft)&&
        (intObjLeft<intDivLeft+calendarDiv.style.posWidth)&&
        (intObjTop+objTemp.clientHeight>intDivTop)&&
        (intObjTop<intDivTop+calendarDiv.style.posHeight)){
         //var intTempIndex=HideElementTemp.length;//已经有的长度
      //save elementTagName is stutas
         //HideElementTemp[intTempIndex]=new Array(objTemp,objTemp.style.visibility);
         HideElementTemp[HideElementTemp.length]=objTemp
         objTemp.style.visibility="hidden";
            }
        }
    }catch(e){alert(e.message)
    }
    }

    function cal_ShowElement(){
        var i;
        for(i=0;i<HideElementTemp.length; i++){
     var objTemp = HideElementTemp[i]
     if(!objTemp||!objTemp.offsetParent)
         continue;
     objTemp.style.visibility=''
        }
        HideElementTemp=new Array();
    }
    function cal_GetOffsetLeft(src){
        var set=0;
        if(src && src.name!="divMain"){
            if (src.offsetParent){
               set+=src.offsetLeft+cal_GetOffsetLeft(src.offsetParent);
     }
     if(src.tagName.toUpperCase()!="BODY"){
         var x=parseInt(src.scrollLeft,10);
         if(!isNaN(x))
                set-=x;
     }
        }
        return set;
    }

    function cal_GetOffsetTop(src){
        var set=0;
        if(src && src.name!="divMain"){
            if (src.offsetParent){
                set+=src.offsetTop+cal_GetOffsetTop(src.offsetParent);
       }
     if(src.tagName.toUpperCase()!="BODY"){
         var y=parseInt(src.scrollTop,10);
         if(!isNaN(y))
      set-=y;
     }
        }
        return set;
    }

    </script>
    <select></select>
    <select></select>
    <div style="position:absolute;left:0;top:0;width:100;height:100;background-color:red" onclick="cal_hideElementAll(this)">
    点击让select隐藏
    </div>
    <br/><br/><br/><br/><br/><br/>
    <input type="button" value="点击让select显示" onclick="cal_ShowElement()">

    以上这种方法,如果对于select框数目少,相对固定的话,直接用obj.style.visibility="hidden"这样进行隐藏是更直接的.

    2.Object对象的优先度较高,可以挡住select框

    <OBJECT id=aa style="display:none;z-index:1000; position:absolute; top:0; left:0; width:152; height: 200;" type="text/x-scriptlet" data="about:<body><div style='position:absolute;left:0;top:0;width:152;height:200;font:14;color:white;background:black;border:1 solid black'>test</div>"></OBJECT>
    <select><option>hellohellohellohello</select><button onclick=aa.style.display=aa.style.display=="none"?"":"none">test</button>

    这种方法虽然也简单,但对复杂的层是来说还不是好的解决方法

     

    3.用iframe作载体

    以下是一简单的例子:

    -----------

    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
    <meta name="GENERATOR" content="Microsoft FrontPage 4.0">
    <meta name="ProgId" content="FrontPage.Editor.Document">
    <title>简单菜单</title>
    <!--
    提供定位函数,用iframe作载体,不会被select挡住
    By Fason(2003-5-21)
    -->
    <style id=s>
    #div1{
    position:absolute;
    z-index:100;
    width:100;
    height:130;
    background-color:#d2e8ff;
    border:1 solid black;
    }
    div{cursor:hand;font-size:12px;}
    a{text-decoration:none;color:red;font-size:12px}
    </style>
    </head>
    <body>
    <script>
    function window.onload(){
    var shtml=div1.innerHTML;
    var ifm=document.createElement("<iframe frameborder=0 marginheight=0 marginwidth=0 hspace=0 vspace=0 scrolling=no></iframe>")
    ifm.style.width=div1.offsetWidth
    ifm.style.height=div1.offsetHeight
    ifm.name=ifm.uniqueID
    div1.innerHTML=""
    div1.appendChild(ifm)
    window.frames[ifm.name].document.write(s.outerHTML+"<body leftmargin=0 topmargin=0>"+shtml+"</body>")
    }

    function show(){
    with(document.all.img1){
    x=offsetLeft;
    y=offsetTop;
    objParent=offsetParent;
    while(objParent.tagName.toUpperCase()!= "BODY"){
    x+=objParent.offsetLeft;
    y+=objParent.offsetTop;
    objParent = objParent.offsetParent;
    }
    y+=offsetHeight-1
    }
    with(document.all.div1.style){
    pixelLeft=x
    pixelTop=y
    visibility=''
    }
    }
    function hide(){
    document.all.div1.style.visibility='hidden'
    }
    </script>
    <img id=img1 onmouseover="show()" onmouseout="hide()" src="ie.gif"><br/><select></select>
    <div id=div1 onmouseover="style.visibility=''" onmouseout="style.visibility='hidden'" style="visibility:hidden;">
    <div href="http://www.csdn.net" onmouseover="style.backgroundColor='highlight'" onmouseout="style.backgroundColor=''" onclick="window.open(href)">中国程序员</div>
    <div href="http://www.sohu.com" onmouseover="style.backgroundColor='highlight'" onmouseout="style.backgroundColor=''" onclick="window.open(href)">sohu</div>
    </div>
    </body>
    </html>

    网站开发中经常用到的javaScript技术

    1 >屏蔽功能类

    1.1 屏蔽键盘所有键
    <script language="javascript">
    <!--
    function document.onkeydown(){
       event.keyCode = 0;
       event.returnvalue = false;
    }
    -->
    </script>

    1.2 屏蔽鼠标右键

    在body标签里加上oncontextmenu=self.event.returnvalue=false

    或者

    <script language="javascript">
    <!--
    function document.oncontextmenu() 

      return false; 

    -->
    </script>

    function nocontextmenu()

        if(document.all) {
            event.cancelBubble=true;
            event.returnvalue=false; 
            return false; 
        }
    }

    或者

    <body onmousedown="rclick()" oncontextmenu= "nocontextmenu()">

    <script language="javascript">
    <!--
    function rclick()
    {
        if(document.all) {
            if (event.button == 2){
                event.returnvalue=false;
            }
        }
    }
    -->
    </script>


    1.3 屏蔽 Ctrl+N、Shift+F10、F5刷新、退格键

    <script language="javascript">
    <!--
      //屏蔽鼠标右键、Ctrl+N、Shift+F10、F5刷新、退格键
    function window.onhelp(){return false} //屏蔽F1帮助
    function KeyDown(){
      if ((window.event.altKey)&&
          ((window.event.keyCode==37)||   //屏蔽 Alt+ 方向键 ←
           (window.event.keyCode==39))){  //屏蔽 Alt+ 方向键 →
         alert("不准你使用ALT+方向键前进或后退网页!");
         event.returnvalue=false;
         }

         /* 注:这还不是真正地屏蔽 Alt+ 方向键,
         因为 Alt+ 方向键弹出警告框时,按住 Alt 键不放,
         用鼠标点掉警告框,这种屏蔽方法就失效了。以后若
         有哪位高手有真正屏蔽 Alt 键的方法,请告知。*/

      if ((event.keyCode == 8) && 
          (event.srcElement.type != "text" && 
          event.srcElement.type != "textarea" && 
          event.srcElement.type != "password") ||           //屏蔽退格删除键   
          (event.keyCode==116)||                            //屏蔽 F5 刷新键
          (event.ctrlKey && event.keyCode==82)){            //Ctrl + R
         event.keyCode=0;
         event.returnvalue=false;
         }
      if ((event.ctrlKey)&&(event.keyCode==78))   //屏蔽 Ctrl+n
         event.returnvalue=false;
      if ((event.shiftKey)&&(event.keyCode==121)) //屏蔽 shift+F10
         event.returnvalue=false;
      if (window.event.srcElement.tagName == "A" && window.event.shiftKey) 
          window.event.returnvalue = false;  //屏蔽 shift 加鼠标左键新开一网页
      if ((window.event.altKey)&&(window.event.keyCode==115)){ //屏蔽Alt+F4
          window.showModelessDialog("about:blank","","dialogWidth:1px;dialogheight:1px");
          return false;}
      }
     /* 另外可以用 window.open 的方法屏蔽 IE 的所有菜单
    第一种方法:
      window.open("你的.htm", "","toolbar=no,location=no,directories=no,menubar=no,scrollbars=no,resizable=yes,status=no,top=0,left=0")
    第二种方法是打开一个全屏的页面:
      window.open("你的.asp", "", "fullscreen=yes")
     */
    //-->
    </script>

    1.4屏蔽浏览器右上角“最小化”“最大化”“关闭”键

    <script language=javascript>
    function window.onbeforeunload()
    {
      if(event.clientX>document.body.clientWidth&&event.clientY<0||event.altKey)
      {
        window.event.returnvalue = "";
      }
    }
    </script>

    或者使用全屏打开页面

    <script language="javascript">
    <!--
    window.open(www.32pic.com,"32pic","fullscreen=3,height=100, width=400, top=0, left=0, toolbar=no, menubar=no, scrollbars=no, resizable=no,location=no, status=no");
    -->
    </script>

    注:在body标签里加上onbeforeunload="javascript:return false"(使不能关闭窗口)

    1.5屏蔽F5键

    <script language="javascript">
    <!--
    function document.onkeydown() 

        if ( event.keyCode==116) 
        { 
            event.keyCode = 0; 
            event.cancelBubble = true; 
            return false; 
        }
    }
    -->
    </script>

    1.6屏蔽IE后退按钮

    在你链接的时候用 <a href="javascript:location.replace(url)">

    1.7屏蔽主窗口滚动条

    在body标签里加上 style="overflow-y:hidden"

    1.8 屏蔽拷屏,不断地清空剪贴板

    在body标签里加上onload="setInterval('clipboardData.setData(\'Text\',\'\')',100)"

    1.9 屏蔽网站的打印功能

    <style>
    @media print {
       * { display: none }
    }
    </style>

    1.10 屏蔽IE6.0 图片上自动出现的保存图标

    方法一:
    <META HTTP-EQUIV="imagetoolbar" CONTENT="no">
    方法二:
    <img galleryimg="no">

    1.11 屏蔽页中所有的script

    <noscrript></noscript>

    2 >表单提交验证类

    2.1 表单项不能为空

    <script language="javascript">
    <!--
    function CheckForm()
    {
    if (document.form.name.value.length == 0) {
       alert("请输入您姓名!");
       document.form.name.focus();
       return false;
    }
       return true;
    }
    -->
    </script>

    2.2 比较两个表单项的值是否相同

    <script language="javascript">
    <!--
    function CheckForm()
    if (document.form.PWD.value != document.form.PWD_Again.value) {
       alert("您两次输入的密码不一样!请重新输入.");
       document.ADDUser.PWD.focus();
       return false;
    }
       return true;
    }
    -->
    </script>

    2.3 表单项只能为数字和"_",用于电话/银行帐号验证上,可扩展到域名注册等

    <script language="javascript">
    <!--
    function isNumber(String)

        var Letters = "1234567890-"; //可以自己增加可输入值
        var i;
        var c;
          if(String.charAt( 0 )=='-')
     return false;
          if( String.charAt( String.length - 1 ) == '-' )
              return false;
         for( i = 0; i < String.length; i ++ )
         {
              c = String.charAt( i );
       if (Letters.indexOf( c ) < 0)
              return false;
    }
         return true;
    }
    function CheckForm()
    {
        if(! isNumber(document.form.TEL.value)) {
      alert("您的电话号码不合法!");
             document.form.TEL.focus();
             return false;
    }
    return true;
    }
    -->
    </script>


    2.4 表单项输入数值/长度限定

    <script language="javascript">
    <!--
    function CheckForm() 
    {
        if (document.form.count.value > 100 || document.form.count.value < 1)
    {
     alert("输入数值不能小于零大于100!");
     document.form.count.focus();
     return false;
    }
        if (document.form.MESSAGE.value.length<10)
    {
     alert("输入文字小于10!");
     document.form.MESSAGE.focus();
     return false;
    }
    return true;
    }
    //-->
    </script>

    2.5 中文/英文/数字/邮件地址合法性判断

    <SCRIPT LANGUAGE="javascript">
    <!--

    function isEnglish(name) //英文值检测
    {
     if(name.length == 0)
      return false;
     for(i = 0; i < name.length; i++) {
      if(name.charCodeAt(i) > 128)
       return false;
     }
     return true;
    }

    function isChinese(name) //中文值检测
    {
     if(name.length == 0)
      return false;
     for(i = 0; i < name.length; i++) {
      if(name.charCodeAt(i) > 128)
       return true;
     }
     return false;
    }

    function isMail(name) // E-mail值检测
    {
     if(! isEnglish(name))
      return false;
     i = name.indexOf("@");
     j = name.lastIndexOf("@");
     if(i == -1)
      return false;
     if(i != j)
      return false;
     if(i == name.length)
      return false;
     return true;
    }

    function isNumber(name) //数值检测
    {
     if(name.length == 0)
      return false;
     for(i = 0; i < name.length; i++) {
      if(name.charAt(i) < "0" || name.charAt(i) > "9")
       return false;
     }
     return true;
    }

    function CheckForm()
    {
     if(! isMail(form.Email.value)) {
      alert("您的电子邮件不合法!");
      form.Email.focus();
      return false;
     }
     if(! isEnglish(form.name.value)) {
      alert("英文名不合法!");
      form.name.focus();
      return false;
     }
     if(! isChinese(form.cnname.value)) {
      alert("中文名不合法!");
      form.cnname.focus();
      return false;
     }
     if(! isNumber(form.PublicZipCode.value)) {
      alert("邮政编码不合法!");
      form.PublicZipCode.focus();
      return false;
     }
     return true;
    }
    //-->
    </SCRIPT>

    2.6 限定表单项不能输入的字符

    <script language="javascript">
    <!--

    function contain(str,charset)// 字符串包含测试函数
    {
      var i;
      for(i=0;i<charset.length;i++)
      if(str.indexOf(charset.charAt(i))>=0)
      return true;
      return false;
    }

    function CheckForm()
    {
     if ((contain(document.form.NAME.value, "%\(\)><")) || (contain(document.form.MESSAGE.value, "%\(\)><")))
    {
      alert("输入了非法字符");
      document.form.NAME.focus();
      return false;
    }
      return true;
    }
    //-->
    </script>

    取得TABLE元素的个数 

    <base href="http://www.flash8.net">
      <div id=demo style=overflow:hidden;height:100;width:90;background:#214984;color:#ffffff><div id=demo1>
      <img src="http://guoblog.com/images/guoblog_logo.gif">
      <img src="http://guoblog.com/images/guoblog_logo.gif">
      <img src="http://guoblog.com/images/guoblog_logo.gif">
      <img src="http://guoblog.com/images/guoblog_logo.gif">
      <img src="http://guoblog.com/images/guoblog_logo.gif">
      <img src="http://guoblog.com/images/guoblog_logo.gif">
      <img src="http://guoblog.com/images/guoblog_logo.gif">
      <img src="http://guoblog.com/images/guoblog_logo.gif">
      <img src="http://guoblog.com/images/guoblog_logo.gif">
      </div>
      <div id=demo2></div>
      </div>
        <script>
        var speed=30
        demo2.innerHTML=demo1.innerHTML
        demo.scrollTop=demo.scrollHeight
        function Marquee(){
        if(demo1.offsetTop-demo.scrollTop>=0)
        demo.scrollTop+=demo2.offsetHeight
        else{
        demo.scrollTop--
        }
        }
        var MyMar=setInterval(Marquee,speed)
        demo.onmouseover=function() {clearInterval(MyMar)}
        demo.onmouseout=function() {MyMar=setInterval(Marquee,speed)}
        </script>


    本人屏蔽Alt键的方法(仅供参考,欢迎大家一起讨论,我的QQ:30836570)
    <script language="javascript">
    <!--
    function document.onkeydown(){
      if ((window.event.altKey)&&
          ((window.event.keyCode==37)||   //屏蔽 Alt+ 方向键 ←
           (window.event.keyCode==39))){  //屏蔽 Alt+ 方向键 →
        window.location.reload();
            window.open();
         }
    }
    -->
    </script>

    <SCRIPT LANGUAGE=javascript>
    function click() {
    alert('这是禁止选择') }
    function click1() {
    if (event.button==2) {alert('这是禁止右键') }}
    function CtrlKeyDown(){
    if (event.ctrlKey) {alert('这是禁止CTRL键') }}
    document.onkeydown=CtrlKeyDown;
    document.onselectstart=click;
    document.onmousedown=click1;
    </SCRIPT>

     


    <body onkeydown="if (event.altKey) {alert('禁用ALT键');return false;}">
    这样就OK了!:P

     

    隐藏网页中的“关闭、最大化、最小化”按钮,也可这样:
    <body onload="window.moveTo(0, -24)">

    统计一串字符中含有某字符个数的方法

    方法1:用split分成数组
    <%
    strings = "lskdjfls,sjdfojsdf,sdfsodjfosd"
    myArray=split(strings,",")
    count=uBound(myArray)
    Response.write strings & "中有" & count & "个','"
    %>

    方法2:用Mid 函数
    <%strings = "slkjdfljs;df,sskdjfs,fsdfj"
    dim count
    count = 0
    For i=1 to len(strings)
      if Mid(strings,i,1) = "," then
        count = count + 1
      end if
    Next
    Response.write strings & "中有" & count & "个';'"
    %>

    方法3:这个不懂,学习ing
    <SCRIPT LANGUAGE="vbScript">
    dim re,str
    set re = new RegExp
    str = "a,b,c"
    re.global =true
    re.Pattern=","
    alert(re.Execute(str).count)
     </SCRIPT>

    将文本文件的内容调入html中并实现对文字配色的设置

    可以用TDC实现数据绑定

    databind.htm

    <OBJECT id="baobao" CLASSID="clsid:333C7BC4-460F-11D0-BC04-0080C7055A83">
     <PARAM NAME="DataURL" VALUE="data.txt">
     <PARAM NAME="UseHeader" VALUE="True">
    </OBJECT>
    <TABLE datasrc="#baobao">
    <TBODY>
    <TR><TD style="color:red"><DIV datafld="TxtData"></DIV></TD></TR>
    </TBODY>
    </TABLE>

    data.txt
    TxtData
    这是要在网页显示的内容.