标签 ‘javascript’

IE下遇到奇怪的问题:左击event.button为0!

一月 15th, 2010 由 Rock 发表

判断鼠标左击方法是:
isLeftClick: function(ev) {
return (((ev.which)
&& (ev.which === 1)) || ((ev.button) && (ev.button === 1)));
}

但今天对话框在IE下左击的时候,event.button却是0,

isLeftClick返回了false,ie6-ie8均如此,

用IE8调试了一下

event.type = ‘click’;没错,,
event.button = 0;
据我所了解,为0即表示无按键,我左击了难道无形中消失了?
我并没有设置setTimeout作延迟,按理还在click事件处理的回调中的,也就是window.event未被重设.

学习前端开发的一些建议

一月 9th, 2010 由 Rock 发表

前端开发越来越流行,即使做嵌入式开发的也可能与B/S沾个边了.

群上有朋友问到,学习JavaScript有路数吗?

我心里立刻浮现出”条条大路通罗马”这句经典,但再仔细想想,这句泛泛而谈,没说服力,就着自己这两年学习JavaScript的经验,花了分许时间,打出了几行字:

1. 看一本权威的书, 推荐《JavaScript权威指南》(非广告-_-!!)

2. 给自己选一个实践的目标,如利用JS做一个UI,或实现某一效果,或做一个小游戏

3. 遇到问题先查查资料书,再g.cn,不行再google.com,最后才是问人

4. 多看看同行的BLOG,里面有不少经验分享

5. 学会调试,推荐Firefox的firebug插件

5. 平时勤于独立思考,试试用不同方式模式实现同一效果

完毕,但总觉得缺点什么,直到在森林群上丸子发来的一个链接,描述“Nicholas C. Zakas如何面试前端工程师”的,才大悟,原来上面我只给出了“如何去做”,但还没说明“要做什么”,看了为之漫笔的这篇译文,从Nicholas C. Zakas的话中引一段过来,丰富了实体,完美了!

如果下面某条还没弄明白,那把好关,把每条都弄个透切先

* DOM结构——两个节点之间可能存在哪些关系以及如何在节点之间任意移动。

* DOM操作——怎样添加、移除、移动、复制、创建和查找节点。

* 事件——怎样使用事件以及IE和DOM事件模型之间存在哪些主要差别。

* XMLHttpRequest——这是什么、怎样完整地执行一次GET请求、怎样检测错误。

* 严格模式与混杂模式——如何触发这两种模式,区分它们有何意义。

* 盒模型——外边距、内边距和边框之间的关系,IE < 8中的盒模型有什么不同。

* 块级元素与行内元素——怎么用CSS控制它们、它们怎样影响周围的元素以及你觉得应该如何定义它们的样式。

* 浮动元素——怎么使用它们、它们有什么问题以及怎么解决这些问题。

* HTML与XHTML——二者有什么区别,你觉得应该使用哪一个并说出理由。

* JSON——它是什么、为什么应该使用它、到底该怎么使用它,说出实现细节来。

附:

Nicholas C. Zakas文章为之漫笔的译文:http://www.cn-cuckoo.com/2010/01/08/how-nicholas-c-zakas-interviewing-the-front-end-engineer-1332.html

Google开放了其内部JS开发工具:Closure Tools

十一月 6th, 2009 由 Rock 发表

在全球,有数百万的用户在使用Google的JavaScript-intensive应用,如GmailGoogle DocsGoogle Maps。相信每个开发者都希望创建Web应用程序能变的更easy。哈哈,有个相当好的消息告诉大家,Google今天开放了Closure Tools,一套内部人员开发JavaScript的工具!上面提到了那几个Google应用都是使用此工具开发的。

Closure Tools中包含三套工具:

1。Closure Compiler

Closure Compiler是用来编译JavaScript的编译器,除了像最常见的JavaScript的压缩机提供的功能,它还会对程序进行分析,把不需要的部份移除,减少的JavaScript程序的大小及提升效率。你可以将compiler与Closure Inspector(Firebug的扩展)一起使用。

由于JavaScript的开发是多样化的,所以建立了很多种运行的方式:提供了一个开源的命令行工具 ;创建了一个Web应用程序,你可以使用text box或一个RESTful API来编译;还提供了一个Firefox的扩展,可以与Page Speed 一起使用,方便查看网页的性能优势。

2。Closure Library

Closure Library是一个广泛,测试良好,模块化,跨浏览器的JavaScript库。它是Google的标准JavaScript类库.这套工具就像是许多一般的JavaScript框架,提供了许多简化的DOM操作的函数库,还有丰富的用户界面组件。

3。Closure Templates

这套工具就是提供一个在JavaScript中做模板的机制及函数库,你可以把HTML布局的部份写成一个*.soy,接着再利用封闭模板提供的工具把这个模板档案编译成的JavaScript代码,这样JavaScript开发者便省去了很多处理页面的负担。
Closure Templates既可以实现JavaScript,又可以实现Java,所以你可以在服务器端和客户端使用相同的模板。

心动不如行动,赶紧去看看吧!

来自: googlecode

Rock :

1. Closure Compiler, 优化和压缩代码的。玩过YUI Compressor的可以玩玩这个,对比一下。

The Closure Compiler is a tool for making JavaScript download and run faster. It is a true compiler for JavaScript. Instead of compiling from a source language to machine code, it compiles from JavaScript to better JavaScript. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what’s left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls.

2。Closure Library

Google页面的JS代码都是压得不成代码样的,现在JS的库和UI都开源了,可以看看Google的工程师是怎么写的。

3. Closure Templates : 这个是否很有效率?

如何有效的捕获JavaScript焦点

八月 14th, 2009 由 Rock 发表

阅读本文可理解并解决以下问题:

  • 设置元素可获得焦点以监听键盘事件
  • 某个元素明明设置了聚焦却没效果
  • 聚焦时抛出异常的

1. 设置元素可获得焦点以监听键盘事件

元素聚焦最大好处就是可允许发送键盘事件,HTML很多元素默认就有可聚焦,如form表单元素,a锚链接等,但大部份默认是不能聚焦的。要使得元素能够聚焦,可以在HTML代码或JavaScript脚本中实现。

