這是一片 HT 的入門級文章,若是您能讀懂
http://www.hightopo.com/guide/guide/core/beginners/examples/example_overview.html
http://www.hightopo.com/guide/guide/core/beginners/examples/example_node.html
兩個例子,那麼能夠跳過這篇文章,若是你對 ht.graph.GraphView,ht.DataModel 和 ht.Node 三者之間的關係還不是很瞭解,不知道如何工做的,那麼不妨看下去,相信這篇文章可以幫到你。html
以前在 cnblog 搜索到關於入門的例子,好比 http://www.cnblogs.com/xhload3d/p/5911978.html,http://www.javashuo.com/article/p-ectsmvml-mh.html 有講解上面三者的關係,可是之前並無看得很明白,我也是經過和 HT 的技術支持接觸才慢慢理解 HT 是如何工做。下面經過一篇小文章像你們講解下這三者整體上的關係,但願能幫助到剛接觸這個框架的人。node
既然你是在入門框架的時候遇到困難而後找到這篇博客,那麼不妨先拋棄 HT ,經過一個小例子模擬下 HT 上三者的關係。
該例子使用了一些 es6 的語法,好比箭頭函數和 class,若是你對es6不熟悉,能夠移步 http://exploringjs.com/es6/ 瞭解。若是你有必定 JavaScript 功底,能夠直接跳過看最終 demo。固然也能夠跟隨 demo,或者邊看過作,這樣或者能更好理解。git
劃 demo 核心點:es6
核心關係:View 綁定 Model,Model 管理不少 Node,Node 發生變化時通知 Model,而後 Model 更新綁定他的 View 組件。github
demo 開始(下面有些地方說的 node,有些地方說的 data,暫時能夠理解爲一個概念,但其實不是,在學習 HT 的過程當中你會了解到),新建一個 index.html,並插入以下內容canvas
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body onload=init()> <script> function init(){ } </script> </body> </html>
下面開始建 View組件,View組件 主要用於展現做用,展現層元素掛載到組件的 _view 上面,script標籤裏插入以下代碼:數組
class View{ constructor(){ this._view = document.createElement('div'); const style = this._view.style; style.position = 'absolute'; style.top = 0; style.right = 0; style.bottom = 0; style.left = 0; } getView(){ return this._view; } addToDom(parentNode){ if(!!parentNode) { parentNode.appendChild(this.getView()); } else { document.body.appendChild(this.getView()); } } }
並在 init 函數裏面新建 view實例 並加入到 DOM 中,init 函數以下:瀏覽器
function init(){ view = new View(); view.addToDom(); }
此時在瀏覽器中打開 index.html,暫時的確什麼都沒有,但若是你在控制檯 Elements 裏面看到有個 div 插入到 script 標籤下面,那麼表明到這裏你是成功的。app
下面開始建立 Model 組件,首先分析一下 Model 的做用框架
因此 Model 組件須要幾個接口
建立 Model 組件代碼以下:
class Model{ constructor() { this._datas = []; this.listeners = []; } addListener(fn){ this.listeners.push(fn); } handleDataChange(){ this.listeners.forEach(fn => fn()); } add(node){ node.setModel(this); if(this._datas.includes(node)){ return; } this._datas.push(node); this.handleDataChange(); } each(fn){ this._datas.forEach((data, index, list) => { fn(data, index, list) }) } getDatas(){ return this._datas; } }
固然如今界面上依然什麼都沒有,由於尚未爲 Model 加入任何展現的 Node,建立Node代碼以下:
class Node{ constructor() { this._node = document.createElement('div'); this._name = ''; const style = this._node.style; style.position = 'absolute'; style.top = 0; style.left = 0; style.height = '100px'; style.width = '100px'; style.overflow = 'hidden'; style.background = '#D8D8D8'; } getElString(){ return this._node.outerHTML; } fireChange(){ !!this._model && this._model.handleDataChange(); } setPosition(x, y){ const style = this._node.style; style.left = x + 'px'; style.top = y + 'px'; this.fireChange(); } setX(x){ this._node.style.left = x + 'px'; this.fireChange() } setY(y){ this._node.style.top = y + 'px'; this.fireChange(); } setImage(url){ const style = this._node.style; if(!!url){ this._node.innerHTML = ''; style.background = `url(${url}) no-repeat center`; this.fireChange(); } } setSize(width, height){ const style = this._node.style; style.width = width + 'px'; style.height = height + 'px'; this.fireChange(); } setWidth(width){ this._node.style.width = width + 'px'; this.fireChange() } setHeigth(height){ this._node.style.height = height + 'px'; this.fireChange(); } setName(name){ this._name = name; this._node.innerHTML = name; this.fireChange(); } setModel(model){ this._model = model; } }
這裏暫時使用 _node 來掛載一個 div,而後操做 div 的一些屬性顯示出來,就像 canvas 上繪製一個矩形,若是你有基本的 JavaScript 功底,這裏的 setXXX 函數功能應該都不會陌生,而 setModel 功能是讓該 node 知道它是被哪個 Model 管理,fireChange 功能則是通知 Model 有更新
當 Model 被通知更新調用 handleDataChange 的時候,功能則是執行註冊的全部更新函數,來達到更新全部綁定該 Model 組件的目的。
此時 init 函數能夠稍微修改一下來顯示出一點內容,修改後 init 函數以下:
function init(){ model = new Model() view = new View(model); view.addToDom(); node1 = new Node(); node1.setPosition(30, 30); node1.setName('我是node1'); model.add(node1); }
此時刷新頁面仍是什麼都沒有,由於 View 組件暫時缺乏綁定 Model 和更新的方法,View 組件更新後代碼以下:
class View{ constructor(model){ this._view = document.createElement('div'); const style = this._view.style; style.position = 'absolute'; style.top = 0; style.right = 0; style.bottom = 0; style.left = 0; !!model && this.setModel(model); } getView(){ return this._view; } setModel(model){ this._model = model; model.addListener(this.invalidate.bind(this)); } invalidate(){ const view = this.getView(); let innerHTML = ''; view.innerHTML = ''; this._model.each((data) => { innerHTML += data.getElString(); }) view.innerHTML = innerHTML; } addToDom(parentNode){ if(!!parentNode) { parentNode.appendChild(this.getView()); } else { document.body.appendChild(this.getView()); } this.invalidate(); } }
在 View 組件的構造函數中支持了可選的 model,setModel 函數能夠供組件在後期更換 Model,在該函數中會讓 model 註冊該 view 組件的 invalidate 函數,invalidate 會在 Model 發生更新的時候被調用,此時再刷新一下瀏覽器,會發現一個 div 處於屏幕上,他的位置由 node.setPosition 決定。
初版的 demo 到此完成,此時你應該理解 view<-->model<-->node 他們的關係,可是此時你可能會有一個疑問,node 的管理爲何不直接在它要顯示的 view 組件上,而是要一個專門的 Model 管理,而後 view 去使用 model,HT 的設計是強大的,他可讓你在不一樣的 view 上顯示相同的 model 類容,並且當 node 改變時,全部的 view 會同步更新。
如今先用兩個不一樣的 view 來演示一下,在 body 下面加入兩個 div 分別命名 view1 和 view2,這部分代碼參考以下:
<body onload=init()> <div id="view1"></div> <div id="view2"></div> <script> class View{ ...
而後爲這兩個 div 加一點樣式,在 title 下面加入 style 標籤並加入以下樣式:
<style> div { box-sizing: border-box; overflow: hidden; } #view1 { position: absolute; top: 0; left: 0; right: 0; width: 50%; height: 400px; border: 2px solid #4080BF; } #view2 { position: absolute; top: 0; right: 0; width: 50%; height: 400px; border: 2px solid #4080BF; } </style>
最後在 init 函數裏面創建兩個 view 對象並分別掛載到 view1 和 view2 下面,修改後的init函數以下:
function init(){ model = new Model() view = new View(model); view.addToDom(document.getElementById('view1')); node1 = new Node(); node1.setPosition(30, 30); node1.setName('我是node1'); model.add(node1); view2 = new View(model); view2.addToDom(document.getElementById('view2')) }
如今刷新瀏覽器,會看到左右兩個藍框的div左上角分別有兩個灰色的方塊,裏面顯示的內容經過 node.setName() 設定
到這裏你應該更加理解 view 和 model 的關係,可是可能你還有一個疑惑,幹嗎須要兩個相同的 view 來顯示相同的內容。在一些場合,可能你不僅是須要展現圖形,還須要一個表格來展現 model 裏面 data 元素的一些具體屬性,好比 http://www.hightopo.com/guide/guide/core/beginners/examples/example_overview.html 左下方 TableView 組件 所示,這兒用 demo 模擬一下他們的工做。要建立一個 TableView,會發現它和已有的 View 有些相似,好比 setModel 和 addToDom,固然二者的內容確定是不同的,因此依靠 es6 class 和 extends,對 view 作一些修改以知足它能夠被擴展,View 代碼修改以下:
class View{ constructor(model){ this._view = document.createElement('div'); const style = this._view.style; style.position = 'absolute'; style.top = 0; style.right = 0; style.bottom = 0; style.left = 0; !!model && this.setModel(model); } getView(){ return this._view; } setModel(model){ this._model = model; model.addListener(this.invalidate.bind(this)); } addToDOM(parentNode){ if(!!parentNode) { parentNode.appendChild(this.getView()); } else { document.body.appendChild(this.getView()); } this.invalidate(); } }
主要修改是去掉 invalidate 方法,而後讓擴張的組件來實現這個方法,創建第一個擴張組件:
class SimulateGraphView extends View{ invalidate(){ const view = this.getView(); let innerHTML = ''; view.innerHTML = ''; this._model.each((data) => { innerHTML += data.getElString(); }) view.innerHTML = innerHTML; } }
此時的 demo 確定是沒法工做,由於 init 函數裏面還在使用View來實例化組件,因此須要將 new View 修改成 new SimulateGraphView,init 函數此時以下:
function init(){ model = new Model() view = new SimulateGraphView(model); view.addToDOM(document.getElementById('view1')); node1 = new Node(); node1.setPosition(30, 30); node1.setName('我是node1'); model.add(node1); view2 = new SimulateGraphView(model); view2.addToDOM(document.getElementById('view2')) }
刷新瀏覽器代碼工做正常。而後要開始創建第二個擴展組件 TableView,一樣繼承自 View,因此也擁有 setModel 等方法,與 SimulateGraphView 的主要不一樣在於 invalidate 函數,TableView 代碼以下:
class TableView extends View{ constructor(model){ super(model); this.content = ` <table> <tr> <th>name</th> <th>x</th> <th>y</th> <th>width</th> <th>height</th> </tr> __content__ <table> `; } invalidate(){ const view = this.getView(); let content = ''; view.innerHTML = ''; this._model.each((data) => { content += ` <tr> <td>${data.getName()}</td> <td>${data.getX()}</td> <td>${data.getY()}</td> <td>${data.getWidth()}</td> <td>${data.getHeight()}</td> </tr> ` }) view.innerHTML = this.content.replace(/__content__/, content); } }
能夠看到此表格主要做用顯示綁定的 Model 裏面 node 的一些屬性,好比 name,座標 x 和 y 和寬度高度,此時 node 對象上還缺乏這些方法,先給 Node 加上這些方法,修改後 Node 代碼以下:
class Node{ constructor() { this._node = document.createElement('div'); this._name = ''; const style = this._node.style; style.position = 'absolute'; style.top = 0; style.left = 0; style.height = '100px'; style.width = '100px'; style.overflow = 'hidden'; style.background = '#D8D8D8'; } getElString(){ return this._node.outerHTML; } fireChange(){ !!this._model && this._model.handleDataChange(); } setPosition(x, y){ const style = this._node.style; style.left = x + 'px'; style.top = y + 'px'; this.fireChange(); } setX(x){ this._node.style.left = x + 'px'; this.fireChange() } setY(y){ this._node.style.top = y + 'px'; this.fireChange(); } getPosition(){ return {x: this._node.style.left, y: this._node.style.top} } getX(){ return this._node.style.left; } getY(){ return this._node.style.top; } setImage(url){ const style = this._node.style; if(!!url){ this._node.innerHTML = ''; style.background = `url(${url}) no-repeat center`; this.fireChange(); } } setSize(width, height){ const style = this._node.style; style.width = width + 'px'; style.height = height + 'px'; this.fireChange(); } setWidth(width){ this._node.style.width = width + 'px'; this.fireChange() } getWidth(){ return this._node.style.width; } setHeigth(height){ this._node.style.height = height + 'px'; this.fireChange(); } getHeight(height){ return this._node.style.height; } setName(name){ this._name = name; this._node.innerHTML = name; this.fireChange(); } getName(){ return this._name; } setModel(model){ this._model = model; } }
此時 table 組件基本能夠正常工做,可是還缺乏一個掛載的 div,修改下 body 下里面內容以下:
<body onload = init()> <div id="view1"></div> <div id="view2"></div> <div id='view3'></div> <script> class View{ ...
而後再修改一下 CSS,修改後 style 以下:
<style> div { box-sizing: border-box; overflow: hidden; } #view1 { position: absolute; top: 0; left: 0; right: 0; width: 50%; height: 400px; border: 2px solid #4080BF; } #view2 { position: absolute; top: 0; right: 0; width: 50%; height: 400px; border: 2px solid #4080BF; } table { border-collapse: collapse; border-spacing: 0px; } table, th, td { padding: 5px; border: 1px solid black; } #view3 { position: absolute; top: 410px; right: 0; width: 100%; height: 300px; border: 2px solid #4080BF; } </style>
接下來 new 一個 table 實例出來掛載到 view3 下面,此時 Model 只有一個圖元,再加入一個演示,修改後 init 函數以下:
function init(){ model = new Model(); view = new SimulateGraphView(model); view.addToDOM(document.getElementById('view1')); node1 = new Node(); node1.setPosition(30, 30); node1.setName('我是node1'); model.add(node1); node2 = new Node(); node2.setPosition(30, 150); node2.setName('我是node2'); node2.setSize(200, 80) node2.setImage('http://www.hightopo.com/images/logo.png'); model.add(node2); view2 = new SimulateGraphView(model); view2.addToDOM(document.getElementById('view2')); table = new TableView(model); table.addToDOM(document.getElementById('view3')); }
刷新瀏覽器,能夠在下方看到一個 table 顯示 Model 裏面 node 的一些屬性,固然須要一些改變才能感覺到效果,因此這時候能夠打開控制檯,而後在 Console 面板下面輸入: node2.setPosition(200, 100) 並執行,這時候你會發現 graphView 和 table 都同步更新了,此時你能夠在控制檯裏對 node1 和 node2 執行下其餘的操做好比 node1.setSize(200, 60), graphView 和 table 一樣都會更新。
這麼長的 dmeo 到此就結束了,其實並不麻煩,主要目的是爲了給你們介紹下 View,Model 和 Node 之間的關係,那麼再回到 HT
劃 HT 重點:
如今結合 demo 的例子再來看這幾條重點,應該好理解多了吧!
若是讀到這裏感受沒有問題,能夠移步 http://www.hightopo.com/guide/guide/core/datamodel/ht-datamodel-guide.html#ref_designpattern 閱讀下官方關於 DataModel 及其餘幾個核心概念的說明。而後基本全部 HT 關於 2d 的demo應該都能看明白。
關於 demo 劃重點:
HT 中文網地址:
http://www.hightopo.com/cn-index.html
最後 demo 下載地址: