4 điểm bởi GN⁺ 2025-03-08 | 1 bình luận | Chia sẻ qua WhatsApp
  • Nút bấm là thành phần thiết yếu để xây dựng ứng dụng web động. Chúng được dùng để mở menu, chuyển đổi tác vụ và gửi biểu mẫu
  • Trong Chrome 135, các thuộc tính mới commandcommandfor cải tiến và thay thế các thuộc tính trước đây là popovertargetactionpopovertarget
  • Những vấn đề thường gặp khi triển khai hành vi của nút bấm theo cách cũ:
    • Trình xử lý onclick của HTML có thể bị hạn chế trong mã thực tế do chính sách bảo mật (CSP)
    • Cần đồng bộ trạng thái giữa nút bấm và các phần tử khác, và mã để quản lý trạng thái trong khi vẫn giữ được khả năng truy cập thì khá phức tạp
    • Ngay cả trong React, AlpineJS, Svelte... việc xử lý trạng thái và sự kiện cũng phức tạp

Mẫu commandcommandfor

  • Khi dùng các thuộc tính commandcommandfor, nút bấm có thể hoạt động theo cách khai báo đối với phần tử khác. Cách này mang lại sự tiện lợi như framework nhưng vẫn giữ được tính linh hoạt
  • Nút commandfor sử dụng ID (tương tự thuộc tính for), còn command nhận các giá trị dựng sẵn để mang đến cách tiếp cận trực quan hơn
  • Ví dụ: triển khai nút mở menu
    • Không cần aria-expanded hay JavaScript bổ sung
    <button commandfor="my-menu" command="show-popover">  
      Open Menu  
    </button>  
    <div popover id="my-menu">  
      <!-- ... -->  
    </div>  
    

commandcommandfor so với popovertargetactionpopovertarget

  • Nếu đã từng dùng popover, bạn có thể đã quen với các thuộc tính popovertargetpopovertargetaction
  • Chúng hoạt động tương tự commandforcommand, nhưng được thiết kế riêng cho popover
  • Các thuộc tính mới thay thế hoàn toàn các thuộc tính cũ và cung cấp thêm chức năng

Lệnh dựng sẵn

  • Thuộc tính command tích hợp sẵn các hành động được ánh xạ tới nhiều API khác nhau
    • show-popover: ánh xạ tới el.showPopover()
    • hide-popover: ánh xạ tới el.hidePopover()
    • toggle-popover: ánh xạ tới el.togglePopover()
    • show-modal: ánh xạ tới dialogEl.showModal()
    • close: ánh xạ tới dialogEl.close()
  • Ví dụ: triển khai hộp thoại xác nhận xóa
    • Có thể quản lý trạng thái và khả năng truy cập mà không cần JavaScript
    <button commandfor="confirm-dialog" command="show-modal">  
      Delete Record  
    </button>  
    <dialog id="confirm-dialog">  
      <header>  
        <h1>Delete Record?</h1>  
        <button commandfor="confirm-dialog" command="close" aria-label="Close">  
          <img role="none" src="/close-icon.svg">  
        </button>  
      </header>  
      <p>Are you sure? This action cannot be undone</p>  
      <footer>  
        <button commandfor="confirm-dialog" command="close" value="cancel">  
          Cancel  
        </button>  
        <button commandfor="confirm-dialog" command="close" value="delete">  
          Delete  
        </button>  
      </footer>  
    </dialog>  
    
    • Mã xử lý kết quả: có thể xử lý giá trị trả về trong sự kiện close của hộp thoại
    dialog.addEventListener("close", (event) => {  
      if (event.target.returnValue === "cancel") {  
        console.log("Cancel was clicked");  
      } else if (event.target.returnValue === "delete") {  
        console.log("Delete was clicked");  
      }  
    });  
    

Lệnh tùy chỉnh

  • Ngoài các lệnh dựng sẵn, bạn còn có thể định nghĩa lệnh tùy chỉnh bằng tiền tố --
  • Lệnh tùy chỉnh sẽ phát sinh sự kiện "command" trên phần tử đích, nhưng không thực hiện thêm logic nào khác
  • Ví dụ: triển khai lệnh xoay ảnh
    <button commandfor="the-image" command="--rotate-landscape">  
      Landscape  
    </button>  
    <button commandfor="the-image" command="--rotate-portrait">  
      Portrait  
    </button>  
    <img id="the-image" src="photo.jpg">  
    
    <script type="module">  
      const image = document.getElementById("the-image");  
      image.addEventListener("command", (event) => {  
        if (event.command === "--rotate-landscape") {  
          image.style.rotate = "-90deg";  
        } else if (event.command === "--rotate-portrait") {  
          image.style.rotate = "0deg";  
        }  
      });  
    </script>  
    