html:

  <div tabIndex="0" style="height:100px;width:100px; background:red;"></div>

JavaScript:
oDiv.tabIndex = 0;

其中tabIndex是TAB键的导航顺序,可有正,负或零。

当元素获得焦点时会有边框指示,如果想不显示这个边框,可以

html:

<div tabIndex="0"  hidefocus="on" ></div>

JavaScript:
oDiv.hideFocus = ‘on’;

2. 元素明明设置了聚焦却没效果

有时用JavaScript设置了元素聚焦,但最后焦点却不落在该元素上,百思不得其解。

问题在于如果在可焦点元素的事件处理函数中聚焦其它元素,就有可能聚不了焦点,因为如果该事件是个可获得焦点的事件,如mouse, keydow(keypress)等等,在这些事件的处理函数内直接聚焦其它元素是失败的。

oDiv.onmousedown = function(){
   document.getElementById('ipt').focus();
};

参考浏览器内核处理流程图:

当浏览器第一次Reflow回流后,焦点停在另一个元素上,但回流返回后,事件处理后默认的操作将继续执行,那就是聚焦到事件源,也就是mousedown的元素,这时引发第二次回流,当回流后焦点聚在该元素上.所以在事件处理函数中的聚焦变得无效.

有没解决方法? 答案是肯定的. 由图可知,只要把聚焦放到第二个Reflow回流之后执行即可.这个可利用setTimeout方法作延迟先放进队列等后再执行.因为由于JavaScript引擎单线程特性,图上整个过程都是连着执行的,该过程中JS引擎一直没有空闲过,当上面所有操作都完成后并后,定时回调才有机会被执行.所以可以:

oDiv.onmousedown = function(){
  setTimeout(function(){
      document.getElementById('ipt').focus();
   }, 0);
};

由上可知,最后那个毫秒数即使设为0也没关系.

3. 聚焦时抛出异常的
在IE中,当元素不可见时如果聚焦的话,会抛出一个异常,因为在很多应用中我们往往不再对元素是否不可见作测试就聚焦了,因为即使这样也没什么问题(谁说不可见元素就不可以聚焦的?)..所以,在IE下可用try{}catch(){}来忽略这个异常.

   try{
     element.focus();
   }catch(e){}

到此,与JavaScript焦点捕获相关的问题讨论就完成了.点击运行示例.

注意 JavaScript ‘delete 属性’ 的使用

八月 11th, 2009 由 Rock 发表

JavaScript对象数据结构基本形式:{ key : value},其中key:value就为对象的一个属性,key作为属性名称,value为属性值,这值可以是任何JavaScript数据类型。
delete 是删除对象的一个属性,例如对于一个对象,

var obj = {key:5};

delete obj.key就是删除该对象的key属性,这个没什么问题,但当对象的原型prototype对象也存在该属性时,就值得注意了。

 var A = function(){};
 A.prototype.testMe = true;
 var a = new A();
 //覆盖原型属性
 a.testMe = true;
 if(a.testMe){
  // 一些关键代码... 
  // ....
  //删除这属性
  delete a.testMe;
}
//第二段 ---------------------------
// 在其它模块中
 if(a.testMe){
  // 一些关键代码... 
  // ....
}

第二段是值得注意的,不要以为a中testMe已尼删除了就不存在了,所以a.testMe就为undefined,即为假,其实它通过原型访问还是存在的,还是true!
这里不留神就中招了。

//附:
检测对象是否存在某属性, 包括原型链的:
if (’attrName’ in obj)…
检测对象是否存在某属性,是对象本身的,而非原型链的:
obj.hasOwnProperty(’attrName’)

TechCrunch:火狐浏览器下载量将突破10亿

七月 31st, 2009 由 Rock 发表
周四上午,火狐浏览器的下载量已达9.99亿,很可能将于周五突破10亿。

Mozilla已经为这一历史性的时刻准备了一个新网站http://www.onebillionplusyou.com,该网站将于下周一上线,给出有关火狐浏览器10亿次下载的详细信息。过去几年中,火狐浏览器从IE手中抢夺了大量市场份额。最新数据显示,IE的市占率仅约为54%,而火狐的市占率接近30%。这在几年前是不可想象的,当时IE的市占率维持在90%以上。

来自: sohu.com

Rock  : 火狐是一个不错的浏览器,一直在进步,可喜可贺 。IE,Chrome, Firefox都用过,最终还是选择了Firefox, IE换内核吧,换了才有质的改变。win 7快点推出吧,都等不及了,一年半后IE8的人数会有质的提升,国内IE6还很顽固,作为WEB开发专题站点,本站IE6访问占了IE的43%。




DOM标准与IE的html元素事件模型区别

七月 30th, 2009 由 Rock 发表

事件

HTML元素事件是浏览器内在自动产生的,当有事件发生时html元素会向外界(这里主要指元素事件的订阅者)发出各种事件,如click,onmouseover,onmouseout等等。

DOM事件流

DOM(文档对象模型)结构是一个树型结构,当一个HTML元素产生一个事件时,该事件会在元素结点与根结点之间的路径传播,路径所经过的结点都会收到该事件,这个传播过程可称为DOM事件流。

主流浏览器的事件模型

早在2004前在HTML元素事件的订阅,发送,传播,处理模型上各浏览器实现并不一致,直到DOM Level3中规定后,多数主流浏览器才陆陆续续支持DOM标准的事件处理模型 — 捕获型与冒泡型。
目前除IE浏览器外,其它主流的Firefox, Opera, Safari都支持标准的DOM事件处理模型。IE仍然使用自己的模型,即冒泡型,它模型的一部份被DOM采用,这点对于开发者来说也是有好处的,只使用DOM标准,IE都共有的事件处理方式才能有效的跨浏览器。

冒泡型事件(Bubbling)

这是IE浏览器对事件模型的实现,也是最容易理解的,至少笔者觉得比较符合实际的。冒泡,顾名思义,事件像个水中的气泡一样一直往上冒,直到顶端。从DOM树型结构上理解,就是事件由叶子结点沿祖先结点一直向上传递直到根结点;从浏览器界面视图HTML元素排列层次上理解就是事件由具有从属关系的最确定的目标元素一直传递到最不确定的目标元素.

捕获型事件(Capturing)

