Hệ thống lấy nét

Hệ thống tiêu điểm theo dõi vị trí (tiêu điểm) của người dùng trong trình chỉnh sửa Blockly. Thành phần này được Blockly và mã tuỳ chỉnh dùng để xác định thành phần nào (khối, trường, danh mục hộp công cụ, v.v.) hiện có tiêu điểm và để di chuyển tiêu điểm đó sang một thành phần khác.

Điều quan trọng là bạn phải hiểu hệ thống lấy nét để có thể đảm bảo mã tuỳ chỉnh của bạn hoạt động chính xác với hệ thống này.

Kiến trúc

Hệ thống lấy nét có 3 phần:

  • FocusManager là một singleton điều phối tiêu điểm trên tất cả Blockly. Thành phần này được Blockly và mã tuỳ chỉnh dùng để tìm hiểu thành phần nào có tiêu điểm Blockly, cũng như để di chuyển tiêu điểm Blockly sang một thành phần khác. Thành phần này cũng theo dõi các sự kiện tiêu điểm DOM, đồng bộ hoá tiêu điểm Blockly và tiêu điểm DOM, đồng thời quản lý các lớp CSS cho biết thành phần nào có tiêu điểm.

    Trình quản lý tiêu điểm chủ yếu được Blockly sử dụng. Đôi khi, mã tuỳ chỉnh dùng thành phần này để tương tác với hệ thống tiêu điểm.

  • IFocusableTree là một khu vực độc lập của trình chỉnh sửa Blockly, chẳng hạn như không gian làm việc hoặc hộp công cụ. Thành phần này bao gồm các nút có thể lấy tiêu điểm, chẳng hạn như các khối và trường. Cây cũng có thể có cây con. Ví dụ: không gian làm việc của đối tượng sửa đổi trên một khối trong không gian làm việc chính là một cây con của không gian làm việc chính.

    IFocusableTree chủ yếu được trình quản lý tiêu điểm sử dụng. Trừ phi viết một hộp công cụ tuỳ chỉnh, nếu không có lẽ bạn sẽ không cần triển khai nó.

  • IFocusableNode là một thành phần Blockly có thể lấy tiêu điểm, chẳng hạn như khối, trường hoặc danh mục hộp công cụ. Các nút có thể lấy tiêu điểm có một phần tử DOM hiển thị nút và có tiêu điểm DOM khi nút có tiêu điểm Blockly. Xin lưu ý rằng cây cũng là các nút có thể lấy tiêu điểm. Ví dụ: bạn có thể tập trung vào không gian làm việc nói chung.

    Các phương thức trong IFocusableNode chủ yếu được trình quản lý tiêu điểm gọi.

    Bản thân IFocusableNode được dùng để biểu thị thành phần có tiêu điểm. Ví dụ: khi người dùng chọn một mục trên trình đơn ngữ cảnh của một khối, khối đó sẽ được truyền đến hàm gọi lại của mục dưới dạng một IFocusableNode.

    Nếu viết các thành phần tuỳ chỉnh, bạn có thể cần triển khai IFocusableNode.

Các loại tiêu điểm

Hệ thống lấy nét xác định một số loại tiêu điểm khác nhau.

Tiêu điểm Blockly và tiêu điểm DOM

Hai loại tâm điểm chính là tâm điểm Blockly và tâm điểm DOM.

  • Tiêu điểm Blockly chỉ định thành phần Blockly nào (khối, trường, danh mục hộp công cụ, v.v.) có tiêu điểm. Bạn cần phải làm việc ở cấp độ của các thành phần Blockly. Ví dụ: trình bổ trợ điều hướng bằng bàn phím cho phép người dùng sử dụng các phím mũi tên để di chuyển từ thành phần này sang thành phần khác, chẳng hạn như từ một khối sang một trường. Tương tự, hệ thống trình đơn ngữ cảnh sẽ tạo một trình đơn phù hợp với thành phần hiện tại – tức là hệ thống này sẽ tạo các trình đơn khác nhau cho không gian làm việc, khối và bình luận trong không gian làm việc.

  • Tiêu điểm DOM chỉ định phần tử DOM nào có tiêu điểm. Điều này là cần thiết để hoạt động ở cấp độ các phần tử DOM. Ví dụ: trình đọc màn hình trình bày thông tin về phần tử hiện có tiêu điểm DOM và các thẻ di chuyển (thay đổi tiêu điểm) từ phần tử DOM sang phần tử DOM.