Xử lý lệnh trong Shadow DOM

  • Trong Shadow DOM, do commandfor hoạt động dựa trên ID nên có các hạn chế sau:
    • Không thể tham chiếu phần tử giữa các Shadow DOM
    • Trong trường hợp này, có thể dùng API JavaScript để thiết lập thuộc tính .commandForElement
  • Ví dụ: kết nối lệnh trong Shadow DOM
    <my-element>  
      <template shadowrootmode="open">  
        <button command="show-popover">Show popover</button>  
        <slot></slot>  
      </template>  
      <div popover><!-- ... --></div>  
    </my-element>  
    
    <script>  
      customElements.define("my-element", class extends HTMLElement {  
        connectedCallback() {  
          const popover = this.querySelector('[popover]');  
          this.shadowRoot.querySelector('button').commandForElement = popover;  
        }  
      });  
    </script>  
    

Kế hoạch sắp tới

  • Chrome đang có kế hoạch bổ sung thêm các lệnh dựng sẵn:
    • Mở và đóng phần tử <details>
    • Hỗ trợ lệnh "show-picker" trên <input><select>
    • Lệnh phát cho <video><audio>
    • Chức năng sao chép văn bản từ phần tử

1 bình luận

 
GN⁺ 2025-03-08
Ý kiến Hacker News
  • Các nhà lý thuyết ngôn ngữ lập trình đã suy đoán về comefrom, một phiên bản mạnh hơn của goto, từ những năm 80. Nó chỉ được triển khai trong intercal. intercal vượt trội hơn các ngôn ngữ như C về độ an toàn, hiệu năng và tính công thái học, nhưng lại gặp khó khăn khi thâm nhập thị trường thương mại. Thật thú vị khi thấy javascript tích hợp tính năng này của intercal. Hy vọng điều này có thể dẫn đến sự bùng nổ của lập trình lịch thiệp, giống như cách các đối tượng dựa trên closure của javascript đã đưa lập trình hàm vào dòng chính

  • Invokers không chỉ dành riêng cho Chrome. Nó đã dùng được trong Firefox nightly

  • Ý tưởng triển khai hành vi UI theo kiểu khai báo mà không cần JS khá hấp dẫn

    • Loại bỏ boilerplate cho popover/modal (không cần thao tác aria-expanded)
    • Các lệnh dựng sẵn như show-modal tích hợp khả năng truy cập ngay trong markup
    • Các lệnh tùy chỉnh (ví dụ --rotate-landscape) cho phép component phơi bày API thông qua HTML
  • Một số băn khoăn:

    • Trừu tượng hóa vs. phép màu: đây có phải chỉ là chuyển độ phức tạp từ JS sang HTML không? Framework vốn đã trừu tượng hóa trạng thái. Vậy chúng sẽ cùng tồn tại thế nào?
    • Ma sát với Shadow DOM: cần JS để đặt .commandForElement giữa các shadow root. Điều này trông như một vấn đề mới chỉ được giải quyết một nửa
    • Khả năng mở rộng trong tương lai: nếu OpenUI thêm hơn 20 lệnh nữa (ví dụ show-picker, toggle-details), liệu nền tảng có phình to vì cú pháp ngách không?
  • Đặc tả:

    • phần tử button, thuộc tính commandfor
    • phần tử button, thuộc tính command
  • Đây có phải là mẫu action/messaging mà Next, Be, Apple và các hãng khác đã dùng khoảng 30 năm trước không, hay là tôi đang bỏ sót điều gì?

    • Nó hữu ích, nhưng đã tiến hóa thành mẫu controller dựa trên interface vì độ phức tạp khi cố duy trì các mẫu thiết kế cơ bản. Vì vậy nếu mở chiếc hộp này ra, tôi đoán sẽ có rất nhiều yêu cầu cải tiến
  • Bộ công cụ UI Java đời đầu của Netscape (IFC) từng cho phép nối các phần tử hành động với nhau

  • Hai thuộc tính mới commandcommandfor cải tiến và thay thế các thuộc tính popovertargetactionpopovertarget

    • Vậy là những cái này mặc định đã dùng được rồi à? “Thay thế” nghĩa là gì? Có định gỡ bỏ chúng vào một lúc nào đó không? Các nhà phát triển web không thể xóa đi những thứ không còn cần chỉ bằng một bản cập nhật
  • Tôi hoàn toàn dị ứng với kiểu lập trình bằng chuỗi. Tôi hiểu lợi ích về khả năng truy cập, nhưng không thấy đặc biệt hứng thú với việc dùng ID phần tử như một lớp hành vi khác cho web app

  • Lẽ ra không nên triển khai cái này nếu chưa có API hoàn chỉnh. Thay vì khoảng 5 lệnh, trông như mọi khả năng của JavaScript đều có thể được hiện thực qua HTML. Như vậy có thể thành hàng nghìn lệnh

  • Tôi đã mong chờ command and conquer trong HTML

  • Việc cải tiến và mở rộng HTML là điều tốt, nhưng vẫn còn một chặng đường dài. Nhóm HTMX có một vài ý tưởng hay