Netscape Navigator的实现,它与冒泡型刚好相反,由DOM树最顶层元素一直到最精确的元素,这个事件模型对于开发者来说(至少是我..)有点费解,因为直观上的理解应该如同冒泡型,事件传递应该由最确定的元素,即事件产生元素开始。
但这个模型在某些情况下也是很有用的,接下来会讲解到。

DOM标准事件模型

因为两个不同的模型都有其优点和解释,DOM标准支持捕获型与冒泡型,可以说是它们两者的结合体。它可以在一个DOM元素上绑定多个事件处理器,并且在处理函数内部,this关键字仍然指向被绑定的DOM元素,另外处理函数参数列表的第一个位置传递事件event对象。

首先是捕获式传递事件,接着是冒泡式传递,所以,如果一个处理函数既注册了捕获型事件的监听,又注册冒泡型事件监听,那么在DOM事件模型中它就会被调用两次。

注册与移除事件监听器

注册事件监听器,或又称订阅事件,当元素事件发生时浏览器回调该监听函数执行事件处理。目前主流浏览器中有两种注册事件的方法,一种是IE浏览器的,另一种是DOM标准的。

1.直接JS或HTML挂载法

<div onclick="alert(this.innerHTML);"></div>
  element.onclick = function(){alert(this.innerHTML);}

移除时将事件属性设为nul即可,这个也是最常用的方法了,优缺点也是显然的:

  • 简单方便,在HTML中直接书写处理函数的代码块,在JS中给元素对应事件属性赋值即可
  • IE与DOM标准都支持的一种方法,它在IE与DOM标准中都是在事件冒泡过程中被调用的。
  • 可以在处理函数块内直接用this引用注册事件的元素
  • 要给元素注册多个监听器,就不能用这方法了

2. IE下注册多个事件监听器与移除监听器方法

IE浏览器中HTML元素有个attachEvent方法允许外界注册该元素多个事件监听器,例如

  element.attachEvent('onclick', observer);

attachEvent接受两个参数。第一个参数是事件名称,第二个参数observer是回调处理函数。这里得说明一下,有个经常会出错的地方,IE下利用attachEvent注册的处理函数调用时this指向不再是先前注册事件的元素,这时的this为window对象了,笔者很奇怪IE为什么要这么做,完全看不出好处所在。
要移除先前注册的事件的监听器,调用element的detachEvent方法即可,参数相同。

    element.detachEvent('onclick', observer);

3. DOM标准下注册多个事件监听器与移除监听器方法

实现DOM标准的浏览器与IE浏览器中注册元素事件监听器方式有所不同,它通过元素的addEventListener方法注册,该方法既支持注册冒泡型事件处理,又支持捕获型事件处理。

  element.addEventListener('click', observer, useCapture);

addEventListener方法接受三个参数。第一个参数是事件名称,值得注意的是,这里事件名称与IE的不同,事件名称是没’on’开头的;第二个参数observer是回调处理函数;第三个参数注明该处理回调函数是在事件传递过程中的捕获阶段被调用还是冒泡阶段被调用

移除已注册的事件监听器调用element的removeEventListener即可,参数不变.

  element.removeEventListener('click', observer, useCapture);

跨浏览器的注册与移除元素事件监听器方案

弄清楚DOM标准与IE的注册元素事件监听器之间的异同后,就可以实现一个跨浏览器的注册与移除元素事件监听器方案:

  //注册
  function addEventHandler(element, evtName, callback, useCapture) {
     //DOM标准
      if (element.addEventListener) {
            element.addEventListener(evtName, callback, useCapture);
      } else {
         //IE方式,忽略useCapture参数
         element.attachEvent('on' + evtName, callback);
      }
  }
 
  //移除
  //注册
  function removeEventHandler(element, evtName, callback, useCapture) {
     //DOM标准
      if (element.removeEventListener) {
            element.removeEventListener(evtName, callback, useCapture);
      } else {
         //IE方式,忽略useCapture参数
         element.dettachEvent('on' + evtName, callback);
      }
  }

如何取消浏览器事件的传递与事件传递后浏览器的默认处理

先说明取消事件传递与浏览器事件传递后的默认处理是两个不同的概念,可能很多同学朋友分不清,或者根本不存在这两个概念。

取消事件传递是指,停止捕获型事件或冒泡型事件的进一步传递。例如上图中的冒泡型事件传递中,在body处理停止事件传递后,位于上层的document的事件监听器就不再收到通知,不再被处理。

事件传递后的默认处理是指,通常浏览器在事件传递并处理完后会执行与该事件关联的默认动作(如果存在这样的动作)。例如,如果表单中input type 属性是 “submit”,点击后在事件传播完浏览器就就自动提交表单。又例如,input 元素的 keydown 事件发生并处理后,浏览器默认会将用户键入的字符自动追加到 input 元素的值中。

要取消浏览器的件传递,IE与DOM标准又有所不同。

在IE下,通过设置event对象的cancelBubble为true即可。

  function someHandle() {
     window.event.cancelBubble = true;
  }

DOM标准通过调用event对象的stopPropagation()方法即可。

  function someHandle(event) {
     event.stopPropagation();
  }

因些,跨浏览器的停止事件传递的方法是:

  function someHandle(event) {
    event = event || window.event;
    if(event.stopPropagation)
       event.stopPropagation();
    else event.cancelBubble = true;
  }

取消事件传递后的默认处理,IE与DOM标准又不所不同。

在IE下,通过设置event对象的returnValue为false即可。

  function someHandle() {
     window.event.returnValue = false;
  }

DOM标准通过调用event对象的preventDefault()方法即可。

  function someHandle(event) {
     event.preventDefault();
  }

因些,跨浏览器的取消事件传递后的默认处理方法是:

  function someHandle(event) {
    event = event || window.event;
    if(event.preventDefault)
       event.preventDefault();
    else event.returnValue = false;
  }

捕获型事件模型与冒泡型事件模型的应用场合

标准事件模型为我们提供了两种方案,可能很多朋友分不清这两种不同模型有啥好处,为什么不只采取一种模型。
这里抛开IE浏览器讨论(IE只有一种,没法选择)什么情况下适合哪种事件模型。

1. 捕获型应用场合

