Context Menus Trong Gojs

Giới Thiệu

Đã bao giờ bạn thực hiện các sự kiện click mouse, right click mouse hay hover mouse trên các của chính mình chưa?

Đã là 1 dev thì k ai k biết tới các event đó. Sau đây mình xin giới thiệu tới các bạn 1 trong các event đó là right click Hay còn được gọi là context menus. Ở đây mình sẽ thực hiện trong seri tìm hiểu gojs của mình.

Mở đầu:

Như những phần trước mình đã giới thiệu. Gojs là 1 framework mạnh hỗ trợ thiết kế giao diện người dùng được tích hợp nhiều chức năng hay và phổ biến 1 trong số đó là context menus. Vơi context menus của gojs mỗi lần bạn right click vào phần giao diện gojs sẽ show ra 1 menus nhỏ cho phép bạn thực hiện các thao tác trên đó ví dụ như gọi 1 function thực hiện delete 1 node trong đó chẳng hạn hoặc như các thao tác trên windown như là copy, paste, cut, v..v..

Sau đây mình sẽ thực hiện 1 đoạn code để gọi ra context menu trong giao diện gojs

  function changeColor(e, obj) {
    diagram.startTransaction("changed color");
    // get the context menu that holds the button that was clicked
    var contextmenu = obj.part;
    // get the node data to which the Node is data bound
    var nodedata = contextmenu.data;
    // compute the next color for the node
    var newcolor = "lightblue";
    switch (nodedata.color) {
      case "lightblue": newcolor = "lightgreen"; break;
      case "lightgreen": newcolor = "lightyellow"; break;
      case "lightyellow": newcolor = "orange"; break;
      case "orange": newcolor = "lightblue"; break;
    }
    // modify the node data
    // this evaluates data Bindings and records changes in the UndoManager
    diagram.model.setDataProperty(nodedata, "color", newcolor);
    diagram.commitTransaction("changed color");
  }
  
    // this is a normal Node template that also has a contextMenu defined for it
  diagram.nodeTemplate =
    $(go.Node, "Auto",
      $(go.Shape, "RoundedRectangle",
        { fill: "white" },
        new go.Binding("fill", "color")),
      $(go.TextBlock, { margin: 5 },
        new go.Binding("text", "key")),
      {
        contextMenu:     // define a context menu for each node
          $(go.Adornment, "Vertical",  // that has one button
            $("ContextMenuButton",
              $(go.TextBlock, "Change Color"),
              { click: changeColor })
            // more ContextMenuButtons would go here
          )  // end Adornment
      }
    );

  // also define a context menu for the diagram's background
  diagram.contextMenu =
    $(go.Adornment, "Vertical",
      $("ContextMenuButton",
        $(go.TextBlock, "Undo"),
        { click: function(e, obj) { e.diagram.commandHandler.undo(); } },
        new go.Binding("visible", "", function(o) {
                                          return o.diagram.commandHandler.canUndo();
                                        }).ofObject()),
      $("ContextMenuButton",
        $(go.TextBlock, "Redo"),
        { click: function(e, obj) { e.diagram.commandHandler.redo(); } },
        new go.Binding("visible", "", function(o) {
                                          return o.diagram.commandHandler.canRedo();
                                        }).ofObject()),
      // no binding, always visible button:
      $("ContextMenuButton",
        $(go.TextBlock, "New Node"),
        { click: function(e, obj) {
          var diagram = e.diagram;
          diagram.startTransaction('new node');
          var data = {};
          diagram.model.addNodeData(data);
          part = diagram.findPartForData(data);
          part.location = diagram.toolManager.contextMenuTool.mouseDownPoint;
          diagram.commitTransaction('new node');
        } })
    );

  diagram.initialContentAlignment = go.Spot.Center;

  var nodeDataArray = [
    { key: "Alpha", color: "lightyellow" },
    { key: "Beta", color: "orange" }
  ];
  var linkDataArray = [
    { from: "Alpha", to: "Beta" }
  ];
  diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);
  diagram.undoManager.isEnabled = true;

Kết quả

