HT for Web的HTML5樹組件有延遲加載的功能,這個功能對於那些需要從服務器讀取具有層級依賴關系數據時非常有用,需要獲取數據的時候再向服務器發起請求,這樣可減輕服務器壓力,同時也減少了瀏覽器的等待時間,讓頁面的加載更加流暢,增強用戶體驗。
進入正題,今天用來做演示的Demo是,客戶端請求服務器讀取系統文件目錄結構,通過HT for Web的HTML5樹組件顯示系統文件目錄結構。
首先,我們先來設計下服務器,這次Demo的服務器采用Node.js,用到了Node.js的express、socket.io、fs和http這四個模塊,Node.js的相關知識,我在這里就不闡述了,網上的教材一堆,這里推薦下socket.io的相關入門http://socket.io/get-started/chat/。
服務端代碼代碼:
var fs = require('fs'),
express = require('express'),
app = express(),
server = require('http').createServer(app),
io = require('socket.io')(server),
root = ‘/Users/admin/Projects/ht-for-web/guide‘;
io.on('connection', function(socket){
socket.on('explore', function(url){
socket.emit('file', walk(url || root));
});
});
app.use(express.static('/Users/admin/Projects/ht-for-web'));
server.listen(5000, function(){
console.log('server is listening at port 5000');
});
io監聽了connection事件,並獲得一個socket;socket再監聽一個叫explore的自定義事件,通過url參數獲取到數據后,派發一個叫file的自定義事件,供客戶端監聽並做相應處理;通過app.use結合express.static設置項目路徑;最后讓server監聽5000端口。
到此,一個簡單的服務器就搭建好了,現在可以通過http://localhost:5000來訪問服務器了。等等,好像缺了點什么。對了,獲取系統文件目錄結構的方法忘記給了,OK,那么我們就先來看看獲取整站文件的代碼是怎么寫的:
function walk(pa) {
var dirList = fs.readdirSync(pa),
key = pa.substring(pa.lastIndexOf('/') + 1),
obj = {
name: key,
path: pa,
children: [],
files: []
};
dirList.forEach(function(item) {
var stats = fs.statSync(pa + '/' + item);
if (stats.isDirectory()) {
obj.children.push(walk(pa + '/' + item));
}
else {
obj.files.push({name: item, dir: pa + '/' + item});
}
});
return obj;
}
如大家所見,采用遞歸的方式,逐層遍歷子目錄,代碼也沒什么高深的地方,相信大家都看得懂。那我們來看看運行效果吧:

duang~文件目錄結構出來了,是不是感覺酷酷的,這代碼量不小吧。其實,代碼並不多,貼出來大家瞅瞅:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>tree-loader</title>
<script src="/socket.io/socket.io.js"></script>
<script src="/lib/core/ht.js"></script>
<script>
var socket = io(), idMap = {};
function init() {
var dm = window.dm = new ht.DataModel(),
tree = new ht.widget.TreeView(dm);
tree.addToDOM();
socket.on('file', function(data) {
var root = dm.getDataById(idMap[data.path]);
createChildren(data.children || [], root, dm);
createFiles(data.files || [], root, dm);
});
socket.emit('explore');
}
function createChildren(children, parent, dm) {
children.forEach(function(child) {
var n = createData(child, parent);
dm.add(n);
createChildren(child.children || [], n, dm);
createFiles(child.files || [], n, dm);
});
}
function createFiles(files, parent, dm){
files.forEach(function(file){
var n = createData(file, parent);
dm.add(n);
});
}
function createData(data, parent){
var n = new ht.Data();
n.setName(data.name);
n.setParent(parent);
n.a('path', data.path);
idMap[data.path] = n.getId();
return n;
}
</script>
</head>
<body onload="init();">
</body>
</html>
這就是全部的HTML代碼,加上空行總共也就50幾行,怎么樣,有沒有感覺HT for Web很強大。廢話不多說,來看看這些代碼都干了些什么:
- 要用到socket.io就需要在頁面引入<script src=“/socket.io/socket.io.js”></script>,其實在我的項目中並不存在/socket.io/socket.io.js文件,但是卻能正常使用,具體什么原因,我就不多說,大家自己研究去吧;
- 最重要的是要引入HT for Web的核心包<script src=“/lib/core/ht.js”></script>,這個包不引入的話,下面的HT for Web組件就無法使用;
- 接下來就是代碼了,首先創建一個數據容器DataModel,用來存放文件目錄的節點數據,再創建一個TreeView對象並引用剛創建到數據容器,接下來通過socket監聽file事件,獲取服務器返回的數據,在回調函數中通過調用createChildren和createFiles函數,創建文件目錄節點對象,並添加到數據容器中,最后是向服務器發起數據請求,即通過socket派發explore事件。
整體的思路是這樣子的,當然這離我們要實現的樹組件的延遲加載技術還有些差距,那么,HT for Web的HTML5樹組件的延遲加載技術是怎么實現的呢?不要着急,馬上開始探討。
首先我們需要改造下獲取文件目錄的方法walk,因為前面介紹的方法中,使用的是加載整站文件目錄,所以我們要將walk方法改造成只獲取一級目錄結構,改造起來很簡單,就是將遞歸部分改造成獲取當前節點就可以了,具體代碼如下:
obj.children.push(walk(pa + '/' + item));
// 將上面對代碼改成下面的代碼
obj.children.push({name: item, path: pa + '/' + item});
這樣子服務器就只請求當前請求路徑下的第一級文件目錄結構。接下來就是要調整下客戶端代碼了,首先需要給tree設置上loader:
tree.setLoader({
load: function(data) {
socket.emit('explore', data.a('path'));
data.a('loaded', true);
},
isLoaded: function(data) {
return data.a('loaded');
}
});
loader包含了兩個方法,load和isLoaded,這兩個方法的功能分別是加載數據和判斷數據是否已經加載,在load方法中,對socket派發explore事件,當前節點的path為參數,向服務器請求數據,之后將當前節點的loaded屬性設置為true;在isLoaded方法中,返回當前節點的loaded屬性,如果返回為true,那么tree將不會在執行load方法向服務器請求數據。
接下來需要移除createChildren的兩個回調方法,並且在createFiles方法中為創建出來的節點的loaded屬性設置成true,這樣在不是目錄的節點前就不會有展開的圖標。createChildren和createFiles兩個方法修改后的代碼如下:
function createChildren(children, parent, dm) {
children.forEach(function(child) {
var n = createData(child, parent);
dm.add(n);
});
}
function createFiles(files, parent, dm){
files.forEach(function(file){
var n = createData(file, parent);
n.a('loaded', true);
dm.add(n);
});
}
如此,HT for Web的HTML5樹組件延遲加載技術就設計完成了,我在服務器的控制台打印出請求路徑,看看這個延遲加載是不是真的,如下圖:


看吧,控制台打印的是4條記錄,第一條是請求跟目錄時打印的,我在瀏覽器中展開里三個目錄,在控制台打印了其對應的目錄路徑。
等等,現在這個目錄看起來好煩,只有文字,除了位子前的展開圖標可以用來區別文件和目錄外,沒有其他什么區別,所以我決定對其進行一番改造,讓每一級目錄都有圖標,而且不同文件對應不同的圖標,來看看效果吧:

怎么樣,是不是一眼就能看出是什么文件,這個都是樣式上面的問題,我就不再一一闡述了,直接上代碼:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script src="/socket.io/socket.io.js"></script>
<script src="/build/ht-debug.js"></script>
<script>
var socket = io(), idMap = {};
function init() {
var icons = ['css', 'dir-open', 'dir', 'file', 'flash', 'gif', 'html', 'jar',
'java', 'mp3', 'pdf', 'png', 'script', 'txt', 'video', 'xml', 'zip'];
icons.forEach(function(c){
ht.Default.setImage(c, 16, 16, '/test/wyl/images/' + c + '.png');
});
var dm = window.dm = new ht.DataModel(),
tree = new ht.widget.TreeView(dm);
tree.setLoader({
load: function(data) {
socket.emit('explore', data.a('path'));
data.a('loaded', true);
},
isLoaded: function(data) {
return data.a('loaded');
}
});
tree.getLabelFont = function(data){
return '13px Helvetica, Arial, sans-serif';
};
tree.getLabelColor = function (data) {
return this.isSelected(data) ? 'white' : 'black';
};
tree.getSelectBackground = function (data) {
return '#408EDB';
};
tree.getIcon = function (data) {
var icon = data.getIcon() || 'file';
if (data.a('isdir')) {
if (this.isExpanded(data)) {
icon = 'dir-open';
} else {
icon = 'dir';
}
}
return icon;
};
tree.addToDOM();
socket.on('file', function(data) {
var root = dm.getDataById(idMap[data.path]);
createChildren(data.children || [], root, dm);
createFiles(data.files || [], root, dm);
});
socket.emit('explore');
}
function createChildren(children, parent, dm) {
children.forEach(function(child) {
var n = createData(child, parent);
n.a('isdir', true);
dm.add(n);
});
}
function createFiles(files, parent, dm){
files.forEach(function(file){
var n = createData(file, parent);
n.a('loaded', true);
dm.add(n);
});
}
function createData(data, parent){
var name = data.name,
icon = 'file';
if (/.jar$/.test(name)) icon = 'jar';
else if (/.css$/.test(name)) icon = 'css';
else if (/.gif$/.test(name)) icon = 'gif';
else if (/.png$/.test(name)) icon = 'png';
else if (/.js$/.test(name)) icon = 'script';
else if (/.html$/.test(name)) icon = 'html';
else if (/.zip$/.test(name)) icon = 'zip';
var n = new ht.Data();
n.setName(data.name);
n.setParent(parent);
n.setIcon(icon);
n.a('path', data.path);
idMap[data.path] = n.getId();
return n;
}
</script>
</head>
<body onload="init();">
</body>
</html>
在最后,附上完整的服務器代碼:
var fs = require('fs'),
express = require('express'),
app = express(),
server = require('http').createServer(app),
io = require('socket.io')(server),
root = '/Users/admin/Projects/ht-for-web/guide';
io.on('connection', function(socket){
socket.on('explore', function(url){
socket.emit('file', walk(url || root));
});
});
app.use(express.static('/Users/admin/Projects/ht-for-web'));
server.listen(5000, function(){
console.log('server is listening at port 5000');
});
function walk(pa) {
var dirList = fs.readdirSync(pa),
key = pa.substring(pa.lastIndexOf('/') + 1),
obj = {
name: key,
path: pa,
children: [],
files: []
};
dirList.forEach(function(item) {
var stats = fs.statSync(pa + '/' + item);
if (stats.isDirectory()) {
obj.children.push({name: item, path: pa + '/' + item});
}
else {
obj.files.push({name: item, dir: pa + '/' + item});
}
});
return obj;
}