捕获型事件传递由最不精确的祖先元素一直到最精确的事件源元素,传递方式与操作系统中的全局快捷键与应用程序快捷键相似。当一个系统组合键发生时,如果注册了系统全局快捷键监听器,该事件就先被操作系统层捕获,全局监听器就先于应用程序快捷键监听器得到通知,也就是全局的先获得控制权,它有权阻止事件的进一步传递。所以捕获型事件模型适用于作全局范围内的监听,这里的全局是相对的全局,相对于某个顶层结点与该结点所有子孙结点形成的集合范围。

例如你想作全局的点击事件监听,相对于document结点与document下所有的子结点,在某个条件下要求所有的子结点点击无效,这种情况下冒泡模型就解决不了了,而捕获型却非常适合,可以在最顶层结点添加捕获型事件监听器,伪码如下:

  function globalClickListener(event) {
     if(canEventPass == false) {
         //取消事件进一步向子结点传递和冒泡传递
         event.stopPropagation();
         //取消浏览器事件后的默认执行
        event.preventDefault();
     }
 }

这样一来,当canEventPass条件为假时,document下所有的子结点click注册事件都不会被浏览器处理。

2. 冒泡型的应用场合

可以说我们平时用的都是冒泡事件模型,因为IE只支持这模型。这里还是说说,在恰当利用该模型可以提高脚本性能。在元素一些频繁触发的事件中,如onmousemove, onmouseover,onmouseout,如果明确事件处理后没必要进一步传递,那么就可以大胆的取消它。此外,对于子结点事件监听器的处理会对父层监听器处理造成负面影响的,也应该在子结点监听器中禁止事件进一步向上传递以消除影响。

综合案例分析

最后结合下面HTML代码作分析:

<div id="div0" onclick="alert('current is '+this.id)">
<div id="div1" onclick="alert('current is '+this.id)">
<div id="div2" onclick="alert('current is '+this.id)">
<div id="event_source" style="height: 200px; width: 200px; background-color: red;" onclick="alert('current is '+this.id)"></div>
</div>
</div>
</div>

HTML运行后点击红色区域,这是最里层的DIV,根据上面说明,无论是DOM标准还是IE,直接写在html里的监听处理函数是事件冒泡传递时调用的,由最里层一直往上传递,所以会先后出现
current is event_source
current is div2
current is div1
current is div0
current is body

添加以下片段:

  var div2 = document.getElementById('div2');
  addEventHandler(div2, 'click', function(event){
     event = event || window.event;
     if(event.stopPropagation)
       event.stopPropagation();
     else event.cancelBubble = true;
  }, false);

当点击红色区域后,根据上面说明,在泡冒泡处理期间,事件传递到div2后被停止传递了,所以div2上层的元素收不到通知,所以会先后出现:
current is event_source
current is div2

在支持DOM标准的浏览器中,添加以下代码:

   document.body.addEventListener('click', function(event){
       event.stopPropagation();
  }, true);

以上代码中的监听函数由于是捕获型传递时被调用的,所以点击红色区域后,虽然事件源是ID为event_source的元素,但捕获型选传递,从最顶层开始,body结点监听函数先被调用,并且取消了事件进一步向下传递,所以只会出现
current is body

建立基本的页面框架 — 第二步丰富西部面板

七月 29th, 2009 由 Rock 发表

最近有点忙,打算上周写完这篇的,但近几天来几乎没碰过电脑了..非常难得少见的一次-_-!!

上次讲了创建页面框架最基本的一步:利用简单有效的BorderLayoutd布局管理器作为整体的客户区域中南西北中布局,总体框架出来了,但具体的面板内容还比较空虚(或许是寂寞更适合点^-^),这一节讲解怎么利用库来丰富页面框架中西部面板.

西部面板一般是放分组菜单,可同时加入多个分组,分组可以是一个树控件,或列表控件,宽度不大,把这些分组看成行状放置,可以用库的行布局管理器布局这些分组.

库中目前的行布局管理器共有两个,CRowLayout类和CRowLayout2类,它们对容器子控件布置方式不同之处主要体现在控件的position属性,前者是static布置,后者是absolute,各有各的优点,这里主要讲解一下第二个,即CRowLayout2布局管理器,因为它比较适合用来布置西部面板.

RowLayout2布局管理器

RowLayout2布局方式与BorderLayout极为相似,纯JavaScript计算方式布局子控件,同样有分隔条.RowLayout2把子控件由上而下布局,每个子控件宽度与容器宽度一致,它们高度与分隔条高度之和始终等于容器高度,除最下边一个行组控件没分隔条外,其它的组都允许有分隔条灵活控制分组高度,而且当容器宽度改变时,各分组将获得均等的高度份额.

利用RowLayout2布局管理器布局西部面板的分组

要指定面板布局管理器为RowLayout2只需设置容器面板的layout属性为’row2′即可,非常简单!

  //此时西部面板的布局设为RowLayout2
  var westPanel = new CPanel({view : 'westPanel', width:191, maxW:450, layout:'row2'});

布局管理器已经生成了,下面来为面部面板添加两个分组,为了好看点,这两个分组都用CTitlePanel类控件,即具有标题栏的面板,并且其内容可以收缩或展开.

  var firstTitlePanel = new CTitlePanel({title:'顶部面板' ,height:210});
  westPanel.add(firstTitlePanel, {split:true});

上面那个高度:120只是提示布局管理器最好用这个高度来设置控件高度,实际中RowLayout2布局管理器设定的并一定是这个,视布局取用的策略而定.
add方法的第二个参数是该控件用于布局管理器布局的配置信息,这里split:true向RowLayout2指明该分组有可拖动的分隔条.
添加了一个标题栏面板控件,再往这面板里面添加分组内容,这里加的是库中的一个组控件.

  //增加一个组控件
  var f = CUtil.createFolder({title:'My Folders', array:[
    {title:'收件箱',icon:'icoIbx'},
    {title:'发件箱',icon:'icoDft'},
    {title:'草稿箱',icon:'icoNote'},
    {title:'清除记录',icon:'icoDel',disabled:true},
    {title:'粉红色',icon:'icoDft'},
    {title:'蓝色',icon:'icoNote'},
    {title:'清除记录',icon:'icoDel'}
 ]});
 
  //指定组f的滚动面板为firstTitilePanel,
  //这样组内选择后的子项会自动滚动到可视范围内.
  f.scrollor = firstTitlePanel;
 
 //添加到标题栏面板中