Trình quản lý tiêu điểm duy trì trạng thái đồng bộ giữa tiêu điểm Blockly và tiêu điểm DOM, vì vậy, khi một nút (thành phần Blockly) có tiêu điểm Blockly, phần tử DOM cơ bản của nút đó sẽ có tiêu điểm DOM và ngược lại.

Tiêu điểm chủ động và tiêu điểm thụ động

Tiêu điểm Blockly được chia thành tiêu điểm đang hoạt độngtiêu điểm thụ động. Tiêu điểm hoạt động có nghĩa là một nút sẽ nhận được hoạt động đầu vào của người dùng, chẳng hạn như một lượt nhấn phím. Tiêu điểm thụ động có nghĩa là một nút trước đây có tiêu điểm hoạt động, nhưng đã mất tiêu điểm đó khi người dùng chuyển đến một nút trong cây khác (ví dụ: họ chuyển từ không gian làm việc sang hộp công cụ) hoặc rời khỏi trình chỉnh sửa Blockly hoàn toàn. Nếu cây lấy lại tiêu điểm, thì nút được lấy tiêu điểm thụ động sẽ lấy lại tiêu điểm hoạt động.

Mỗi cây có một bối cảnh tiêu điểm riêng. Tức là tối đa một nút trong cây có thể có tiêu điểm. Tiêu điểm đó ở trạng thái chủ động hay thụ động phụ thuộc vào việc cây có tiêu điểm hay không. Trên toàn bộ trang, có thể có tối đa một nút có tiêu điểm đang hoạt động.

Trình quản lý tiêu điểm sử dụng các điểm nổi bật (lớp CSS) khác nhau cho các nút được lấy tiêu điểm chủ động và thụ động. Các nút này giúp người dùng biết họ đang ở đâu và sẽ quay lại đâu.

Tiêu điểm tạm thời

Ngoài ra còn có một loại tiêu điểm khác gọi là tiêu điểm tạm thời. Các quy trình riêng biệt, chẳng hạn như hộp thoại hoặc trình chỉnh sửa trường, yêu cầu tiêu điểm tạm thời từ trình quản lý tiêu điểm. Khi trình quản lý tiêu điểm cấp tiêu điểm tạm thời, trình quản lý này sẽ tạm ngưng hệ thống tiêu điểm. Trên thực tế, điều này có nghĩa là những quy trình như vậy có thể ghi lại và hành động dựa trên các sự kiện tiêu điểm DOM mà không phải lo lắng rằng hệ thống tiêu điểm cũng có thể hành động dựa trên các sự kiện đó.

Khi trình quản lý tiêu điểm cấp tiêu điểm tạm thời, trình quản lý này sẽ thay đổi nút đang được lấy tiêu điểm thành tiêu điểm thụ động. Thao tác này khôi phục tiêu điểm hoạt động khi tiêu điểm tạm thời được trả về.

Ví dụ

Các ví dụ sau đây minh hoạ cách Blockly sử dụng hệ thống tiêu điểm. Các ví dụ này sẽ giúp bạn hiểu cách mã của bạn phù hợp với hệ thống lấy tiêu điểm và cách mã của bạn có thể sử dụng hệ thống lấy tiêu điểm.

Di chuyển tiêu điểm bằng bàn phím

Giả sử một khối có 2 trường có tiêu điểm Blockly, như được biểu thị bằng một điểm đánh dấu (lớp CSS) trên phần tử DOM của khối. Bây giờ, giả sử người dùng nhấn vào mũi tên bên phải:

  1. Trình bổ trợ điều hướng bằng bàn phím:
    • Nhận một sự kiện nhấn phím.
    • Yêu cầu hệ thống điều hướng (một phần của Blockly cốt lõi) di chuyển tiêu điểm đến thành phần "tiếp theo".
  2. Hệ thống định vị:
    • Hỏi trình quản lý tiêu điểm thành phần nào có tiêu điểm Blockly. Trình quản lý tiêu điểm trả về khối dưới dạng IFocusableNode.
    • Xác định rằng IFocusableNode là một BlockSvg và xem xét các quy tắc của nó để điều hướng các khối, trong đó nêu rõ rằng nó sẽ di chuyển tiêu điểm Blockly từ khối nói chung đến trường đầu tiên trên khối.
    • Yêu cầu trình quản lý tiêu điểm di chuyển tiêu điểm Blockly đến trường đầu tiên.
  3. Trình quản lý tiêu điểm:
    • Cập nhật trạng thái để đặt tiêu điểm Blockly vào trường đầu tiên.
    • Đặt tiêu điểm DOM trên phần tử DOM của trường.
    • Di chuyển lớp làm nổi bật từ phần tử của khối sang phần tử của trường.

