很多人都有這樣的疑問,基於HTML5 Canvas實現的元素怎么和用戶進行交互?在這里我們用到HT for Web(http://www.hightopo.com/guide/guide/core/beginners/ht-beginners-guide.html)寫了個Demo進行示例。
場景如下所示,在該場景中雙擊溫度和濕度下的Node,會生成輸入框供用戶填寫內容,這之后,用戶按下“Enter”鍵可以將輸入內容傳到Node中,同時刪除輸入框,地址::http://www.hightopo.com/demo/GraphInput/display.html
接下來我們探討一下具體實現:
准備工作如下:
<script src='ht.js'></script>
dataModel = new ht.DataModel(); graphView = new ht.graph.GraphView(dataModel); graphView.addToDOM();
1、利用系統中定義好的矢量資源進行反序列化來實現場景圖:
ht.Default.xhrLoad('TemperatureIndex.json', function(text) { var json = ht.Default.parse(text); if(json.title) document.title = json.title; dataModel.deserialize(json); }
2、雙擊事件
本例雙擊會產生輸入框,在我們的HT中,GraphView默認內置了一些交互器,以實現基本的選擇、單雙擊、縮放、平移和編輯等交互的功能,內置的交互器有:
內置的Interactor在交互過程中會派發事件,可通過GraphView#addInteractorListener進行監聽,簡寫為mi(詳情可看HT for Web 入門手冊http://www.hightopo.com/guide/guide/core/beginners/ht-beginners-guide.html#ref_graphviewinteraction),在這里,我們用內置的graphView.addInteractorListener監聽雙擊事件:
graphView.addInteractorListener(function(e){ if (e.kind !== 'doubleClickData') return; if (currentInput) removeInput(); var data = e.data; if (clickableTags[data.getTag()]){ setTimeout(function(){ createInput('input', data); }, 0); } });
3、創建輸入框
在雙擊事件發生時,首先需要判斷發生雙擊事件的元素是不是場景中定義的標簽名‘temperature’和‘humidity’的node圖元,我們用clickableTags對象來保存兩個node:
var clickableTags = { 'temperature': true, 'humidity': true }
在雙擊的圖元是‘temperature’或者‘humidity’時,調用createInput()函數生成輸入框,createInput()代碼如下:
function createInput(tagName,node){ if (currentInput) { removeInput(graphView, currentInput); return; } else { var element = document.createElement(tagName); graphView.getView().appendChild(element); element.bindingNode = node; ht.Default.setFocus(element); currentInput = element; layout(currentInput);//布局 return currentInput; } }
在createInput()函數中,用全局變量currentInput保存着當前生成的輸入框元素,為保證再次生成輸入框時,調用removeInput()清除上次生成的輸入框元素,從而不影響性能。
4、布局
生成的輸入框應該放在哪兒?這就是layout()函數中所做的事情。layout()函數修改生成的輸入框的位置信息,讓其在GraphView拓撲圖組件上的位置剛好的node圖元的位置相同。
function layout(element){ var rect = element.bindingNode.getRect(); var x = rect.x; var y = rect.y; element.style.position = 'absolute'; element.style.width = rect.width + 'px'; element.style.height = rect.height + 'px'; element.style.top = y + 'px'; element.style.left = x + 'px'; element.style.background = '#fff'; element.style.color = '#000'; element.style.textAlign = 'center'; }
以‘temperature’為例,在點擊標簽名為‘temperature’的node圖元時,會在其上生成一個輸入框,獲取該node圖元的寬、高、位置信息,並分別賦值給絕對定位后輸入框的寬、高、位置,這樣即可讓輸入框剛好覆蓋住node圖元。
5、平移和縮放
可能細心思考的朋友也會發現,在對整個場景圖進行平移和縮放時,按照上訴布局方式,輸入框的位置和大小卻沒有跟隨着node圖元的位置進行改變,所以我們在布局時還需要思考到平移、縮放事件。
首先,layout函數的內容中,元素的寬、高、位置信息必須加入平移和縮放產生的結果,所以,最終layout代碼如下:
function layout(element){ var rect = element.bindingNode.getRect(); var zoom = graphView.getZoom(); var tx = graphView.tx(); var ty = graphView.ty(); rect.x *= zoom; rect.y *= zoom; rect.width *= zoom; rect.height *= zoom; var x = tx + rect.x; var y = ty + rect.y; element.style.position = 'absolute'; element.style.width = rect.width + 'px'; element.style.height = rect.height + 'px'; element.style.top = y + 'px'; element.style.left = x + 'px'; element.style.background = '#fff'; element.style.color = '#000'; element.style.textAlign = 'center'; }
其次,我們需要對平移和縮放事件添加監聽,以便能在該事件發生時,再次調用layout()函數將輸入框的位置進行同步,在這里,我們用內置的交互器addPropertyChangeListener(簡寫為mp),監聽zoom、translateX、translateY屬性的變化:
var changeProperties = { 'zoom': true, 'translateX': true, 'translateY':true }
graphView.mp(function(e) { if (changeProperties[e.property]) { var elements = document.getElementsByTagName('input'); for (var i = 0; i < elements.length; i++) { layout(elements[i]); } } });
6、更新node
大家在Demo中可以發現,我們按下Enter鍵時,輸入的文字會同步到node中,其實這里做了兩件事: 給node設值后刪除輸入框。
a、給node設值,是用一個名為setText()的函數來實現的,實現代碼如下:
function setText(tagName){ var element = document.getElementsByTagName(tagName); if(!element) return; for (var i = 0; i < element.length; i++) { var value = (element[i].value) ? element[i].value : 32 ; element[i].bindingNode.s('text', value); } }
在檢測輸入框中值得存在性后,給node圖元賦值用到我們HT的setStyle(簡寫為s)方法。
b、刪除輸入框
function removeInput(){ if(!currentInput) return; graphView.getView().removeChild(currentInput); currentInput = null; }
c、添加Enter的事件監聽器
因為沒有監聽鍵盤的內置交互器,所以我們通過graphView.getView().addEventListener直接對底層的div添加監聽。
graphView.getView().addEventListener('keydown', function(event){ if(ht.Default.isEnter(event)){ setText('input'); removeInput(); } else if(ht.Default.isEsc(event)){ removeInput(); } }, false);
最后,再次貼上Demo地址(http://www.hightopo.com/demo/GraphInput/display.html),希望能夠幫助那些需要在拓撲圖中加入原生HTML的朋友,也望大家不吝賜教。