Mình xin phép giải thích 1 chút về đoạn code trên

      {
        contextMenu:     // define a context menu for each node
          $(go.Adornment, "Vertical",  // that has one button
            $("ContextMenuButton",
              $(go.TextBlock, "Change Color"),
              { click: changeColor })
            // more ContextMenuButtons would go here
          )  // end Adornment
      }

như chú thích ở bên. việc gọi contextMenu sẽ gọi ra function mặc định của gojs có sẵn trong thực viện gojs nhằm định nghĩa 1 context menu mặc định tiếp theo:

            $("ContextMenuButton",
              $(go.TextBlock, "Change Color"),
              { click: changeColor })

sẽ tạo ra 1 button khi right click. button này click sẽ gọi tới function changeColor đã được định nghĩa ở trên

        $(go.TextBlock, "Undo"),
        { click: function(e, obj) { e.diagram.commandHandler.undo(); } },
        new go.Binding("visible", "", function(o) {
                                          return o.diagram.commandHandler.canUndo();
                                        }).ofObject()),
      $("ContextMenuButton",
        $(go.TextBlock, "Redo"),
        { click: function(e, obj) { e.diagram.commandHandler.redo(); } },
        new go.Binding("visible", "", function(o) {
                                          return o.diagram.commandHandler.canRedo();
                                        }).ofObject()),

phần này sẽ định nghĩa 2 function cho phép bạn undo và redo sau khi đã thực hiện các function khi right click

      $("ContextMenuButton",
        $(go.TextBlock, "New Node"),
        { click: function(e, obj) {
          var diagram = e.diagram;
          diagram.startTransaction('new node');
          var data = {};
          diagram.model.addNodeData(data);
          part = diagram.findPartForData(data);
          part.location = diagram.toolManager.contextMenuTool.mouseDownPoint;
          diagram.commitTransaction('new node');
        } })

function này chịu trách nhiệm khi bạn right click sẽ show ra button "New Node" Có tác vụ thêm 1 node mới vào giao diện gojs

Vậy câu hỏi đặt ra ở đây là khi right click thì menu context sẽ show ra cả 2 button changeColor và New Node luôn hay sao? Thật ra k phải như vậy. Change Color được định nghĩa ở trong 1 node. nên chỉ khi right click vào 1 node thì mới xuất hện context menus change color này thôi Còn New Node lại được định nghĩa trong 1 diagram. Vì thế khi right click vào diagram gojs thì nó sẽ xuất hiện và thực hiện new node mới

Custom context menus

Tuy nhiên với những người có gu thẩm mỹ cao thì với 1 context menus như thế này sẽ khá tệ vậy tai sao chúng ta không thực hiện thêm 1 vài động tác nhỏ để context menus của chúng ta lung linh hơn 1 tí nhỉ? It ra cũng gần ngang ngửa windown chứ 😄

Ngoài view chúng ta sẽ thực hiện thêm 1 loại các thẻ li để tạo ra menu custom theo ý của mình

      <div id="contextMenu">
        <ul>
          <li id="cut" onclick="cxcommand(event)"><a href="#" target="_self">Cut</a></li>
          <li id="copy" onclick="cxcommand(event)"><a href="#" target="_self">Copy</a></li>
          <li id="paste" onclick="cxcommand(event)"><a href="#" target="_self">Paste</a></li>
          <li id="delete" onclick="cxcommand(event)"><a href="#" target="_self">Delete</a></li>
          <li id="color" class="hasSubMenu"><a href="#" target="_self">Color</a>
            <ul class="subMenu" id="colorSubMenu">
                <li style="background: crimson;" onclick="cxcommand(event, 'color')"><a href="#" target="_self">Red</a></li>
                <li style="background: chartreuse;" onclick="cxcommand(event, 'color')"><a href="#" target="_self">Green</a></li>
                <li style="background: aquamarine;" onclick="cxcommand(event, 'color')"><a href="#" target="_self">Blue</a></li>
                <li style="background: gold;" onclick="cxcommand(event, 'color')"><a href="#" target="_self">Yellow</a></li>
            </ul>
          </li>
        </ul>
      </div>

Trong code gojs chúng ta sẽ thực hiện 1 vài đoạn code nhận các event ngoài view gửi vào

  // This is the actual HTML context menu:
  var cxElement = document.getElementById("contextMenu");

  // Since we have only one main element, we don't have to declare a hide method,
  // we can set mainElement and GoJS will hide it automatically
  var myContextMenu = $(go.HTMLInfo, {
    show: showContextMenu,
    mainElement: cxElement
  });

