2008年8月6日

有备无患:一个 jQuery 的时间选择插件

Yet another jQuery time picker plugin. Google 上兜了一圈,没找到中意的时间选择(Time Picker)插件,于是自己写了一个。timepicker 是一个弹出式的对话框,分为上下两个部分,上半部分是可以分别设定时分秒的计数器,下半部分提供一些常用的时间选项,比如“Now”,“Noon”,“8 p.m.”等。通过聚焦(focus)到相应的 input 控件,可以激活 timepicker。选择好时间后,点击对话框底部的 Close 按钮会将结果保存到 input 控件中,而点击 Clear 按钮会清除 input 控件中已存的时间。键盘上的 Tab 键和 ESC 键也有与之对应的功能。

这是我的第一个 jQuery 插件,欢迎大家使用,我也会继续完善这个插件。如果有任何的 Bug,建议和意见,欢迎在文章后留言,或者 Email 给我,或者在插件的主页写下你的评论。:-)

使用方法

$('#time-picker').timepicker();

截图


Yet another jQuery time picker plugin.

演示

演示网址



下载

你可以选择下载压缩档。
下载

或者用 Git 从架设在 GitHub 的软件仓库中检出源代码使用。
$ git clone git://github.com/wuyuntao/timepicker.git

标签:

2008年8月4日

有备无患:Google Reader 风的豆瓣广播 widget

最近,豆瓣连续发布了几个重要的 API 更新,包括了备受期待的友邻广播相关的接口。特别是广播的 API,自从用豆瓣广播取代了 Twitter 当作吐口水的工具之后,我一直期待广播也有像 TwitterFox 一样的客户端,有个 widget 可以在 blog 上显示最近的状态,而这些都是要以广播的 API 为基础的。

言归正传,我写了一个类似 Google Reader 的分享阅读剪辑(Shared Item Clips)风格的豆瓣广播 widget,可以放在 blog 的侧边栏。使用的方法很简单,只需要将下面这段代码插入到你的 blog 模板中。(wyt:如果不知道怎么操作的话,可以参考豆瓣秀指南。)

<script type="text/javascript" src="http://luliban.com/scripts/miniblog.js?username=wyt&amp;maxresults=16&amp;style=blue"></script>

注意上面代码中粗体的部分,首先我们要将``wyt``换成自己的用户名;``maxresults``为 widget 显示的广播条数(最大值为50,默认为10,可以省略);``style``表示 widget 的配色,和 Google Reader 分享阅读的配色基本一致,包括:black、blue、gray、green、khaki、pink、slate 和 none。默认为 green,如果输入的 style 不在上述配色中,则返回 none。none 为不带任何 style 的原始 HTML,适合希望自定义广播风格的 blogger。

最终效果如下图。想看看实物 demo 的话,可以点击这里。要说明的是,看得到漂亮的圆角效果的只有 Firefox 用户(wyt:这也是 Google Reader 的风格之一,:p),IE、OperaSafari 用户对不住了。如果有任何的建议或意见,欢迎留言 :-)

豆瓣广播widget

标签: , ,

2008年7月4日

有备无患:浏览器端缓存(基于 jQuery)

最近在翻《Ajax 设计模式》,所以想把其中的一部分模式实现出来练手 jQuery,今天写了一个简单的引入 LRU 算法的浏览器端缓存(Browser Side Cache)。

浏览器端缓存用来保留服务器返回的查询结果。这种缓存是一个 Javascript 里类似映射的对象,存储成对的查询结果;查询是缓存的键(Key),服务器返回的结果是缓存的值(Value)。因此,每当浏览器需要查询服务器时,先检查缓存。如果该查询是缓存中的一个键,则与键对应的值将被当作结果,而不必再向服务器查询。

LRU (Least Recently Used)是将存储在缓存中的自上一次获取之后,最长时间未被使用的项目(Item)丢弃的一种算法。可以用两个数组(Array)来实现,其一是用来存储查询结果的键值对(Key-Value Pairs),其二是一个先进先出(FIFO)的队列,每一个新项目被塞入队列的尾部,并随着后续项目的跟进,逐渐逼近队列的头部。当队列全满时,每次向尾部塞入一个新项目,就要从头部弹出一个旧项目。而每当一个项目被缓存查询时,它会被送回到队列的尾部,这样可以确保最长时间未被使用的项目总是在开头处。

