将 Blogroll 分为两栏 (4)

Jul 24th, 2008 Add Comment

经过前面几回, 我们已经将想法作成了一个简单的插件, 先回顾一下吧:
将 Blogroll 分为两栏 (1)
将 Blogroll 分为两栏 (2)
将 Blogroll 分为两栏 (3)

插件的基本功能都实现了, 但它的不足也是显而易见的, 当链接超过 30 个的时候, widget 还是会变得很长. 可能你会想到限制显示数量并采用随机显示, 用公平的方式显示少量链接以达到界面的美观. 但很遗憾, 当你想找到某人的链接时, 可能刷新好几次页面都无法将你想要的显示出来. 所以我们还需要一个功能, 将所有链接都显示出来, 而我们显示所有时不需要重载页面. 翻页? 没必要吧. 我相信你不会有 100 个链接, 假如你有, 你一定会将他们进行分类的.

分析:

为了使用这个 "显示所有链接" 的功能, 我们追加一个叫 "show all" 的按钮. 大概你已经知道 show all 是干什么用的, 也知道在点击 show all 之后会发生些什么事情. 但在为插件添加 show all 之前, 我们还需要确定的几个问题:

WHEN? 什么时候显示 show all?

1. 插件是可以限制或者不限制显示数量的. 如果不限制显示数量, 那么所有链接都会被显示出来, 而无需显示 show all.
2. 如果限制显示数量了, 但限制的数量比当前链接总数还大, 那么所有的链接还是会被显示出来, 不需要显示 show all.
3. 可能有人觉得 show all 功能是多余的, 或者访客点击后会影响他的界面布局, 并不希望使用. 为此, 定义参数 navigator, 当 navigator 为 true 时才显示 show all.

也就是说, 当且仅当显示数量有限制, 限制的数量小于当前链接总数, 并且参数 navigator 为 true 的时候, show all 才会被显示出来.

WHERE? 在哪里显示显示 show all?

widget 的结构是 ul 里面放了很多个 li, 而每个 li 都是纵向排列的. 我们可以将 show all 放到最后一个 li 中. 而出于对用户习惯的考虑, 将它摆放在最后一个 li 的右边.

HOW? 点击 show all 后如何处理?

这个用文字很难解释清楚, 作了个不大规范的顺序图:

image

访客点击页面上的 show all 按钮, 浏览器显示 "Loading ...", 并从服务器获取包含全部链接的 widget 代码. 当响应返回的代码传输完毕, 将链接 widget 替换掉.

在访客点击按钮后, 浏览器接管了所有的工作, 浏览器会跟服务器打交道, 将生成的 widget 代码替换到原来的位置上.

编码:

追加 navigator 参数

navigator 默认为 ture, 即默认显示 show all 按钮.

// 默认参数
$defaults = array(
	'columns' => 1,				// 分栏数量
	'limit' => 0,				// 收藏数量
	'category' => '',			// 分类名称
	'orderby' => 'name', 		// 排序对象
	'order' => 'ASC',			// 排序方法
	'target' => 'false',		// 是否应用 target 便签, true: 应用; false: 不应用
	'navigator' => 'true'		// 是否显示翻页的导航, true: 显示; false: 不显示
);
 
...
 
if ( $args['navigator'] != 'false' ) {
	$args['navigator'] = 'true';
}

放置 show all 按钮

1. 判断是否需要显示
如果用户定义了限定长度, 将 limit 长度加 1, 判断是否在限定长度外还有更多的链接.

if ($args['limit'] > 0) {
	// 准备多取一个, 以便获知是否有更多链接存在
	$limit = ' LIMIT ' . ($args['limit'] + 1);
}

如果存在更多链接, 设置标志变量 $hasMore, 并去除最后那个多余的链接.

if ($args['limit'] > 0) {
	// 如果能够获取多一个元组, 证明有更多链接存在
	$hasMore = (count($links) - $args['limit'] > 0);
	// 有更多链接存在时, 删除最后那个多余的
	if ( $hasMore ) {
		array_pop($links);
	}
}