Di chuyển tiêu điểm bằng chuột

Giả sử người dùng nhấp vào trường thứ hai trên khối. Trình quản lý tiêu điểm:

  1. Nhận một sự kiện focusout DOM trên phần tử DOM của trường đầu tiên và một sự kiện focusin trên phần tử DOM của trường thứ hai.
  2. Xác định rằng phần tử DOM nhận được tiêu điểm tương ứng với trường thứ hai.
  3. Cập nhật trạng thái để đặt tiêu điểm Blockly vào trường thứ hai. (Trình quản lý tiêu điểm không cần đặt tiêu điểm DOM vì trình duyệt đã thực hiện việc này.)
  4. Di chuyển lớp làm nổi bật từ phần tử của trường đầu tiên sang phần tử của trường thứ hai.

Ví dụ khác

Sau đây là một số ví dụ khác:

  • Khi người dùng kéo một khối từ hộp công cụ vào không gian làm việc, trình xử lý sự kiện chuột sẽ tạo một khối mới và gọi trình quản lý tiêu điểm để đặt tiêu điểm Blockly trên khối đó.

  • Khi một khối bị xoá, phương thức dispose của khối đó sẽ gọi trình quản lý tiêu điểm để di chuyển tiêu điểm đến phần tử mẹ của khối.

  • Phím tắt sử dụng IFocusableNode để xác định thành phần Blockly mà phím tắt áp dụng.

  • Trình đơn theo bối cảnh sử dụng IFocusableNode để xác định thành phần Blockly mà trình đơn được gọi.

Chế độ tuỳ chỉnh và hệ thống lấy tiêu điểm

Khi tuỳ chỉnh Blockly, bạn cần đảm bảo rằng mã của mình hoạt động chính xác với hệ thống tiêu điểm. Bạn cũng có thể sử dụng hệ thống tiêu điểm để xác định và đặt nút hiện đang được lấy tiêu điểm.

Khối tuỳ chỉnh và nội dung hộp công cụ

Cách phổ biến nhất để tuỳ chỉnh Blockly là xác định các khối tuỳ chỉnh và tuỳ chỉnh nội dung của hộp công cụ. Không có hành động nào trong số này ảnh hưởng đến hệ thống tiêu điểm.

Lớp học tuỳ chỉnh

Các lớp tuỳ chỉnh có thể cần triển khai một hoặc cả hai giao diện tiêu điểm (IFocusableTreeIFocusableNode). Không phải lúc nào cũng rõ ràng khi nào thì cần làm như vậy.

Một số lớp rõ ràng cần triển khai các giao diện tiêu điểm. bao gồm:

  • Một lớp triển khai hộp công cụ tuỳ chỉnh. Lớp này cần triển khai IFocusableTreeIFocusableNode.

  • Các lớp tạo một thành phần hiển thị (chẳng hạn như trường hoặc biểu tượng) mà người dùng có thể chuyển đến. Các lớp này cần triển khai IFocusableNode.

Một số lớp cần triển khai IFocusableNode mặc dù chúng không tạo thành phần hiển thị hoặc chúng tạo thành phần hiển thị mà người dùng không thể chuyển đến. bao gồm:

  • Các lớp triển khai một giao diện mở rộng IFocusableNode.

    Ví dụ: biểu tượng di chuyển trong trình bổ trợ điều hướng bằng bàn phím sẽ hiển thị một mũi tên bốn chiều cho biết rằng bạn có thể di chuyển khối bằng các phím mũi tên. Bản thân biểu tượng này không xuất hiện (mũi tên bốn chiều là một bong bóng) và người dùng không thể chuyển đến biểu tượng này. Tuy nhiên, biểu tượng phải triển khai IFocusableNode vì các biểu tượng triển khai IIconIIcon mở rộng IFocusableNode.

  • Các lớp được dùng trong một API yêu cầu phải có IFocusableNode.

    Ví dụ: lớp FlyoutSeparator tạo khoảng trống giữa 2 mục trong một bảng chọn. Thành phần này không tạo ra bất kỳ phần tử DOM nào, vì vậy, thành phần này không có thành phần hiển thị và người dùng không thể chuyển đến thành phần này. Tuy nhiên, mã này phải triển khai IFocusableNode vì được lưu trữ trong FlyoutItem và hàm khởi tạo FlyoutItem yêu cầu IFocusableNode.

  • Các lớp mở rộng một lớp triển khai IFocusableNode.

    Ví dụ: ToolboxSeparator mở rộng ToolboxItem, triển khai IFocusableNode. Mặc dù các đường phân cách trong hộp công cụ có một thành phần hiển thị, nhưng người dùng không thể chuyển đến các đường phân cách này vì họ không thể thực hiện hành động và không có nội dung hữu ích.