另外,还有一种常用的缓存算法,LFU (Least Frequently Used),它将自上一次获取后最少被使用的项目丢弃。

顺便一说,下面贴出的代码是由代码发芽网生产的纯 HTML。代码发芽网是一个“无需插件支持 Blog 代码高亮,支持近百种编程语言,多种配色主题支持,代码版本管理”的代码片段管理网站。因为不是由 Javascript 脚本来高亮处理,所以在 feed 里也一样可以看到效果,这一点很赞。不过,由于用处不大的 id 和 class 也包含在生成的 HTML 里,所以体积偏大。比如说下面这段代码,在删除了多余的 id 和 class,套上代码专用的 precode 标签,并将 &nbsp; 还原成空格之后,体积可以缩小一半仅17KB(原来35KB),更适合 blog 来贴代码。


cache.js

01 function Cache(size) {
02     /* A browser side LRU cache 
03      * Author: Wu Yuntao <http://luliban.com/blog/>
04      * License: GPLv3
05      *
06      * Usage:
07      * var cache = new Cache(10);   // create a new cache object
08      * cache.put('w', 'wiki');      // put an item into cache
09      * cache.get('w');              // get the value of item with key
10      * cache.remove('w');           // remove an item with specified key
11      * cache.initialize()           // re-initialize the cache
12      * cache.size(10)               // resize the cache
13      */
14     this.initialize(size);
15 }
16 
17 Cache.prototype = {
18     initialize: function(size) {
19         /* Initialize cache.
20          * ``size`` is the number of maxmium items this cache should hold.
21          * Default is maxium integer.
22          */
23         this._keys = new Array();
24         this._items = new Array();
25         this._size = size || this._size || Number.MAX_VALUE;
26     },
27 
28     is_empty: function() {
29         /* Check if cache is empty
30          */
31         return (this._keys.length == 0 && this._items.length == 0);
32     },
33 
34     size: function(size) {
35         /* Resize cache if ``size`` is specified, or return accual size of cache.
36          */
37         if (typeof size == 'undefined') return this._keys.length;
38         return this._size = size;
39     },
40 
41     put: function(key, value) {
42         /* Put a new item into cache, if the size of cache reaches limit,
43          * cache will remove the least recently used (LRU) automatically.
44          * ``key`` of an item should be a string.
45          * ``value`` of an item could be anything, string, array or object.
46          * If ``value`` is not defined, returns null.
47          */
48         if (typeof value != 'undefined') {
49             this._keys.push(key);
50             this._items[key] = value;
51 
52             if (this._keys.length > this._size) {
53                 this.remove_least();
54             }
55         }
56         return value;
57     },
58 
59     get: function(key) {
60         /* Retrieve an item by its key and move it to the tail of cache.
61          * If the item of ``key`` does not exist, returns null.
62          */
63         if (typeof this._items[key] == 'undefined') return null;
64         var used_key = this._remove_key(key);
65         this._keys.push(used_key);
66         return this._items[key];
67     },
68 
69     remove_least: function() {
70         /* Manually remove the least recently used item. */
71         if (!this.is_empty()) this.remove(this._keys[0]);
72     },
73 
74     remove: function(key) {
75         /* Remove an item by its key.
76          * If the item of ``key`` does not exist, returns null.
77          */
78         if (typeof this._items[key] == 'undefined') return null;
79         this._remove_key(key);
80         return this._remove_value(key);
81     },
82 
83     _remove_key: function(key) {
84         /* Remove the ``key`` in ``this._keys``.
85          */
86         var i = $.inArray(key, this._keys);
87         return this._keys.splice(i, 1);
88     },
89 
90     _remove_value: function(key) {
91         /* Remove the item in ``this._items``. */
92         var value = this._items[key];
93         delete this._items[key];
94         return value;
95     }
96 
97 };

标签:

2008年5月27日

有备无患:增强豆瓣收藏对话框的 Greasemonkey 脚本

经常使用 del.icio.us 的人,一定也很熟悉 del.icio.us 的跟随键盘输入自动提示标签(Tag Suggest)的功能。它为用户管理大量的标签带来方便,比如不必去费神记忆单词的拼写,避免为同一语义定义多个标签等。这是靠我们的大脑来记忆管理,或者类似豆瓣只提供10个最常用的标签,不能做到的。

这个 Greasemonkey 脚本的主要功能,是为豆瓣收藏对话框提供类似 del.icio.us 的标签提示功能。另外还重新布置了读过/在读/想读等按钮的布局,并添加了删除收藏的按钮。