firstTitlePanel.add(f);

第二个分组是一个树型控件,添加方式与上面一致,只是由组控件换成树型控件.

  var secondTitlePanel = new CTitlePanel({title:'树型面板' });
  westPanel.add(secondTitlePanel);
  //往标题栏面板中增加一个树型控件 
  //先要创建根结点
  var tree1Root = new CTreeItem({
     //nodes=true说明是一个有子结点的结点
    nodes: true
   });
 
  var tree = new CTree({
      root: tree1Root,
      title: '树根目录',
      //加上下面两句就可以利用ajax加载子结点了
      //url:'/q?bg_q=test_group', 
      //autoConnect:true
   });
 
   tree.scrollor = secondTitlePanel;
   secondTitlePanel.add(tree);

同样道理,无需额外的工作,就可以添加任意多个分组,这里就不一一列出了.

演示地址:http://www.bgscript.com/bgjs/samples/basicframe2.html

建立基本的页面框架 — 第一步小试牛刀

七月 21st, 2009 由 Rock 发表

可分为几个步骤:

  • 创建HTML页面
  • 创建Viewport
  • 利用BorderLayout布局viewport
  • 放置tab或其它控件

什么是viewport?
viewport从结构上可以看成一个层,它布满整个浏览器客户区,当浏览器宽高(size)改变时会自动适应.从这个意义上来说,与EXT的viewport功能是一致的.在库中一个viewport是一个DIV结点,绝对方式定位,放在body下.
viewport的作用就是,当宽高改变时,可通知内部的布局管理器重新布局子控件.众所周知,当用CSS改变一个元素的宽高时在JavaScript端是得不到任何通知的,例如将一个CSS类作用到一个元素时,你不作测试的话没法得知其高度已经改变,它不像事件监听那样,可以给元素添加事件监听器,当有动作发生时得到通知.除非所有元素都通过CSS布局,但这是不够灵活的,试想用CSS实现BorderLayout布局,并具有其特性,始终还得通过脚本控制CSS属性来实现,此时就会遇到一个问题,既然容器宽高已经改变,就得重新布局子控件,如上所说,CSS改变脚本得不会通知,要重新布局子控件就必须自定容器的事件机制,当通过脚本来改变容器宽高时(这里再次强调是通过脚本,而非直接设置CSS),容器就发送一个表明宽高已改变的事件,并通知布局管理器重新布局子控件.

了解了viewport,清楚为什么要用viewport,就开始搭建一个最简单的最常用到的页面框架,简单起见,元素不多,北面一个面板+tab,西面一面板和中间一个面板.

1.创建HTML页面
HTML页面是添加面板的HTML内容,具体可参见示例,这里不一一列出.

1.创建viewport,指定布局为border layout

  var viewport = new CViewport({layout:'border'});

2.利用布局viewport,这里主要创建北,西,中三个面板,并在北面板中添加一个tab控件

  //添加北,西,中面板
  //
  var northPanel = new CPanel({view : 'northPanel', maxH:70,height:70});
  var westPanel = new CPanel({view : 'westPanel', width:191, maxW:450});
  //card布局:容器里面的元素宽高与窗口一致.
  var centerPanel = new CPanel({view : 'centerPanel', layout:'card'});
  //在北面板中添加Tab控件
  //itemAutoConnect : true TabItem当选择时自动加载
  //contentPanel:存放TabItem内容面板的父容器,
  //当TabItem未指定Panel时,自动生成的Panel就被添加到该容器中.
  var tab = new CTab({
      id:'fr-tab', 
      itemAutoConnect:true,
      contentPanel:centerPanel, 
      array:[
       {title:'桌面 - DIV', panel:CC.$('deskPanel')},
       {title:'谷歌 - Google - IFRAME', src:'http://www.google.com'},
       {title:'百度 - Baidu - IFRAME', src:'http://www.baidu.com'},
       {title:'背光脚本', src:'http://www.bgscript.com'},
       {title:'通过Ajax载入内容', url:'http://www.bgscript.com/q?bg_q=changelog'}
  ]});
 
  northPanel.add(tab);
  //
  // dir:north,指明为北边容器,gap:1,空隙为1, split:true指明允许分隔条
  //
  viewport.add(northPanel, {dir:'north', gap:0})
             .add(westPanel, {dir:'west', gap:1, split:true})
	     .add(centerPanel, {dir:'center'});
  //集中渲染显示
  viewport.render();

3.利用tab加载IFRAME页面示例

  //选择第一项
  tab.select(0);

至此,一个最基本的页面框架已经建好了!

演示地址:http://www.bgscript.com/bgjs/samples/basicframe.html

库JavaScript Tab控件应用文档

七月 20th, 2009 由 Rock 发表

说明:
Tab派生于选择容器类,由多个TabItem作为子项,TabItem有个panel作为显示页面,这个panel可以随意指定,在任意地方显示,除此以外,panel与Tab子项也没什么关系了.

依赖类文件:
如果打散发布,Tab类依赖以下文件类:
库:bglib.js
容器基类:container.js
Tab类:tab.js

Tab关键事件:
itemclick : 子项点击时触发
select : 子项选择前触发
selected : 子项选择后触发
close : 子项关闭前触发
closed : 子项关闭后触发

创建一个Tab:

  var tab = new CTab({showTo:document.body, autoRender:true});

添加TabItem项:

  tab.add(new CTabItem({title:'小强'}));

或通过数组添加子项:

  tab.fromArray([
    {title:'旺财'},
    {title:'小强', closeable:false}
  ]);

或在创建时通过指定array来添加子项:

  var tab = new CTab(
    showTo:document.body, 
    autoRender: true,
    array:[
    {title:'旺财'},
    {title:'小强'}
   ]);

或通过ajax json加载子项, 返回的json格式与array添加子项时格式一致:

  var tab = new CTab(
    showTo:document.body, 
    autoRender: true,
    autoConnect : true,
    url : 'http://www.bgscript/q?type=getTabItems'
   ]);