đoạn code trên sẽ thực hiện function showcontextmenus định nghĩa ở dưới và gần element id vào biến myContextMenu để thực hiện xử lý ở phần code tiếp theo

  myDiagram.nodeTemplate =
    $(go.Node, "Auto",
      { contextMenu: myContextMenu },
      $(go.Shape, "RoundedRectangle",
        // Shape.fill is bound to Node.data.color
        new go.Binding("fill", "color")),
      $(go.TextBlock,
        { margin: 3 },  // some room around the text
        // TextBlock.text is bound to Node.data.key
        new go.Binding("text", "key"))
    );

phần code này chúng ta sẽ thực hiện khi right click vào node, sẽ show ra context menus , context menus sẽ gọi myContextMenu ta đã định nghĩa ở trên, tạo màu và show text ra cho context menus

  function showContextMenu(obj, diagram, tool) {
    // Show only the relevant buttons given the current state.
    var cmd = diagram.commandHandler;
    document.getElementById("cut").style.display = cmd.canCutSelection() ? "block" : "none";
    document.getElementById("copy").style.display = cmd.canCopySelection() ? "block" : "none";
    document.getElementById("paste").style.display = cmd.canPasteSelection() ? "block" : "none";
    document.getElementById("delete").style.display = cmd.canDeleteSelection() ? "block" : "none";
    document.getElementById("color").style.display = (obj !== null ? "block" : "none");

    // Now show the whole context menu element
    cxElement.style.display = "block";
    // we don't bother overriding positionContextMenu, we just do it here:
    var mousePt = diagram.lastInput.viewPoint;
    cxElement.style.left = mousePt.x + "px";
    cxElement.style.top = mousePt.y + "px";
  }

function này thực hiện các hành động paster delete copy cut của gojs theo điều kiện Ví dụ như:

document.getElementById("cut").style.display = cmd.canCutSelection() ? "block" : "none";

khi right click vào node nó sẽ show ra tool Cut của gojs. nếu chúng ta không muốn nó show ra thì ta có thể đổi chỗ "none" và "block" cho nhau Ngoài ra nó sẽ thực hiện lấy vị trí hiện tại của chuột khi right click nhận và thực hiện show context menus tại vị trí đó

function cxcommand(event, val) {
  if (val === undefined) val = event.currentTarget.id;
  var diagram = myDiagram;
  switch (val) {
    case "cut": diagram.commandHandler.cutSelection(); break;
    case "copy": diagram.commandHandler.copySelection(); break;
    case "paste": diagram.commandHandler.pasteSelection(diagram.lastInput.documentPoint); break;
    case "delete": diagram.commandHandler.deleteSelection(); break;
    case "color": {
        var color = window.getComputedStyle(document.elementFromPoint(event.clientX, event.clientY).parentElement)['background-color'];
        changeColor(diagram, color); break;
    }
  }
  diagram.currentTool.stopTool();
}

function này sẽ thực hiện gọi các tool cut copy, paster, delete của gojs khi thực hiện các hành động click vào các id tương ứng Hoặc sẽ gọi function changeColor khi click id "#color"

Và cuối cùng là function changeColor đùng dể thay đổi màu sắc của node đang right click

function changeColor(diagram, color) {
  // Always make changes in a transaction, except when initializing the diagram.
  diagram.startTransaction("change color");
  diagram.selection.each(function(node) {
    if (node instanceof go.Node) {  // ignore any selected Links and simple Parts
        // Examine and modify the data, not the Node directly.
        var data = node.data;
        // Call setDataProperty to support undo/redo as well as
        // automatically evaluating any relevant bindings.
        diagram.model.setDataProperty(data, "color", color);
    }
  });
  diagram.commitTransaction("change color");
}

Kết quả:

Kết thúc

Cảm ơn các bạn đã theo dõi bài viết của mình. bài viết còn nhiều thiếu sot. mình sẽ rút kinh nghiệm và bổ sung trong các phần tiếp theo.

Tài Liệu:

http://gojs.net


All Rights Reserved