2. 根据前面的判断和 navigator 参数, 决定是否处理 show all
如果需要显示, 将它加到 widget 的最后. 按钮的 href 设为 javascript:void(0);, 以免用户直接看到参数信息. onclick 调用 JavaScript 方法 mlChange 对 widget 进行处理, 该方法将在本文的后面提到.

// 当需要显示 Show all 按钮时才显示这一栏
if ( $hasMore && $args['navigator'] == 'true' ) {
	// Show all 按钮
	$showAll = '<a class="ml_showall" href="javascript:void(0);" onclick="mlChange(\'' . get_bloginfo('wpurl') . '\',\'' . $argsf . '\',\'' . __('Loading', 'wp-multicollinks') . '\');">' . __('Show all &raquo;', 'wp-multicollinks') . '</a>';
	$result .= '<li id="ml_nav"><div>' . $showAll . '<div class="ml_fixed"></div></div></li>';
}

为 show all 定义样式.

.ml_showall {
	float:right;
}

创建 AJAX 处理
勉强算是用了 AJAX, 但这是最简单的应用了. 如果你还没看过相关资料, 你可以到网上找个 AJAX 的 Hello World 来看一下, 只要弄清楚在不同的浏览器中如何获取 XMLHttp 对象和 AJAX 在不同浏览器中的生命周期就足够了.
这里使用 GET 方式处理, 当参数长度不超过 255 的时候, GET 有它一定的优势, 有兴趣可以百度一下或 Google it.

var mlXmlHttp;
var mlLoadingText;
 
function mlGetXmlHttpObject() {
	var xmlHttp = null;
 
	// Firefox, Opera 8.0+, Safari
	try {
		xmlHttp = new XMLHttpRequest();
 
	// Internet Explorer
	} catch(e) {
		try {
			xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
		} catch(e) {
			xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
		}
	}
 
	return xmlHttp;
}
 
function mlChange(wpurl, args, loading) {
	// 检验 XMLHttp 对象
	mlXmlHttp = mlGetXmlHttpObject();
	if (mlXmlHttp == null) {
		alert ("Oop! Browser does not support HTTP Request.")
		return;
	}
 
	// loading 参数不存在时使用 "Loading"
	mlLoadingText = (loading == undefined) ? "Loading" : loading;
 
	// 使用博客的地址
	var url = wpurl;
	// action 参数
	url += "?action=ml_ajax";
	// 插件参数
	url += "&args=" + args;
 
	mlXmlHttp.onreadystatechange = runMlChange;
	// GET 方式处理
	mlXmlHttp.open("GET", url, true);
	mlXmlHttp.send(null);
}
 
function runMlChange() {
	// show all 所在容器
	var navigator = document.getElementById("ml_nav");
	// widget 的容器
	var parent = navigator.parentNode;
 
	// 完成之前的所有状态
	if (mlXmlHttp.readyState < 4) {
		// 鼠标指针变为漏洞状
		document.body.style.cursor = 'wait';
		// 显示 "加载中 ..."
		navigator.innerHTML = mlLoadingText + " ..."
 
	// 响应完成后
	} else if (mlXmlHttp.readyState == 4 || mlXmlHttp.readyState=="complete") {
		// 替换 widget
		parent.innerHTML = mlXmlHttp.responseText;
		// 鼠标指针恢复原状
		document.body.style.cursor = 'auto';
	}
}

为什么 mlChange 方法需要 loading 这个参数?
因为通过本地化处理后, 加载时显示的字符串是不统一的, 而处理本地化不能在纯 JavaScript 代码中实现, 所以需要作为参数传进来.

在 wp-multicollinks.php 处理 head 的方法中追加应用引用 JavaScript 的方法.

echo "\n" . '<script type="text/javascript" src="' . get_bloginfo('wpurl') . '/wp-content/plugins/wp-multicollinks/wp-multicollinks.js"></script>';

在 core.php 处理 create_multicollinks 方法里格式化参数串, 因为 "=" 和 "&" 两个符号与 URL 中参数段的字符冲突, 所以要进行转化处理. 这里将 "=" 转为 "--", "&" 号转为 "---".

// AJAX 翻页时用的参数
$argsf = str_replace('=', '--', $args);
$argsf = str_replace('&', '---', $argsf);

定义 AJAX 请求响应的方法, 并它注册到 WordPress 系统中.

