日志更新

DOM 元素如何获得焦点

首先让我们看看哪些元素可以直接获得焦点(element..focus()):

// Form : http://www.w3.org/TR/html5/editing.html#focusable

  • a elements that have an href attribute
  • link elements that have an href attribute
  • button elements that are not disabled
  • input elements whose type attribute are not in the Hidden state and that are not disabled
  • select elements that are not disabled
  • textarea elements that are not disabled
  • command elements that do not have a disabled attribute
  • Elements with a draggable attribute set, if that would enable the user agent to allow the user to begin a drag operations for those elements without the use of a pointing device
  • Each shape that is generated for an area element

而除上面以外的元素(比如:div,p)一般都无法直接获得焦点,那如何处理呢?

  1. 给元素添加 contenteditable 属性。

    // From : http://www.w3.org/TR/2009/WD-html5-20090423/editing.html#attr-contenteditable

    User agents must make editing hosts focusable (which typically means they enter the tab order).

  2. 给元素添加 tabindex 属性。

    // From : http://www.w3.org/TR/2009/WD-html5-20090423/editing.html#attr-tabindex

    The tabindex content attribute specifies whether the element is focusable, whether it can be reached using sequential focus navigation, and the relative order of the element for the purposes of sequential focus navigation.

    非常推荐使用 tabindex = -1 ,基本无副作用!!

    // From : http://www.w3.org/TR/2009/WD-html5-20090423/editing.html#attr-tabindex

    If the value is a negative integer: The user agent must allow the element to be focused, but should not allow the element to be reached using sequential focus navigation.

综述,使无法直接获得焦点的元素获得焦点的最佳实践就是:给元素添加 tabindex = -1 属性。

当然你还可以通过子元素(focusable)的 focus 事件的捕获或冒泡来模拟,但需要处理事件的浏览器兼容(《如何在事件代理中正确使用 focus 和 blur 事件》)。

获取 Textarea 的光标位置

在任何编辑器中,获取光标位置都是非常重要的,很多人可能认为较难,其实只要处理好浏览器的兼容,还是比较容易实现的。

下面我们一起来看看如何获取到 Textarea 元素中的光标位置(测试地址)。

首先,我们用 rangeData 对象作为数据存储,并获得焦点:

var rangeData = {start: 0, end: 0, text: "" };
textarea.focus();

对于非 IE 浏览器获取选区的起始和末尾位置其实非常容易:

rangeData.start= el.selectionStart;
rangeData.end = el.selectionEnd;

通过截取我们可以得到光标的选区内容:

rangeData.text = (rangeData.start != rangeData.end) ? el.value.substring(rangeData.start, rangeData.end): "";

而对于 IE 浏览器处理起来就比较麻烦了,但我们依旧可以获取到选区:

oS = document.selection.createRange();

同时还可获取 Textarea 元素的选区:

// 为了使 oR 与 oS 在同一等级上比较,请勿使用:oR = textarea.createTextRange()
oR = document.body.createTextRange();
oR.moveToElementText(textarea);

如果光标在 Textarea 元素内,很自然 oS.text 就是我们需要的选区内容:

rangeData.text = oS.text;

并且我们可以通过 oS.getBookmark() 方法获取到选区的位置数据,该位置数据可以通过 moveToBookmark() 方法设置回去。

getBookmark: Retrieves a bookmark (opaque string) that can be used with moveToBookmark to return to the same range.

moveToBookmark: Moves to a bookmark.

我们用 rangeData.bookmark 来记录该位置数据:

rangeData.bookmark = oS.getBookmark();

下面是最重要的步骤:我们比较 oR 与 oS 的选区起始位置(使用 object.compareEndPoints(sType, oRange) 方法比较),如果 oR 的起始位置在 oS 之前,我们向前移动 oS 的起始位置1个字符(使用 object.moveStart(sUnit [, iCount]) 方法移动),一直当 oS 的起始位置在 oR 之前停止,移动的位置,则是选区的起始位置。

compareEndPoints: Compares an end point of a TextRange object with an end point of another range.

moveStart: Changes the start position of the range.

for (i = 0; oR.compareEndPoints('StartToStart', oS) < 0 && oS.moveStart("character", -1) !== 0; i ++) {}
rangeData.start = i;

但由于在 IE 中,Textarea 元素中的所有换行符都占 1 个字符,可以通过 alert(textarea.value.length) 查看,故要对上面的代码做部分处理:

for (i = 0; oR.compareEndPoints('StartToStart', oS) < 0 && oS.moveStart("character", -1) !== 0; i ++) {
    // Why? You can alert(textarea.value.length)
    if (textarea.value.charAt(i) == '\n') {
        i ++;
    }
}
rangeData.start = i;

既然得到了选区的起始位置和选区字符串的字符,很自然我们可以计算得到选区的末尾位置:

rangeData.end = rangeData.text.length + rangeData.start;

获取 Textarea 的光标位置的 getCursorPosition 函数方法整理如下:

/**
* getCursorPosition Method
*
* Created by Blank Zheng on 2010/11/12.
* Copyright (c) 2010 PlanABC.net. All rights reserved.
* 
* The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license.
*/
function getCursorPosition(textarea) {
    var rangeData = {text: "", start: 0, end: 0 };
	textarea.focus();
    if (textarea.setSelectionRange) { // W3C
        rangeData.start= textarea.selectionStart;
        rangeData.end = textarea.selectionEnd;
        rangeData.text = (rangeData.start != rangeData.end) ? textarea.value.substring(rangeData.start, rangeData.end): "";
    } else if (document.selection) { // IE
        var i,
            oS = document.selection.createRange(),
            // Don't: oR = textarea.createTextRange()
            oR = document.body.createTextRange();
        oR.moveToElementText(textarea);
      
        rangeData.text = oS.text;
        rangeData.bookmark = oS.getBookmark();
        
        // object.moveStart(sUnit [, iCount]) 
        // Return Value: Integer that returns the number of units moved.
        for (i = 0; oR.compareEndPoints('StartToStart', oS) < 0 && oS.moveStart("character", -1) !== 0; i ++) {
            // Why? You can alert(textarea.value.length)
            if (textarea.value.charAt(i) == '\n') {
                i ++;
            }
        }
        rangeData.start = i;
        rangeData.end = rangeData.text.length + rangeData.start;
    }
	
    return rangeData;
}

得到 Textarea 元素光标位置,当Textarea 中的光标丢失了,再设置回来就简单多了:

/**
* setCursorPosition Method
*
* Created by Blank Zheng on 2010/11/12.
* Copyright (c) 2010 PlanABC.net. All rights reserved.
* 
* The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license.
*/
function setCursorPosition(textarea, rangeData) {
    if(!rangeData) {
        alert("You must get cursor position first.")
    }
    if (textarea.setSelectionRange) { // W3C
        textarea.focus();
        textarea.setSelectionRange(rangeData.start, rangeData.end);
    } else if (textarea.createTextRange) { // IE
        var oR = textarea.createTextRange();
        // Fixbug :
        // In IE, if cursor position at the end of textarea, the setCursorPosition function don't work
        if(textarea.value.length === rangeData.start) {
            oR.collapse(false)
            oR.select();
        } else {
            oR.moveToBookmark(rangeData.bookmark);
            oR.select();    
        }
    }
}

测试地址:http://www.planabc.net/demo/range/textarea-cursor-position.html

扩展阅读:

用 Python 架设一个简单的服务器

import string,cgi,time
from os import curdir, sep
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer

class MyRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        try:
            if self.path.endswith('.html'):
                f = open(curdir + sep + self.path)
                self.send_response(200)
                self.send_header('Content-type','text/html')
                self.end_headers()
                self.wfile.write(f.read())
                f.close()
                return
 
            elif self.path.endswith('.py'):
                self.send_response(200)
                self.send_header('Content-type','text/html')
                self.end_headers()
                self.wfile.write("hey, today is the" + str(time.localtime()[7]))
                self.wfile.write(" day in the year " + str(time.localtime()[0]))
                return
            
            else:
                self.send_error(404, 'File Not Found %s' % self.path)  
                
        except IOError:
            self.send_error(404,'File Not Found: %s' % self.path)
     

    def do_POST(self):
        global rootnode
        try:
            ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) #('multipart/form-data', {'boundary': '303161840321948'})
            if ctype == 'multipart/form-data':
                query = cgi.parse_multipart(self.rfile, pdict)
            self.send_response(301)
            self.end_headers()
            
            upfilecontent = query.get('upfile')
            print "filecontent", upfilecontent[0]
            self.wfile.write('POST OK!')
            self.wfile.write(upfilecontent[0])
            
        except :
            pass

def main():
    try:
        server = HTTPServer(('', 80), MyRequestHandler)
        print 'started httpserver...'
        server.serve_forever()
        
    except KeyboardInterrupt:
        print '^C received, shutting down server'
        server.socket.close()

if __name__ == '__main__':
    main()

扩展阅读:

类型转换的小乐趣

玉伯 的文章 《一道大题目,嘿嘿》 中有这样一段代码:

[] == ![]

也许很多同学迷惑:咦,这个如何转换呢?

首先,我们了解下逻辑 NOT(!)运算与等号(==)运算的一些基本规则:

  1. 逻辑 NOT 运算中,如果运算数是对象,返回 fasle;
  2. 等号运算中,如果一个运算数是 Boolean 值,在运算前,会将其转换成数字:false -> 0,true -> 1;
  3. 等号运算中,如果一个运算数是对象,另一个是数字,在运算前,会将对象转换成数字。

2010年11月2日补充:对象不能直接转换成数字型,可以将其转换成字符型,再将字符型转换成数字型。

根据上面的一些规则,我们再来看看原来的表达式如何演变:

Step01: [] == ![] // 由于[]是对象,则 ![] 返回fasle,演变为 Step02。
Step02: [] == fasle // 由于 fasle 是 Boolean 值,则转换成数字 0,演变为 Step03。
Step03: [] == 0 // 由于 0是数字,[]是对象,[] 转换成数字 Number([]) -> 0,[] 转换成字符型 [].toString() -> "","" 再转换成数字型 -> 0 ,演变为 Step04。
Step04: 0 == 0 // 返回最终结果:true

思考题:

{} == ! {}; //返回什么值?
[] == [] //返回什么值?

PS:如果你对玉伯的题目很感兴趣,推荐阅读: