在很多开发者看来,富文本编辑器的编写是一件很神秘或者复杂的事情。神秘倒没有,复杂的话,确实如此。但是它的基本原理并不复杂,入门也不难。今天我们的主题是讲述基本原理,并逐步演示一个简单富文本编辑器的产生。这是我在 D2 上的一个分享内容,在台上的演讲效果不佳,固写下来,希望能够对感兴趣的读者有所帮助。
富文本编辑器的基本原理这个原理实在是太简单了!对于支持富文本编辑的浏览器来说,其实就是设置 document 的 designMode 属性为 on 后,再通过执行 document.execCommand(commandName[, UIFlag[, value]]) 即可。commandName 和 value 可以在 MSDN 上和MDC 上找到,它们就是我们创建各种格式的命令,比方说,我们要加粗字体,执行 document.execCommand(bold, false) 即可。很简单是吧?但是值得注意的是,通常是选中了文本后才执行命令,被选中的文本才被格式化。对于未选中的文本进行这个命令,各浏览器有不同的处理方式,比方 IE 可能是对位于光标中的标签内容进行格式化而且它浏览器不做任何处理,这超出本文的内容,不细述。同时需要注意的是,UIFlag 这个参数设置为 true 表示 display any user interface triggered by the command (if any), 在我们今天的教程中都是 false, 而 value 也只在某些 commandName 中才有,具体参考以上刚给出的两个链接。
为了不影响当前 document, 通常的做法是在页面中嵌入一个 iframe 元素,然后对这个 iframe 内的 document(通过 iframe.contentWindow.document 获得)进行操作。
十分简单,是吧?下面我们来动手做一个。
编写一个简单的富文本编辑器这个例子使用了 YUI. 即使你对它不是很熟悉也没有关系,我在这里只使用了它的 DOM 和 Event 的一些跨平台基本方法。
搭架在此强调一下很久未曾提及的 unobtrusive. 我们的编辑器是对 textarea 元素的一个增强(enhencement),就是说,即使 JavaScript 被禁用了,用户还可以通过 textarea 编辑内容。
在这个例子中,我们将使用 YAHOO.realazy 的命名空间,在之下实现一个 RTE 的类。我们今天的编辑器很简单,因此构造器(constructor) 的参数也只有 textarea 一个。我们使用一个实例变量来保存工具条的各个项目。实例初始化放到一个叫 render 的方法中。这一步的页面和代码见第 1 步。
创建 iframe 并替换 textarea搭好架子,正如我在前面所说,建立一个 iframe, 编辑器的所有操作都在 iframe 的 document 内执行。并且把 textarea 隐藏起来。从第 2 步中可以看到,我们已经有了一个 iframe, 但不能输入任何东西,很正常,我们没有打开它的 designMode 嘛。
开启 designMode这一步涉及的东西挺多,也是关键。我们会创建获取 iframe 的 document 的方法,并通过程序的方式向 iframe 写入空页而非使用一个外接的 blank.html. 我们使用一个类属性 YAHOO.realazy.RTE.htmlContent 来保存空页的 html. 在准备好一切后,就可以开启 designMode 了。页面和代码详见第 3 步。看,我们已经可以在 iframe 里输入东西了。
构建工具条我们需要操作的工具条!这样才可以控制 iframe 里的内容,才能称之为编辑器。在此我并不打算实现太多的功能,只是选择字形、字号、加粗、斜体、下划线、居左、居中、居右、超链接和插图作为示例。对于跨平台,Mozilla Midas Specification 是不错的参考。ok, 请看第 4 步,我们的工具条出来了,虽然很丑。我同时用 CSS 对 iframe 的宽度做出了一些调整。
给工具条加上事件嗯,工具条出来了,编辑器看起来也“人模狗样”了,你兴奋的点啊点,没什么效果……意料中嘛。我们接着给工具条绑定一些事件,让编辑器内容能够响应工具条。在这一步,我们把 execCommand 再封一层,前面说过,我们用不上 UIFlag,让它永远是 false 好了。好,有代码就有真相,请看第 5 步。如果是正使用 IE, 请先暂时转移到其它浏览器。看到了吧,工具条生效了!
解决 IE 的问题well, 如果你没有听我的劝告,依然使用 IE, 你会发现除了字型和字号其它的都不能用。为什么呢?你观察一下,有没有发现,其它浏览器选择文本后,再点击工具条上的项目,被选中的文本是否依然选中的?而 IE 呢,在点击工具条时,选中的文本马上失去选中的状态,所以它们就失败了。所以,如果我们能够保证点击工具条文本保持选中状态,就可以解决 IE 的问题了。
Microsoft 给 HTML 标签一个很奇怪的属性 unselectable, 只要设置为 on, 焦点不会转移到点击的元素上,从而保证文本的选中状态。
请看第 6 步。这也是解决 IE 头痛问题的关键所在。我曾经在这上面费了很大脑筋。
高级主题展望good, 看看我们现在的代码,224 行。相比其它动辄上万行的编辑器,你可能会觉得不可思议。因为我们这个最基本的编辑器,连 selection 都没有用到。很多很酷的效果,比如 Google Doc 里能够动态改变链接文本,使用页内层而非弹出的 prompt 来操作等高级功能,基本上都要用到 TextRange(IE) 或者 Range(W3C). 要命的是这两个东西互补兼容,只是相似而已。入门推荐看PPK 的 Introduction to Range.
在此我们就不深入了,等我有时间我会总结一些奇技淫巧(呜呼,前端开发需要的奇技淫巧太多了,这不是好事情)出来。
]]>Memoization 原理非常简单,就是把函数的每次执行结果都放入一个散列表中,在接下来的执行中,在散列表中查找是否已经有相应执行过的值,如果有,直接返回该值,没有才真正执行函数体的求值部分。很明显,找值,尤其是在散列中找值,比执行函数快多了。现代 JavaScript 的开发也已经大量使用这种技术。
我通过 Google 寻找了好几种 JavaScript Memoization 的实现,都不太如人愿,有的实现不能缓存递归函数,有的需要修改函数的 prototype,于是自己实现一个:
/** * JavaScript Momoization * @param {string} func name of function / method * @param {object} [obj] motheds object or scope correction object * * MIT / BSD license */ function Memoize(func, obj){ var obj = obj || window, func = obj[func], cache = {}; return function(){ var key = Array.prototype.join.call(arguments, ); if (!(key in cache)) cache[key] = func.apply(obj, arguments); return cache[key]; } }
并写了一个测试案例,空口无凭,让大家亲自看看 Memoization 的威力。
]]>废话太多了……这个测试 demo 叫 chatlazy, 位于 http://chatlazy.appspot.com. 是一个简易聊天室,后台部分,就是 Python 了,具体一点,是 webpy 0.3 (开发版,未发布)。机制十分简单,就是前端使用 JavaScript 隔 5 秒去提取后台的最新消息。有几个小细节还是值得总结一下的:
由于 GAE 的数据 ID 使不能用在 Gql 中的,我只能通过时间戳来比对消息状态。把 datetime 和秒数 + 毫秒数的互转,还是比较繁琐的。Python 技术手册帮了我很大忙。解决方案大致如此:str(time.mktime(d.timetuple()))[:-1] + str(d.microsecond)
反过来则是:
p = str(t).split(.) tp = time.localtime(float(p[0])) dt = datetime(tp[0], tp[1], tp[2], tp[3], tp[4], tp[5]+1, int(p[1])) 对于 iterable 的对象, 先要 list 它转成列表,才可以使用 reversed 等相关方法。 需要取最新的 n 条信息,即数据库末尾的 n 条,但是又要顺序,可以先按逆序取 n 条,再反向排序(由此引发上条启示)。 对于任何用户输入的东西都要做过滤,一开始我在用户名那块忽略了,结果马上有人 XSS 了。这应该是基本常识,应铭记于心。
由于 GAE 这个天上掉的馅饼,我想我近期的精力会放到 Python 上了,有计划地把 blog 迁徙到 GAE 上,并开发一些有趣地程序。GAE rocks. 老实说,这是搜索、Gmail 后,对我而言可以排到第三的 Google 服务了。
]]>让我们看看我之前的文章:JavaScript的9个陷阱及评点,在第 9 点 Focus Pocus 中提到的问题。原作者对这个认识有所偏差,其实不只是 IE 的问题,而是现有 JavaScript 引擎对于线程实现的问题(关于线程,我的概念其实不多,如果不对,希望读者多多指教)。我们通过一个例子来说明,请访问 http://realazy.org/lab/settimeout.html. 我们来看 1 和 2。如果你能看看源代码,会发现我们的任务很简单,就是给文档增加一个 input 文本框,并聚焦和选中。请现在分别点击一下,可以看到,1 并没有能够聚焦和选中,而 2 可以。它们之间的区别在于,在执行
input.focus(); input.select();
时, 2 多了一个延迟时间为 0 的 setTimeout 的外围函数,即:
setTimeout(function(){ input.focus(); input.select(); }, 0);
按照 JavaScript: The Definitive Guide 5th 的 14.1 所说:
在实践中,setTimeout 会在其完成当前任何延宕事件的事件处理器的执行,以及完成文档当前状态更新后,告诉浏览器去启用 setTimeout 内注册的函数。
其实,这是一个把需要执行的任务从队列中跳脱的技巧。回到前面的例子,JavaScript 引擎在执行 onkeypress 时,由于没有多线程的同步执行,不可能同时去处理刚创建元素的 focus 和 select 事件,由于这两个事件都不在队列中,在完成 onkeypress 后,JavaScript 引擎已经丢弃了这两个事件,正如你看到的例子 1 的情况。而在例子 2 中,由于setTimeout可以把任务从某个队列中跳脱成为新队列,因而能够得到期望的结果。
这才是延迟事件为 0 的setTimeout的真正目的。在此,你可以看看例子 3,它的任务是实时更新输入的文本,现在请试试,你会发现预览区域总是落后一拍,比如你输 a, 预览区并没有出现 a, 在紧接输入 b 时, a 才不慌不忙地出现。其实我们是有办法让预览区跟输入框同步地,在此我没有给出答案,因为上面所说的,就是解决思路,try it yourself!
]]>这是一本没有 “hello world” 的书,在未翻译前我已经推荐过。希望窥探 JavaScript 高级应用的读者可以一读。推荐大家从网上购买,这样折扣多些。
如果你有任何想法,可以致信 projsch@gmail.com, 欢迎交流。
p.s. 最近更新不勤,一则工作忙,二则积蓄待发。自学 C 语言几月有余,略有小成。新近在看 Objective-C 和 Cocoa, 希望能开发些客户端程序。数据结构和算法依然在门外,一步一步来吧。God bless you, also me.
]]>工作职责:
使用 HTML/CSS/Javascript 开发符合 W3C 标准的网站前端页面; 使用AJAX,Flash等技术丰富网站功能,增强用户体验; 和后台工程师一起研讨技术实现方案,制定服务接口等; 积累并完善自己的前端WEB开发框架,Javascript开发框架; 积极探索并积累前端开发模式和规范。职位要求:
本科以上学历,能熟练阅读英文技术文档; 具备良好的团队合作精神和积极主动的沟通意识; 具有强烈的进取心和求知欲,勇于挑战; 精通HTML/CSS/Javascript等前端技术,习惯于手写符合W3C标准、兼容多种浏览器的前端页面代码,而不是依赖IDE; 有Flash,ActionScript开发经验者优先; 有设计经验,熟悉Photoshop、Illustrator者优先; 计算机相关专业毕业者优先。有意者请致信 job(a)hainei.com.
另外还招聘产品经理、研发工程师和高级研发工程师/架构师。详情见 http://hainei.com/job.
]]>那么,从技术角度来说,bookmarklet 有什么需要注意的呢?我个人意见如下:
首先,因为它是一段 JavaScript, 所以应该遵循普世的 JavaScript 编程原则。最重要的一点是,不要污染当前网页的命名空间,否则可能会破坏当前网页的 JavaScript. 通常,可以使用闭包来隐藏你所有的变量。同样,如果您的 bookmarklet 的 CSS 可能会入侵当前网页(很遗憾,CSS 没有命名空间,也没有类似闭包的东西,很容易就会冲突),那么请考虑将 bookmarklet 的内容放到 iframe 中去。
其次,防止函数执行后不经意的副作用,一个比较好用的贴士是,使用不返回值的 void, 它可以接受任何参数,因此,把你的闭包放到 void 中是个不错的主意:
javascript:void((function(){...})());
最后,有一个比较恼火的问题也需要加以注意。目前世界上最流行的浏览器,IE6, 它对 bookmarklet 所能容忍的长度仅为 508!
]]>由于 Mac OS 身上淌着 Unix 的血液,要找到一些不用花钱的生产力工具还是很容易的。最近习惯了 GNU Emacs (以下简称 Emacs), 所以不管三七二十一,找一个来安装上再说。
才发现 Mac OS 下的 Emacs 版本如此之多。我分别尝试了 Emacs App, Carbon Emacs 和 Aquamacs.
首先,Emacs App 基于还在开发中的 Emacs 23,这个版本最大的改动之一就是字体的处理方式,无疑,在 Mac OS 下,三者中也是它的字体支持最好。Carbon Emacs 和 Aquamacs 都是基于 Emacs 22 的,对字体的支持不足(比如中英混排,或者对字型的选择都比较奇怪)。我对Carbon Emacs 没有什么印象,倒是因为 Aquamacs 解决一个至关重要的问题,我才不得不选择它。
如果你用过 Emacs, 你就知道它是多么依赖于各种快捷键。问题在于,MacBook 键盘右边没有 control 键。当然可以通过设置来让 command 或者 option 来充当 control 键来解决问题,但是为了一个 Emacs 而改变整个系统的键盘布局方式,有点得不偿失。而 Aquamacs 可以使得这样的设置只在 Emacs 内生效而不影响系统:
(setq mac-command-modifier control) (setq mac-control-modifier alt) (setq mac-option-modifier meta)
p.s. 至于 Textmate, 先慢慢尝试一下吧。我的工作环境不可能不处理中文,Textmate 不支持中文严重降低了我去使用它的频度。
]]>For some reasons we have to excute the JavaScript function in the img tag’s attribute onload, e.g.
开启 Opera, CPU 狂窜到 100%……
When using Opera browser, the CPU usage is up to 100%…
原来丫会重复执行 jsFunction……只好加个变量来记录是否已经执行。
Because Opera execute jsFunction repeatedly. The solution is adding an flag variable to track the function be executed or not.
Google 一下并没有发现这方面的资料,所以记下来,希望能帮助碰到这个问题的人。为了表现我的国际主义精神,特翻译了一下(至于这句,就不翻了……)。
]]>2006末,在不懂MySQL, Ruby的情况下,用RoR折腾出一个十分简单的jobz board, 向世界宣布我“会”编程了。侥幸进入中国雅虎,虽大部分时间还是折腾HTML和CSS, 但也有了练习JavaScript的机会,怎么说,以前玩linux积累一些非编程但有助于了解编程的经验,在练习过程中得以入门编程,还写得真像那么回事了。
记得2007的展望是精通JavaScript,这个完成得有60%吧,对PHP和MySQL也有一定的了解。至于Ruby,没地方用,所以顶多掌握了一些概念和语法,倒是Python,用得还多点,年末用webpy和SQLite3写了一个blog程序,算是对Python入了点门。
还好,不经意间踏上了编程之路。
但一提到“编程”二字,常常让我觉得汗颜。程序者,经典公式为程序=算法+数据结构。但是我既不懂算法,也不懂数据结构。我说我在编程(programming),实在是跟全天下的程序员(programmer)过不去,我顶多是个编码者(coder)而已。
因此,2008, 北京要举办奥运会,我要学习算法和数据结构。从而需要自补高等数学(不好意思,我是文科生
),也从而需要学好C语言。高数、C语言、算法和数据结构,用我党的话说,这是2008的主旋律。
如果时间充足的话,还计划使用Python做些GUI应用。计算机的精彩决不仅仅局限于web.
回顾一下年初,有些话说过了头,但基本上都履行了。虽然没法攒够钱给支持家庭,但也算是最有力度的一年了。
2007年10月,我奶奶安详辞世,享86岁。愿往者安息,生者健康,以奋斗来慰藉往者的关怀与爱护!
]]>请给Array本地对象增加一个原型方法,它的用途是删除数组条目中重复的条目(可能有多个),返回值是一个包含被删除的重复条目的新数组。
这是我的答案:
Array.prototype.uniq = function(){ var ret = this.slice(), i = 0, j = 0; while (ret[i]){ j = i + 1; while(ret[j]){ if (ret[i] == ret[j]){ ret.splice(j,1); } else { ++j; } } ++i; } return ret; } ]]>
假设我们有一个 Form 节点(node)的引用,姑且名之为 elForm,现在需要克隆一份,可以这么做:var elFormClone = elForm.cloneNode(true).
在插入这份克隆到 DOM 树中后,IE, Firefox 均未发现问题。Opera会产生这样的问题:表单内的字段无法引用。比如,假设刚才我们的elForm 有一个 , 此时你无法通过 elFormClone.title 或者 elFormClone[title] 获取它。
解决方案使用 document.createElement 创建 form 元素,然后设置该元素的 innerHTML(感谢 MS 发明了它) 为elForm 的 innerHTML 即可:
var elFormClone = document.createElement(form); // 设置一些elForm的原属性,有必要的话 ... elFormClone.innerHTML = elForm.innerHTML; // 处理这个clone, 该咋办就咋办了 ... ]]>
关键在于keyup, 如果世界是美好的,那么就不会有这篇blog. 可是……
世界是不美好的。我们活在中文世界,我们要用输入法。在输入法开启的情况下,您会碰到不美好的事情:keyup失效。对于您绑定到keyup的任何回调函数,除非您把输入法切换回英文状态,否则它会无动于衷。如果能称之为bug,我会很高兴,因为bug会有修复的可能,如果是特征(feature),那么,我只好叹息一下。
问题在开启输入法的情况下,三个浏览器的具体问题如下:
IE:触发keydown和keyup, 不触发keypress. 能够获得输入值。 Firefox:触发keydown和keypress, 不触发keyup. 输入值未能获得。在回车后会触发keyup, 可获得输入值。 Opera:keydown, keypress和keyup都不触发,输入值也未能获。(如果您能帮我试用一下Safari,我会很高兴并谢谢您。这里有一个测试页面:http://tonextone.com/test/eventTest.html)
解决方案总结出以上问题,没有兴奋反而陷入绝望,因为没有google出解决方案(是的,对于拉丁语系的老外来说,不会存在输入法)。但是,wait, 谷歌搜索的自动补全不是工作得好好的吗?于是研究一下这个 http://www.google.cn/ac.js。嘿嘿,虽然混淆得还可以,但还是可以发现蛛丝马脚的。它使用一个计时器,当输入框处于聚焦(focus)状态时,每10秒执行一次回调函数。
虽然挺耗资源(所以建议在输入框失焦(blur)时,一定要清除这个计时器),但也只能如此了。作前端开发的,不仅要与语言(JavaScript, CSS, HTML) 斗,还要与浏览器斗,其乐无穷也。
]]>我们知道,自CSS革命以降,HTML倾向于语义化,在一般情况下不再在标记里写装饰性的内容而是把呈现的任务交给了CSS。GUI是缤纷多彩的,少不了各种漂亮的图来装点。新时代的生产方式是,在HTML布满各种各样的钩子(hook),然后交由CSS来处理。在需要用到图片的时候,现阶段是通过CSS属性background-image组合background-repeat, background-position等来实现(题外话:为何我提现阶段,因为未来浏览器若支持content则又新增另外的实现方法)。我们的主角是,你一定猜到了,就是background-position。通过调整background-position的数值,背景图片就能以不同的面貌出现在你眼前。其实图片整体面貌没有变,由于图片位置的改变,你看到只该看到的而已。就好比手表上的日期,你今天看到是21,明天看到是22,是因为它的position往上跳了一格。所以你也大概了解到,CSS Sprites一般只能使用到固定大小的盒子(box)里,这样才能够遮挡住不应该看到的部分。
我们使用YUI的sprite.png举个例子,假如我们有这么一段代码,max代表最大化,min代表最小化,我们需要给它们配上相应的漂亮图片(这样我们的网站才能够吸引人,才可以卖钱,才可以到佛罗里达晒太阳:D):
这两个class都使用同一个图片:
.min, max { width:16px; height:16px; background-image:url(http://developer.yahoo.com/yui/build/assets/skins/sam/sprite.png); background-repeat: no-repeat; //我们并不想让它平铺 text-indent:-999em; //隐藏文本的一种方法 }
效果如下:
最大化 最小化我们看到一团灰,没错,因为我们还没有指定background-position,默认为 0 0,可以看下sprite.png, 处于这个位置正是灰块。好了,我们要找到代表最大化的加号和代表最小化的减号的位置找出来。经过测量,最大化按钮位于Y轴的350px处,最小化按钮位于Y轴400px处。想一想我们如何才能让它们能够显示出来呢,明显,要向上提升sprite.png,得到代码如下:
.max { background-position: 0 -350px; } .min { background-position: 0 -400px; }
耶,我们成功了:
最大化 最小化(注意:为了举例的方便,本例子直接在HTML内置样式,切勿在实践中的非特殊情况使用这种方式)。
优点我们从前面了解到,CSS Sprites为什么突然跑火,跟能够提升网站性能有关。显而易见,这是它的巨大优点之一。普通制作方式下的大量图片,现在合并成一个图片,大大减少了HTTP的连接数。HTTP连接数对网站的加载性能有重要影响。
缺点至于可维护性,这是一般双刃剑。可能有人喜欢,有人不喜欢,因为每次的图片改动都得往这个图片删除或添加内容,显得稍微繁琐。而且算图片的位置(尤其是这种上千px的图)也是一件颇为不爽的事情。当然,在性能的口号下,这些都是可以克服的。
前面我们也提到了,必须限制盒子的大小才能使用CSS Sprites,否则可能会出现出现干扰图片的情况。这就是说,在一些需要平铺背景和需要网页缩放的情况下,CSS Sprites并不合适。YUI的解决方式是,加大图片之间的距离,这样可以保持有限度的缩放。
总结性能压倒一切。CSS Sprites是值得推广的一种技术。尤其适宜用于FIR,比如固定大小的icon替换。为保持兼容性,图片中的各个部分保持一定的距离是一种不错的做法。
推荐阅读: CSS Sprites: Image Slicing’s Kiss of Death 14 Rules for Faster-Loading Web Sites High Performance Web Sites ]]>
说实话,我从没见过sqlserver占用内存在1G以上的
现在网站的登录速度特别慢
分析了一下现有网站的整体架构,采用的是标准的三层架构,
每当用户登录一次,都会经过逻辑层、数据层再返回给逻辑层最后经UI展现给用户。
在70多W用户的情况下,每次对用户表进行select操作,即便是存储过程,这个速度也是可想知。
用户登录成功之后,还会做一系列操作,比如记录登录日志,获取该用户的实体信息,写到session中......
用户在可以用id与email登录的情况下,表居然没有对id与email做索引,而且像id这种字段居然是varchar型。
暂时的规划:
独立出一台服务器只做对用户的校验工作,优化表结构,不必要的信息不需要进行获取(如获取用户的实体类),只在需要时进行获取。

这张图是我每天上下班的行车路线,当然,这里Google推荐的是走公路,我一直选择的是2号线转1号线
平均花费在路上的时间为3小时(上下班一起)
我开始以为我自己不可能很快去适应这么长的上班时间,于是开始在附近搜寻房源,直至现在我打消了这个念头。
我们这个团队还有许多需要磨合的地方,项目上还有许多需要改进的地方
虽然有时会觉得很累,恨不能趴在桌上就睡过去,但同时有一个声音在对我说:这也许就是你的责任。
所以,累并快乐着。
今天很长一段时间Blog无法访问,公司的网络可能作了一些限制,死活也无法远程登录服务器。
晚上一看,Blog的服务被停掉了。
]]>作为JavaScript Guru, Douglas Crockford提出了自己对JavaScript风格的创见(第一部分,第二部分)。
第一部分主要讨论JavaScript语言本身,包括:
淘汰过时的构建。讨论了在HTML页面引入JavaScript的方式。经典的写法是:
language并不是W3C所认同的标准,建议使用的是type, 但作为type值的MIME type并没有标准化(有时是text/javascript, 有时是application/ecmascript),但目前所有的浏览器都是使用JavaScript作为默认的脚本语言,因此仅仅些