响应tab子项选择事件例子:

  tab.on('selected', function(item){
     alert('您选择了'+item.title+'项');
  });

响应tab子项关闭事件例子:

  tab.on('closed', function(item){
     alert('您关闭了'+item.title+'项');
  });

根据子项下标或子项ID选择子项:

  tab.select(0);
  tab.select('tabitemId');

结合子项panel例子,

<html>
<body>
  <div id="tab"></div>
  <div id="layer">
  <!-- 第一个tab子项的面板-->
  <div id="panel1">Tab 子项1的面板</div>
  <div id="panel2">Tab 子项2的面板</div>
  <div id="panel3">Tab 子项3的面板</div>
  </div>
</body>
<script>
  Event.ready(function(){
     var tab = new CTab(
        view:'tab' ,
        array:[
          {title:'子项1'}, panel:CC.$('panel1')},
          {title:'子项2'}, panel:CC.$('panel2')},
          {title:'子项3'}, panel:CC.$('panel3')}
        ]
     );
 
    tab.render();
  });
</script>
</html>

子项内容面板自动Ajax或IFRAME加载,只需在Tab控件中指定内容面板,设置itemAutoConnect即可:

   var contentPanel = new CPanel({view:'tabItemPanels'});
   var tab = new CTab({itemAutoConnect:true, contentPanel:contentPanel});
  //为Tab子项加多一个src属性,表明IFrame加载,url属性表示Ajax加载.
  tab.fromArray([
    {title:'Google', src:'http://www.google.com'},
    {title:'Bgscript', src:'http://www.bgscript.com'},
    {title:'Ajax Load', url:'http://www.bgscript.com/q?bg_q=changelog'}
  ]);

关于IFrame自动高度与滚动条问题
平时做WEB开发的时候,IFRAME在IE下自动高度通过CSS设置不了,(如果您有很好的方案,请告诉我,谢谢),不得不用脚本控制,如果IFRAME用多了控制就麻烦了. 在背光库中,解决方法是通过布局管理器来管理IFRAME的宽高,原理是当父容器宽高改变时更新子容器宽高.
就上面的例子,id为’layer’的DIV元素作为父容器, 引入CardLayout布局管理器,该布局管理器使得所有子控件在父容器宽高改变时同步自身宽高,使得子控件宽高始终与父容器保持一致.

   var contentPanel = new CPanel({view:'tabItemPanels', layout:'card'});
   var tab = new CTab({itemAutoConnect:true, contentPanel:contentPanel});
  //为Tab子项加多一个src属性,表明IFrame加载,url属性表示Ajax加载.
  tab.fromArray([
    {title:'Google', src:'http://www.google.com'},
    {title:'Bgscript', src:'http://www.bgscript.com'},
    {title:'Ajax Load', url:'http://www.bgscript.com/q?bg_q=changelog'}
  ]);

以后改变contentPanel宽高时,容器中的IFRAME就自动适应其高度.

查看演示:http://www.bgscript.com/bgjs/samples/tab.html

JavaScript可否实现多线程 —- 深入理解JavaScript定时机制

七月 16th, 2009 由 Rock 发表

容易欺骗别人感情的JavaScript定时器

JavaScript的setTimeout与setInterval是两个很容易欺骗别人感情的方法,因为我们开始常常以为调用了就会按既定的方式执行, 我想不少人都深有同感, 例如

setTimeout( function(){ alert(’你好!’); } , 0);
setInterval( callbackFunction , 100);

认为setTimeout中的问候方法会立即被执行,因为这并不是凭空而说,而是JavaScript API文档明确定义第二个参数意义为隔多少毫秒后,回调方法就会被执行. 这里设成0毫秒,理所当然就立即被执行了.
同理对setInterval的callbackFunction方法每间隔100毫秒就立即被执行深信不疑!

但随着JavaScript应用开发经验不断的增加和丰富,有一天你发现了一段怪异的代码而百思不得其解:

div.onclick = function(){
setTimeout( function(){document.getElementById(’inputField’).focus();}, 0);
};

既然是0毫秒后执行,那么还用setTimeout干什么, 此刻, 坚定的信念已开始动摇.

直到最后某一天 , 你不小心写了一段糟糕的代码:

setTimeout( function(){ while(true){} } , 100);
setTimeout( function(){ alert(’你好!’); } , 200);
setInterval( callbackFunction , 200);

第一行代码进入了死循环,但不久你就会发现,第二,第三行并不是预料中的事情,alert问候未见出现,callbacKFunction也杳无音讯!

这时你彻底迷惘了,这种情景是难以接受的,因为改变长久以来既定的认知去接受新思想的过程是痛苦的,但情事实摆在眼前,对JavaScript真理的探求并不会因为痛苦而停止,下面让我们来展开JavaScript线程和定时器探索之旅!

拔开云雾见月明

出现上面所有误区的最主要一个原因是:潜意识中认为,JavaScript引擎有多个线程在执行,JavaScript的定时器回调函数是异步执行的.

而事实上的,JavaScript使用了障眼法,在多数时候骗过了我们的眼睛,这里背光得澄清一个事实:

JavaScript引擎是单线程运行的,浏览器无论在什么时候都只且只有一个线程在运行JavaScript程序.

JavaScript引擎用单线程运行也是有意义的,单线程不必理会线程同步这些复杂的问题,问题得到简化.

那么单线程的JavaScript引擎是怎么配合浏览器内核处理这些定时器和响应浏览器事件的呢?
下面结合浏览器内核处理方式简单说明.

浏览器内核实现允许多个线程异步执行,这些线程在内核制控下相互配合以保持同步.假如某一浏览器内核的实现至少有三个常驻线程:javascript引擎线程,界面渲染线程,浏览器事件触发线程,除些以外,也有一些执行完就终止的线程,如Http请求线程,这些异步线程都会产生不同的异步事件,下面通过一个图来阐明单线程的JavaScript引擎与另外那些线程是怎样互动通信的.虽然每个浏览器内核实现细节不同,但这其中的调用原理都是大同小异.

由图可看出,浏览器中的JavaScript引擎是基于事件驱动的,这里的事件可看作是浏览器派给它的各种任务,这些任务可以源自JavaScript引擎当前执行的代码块,如调用setTimeout添加一个任务,也可来自浏览器内核的其它线程,如界面元素鼠标点击事件,定时触发器时间到达通知,异步请求状态变更通知等.从代码角度看来任务实体就是各种回调函数,JavaScript引擎一直等待着任务队列中任务的到来.由于单线程关系,这些任务得进行排队,一个接着一个被引擎处理.

上图t1-t2..tn表示不同的时间点,tn下面对应的小方块代表该时间点的任务,假设现在是t1时刻,引擎运行在t1对应的任务方块代码内,在这个时间点内,我们来描述一下浏览器内核其它线程的状态.

t1时刻:

GUI渲染线程:

该线程负责渲染浏览器界面HTML元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行.本文虽然重点解释JavaScript定时机制,但这时有必要说说渲染线程,因为该线程与JavaScript引擎线程是互斥的,这容易理解,因为JavaScript脚本是可操纵DOM元素,在修改这些元素属性同时渲染界面,那么渲染线程前后获得的元素数据就可能不一致了.

在JavaScript引擎运行脚本期间,浏览器渲染线程都是处于挂起状态的,也就是说被”冻结”了.

所以,在脚本中执行对界面进行更新操作,如添加结点,删除结点或改变结点的外观等更新并不会立即体现出来,这些操作将保存在一个队列中,待JavaScript引擎空闲时才有机会渲染出来.或许这里你有个疑问了,为什么JS代码块的alert时界面有更新的,JS不是正在运行吗?其实当alert发生时,浏览器内核就会挂起JavaScript引擎线程,并促使界面执行了更新.

GUI事件触发线程:

JavaScript脚本的执行不影响html元素事件的触发,在t1时间段内,首先是用户点击了一个鼠标键,点击被浏览器事件触发线程捕捉后形成一个鼠标点击事件,由图可知,对于JavaScript引擎线程来说,这事件是由其它线程异步传到任务队列尾的,由于引擎正在处理t1时的任务,这个鼠标点击事件正在等待处理.

定时触发线程:

注意这里的浏览器模型定时计数器并不是由JavaScript引擎计数的,因为JavaScript引擎是单线程的,如果处于阻塞线程状态就计不了时,它必须依赖外部来计时并触发定时,所以队列中的定时事件也是异步事件.

由图可知,在这t1的时间段内,继鼠标点击事件触发后,先前已设置的setTimeout定时也到达了,此刻对JavaScript引擎来说,定时触发线程产生了一个异步定时事件并放到任务队列中, 该事件被排到点击事件回调之后,等待处理.
同理, 还是在t1时间段内,接下来某个setInterval定时器也被添加了,由于是间隔定时,在t1段内连续被触发了两次,这两个事件被排到队尾等待处理.

可见,假如时间段t1非常长,远大于setInterval的定时间隔,那么定时触发线程就会源源不断的产生异步定时事件并放到任务队列尾而不管它们是否已被处理,但一旦t1和最先的定时事件前面的任务已处理完,这些排列中的定时事件就依次不间断的被执行,这是因为,对于JavaScript引擎来说,在处理队列中的各任务处理方式都是一样的,只是处理的次序不同而已.

t1过后,也就是说当前处理的任务已返回,JavaScript引擎会检查任务队列,发现当前队列非空,就取出t2下面对应的任务执行,其它时间依此类推,由此看来:

如果队列非空,引擎就从队列头取出一个任务,直到该任务处理完,即返回后引擎接着运行下一个任务,在任务没返回前队列中的其它任务是没法被执行的.

可以试试运行以下例子作测试,第一个定时器是一个死循环,第二个定时器是向界面输出一串字符串,第一个定时器比第二个定时器要来得早.

setTimeout(function(){
for(;;);
}, 50);
setTimeout(function(){alert(’Hello’);}, 51);

运行后可看出,在没浏览器没提示脚本繁忙前第二个定时器alert是不会弹出的,因为单线程关系,第一个定时器回调脚本未返回前第二个脚本是没机会运行.

相信您现在已经很清楚JavaScript是否可多线程,也了解理解JavaScript定时器运行机制了,下面我们来对一些案例进行分析:

案例1:setTimeout与setInterval

setTimeout(function(){
   /* 代码块... */
   setTimeout(arguments.callee, 10);
}, 10);
 
setInterval(function(){
   /*代码块... */
 }, 10);

这两段代码看一起效果一样,其实非也,第一段中回调函数内的setTimeout是JavaScript引擎执行后再设置新的setTimeout定时, 假定上一个回调处理完到下一个回调开始处理为一个时间间隔,理论两个setTimeout回调执行时间间隔>=10ms .第二段自setInterval设置定时后,定时触发线程就会源源不断的每隔十秒产生异步定时事件并放到任务队列尾,理论上两个setInterval回调执行时间间隔<=10.

案例2:ajax异步请求是否真的异步?

很多同学朋友搞不清楚,既然说JavaScript是单线程运行的,那么XMLHttpRequest在连接后是否真的异步?
其实请求确实是异步的,不过这请求是由浏览器新开一个线程请求(参见上图),当请求的状态变更时,如果先前已设置回调,这异步线程就产生状态变更事件放到JavaScript引擎的处理队列中等待处理,当任务被处理时,JavaScript引擎始终是单线程运行回调函数,具体点即还是单线程运行onreadystatechange所设置的函数.

封装Ajax系列之认识HTTP请求

七月 14th, 2009 由 Rock 发表

Ajax的好处就是在不刷新当前页面的情况下,可以默默的从浏览器后台异步发一个请求到服务器上,服务器响应该请求,并向浏览器返回一些有用的数据,浏览器处理脚本,即是所谓的回调,利用这些数据更新页面上应用。


我们把以上这个过程称为浏览器的一次异步HTTP请求。


要灵活使用jQuery的Ajax功能就得弄清楚什么是HTTP请求,以及请求是怎样响应的。
来看看完成这个过程需要什么条件:

1. 目标URL, 这个URL用于定位服务器上的资源。


2. 请求方式,POST或者GET。



初学者难以搞清POST与GET的区别,其实它们的差别主要在于浏览器端。