Các lớp khác tạo ra những thành phần mà người dùng có thể chuyển đến nhưng không cần triển khai IFocusableNode. bao gồm:

  • Các lớp tạo một thành phần hiển thị quản lý tiêu điểm của riêng thành phần đó, chẳng hạn như trình chỉnh sửa trường hoặc hộp thoại. (Xin lưu ý rằng các lớp như vậy cần lấy tiêu điểm tạm thời khi bắt đầu và trả lại tiêu điểm đó khi kết thúc. Việc sử dụng WidgetDiv hoặc DropDownDiv sẽ xử lý việc này cho bạn.)

Cuối cùng, một số lớp không tương tác với hệ thống lấy tiêu điểm và không cần triển khai IFocusableTree hoặc IFocusableNode. bao gồm:

  • Các lớp tạo một thành phần hiển thị mà người dùng không thể chuyển đến hoặc thực hiện hành động và không chứa thông tin mà trình đọc màn hình có thể sử dụng. Ví dụ: một phông nền chỉ mang tính trang trí trong trò chơi.

  • Các lớp hoàn toàn không liên quan đến hệ thống tiêu điểm, chẳng hạn như các lớp triển khai IMetricsManager hoặc IVariableMap.

Nếu bạn không chắc chắn liệu lớp học của mình có tương tác với hệ thống tiêu điểm hay không, hãy kiểm thử lớp học đó bằng trình bổ trợ điều hướng bằng bàn phím. Nếu không được, bạn có thể cần triển khai IFocusableTree hoặc IFocusableNode. Nếu thành công nhưng bạn vẫn không chắc chắn, hãy đọc mã sử dụng lớp của bạn để xem có cần giao diện nào không hoặc có tương tác nào khác không.

Triển khai các giao diện tiêu điểm

Cách dễ nhất để triển khai IFocusableTree hoặc IFocusableNode là mở rộng một lớp triển khai các giao diện này. Ví dụ: nếu bạn đang tạo một hộp công cụ tuỳ chỉnh, hãy mở rộng Toolbox, triển khai IFocusableTreeIFocusableNode. Nếu bạn đang tạo một trường tuỳ chỉnh, hãy mở rộng Field, triển khai IFocusableNode. Hãy nhớ kiểm tra để đảm bảo mã của bạn không can thiệp vào mã giao diện tiêu điểm trong lớp cơ sở.

Nếu mở rộng một lớp triển khai giao diện tiêu điểm, bạn thường không cần ghi đè bất kỳ phương thức nào. Ngoại lệ phổ biến nhất là IFocusableNode.canBeFocused. Bạn cần ghi đè ngoại lệ này nếu không muốn người dùng chuyển đến thành phần của bạn.

Ít phổ biến hơn là nhu cầu ghi đè các phương thức gọi lại tiêu điểm (onTreeFocusonTreeBlur trong IFocusableTreeonNodeFocusonNodeBlur trong IFocusableNode). Lưu ý rằng việc cố gắng thay đổi tiêu điểm (gọi FocusManager.focusNode hoặc FocusManager.focusTree) từ các phương thức này sẽ dẫn đến một ngoại lệ.

Nếu tự viết một thành phần tuỳ chỉnh, bạn sẽ cần tự triển khai các giao diện tiêu điểm. Hãy xem tài liệu tham khảo về IFocusableTreeIFocusableNode để biết thêm thông tin.

Sau khi triển khai lớp, hãy kiểm thử lớp đó với trình bổ trợ điều hướng bằng bàn phím để xác minh rằng bạn có thể (hoặc không thể) điều hướng đến thành phần của mình.

Sử dụng trình quản lý tiêu điểm

Một số lớp tuỳ chỉnh sử dụng trình quản lý tiêu điểm. Lý do phổ biến nhất để làm việc này là lấy nút hiện đang được lấy tiêu điểm và lấy tiêu điểm vào một nút khác. Để lấy trình quản lý tiêu điểm, hãy gọi Blockly.getFocusManager:

const focusManager = Blockly.getFocusManager();

Để lấy nút hiện đang được đặt tiêu điểm, hãy gọi getFocusedNode:

