HT for Web列表和3D拓撲元件的拖拽應用

圖撲軟體發表於2015-07-12

很多視覺化編輯器都或多或少有一些拖拽功能,比如從一個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的原始碼,感興趣的朋友可以載下來看看,同時也歡迎大家留言質詢。

 

相關文章