目前脚本的版本为0.1,欢迎试用。有什么建议或意见,欢迎留言:-)


Screenshot - Enhanced Douban Collecting Dialog

功能

  • 类似 del.icio.us 的随输入自动提示标签(Tag Suggest)
  • 重新安排了读过/在读/想读等按钮的布局,保证 UI 路径的一致性
  • 增加了删除收藏的按钮

下载

下载脚本


2008-05-31 UPDATE

脚本更新至0.2,有下列更新

  • 增加了对个人收藏页,标签页,豆瓣猜,TOP250等页面中的收藏对话框的支持
  • 修正了之前脚本中已知的 BUG

标签: , ,

2008年3月16日

有备无患:为Blogger传统模板(FTP发布)添加标签云

Blogger的传统模板一直不支持标签的输出。虽然有hack可以从feed中抓取标签列表,但不能分析每个标签所含的文章数,所以仍然不能生成随标签使用频率而字体大小和颜色变化的真正意义上的“标签云”。

所以,我写了一个python的自动化脚本,可以从ftp服务器上分析blogger文件,计算出每个标签的使用频率,并将这些信息保存在服务器上,供模板中的javascript脚本调用。效果么,你可以看看这个演示网页,或者点击我blog下方widget dock左起第四个“文件夹”图标(wyt:还在用IE6的同学们应该看不到。因为IE6不支持widget dock用的透明png,所以我把widget dock给隐藏起来了,实际上“标签云”还在那里。IE7、Firefox、Opera、Safari等应该都没问题)

使用方法很简单,要执行脚本首先得有python。Linux大多默认安装,Windoze上安装Python的方法见这篇文章

另外,脚本中用到了BeautifulSoup分析HTML文件,simpleJSON生成JSON格式的数据流。你也必须安装这两个python模块。如果你和我一样用Gentoo,just emerge it。Windoze用户可以用easy install工具快速安装。

接下来下载压缩文件:konatag.tar.gz,解压缩用得到两个文件konatag.py和konatag.js。

konatag.tar.gz

修改konatag.py其中的ftp服务器,用户名,密码以及归档(archives)的路径参数,以便脚本可以访问你的ftp服务器。

if __name__ == '__main__':
    options = dict(
        server='yourftpserver',    # 服务器地址不用包含ftp://
        username='yourusername',   # 用户名
         password='yourpassword',   # 密码
         path='/path/to/archives/'  # blogger的归档路径
    )
    ftp = FTPServer(**options)
    blogger = Blogger(ftp)

保存退出之后,在终端(wyt:也就是Windoze的命令行)执行:

$ python konatag.py

事实上,如果高兴的话,可以像我一样把这个脚本扔到crontab的计划任务中,每星期执行一次自动更新你的Blogger的标签信息。

执行完脚本之后,登录ftp,在你的blogger归档目录中会多出一个konatag.json的文件,这是一个包含JSON格式数据的文本文件。不用去管它,将konatag.js上传到任何你能记住的目录。接下来我们将要修改Blogger模板。需要说明的是,konatag.js使用prototype库,所以在模板里还必须包含相应的js文件。

<!-- 将js的调用加入<head> -->
<script type="text/javascript" src="/scripts/prototype.js"></script>
<script type="text/javascript" src="/scripts/konatag.js"></script>
<!-- or: 如果你想直接调用我这儿的javascript脚本的话 -->
<script type="text/javascript" src="http://demos.luliban.com/scripts/prototype.js"></script>
<script type="text/javascript" src="http://demos.luliban.com/scripts/konatag.js"></script>

<!-- 这个div是“标签云”的容器,加入<body>中
     id可以随意指定,但要与下面的javascript对象的第一个参数相一致 -->
<div id="konatag"></div>

<script type=text/javascript>//<![CDATA[ 
    // Konatag的第一个参数是刚才提到的div的id
    // 第二个参数是脚本上传的JSON数据的路径
    new Konatag('konatag', '/blog/archives/konatag.json', {
       startSize: 13,    // 使用频率最低的标签的字号,单位是px,请随意设定,下同
        stopSize: 24,     // 使用频率最高的标签的字号,单位同样是px
       startColor: '#3d81ee',    // 使用频率最低的标签的颜色
        stopColor: '#930fe3'      // 使用频率最高的标签的颜色
    });
//]]></script>

