很多視覺化編輯器都或多或少有一些拖拽功能,比如從一個List列表中拖拽一個節點到拓撲元件上進行建模,並且在拖拽的過程中滑鼠位置下會附帶一個被拖拽節點的縮圖,那麼今天我們就來實現這樣的拖拽效果。
首先我們需要建立一個ListView列表,在列表中加入圖片資訊,讓List列表不那麼單調,先來看看效果圖。
接下來我們一步一步來是想這個ListView列表,先來解決下資料,在這裡我就列舉一兩個:
var products = [ { ProductId : 1, ProductName : "Chai", QuantityPerUnit : "10 boxes x 20 bags", UnitPrice : 18.00, Description : "Soft drinks, coffees, teas, beers, and ales" }, { ProductId : 2, ProductName : "Chang", QuantityPerUnit : "24 - 12 oz bottles", UnitPrice : 19.00, Description : "Soft drinks, coffees, teas, beers, and ales" }, …… ];
有了資料,我們就可以來建立ListView元件了:
var listView = new ht.widget.ListView(); var view = listView.getView(); document.body.appendChild(view);
這時我們建立的是一個空的ListView元件,在瀏覽器上看不到任何東西,那麼接下來我們就該把我們定義的資料新增到ListView元件上了:
products.forEach(function(product){ var data = new ht.Data(); data.a(product); listView.dm().add(data); });
資料的新增是不是很簡單,但是ListView元件上顯示的內容預設是Data的name屬性或displayName屬性,在建立Data時,並沒有對Data設定displayName或者name屬性,所以這個時候在頁面上看到的還是一個空的List元件,別急,我們可以在不設定displayName或name屬性的情況下讓元件顯示效果圖上的文字內容,請看:
listView.getLabel = function(data){ return data.a('ProductName') + ' - $' + data.a('UnitPrice').toFixed(2); };
嘿嘿,ListView元件提供了getLabel方法供使用者過載來實現自定義顯示文字內容,這下應該就可以顯示文字內容了吧~
oh no~還是什麼都沒有,是不是還少了點什麼呢~對了,忘記給ListView元件新增鋪滿瀏覽器的樣式了,將廈門的樣式新增到head標籤中:
<style>
html, body {
padding: 0px;
margin: 0px;
}
.main {
margin: 0px;
padding: 0px;
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
}
</style>
接下來指定view的className屬性:
view.className = 'main';
噢~總算出來了~
行高太小了,背景也太單調了,向效果圖看齊:
listView.setRowHeight(50); listView.drawRowBackground = function(g, data, selected, x, y, width, height){ if(this.isSelected(data)){ g.fillStyle = '#87A6CB'; } else if(this.getRowIndex(data) % 2 === 0){ g.fillStyle = '#F1F4F7'; } else{ g.fillStyle = '#FAFAFA'; } g.beginPath(); g.rect(x, y, width, height); g.fill(); };
通過setRowHeight()方法設定行高,通過過載drawRowBackground()方法繪製交叉背景。
嘿,有點樣子了,和效果圖越來越近了~那麼就差圖示了呢。
ht.Default.setImage('1', 40, 40, 'data:image/jpeg;base64,...'); ht.Default.setImage('2', 40, 40, ‘data:image/jpeg;base64,...'); …… listView.setIndent(60); listView.getIcon = function(data){ return data.a('ProductId'); };
通過ht.Default.setImage()方法定義ProductId對應的圖片資源,以ProductId作為圖片的別名,然後接下來定義icon位置大小為60,過載ListView的getIcon方法返回資料中定義的ProductId屬性,如此就可以看到圖示了。
還沒完,效果圖上顯示的圖片是圓形的,這該如何是好呢?別急,我們有萬能的向量,上麼樣的圖形都難不倒我們:
ht.Default.setImage('productIcon', { width: 50, height: 50, clip: function(g, width, height) { g.beginPath(); g.arc(width/2, height/2, Math.min(width, height)/2-3, 0, Math.PI * 2, true); g.clip(); }, comps: [ { type: 'image', stretch: 'uniform', rect: [0, 0, 50, 50], name: {func: function(data){return data.a('ProductId');}} } ] });
在程式碼中我們定義了一個名稱為productIcon的向量,在向量中通過clip屬性定義裁切區域,效果就是超出該裁切區域外的內容將被隱藏。現在向量定義好了,我們只需要在ListView的getIcon()方法中返回我們定義的向量名稱就可以實現圓形圖示了:
listView.getIcon = function(data){ return 'productIcon'; };
到這裡,和效果圖的效果就一模一樣了~那麼接下來我們就該建立3D拓撲元件了,來看看效果圖:
很簡單,就在3D拓撲中放兩個正方體:
var g3d = new ht.graph3d.Graph3dView(); var node = new ht.Node(); node.s3(30, 30, 30); node.p3(-30, 15, 0); node.s('all.color', '#87A6CB'); g3d.dm().add(node); node = new ht.Node(); node.s3(30, 30, 30); node.p3(30, 15, 0); node.s('all.color', '#87A6CB'); node.setElevation(15); g3d.dm().add(node);
這是你會發現並沒有像效果圖中顯示的那麼會有網格效果,並且視角也不對,沒事,待我新增幾個屬性:
g3d.setEye(-100, 100, 80); g3d.setGridVisible(true); g3d.setGridColor(‘#F1F4F7');
如此就和效果圖一模一樣了~
ListView和3D拓撲是兩個獨立的元件,我們該如何將這兩個元件組合在一起呢?這時候,我想到了BorderPane元件,將List元件放在左邊,將3D拓撲元件放在右邊:
var borderPane = new ht.widget.BorderPane(); borderPane.setLeftView(listView, 350); borderPane.setCenterView(g3d);
看,成功將兩個元件合併在一起了,離成功不遠了。接下來就是今天的重頭戲了,該如何實現拖拽List上的節點到3D拓撲上,並實現節點的圖示吸附到3D拓撲的圖元上呢,我給大家細細道來。
首先先來了解下ListView的handleDragAndDrop()方法,draganddrop一共有4個狀態:prepare、begin、between和end,可更具這4個不同狀態來做不同的業務處理。
第一步,我們來實現滑鼠附帶圖示的效果,在拖拽ListView的節點時,在滑鼠下方增加一個該節點的縮圖:
思路是這樣的:
1. 在prepare狀態時獲取當前拖拽節點的ProductId屬性,並通過呼叫ht.Default.toCanvas()方法將當前拖拽節點結合向量productIcon獲得一個canvas物件;
2. 在begin狀態時根據滑鼠當前位置設定canvas物件的left和top屬性,並將其新增到DOM樹中;
3. 在between狀態時,根據滑鼠位置資訊,重新設定canvas物件的left和top屬性,令canvas物件一直跟著滑鼠在移動;
4. 在end狀態時,將canvas物件移除DOM樹。
var dragImage = null, productId = null; listView.handleDragAndDrop = function(e, state) { if (state === 'prepare') { var data = listView.getDataAt(e); listView.sm().ss(data); if (dragImage && dragImage.parentNode) { document.body.removeChild(dragImage); } dragImage = ht.Default.toCanvas('productIcon', 30, 30, 'uniform', data); productId = data.a('ProductId'); } else if (state === 'begin') { if (dragImage) { var pagePoint = ht.Default.getPagePoint(e); dragImage.style.left = pagePoint.x - dragImage.width / 2 + 'px'; dragImage.style.top = pagePoint.y - dragImage.height / 2 + 'px'; document.body.appendChild(dragImage); } } else if (state === 'between') { if (dragImage) { var pagePoint = ht.Default.getPagePoint(e); dragImage.style.left = pagePoint.x - dragImage.width / 2 + 'px'; dragImage.style.top = pagePoint.y - dragImage.height / 2 + 'px'; } } else { if (dragImage) { if (dragImage.parentNode) { document.body.removeChild(dragImage); } dragImage = null; productId = null; } } };
如此在拖拽ListView節點時就能夠看到有一個小圖示一直跟著滑鼠在移動。
OK,接下來該解決圖元吸附功能,當滑鼠拖拽ListView節點到3D拓撲上的圖元是,將該節點的圖示設定為圖元當前面的貼圖。
思路是這樣子的:
1. 在between狀態時,通過ht.Default.containedInView()方法判斷殿前滑鼠是否在3D拓撲元件上;
2. 若滑鼠在3D拓撲上,則通過g3d.getHitFaceInfo()方法,根據滑鼠當前資訊獲取當前滑鼠下的圖元表面資訊;
3. 若當前滑鼠在圖元的某個表面上,則先儲存該圖元表面資訊的貼圖,然後設定當前圖元表面的貼圖為拖拽節點對應的圖片,最後將當前圖元表面資訊快取下來,當滑鼠離開該表面時,還原圖元的貼圖;
4. 在end狀態時,如果當前滑鼠位置在某個圖元表面時,就將當前拖拽節點的對應的圖片做為當前圖元表面的貼圖。
那麼接下來就需要對ListView元件的handleDragAndDrop()方法做些微的修改了。
listView.handleDragAndDrop = function(e, state) { if (state === 'prepare') { var data = listView.getDataAt(e); listView.sm().ss(data); if (dragImage && dragImage.parentNode) { document.body.removeChild(dragImage); } dragImage = ht.Default.toCanvas('productIcon', 30, 30, 'uniform', data); productId = data.a('ProductId'); } else if (state === 'begin') { if (dragImage) { var pagePoint = ht.Default.getPagePoint(e); dragImage.style.left = pagePoint.x - dragImage.width / 2 + 'px'; dragImage.style.top = pagePoint.y - dragImage.height / 2 + 'px'; document.body.appendChild(dragImage); } } else if (state === 'between') { if (dragImage) { var pagePoint = ht.Default.getPagePoint(e); dragImage.style.left = pagePoint.x - dragImage.width / 2 + 'px'; dragImage.style.top = pagePoint.y - dragImage.height / 2 + 'px'; if (ht.Default.containedInView(e, g3d)) { if (lastFaceInfo) { lastFaceInfo.data.s(lastFaceInfo.face + '.image', lastFaceInfo.oldValue); lastFaceInfo = null; } var faceInfo = g3d.getHitFaceInfo(e); if (faceInfo) { faceInfo.oldValue = faceInfo.data.s(faceInfo.face + '.image'); faceInfo.data.s(faceInfo.face + '.image', productId); lastFaceInfo = faceInfo; } } } } else { if (dragImage) { if (lastFaceInfo) { lastFaceInfo.data.s(lastFaceInfo.face + '.image', lastFaceInfo.oldValue); lastFaceInfo = null; } if (ht.Default.containedInView(e, g3d)) { var faceInfo = g3d.getHitFaceInfo(e); if (faceInfo) { faceInfo.data.s(faceInfo.face + '.image', productId); } } if (dragImage.parentNode) { document.body.removeChild(dragImage); } dragImage = null; productId = null; } } };
今天就到這吧,將的內容有點多,涉及到HT for Web的知識點也比較多,下面附上本次Demo的原始碼,感興趣的朋友可以載下來看看,同時也歡迎大家留言質詢。