HT for Web的HTML5樹元件有延遲載入的功能,這個功能對於那些需要從伺服器讀取具有層級依賴關係資料時非常有用,需要獲取資料的時候再向伺服器發起請求,這樣可減輕伺服器壓力,同時也減少了瀏覽器的等待時間,讓頁面的載入更加流暢,增強使用者體驗。
http://www.hightopo.com/guide/readme.html
進入正題,今天用來做演示的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;
}