以GET方式发送的数据将被追加到URL中发送,常用来传递一些简单的数据,而实际上,URL不存在参数上限的问题,HTTP协议规范没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE对URL长度的限制是2083字节(2K+35)。对于其他浏览器,如Netscape、FireFox等,理论上没有长度限制,其限制取决于操作系统的支持。此外,由于数据放在URL中,所以会被缓存起来,故存在一定的安全风险。
以POST方式发送的数据作为HTTP消息的实体内容发送给服务器,并不是追加到URL中,故使用POST方式传递的数据量要比使用GET方式传送的数据量大的多。

3. 发送的数据。


这些数据往往来自表单中元素的数据,以键/值成对的方式组装,例如name=Rock&password=123。如果当前请求方式是GET,这些数据被追加到URL结尾;如果是POST方式,
数据将作为HTTP消息的实体内容发送。


4. 回调处理。


因为请求是异步执行的,所以你不清楚这个请求什么时候才能返回,就像寄出的一封信,你不知道什么时候有回信,直到邮递员自动把信送到家门口才得知。回调即是这样,请求发起前得向浏览器注册个“提醒”函数,当该请求完毕后返回数据后浏览器就调用执行刚才注册的函数,所以自然就得到了通知,可以对返回的数据进行处理。


世界是美好的,但不是完美的,我们希望次次请求都能顺顺利利,但应该也能容忍失败,回调也给了处理失败的机会,它会向回调函数传递几个状态,利用这些状态我们可以判断当前请求是成功还是失败的。


理解了HTTP请求处理所要的条件,再来看看浏览器为了完成这个请求给我们提供了什么样的方案。


XMLHttpRequest对象


浏览器内置的JavaScript异步传输对象XMLHttpRequest就是处理以上请求过程的完整实现。不过由于它太“底层”而利用起来比较麻烦。
以上处理过程几乎每一步都要明确的写出来。举个例子,要发送一个请求到服务器,询问服务器当前的时间,利用XMLHttpRequest对象处理为:

1. 获得XMLHttpRequest对象,IE与非IE浏览器获取方式不同。


2. 注册XMLHttpRequest对象的onReadyStateChange回调,也就是上面所说的“提醒”函数。


3. 调用对象的open方法,传递几个参数,method, url, async,即发送方式(GET/POST),URL与是否异步调用。
由于是请求返回时间,发送数据量很小,请求发送的信息放在URL中,这里method设为GET,URL假设为 /request?target=server_time


4. 调用对象的send方法,如果为POST并有数据,数据作为方法参数传递。


5. 处理回调函数中的状态。
在回调函数中必须判断请求的当前状态,成功或失败?
如果成功后读取XMLHttpRequest对象的responseText以获得服务器返回的数据。



如果你觉得上面过程比较简单,还可以接受,或者想练练自己的耐性,那么很好,多写几个应用试试,每写一个请求就得重复上面5个过程,我敢包不出第三个你就会觉得麻烦透了,是时候封装XMLHttpRequest了.

JavaScript日期选择控件

七月 12th, 2009 由 Rock 发表

一个简单,轻量级的日期选择控件实现类,也是背光控件库的默认类,解释一下它的构成和功能。
它继承CPanel类,所以很容易获得阴影效果,在改变位置时阴影也会跟随。

该控件不对日期过滤作过多的处理,但预留了一个接口回调函数,利用该接口可以自定义对日期的过滤。

支持mm/dd/yy,yy-mm-dd等多种格式转换.
日期控件已与表单集成了,CDatepickerField类作为表单元素以提供日期选择,具体可查看演示。

应用也简单,例如

//生成日期选择控件实例
var d = new CDatepicker({value: '2012/12/1',showTo: document.body, autoRender:true});
 
//生成用于表单日期选择的日期选择文本域
var field = new CDatepickerField({showTo: document.body, width: 300, autoRender: true});

查看演示:http://www.bgscript.com/bgjs/samples/datepicker.html

JavaScript - Grid表格控件

七月 6th, 2009 由 Rock 发表

表格控件,
列置换,列宽变,列排序,分组等等.
可自由扩展,如增加列选择,编辑功能等.
查看演示:

http://www.bgscript.com/bgjs/samples/grid.html

JavaScript,CSS压缩工具YUI Compressor 界面GUI版

七月 5th, 2009 由 Rock 发表

压缩JavaScript,CSS文件是比较有意义的,浏览器解析这些文件时不需要多余的空格,压缩后可以加快文件下载速度,再加上混淆处理后降低源码可读性,保护源码版权。

目前不少JavaScript压缩工具,有些直接基于HTML的,但大部份的压缩工具都不够理想,一来要求源码JS语法要符合一定的格式,如每句后须有分号,每个块需用{}补全,二来压缩后常常出现奇怪的错误,例如不能很好的处理复杂的正规表达式引发的错误,主要原因就是正则表达式匹配的局限性。

YUI Compressor是一个优秀的JavaScript,CSS压缩工具,它不限制你书写JS的格式,也避免了由于正则表达式匹配时的局限性,因为它是用Mozilla的JavaScript解析器去解析JavaScript代码的,Firefox的JavaScript引擎也是用这个去解析JS源码词法语法,用上了这个,功能就强大了很多,例如可以

  • 测出代码的语法错误,这是其它压缩工具难以做到的;

  • 测出代码的潜在错误,例如警告未使用的局部变量。

但YUI Compressor工具包提供的功能也有限的:

  • 控制台操作,即常说的DOS下操作,这对于被windows宠惯的用户来说比较麻烦;

  • 一次只能压缩一个文件;
  • 无中文版;
  • 调试信息在控制台输出,查找不方便;

为此,最近写了一个GUI版的,常见的功能也齐全了:

  • 可添加多个目录文件,批量压缩JavaScript,CSS文件;
  • 可设置发布目录,将压缩后的文件集中保存到该处;
  • 可将调试信息保存到文件中;
  • 可保置当前压缩文件列表,方便下次重新打开;
  • 在读入文件列表时可设置文件过滤;
  • 可将多个文件压缩后打包到一个文件中;

该GUI是基于Java的,
确保JDK >= 1.6,其它版本未经测试
确保在系统环境变量path中已设置JDK的bin目录
双击run.bat运行
运行后可先打开配置设置.

下载YUI Compressor 界面GUI版