这样就大功告成,只需要重新发布一遍模板就可以了。有什么意见,建议和砖头,欢迎留言。

标签: , ,

2008年1月3日

有备无患:2个javascript倒计时的应用(基于prototype库)

第一个应用实现了类似下载Firefox扩展之前,3秒倒计时的“安装”按钮的功能。第二个应用实现了在倒计时的同时,用背景色表示“压力”程度,一开始是淡黄色,逐渐转变成更有紧迫感的红色。两个应用都是基于prototype库,另外还用到了Stoyan Stefanov写的rgbcolor.js——一个实用的将字符串转换为Hex、RGB等格式色彩的javascript类。

倒计时按钮包括函数buttonCountdown,输入倒计时秒数,按钮的element或id,倒数时候的标签。


代码:buttonCountdown


function buttonCountdown(seconds, element, label) {
 if (typeof element == 'string') element = $(element);
 element.disabled = true;

 Element.addMethods({
     updateButton: function(num, sec, elt, lab) {
   var remaining = sec - num;
   if (remaining == 0) {
          elt.value = lab;
          elt.disabled = false;
      } else {
          elt.value = lab + " (" + remaining + ")";
   }
        },
    });

    if (element.tagName == 'BUTTON' || 'INPUT') {
        for (var i = 0; i <= seconds; i++) {
            Element.updateButton.delay(i, i, seconds, element, label);
        }
    }
}

背景色渐变倒计时包含两个函数divisionCountdown和CalculatePresentColor。divisionCountdown和buttonCountdown类似,输入秒数,element或id,标签。CalculatePresentColor输入倒计时的百分比,倒计时开始时和结束时的色彩。


代码:divisionCountdown和CalculatePresentColor


var START_COLOR = "#fff1a8";
var END_COLOR = "#b31e1e";

function divisionCountdown(seconds, element, label) {
    if (typeof element == 'string') element = $(element);
    
    Element.addMethods({
        updateDivision: function(num, sec, elt, lab) {
            elt.style.backgroundColor = calculatePresentColor(
                Math.round(num / sec * 100), START_COLOR, END_COLOR
            );
            var remaining = Math.round(sec - num);
            if (remaining == 0) {
                elt.innerHTML = lab.toUpperCase();
            } else {
                elt.innerHTML = lab + " (" + remaining + ")";
            }
        },
    });
    
    if (element.tagName == 'DIV') {
        for (var i = 0; i <= seconds; i+=0.1) {
            Element.updateDivision.delay(i, i, seconds, element, label);
        }
    }
}

function calculatePresentColor(percentage, startColor, endColor) {
    startColor = new RGBColor(startColor);
    endColor = new RGBColor(endColor);

    if (startColor.ok && endColor.ok) {
        var r = Math.round(startColor.r > endColor.r ?
            startColor.r - Math.abs(startColor.r - endColor.r) * percentage / 100 :
            startColor.r + Math.abs(startColor.r - endColor.r) * percentage / 100);
        var g = Math.round(startColor.g > endColor.g ?
            startColor.g - Math.abs(startColor.g - endColor.g) * percentage / 100 :
            startColor.g + Math.abs(startColor.g - endColor.g) * percentage / 100);
        var b = Math.round(startColor.b > endColor.b ?
            startColor.b - Math.abs(startColor.b - endColor.b) * percentage / 100 :
            startColor.b + Math.abs(startColor.b - endColor.b) * percentage / 100);
        return 'rgb(' + r + ', ' + g + ', ' + b + ')';
    }
}

点击查看演示

标签:

2007年12月18日

有备无患:用饭否动态更新博客的slogan

有时候灵感来了,会蹦出一个很赞的blog口号,但每每想到还得重新发布一遍整个blog,常常就此放弃了。刚才想到一个利用饭否的解决办法。分享之。

方法很简单:把红色的"blog-description"换成相应的元素id,把橙色的"luliban"换成你的饭否ID,然后把下面这段javascript代码复制粘贴到模板最后(即</body>之前)即可。(wyt:Twitter,或其他提供API的微内容服务也可以用这种方法,代码需根据其API作相应的改动)

<script type="text/javascript">//<![CDATA[
function getDescription(json) {
    document.getElementById('blog-description').innerHTML = json[0].text;
}
//]]></script>
<script type='text/javascript'
    src='http://api.fanfou.com/statuses/user_timeline.json?id=luliban&amp;count=1&amp;callback=getDescription'>
</script>

标签: , ,