HT for Web的HTML5樹組件消極式載入技術實現,hthtml5
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;}