HT for Web的HTML5樹組件有延遲加載的功能,這個功能對於那些須要從服務器讀取具備層級依賴關係數據時很是有用,須要獲取數據的時候再向服務器發起請求,這樣可減輕服務器壓力,同時也減小了瀏覽器的等待時間,讓頁面的加載更加流暢,加強用戶體驗。javascript
http://www.hightopo.com/guide/readme.html
css
進入正題,今天用來作演示的Demo是,客戶端請求服務器讀取系統文件目錄結構,經過HT for Web的HTML5樹組件顯示系統文件目錄結構。html
首先,咱們先來設計下服務器,此次Demo的服務器採用Node.js,用到了Node.js的express、socket.io、fs和http這四個模塊,Node.js的相關知識,我在這裏就不闡述了,網上的教材一堆,這裏推薦下socket.io的相關入門http://socket.io/get-started/chat/。java
服務端代碼代碼:node
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端口。web
到此,一個簡單的服務器就搭建好了,如今能夠經過http://localhost:5000來訪問服務器了。等等,好像缺了點什麼。對了,獲取系統文件目錄結構的方法忘記給了,OK,那麼咱們就先來看看獲取整站文件的代碼是怎麼寫的:express
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很強大。廢話很少說,來看看這些代碼都幹了些什麼:app
總體的思路是這樣子的,固然這離咱們要實現的樹組件的延遲加載技術還有些差距,那麼,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; }