const focusedNode = focusManager.getFocusedNode();
// Do something with the focused node.

Để di chuyển tiêu điểm đến một nút khác, hãy gọi focusNode:

// Move focus to a different block.
focusManager.focusNode(myOtherBlock);

Để di chuyển tiêu điểm đến một cây, hãy gọi focusTree. Thao tác này cũng đặt tiêu điểm nút vào nút gốc của cây.

// Move focus to the main workspace.
focusManager.focusTree(myMainWorkspace);

Một lý do phổ biến khác để sử dụng trình quản lý tiêu điểm là lấy và trả lại tiêu điểm tạm thời. Hàm takeEphemeralFocus trả về một lambda mà bạn phải gọi để trả về tiêu điểm tạm thời.

const returnEphemeralFocus = focusManager.takeEphemeralFocus();
// Do something.
returnEphemeralFocus();

Nếu bạn sử dụng WidgetDiv hoặc DropDownDiv, các thành phần này sẽ xử lý tiêu điểm tạm thời cho bạn.

Vị trí đánh dấu tab

Hệ thống tiêu điểm đặt một điểm dừng phím tab (tabindex của 0) trên phần tử gốc của tất cả các cây (không gian làm việc chính, hộp công cụ và không gian làm việc bật lên). Điều này cho phép người dùng sử dụng phím tab để di chuyển xung quanh các vùng chính của trình chỉnh sửa Blockly, sau đó (bằng cách sử dụng trình bổ trợ điều hướng bằng bàn phím) sử dụng các phím mũi tên để di chuyển trong các vùng đó. Đừng thay đổi các điểm dừng phím tab này, vì điều này sẽ ảnh hưởng đến khả năng quản lý các điểm dừng phím tab của trình quản lý tiêu điểm.

Nhìn chung, bạn nên tránh đặt các điểm dừng phím tab trên các phần tử DOM khác mà Blockly sử dụng, vì điều này sẽ ảnh hưởng đến mô hình sử dụng phím tab của Blockly để di chuyển giữa các vùng của trình chỉnh sửa và các phím mũi tên trong các vùng đó. Ngoài ra, các dấu tab như vậy có thể không hoạt động như dự kiến. Điều này là do mỗi nút có thể lấy tiêu điểm đều khai báo một phần tử DOM làm phần tử có thể lấy tiêu điểm. Nếu bạn đặt một điểm dừng phím tab trên một phần tử con của phần tử có thể lấy tiêu điểm và người dùng nhấn phím tab vào phần tử đó, trình quản lý tiêu điểm sẽ di chuyển tiêu điểm DOM đến phần tử có thể lấy tiêu điểm đã khai báo.

Bạn có thể đặt các điểm dừng phím Tab trên những phần tử trong ứng dụng nằm ngoài trình chỉnh sửa Blockly. Khi người dùng nhấn phím Tab từ trình chỉnh sửa đến một phần tử như vậy, trình quản lý tiêu điểm sẽ thay đổi tiêu điểm Blockly từ hoạt động sang thụ động. Để hỗ trợ tiếp cận, bạn nên đặt thuộc tính tabindex thành 0 hoặc -1, theo khuyến nghị của cảnh báo trong nội dung mô tả của MDN về thuộc tính tabindex.

Tiêu điểm DOM

Vì lý do hỗ trợ tiếp cận, các ứng dụng nên tránh gọi phương thức focus trên các phần tử DOM. Điều này khiến người dùng trình đọc màn hình bị mất phương hướng vì họ đột ngột được chuyển đến một vị trí không xác định trong ứng dụng.

Một vấn đề khác là trình quản lý tiêu điểm phản ứng với các sự kiện tiêu điểm bằng cách đặt tiêu điểm DOM trên tổ tiên gần nhất hoặc chính phần tử được lấy tiêu điểm là một phần tử có thể lấy tiêu điểm đã khai báo. Điều này có thể khác với phần tử mà focus được gọi. (Nếu không có tổ tiên hoặc chính nó có thể lấy tiêu điểm gần nhất, chẳng hạn như khi focus được gọi trên một phần tử bên ngoài trình chỉnh sửa Blockly, trình quản lý tiêu điểm chỉ cần thay đổi nút đang được lấy tiêu điểm thành tiêu điểm thụ động.)

Positionables

Positionable là những thành phần nằm trên cùng của không gian làm việc và triển khai IPositionable. Ví dụ: thùng rác và ba lô trong trình bổ trợ ba lô. Positionable chưa được tích hợp vào hệ thống tiêu điểm.