function ml_ajax() {
	if( $_GET['action'] == 'ml_ajax' ) {
		$argsf = $_GET["args"];
 
		// 参数还原
		$args = str_replace('---', '&', $argsf);
		$args = str_replace('--', '=', $args);
 
		// 如果 limit 参数存在, 将它换为未知参数, 即使它失效, 显示全部链接
		if ( strstr($args, 'limit=') ) {
			$args = str_replace('limit=', 'unknown=', $args);
		// 如果参数不存在, 将它置为 0, 即显示全部链接
		} else {
			$args .= '&limit=0';
		}
 
		// 向 response 输出新的 widget
		echo create_multicollinks( $args );
		// 退出脚本处理
		die();
	}
}
add_action('init', 'ml_ajax');

总结:

经过几个环节, 这个插件已经基本完成了.
这并不是一个复杂且强大的插件, 但我觉得它还是有一定实用性的, 这正是我选择它作为教程的原因. 目前并不支持 widget, 我觉得 "如何让插件支持 Widget" 应该是另一个问题, 不应混在一起讨论, 以后有机会我会再介绍一下的.

下载:

还不是正式版, 有兴趣可以试用一下. (如无意外, 正式版中只会加入对 Widget 的支持) :)
下载请点: wp-multicollinks.0.4.zip

声明: 本文采用 BY-NC-SA 协议进行授权. 转载请注明转自: 将 Blogroll 分为两栏 (4)

  1. http://0.gravatar.com/avatar/490cf262668eebb0f0f1a50d9d48d702?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    @王磊
    我发布过一个插件, 就是干这个的, 你可以到 http://www.neoease.com/plugins/ 去下载.

  2. http://0.gravatar.com/avatar/c717da866680e904e2f4373bf8d53003?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    看完了,感觉自己还是不知道怎么弄

  3. http://1.gravatar.com/avatar/32744f6e136689ea16c4c33f46a10fa4?s=32&d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    请问博主,这个插件能不能让分类目录也按照这样的方式显示呢?如果可以,请赐教!谢谢!

  4. http://0.gravatar.com/avatar/891cb0b26cd8d4bd3bb39f9b7060d131?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    我也是直接改css的,不过用插件不用动主题文件,以后升级比较方便。
    博主用的是什么评论插件啊,这个我比较喜欢,呵呵。

  5. http://0.gravatar.com/avatar/2044107496b3466415160f6f75c83716?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    还要写代码 囧 不直接css就OK了么。 :arrow:

  6. http://0.gravatar.com/avatar/62215f244e698248889b5f9dd4344f7b?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    @mg12
    如果只是单存的2,3,4栏的话,用css就可以定义了,没必要用php来参与, li 的 display:block, width: height: float:left ....
    结构就是默认的 .........

  7. http://0.gravatar.com/avatar/490cf262668eebb0f0f1a50d9d48d702?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    @Yacca
    IE7 可以用 *+ 作为识别符号可以个别处理.
    IE8 我没测试过, 如果 IE8 支持 W3C 更完美些, 那应该跟其他浏览器没太大差异的.
    因为 IE7 是认 !important, 而又以 IE 方式显示, 十分麻烦.
    但你可以处理如下:

    .example {
    width:120px !important;
    width:100px;
    }
    *+html .example {
    width:100px !important;
    }

    BTW: 你博客导航的 JavaScript 处理可能有点问题, Firefox2 无法选中 MenuItem.

  8. http://0.gravatar.com/avatar/401b6aa08d03f959bc41a4f33e4fea6c?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    mg12:
    @benmao
    设置参数 target=true
    正式版中将会撤销这个参数.

    mg12...这些天我看到chada的侧边栏后自己也写了代码来实现这分列效果(实际使用中2栏足矣^^) 不过我在css里遇到了些问题,譬如在ie的5.5-8之间 ie8无法兼顾到...出现了些许问题,具体可以去我那里看下,我截了图发了篇文来解决这个问题,唉...-.-

    虽然插件很方便,但我又不喜欢widgets形式,所以最终还是希望以code形式实现它.

  9. http://0.gravatar.com/avatar/490cf262668eebb0f0f1a50d9d48d702?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    @benmao
    设置参数 target=true
    正式版中将会撤销这个参数.

  10. http://0.gravatar.com/avatar/8f5f791a96da1149371856ed81518c07?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    请问如何设置为 点击连接打开新页面?我设置这个插件后在原页面访问。

  11. http://1.gravatar.com/avatar/7c9412d8d9386b950a3ebfe8bfedc053?s=32&d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    我的工作是这方面的, 所以比较敏感, 哈哈..
    你说的对, 这个GET里的参数如果只有站长可以manipulate, 那么攻击的可能性就不大..
    解决办法对这个input的sink不同而不同..
    比如写入HTML就要用htmlspecialchars来encoding input,
    用mysql_real_escape_string来对付sql injection :smile:

  12. http://0.gravatar.com/avatar/490cf262668eebb0f0f1a50d9d48d702?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    @schuen
    非常谢谢, 又学到东西了. :)
    这个确实, 如果被植入代码, 可以被利用来投放广告和追加链接. 但原理上即使是 POST 方法也会存在这样的问题, 解决办法是对所有参数的有效范围进行 validate. 不知道是不是还有其他更好的解决办法, 如果需要这样验证, 那还真够汗的...
    但要利用这样的漏洞, 需要向页面植入代码. 但这些参数都是站长写入的, 除非自己搞破坏, 否则输入不正也只是页面异常而已.

    BTW, PHP 真奇妙...

  13. http://1.gravatar.com/avatar/7c9412d8d9386b950a3ebfe8bfedc053?s=32&d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    但是对参数的检验不够严格..
    比如$args['columns']如果等于 3">..., 小于1和大于4的检验无效了就..
    这样在这里
    '
    就会有XSS的漏洞..

  14. http://0.gravatar.com/avatar/490cf262668eebb0f0f1a50d9d48d702?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    schuen:

    args是通过get传送的, 然后会写入sql statement.. 有sql injection漏洞哦..

    不太明白 sql injection 这个概念, 但如果你的意思是 hacker 可以通过插件修改数据库或者任意查看数据, 那是办不到的. 这只是个查询的插件, 而且参数是定死的, 后台会对参数进行检验.

  15. http://0.gravatar.com/avatar/490cf262668eebb0f0f1a50d9d48d702?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    @etian007
    最大就 4 栏, 再多两栏你放得下吗?

    @2sLow
    我试不出来, 是有点儿怪.

    @毛蛋
    我是用了 wp-postrating 这个插件.

  16. http://1.gravatar.com/avatar/9194c69e896620e202e0768241ed7267?s=32&d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    我重新把core.php内容复制一份,新建一个php文件,上传之后就可以了

  17. http://1.gravatar.com/avatar/bd3f5b94b866c638a03d3e0152b0989f?s=32&d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    请问,你的侧栏的Most Popular Posts是用的啥插件?是不是wp-rating做过了什么修改?

  18. http://1.gravatar.com/avatar/9194c69e896620e202e0768241ed7267?s=32&d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    Parse error: syntax error, unexpected T_FUNCTION in /udata/k2/7/ku68187/public_html/wp-content/plugins/wp-multicollinks/core.php on line 1

    wp-multicollinks.0.5.zip启用插件的时候出现这个匪夷所思的问题。。。。。。

  19. http://1.gravatar.com/avatar/193101bee7534062e02bcc2154e0b6dd?s=32&d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    1.2.3.4...接下来?还有几栏呢?

  20. http://1.gravatar.com/avatar/7c9412d8d9386b950a3ebfe8bfedc053?s=32&d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    args是通过get传送的, 然后会写入sql statement.. 有sql injection漏洞哦..

  21. http://0.gravatar.com/avatar/490cf262668eebb0f0f1a50d9d48d702?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    WordPress 还没通过我的插件申请. 扁扁嘴 :|

  22. http://1.gravatar.com/avatar/d49fd5453de5b190e03b9ac9feedc9d8?s=32&d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    Great 我喜欢这个插

  23. http://0.gravatar.com/avatar/8f5f791a96da1149371856ed81518c07?s=32&d=http%3A%2F%2F0.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G

    确实不错。。。。都是很实用的插件。。支持你。等待widget版本。

  1. Loading...