4 คะแนน โดย GN⁺ 2025-03-08 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ปุ่มเป็นองค์ประกอบสำคัญในการสร้างเว็บแอปพลิเคชันแบบไดนามิก ใช้สำหรับเปิดเมนู สลับงาน และส่งฟอร์ม
  • ใน Chrome 135 มีการเพิ่มแอตทริบิวต์ command และ commandfor ใหม่ เพื่อปรับปรุงและแทนที่แอตทริบิวต์เดิมอย่าง popovertargetaction และ popovertarget
  • ปัญหาที่เกิดขึ้นเมื่อสร้างพฤติกรรมของปุ่มแบบเดิม:
    • ตัวจัดการ onclick ของ HTML อาจถูกจำกัดการใช้งานในโค้ดจริงเนื่องจากนโยบายความปลอดภัย (CSP)
    • ต้องซิงก์สถานะของปุ่มกับองค์ประกอบอื่น และโค้ดสำหรับจัดการสถานะโดยยังคงการเข้าถึงได้ดีนั้นมีความซับซ้อน
    • แม้ใน React, AlpineJS, Svelte เป็นต้น การจัดการสถานะและอีเวนต์ก็ยังซับซ้อน

แพตเทิร์น command และ commandfor

  • เมื่อใช้แอตทริบิวต์ command และ commandfor ปุ่มจะสามารถทำงานกับองค์ประกอบอื่นแบบประกาศได้ ช่วยมอบความสะดวกแบบเฟรมเวิร์กพร้อมคงความยืดหยุ่นไว้
  • ปุ่ม commandfor ใช้ ID (คล้ายแอตทริบิวต์ for) และ command รับค่าที่มีมาให้ในตัว ทำให้แนวทางนี้เข้าใจง่ายขึ้น
  • ตัวอย่าง: การทำปุ่มเปิดเมนู
    • ไม่ต้องใช้ aria-expanded หรือ JavaScript เพิ่มเติม
    <button commandfor="my-menu" command="show-popover">  
      Open Menu  
    </button>  
    <div popover id="my-menu">  
      <!-- ... -->  
    </div>  
    

command และ commandfor เทียบกับ popovertargetaction และ popovertarget

  • หากเคยใช้ popover มาก่อน คุณอาจคุ้นเคยกับแอตทริบิวต์ popovertarget และ popovertargetaction
  • ทั้งสองทำงานคล้ายกับ commandfor และ command แต่เจาะจงสำหรับ popover
  • แอตทริบิวต์ใหม่เข้ามาแทนที่แอตทริบิวต์เดิมทั้งหมด พร้อมเพิ่มความสามารถเพิ่มเติม

คำสั่งในตัว

  • แอตทริบิวต์ command มีการทำงานในตัวที่แมปกับ API หลายตัว
    • show-popover: แมปกับ el.showPopover()
    • hide-popover: แมปกับ el.hidePopover()
    • toggle-popover: แมปกับ el.togglePopover()
    • show-modal: แมปกับ dialogEl.showModal()
    • close: แมปกับ dialogEl.close()
  • ตัวอย่าง: การทำไดอะล็อกยืนยันการลบ
    • จัดการสถานะและการเข้าถึงได้โดยไม่ต้องใช้ 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>  
    
    • โค้ดสำหรับจัดการผลลัพธ์: สามารถจัดการค่าที่ส่งกลับได้ในอีเวนต์ close ของไดอะล็อก
    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");  
      }  
    });  
    

คำสั่งแบบกำหนดเอง

  • นอกจากคำสั่งในตัวแล้ว ยังสามารถกำหนดคำสั่งเองได้ด้วยการใช้คำนำหน้า --
  • คำสั่งแบบกำหนดเองจะทำให้เกิดอีเวนต์ "command" บนองค์ประกอบเป้าหมาย แต่จะไม่ทำตรรกะเพิ่มเติมเอง
  • ตัวอย่าง: การทำคำสั่งหมุนภาพ
    <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>  
    

การจัดการคำสั่งใน Shadow DOM

  • ใน Shadow DOM มีข้อจำกัดต่อไปนี้เนื่องจาก commandfor ทำงานบนพื้นฐานของ ID:
    • ไม่สามารถอ้างอิงองค์ประกอบข้าม Shadow DOM ได้
    • ในกรณีนี้สามารถใช้ JavaScript API เพื่อตั้งค่าพร็อพเพอร์ตี .commandForElement ได้
  • ตัวอย่าง: การเชื่อมคำสั่งใน 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>  
    

แผนในอนาคต

  • Chrome มีแผนจะเพิ่มคำสั่งในตัวเพิ่มเติม เช่น:
    • การเปิดและปิดองค์ประกอบ <details>
    • รองรับคำสั่ง "show-picker" ใน <input> และ <select>
    • คำสั่งเล่นสำหรับ <video> และ <audio>
    • ความสามารถในการคัดลอกข้อความจากองค์ประกอบ

1 ความคิดเห็น

 
GN⁺ 2025-03-08
ความเห็นจาก Hacker News
  • นักทฤษฎีภาษาการเขียนโปรแกรมคาดเดาเกี่ยวกับ "comefrom" ซึ่งเป็นเวอร์ชันที่ทรงพลังกว่าของ "goto" มาตั้งแต่ยุค 80 โดยสิ่งนี้ถูกนำไปใช้จริงเฉพาะใน intercal เท่านั้น แม้ intercal จะเหนือกว่าภาษาอย่าง C ในด้านความปลอดภัย ประสิทธิภาพ และการยศาสตร์ แต่กลับประสบปัญหาในการเข้าสู่ตลาดเชิงพาณิชย์ การได้เห็น javascript นำความสามารถนี้ของ intercal มาใช้เป็นเรื่องน่าสนใจ และหวังว่าสิ่งนี้จะนำไปสู่การเติบโตของการเขียนโปรแกรมแบบสุภาพ เช่นเดียวกับที่อ็อบเจ็กต์แบบ closure ของ javascript เคยผลักดัน functional programming เข้าสู่กระแสหลัก

  • Invokers ไม่ได้มีเฉพาะใน Chrome เท่านั้น ใน Firefox nightly ก็ใช้งานได้แล้วเช่นกัน

  • แนวคิดในการทำพฤติกรรม UI แบบประกาศโดยไม่ใช้ JS นั้นน่าสนใจ

    • ช่วยลบ boilerplate ของ popover/modal ออกไป (ไม่ต้องจัดการ aria-expanded)
    • คำสั่งที่มีมาให้ในตัวอย่าง show-modal ผสานเรื่องการเข้าถึงเข้ากับมาร์กอัป
    • คำสั่งแบบกำหนดเอง (เช่น --rotate-landscape) ทำให้คอมโพเนนต์สามารถเปิดเผย API ผ่าน HTML ได้
  • ข้อสงสัย:

    • abstraction vs. magic: นี่เป็นแค่การย้ายความซับซ้อนจาก JS ไปไว้ใน HTML หรือไม่? เฟรมเวิร์กก็มีการ abstraction สถานะอยู่แล้ว แล้วสิ่งนี้จะอยู่ร่วมกันอย่างไร?
    • ปัญหากับ Shadow DOM: การตั้งค่า .commandForElement ข้าม shadow roots ยังต้องใช้ JS อยู่ดี จึงดูเหมือนเป็นปัญหาที่แก้ไปได้แค่ครึ่งเดียว
    • การรองรับอนาคต: ถ้า OpenUI เพิ่มคำสั่งมากกว่า 20 รายการ (เช่น show-picker, toggle-details) แพลตฟอร์มจะบวมด้วยไวยากรณ์เฉพาะทางหรือไม่?
  • สเปก:

    • button element, แอตทริบิวต์ commandfor
    • button element, แอตทริบิวต์ command
  • นี่คือแพตเทิร์น action/messaging ที่ Next, Be, Apple และรายอื่น ๆ ใช้เมื่อราว 30 ปีก่อนหรือเปล่า หรือฉันกำลังพลาดอะไรบางอย่าง?

    • มันมีประโยชน์ แต่ก็พัฒนาไปเป็นแพตเทิร์นคอนโทรลเลอร์แบบอิงอินเทอร์เฟซ เนื่องจากความซับซ้อนในการพยายามคงแพตเทิร์นการออกแบบพื้นฐานเอาไว้ ดังนั้นถ้าเปิดกล่องนี้ขึ้นมา ก็คาดว่าจะมีคำขอปรับปรุงตามมาอีกมาก
  • ชุดเครื่องมือ Java UI ยุคแรกของ Netscape (IFC) ก็เคยทำให้สามารถเชื่อมองค์ประกอบแบบ action เข้าด้วยกันได้

  • แอตทริบิวต์ command และ commandfor ใหม่ ช่วยปรับปรุงและมาแทนที่แอตทริบิวต์ popovertargetaction และ popovertarget

    • สิ่งเหล่านี้กลายเป็นสิ่งที่ใช้ได้โดยปริยายแล้วหรือ? คำว่าแทนที่หมายถึงอะไร? สุดท้ายแล้วจะลบของเดิมออกหรือไม่? นักพัฒนาเว็บไม่สามารถลบของที่ไม่จำเป็นแล้วออกจากอัปเดตได้
  • แพ้ทางอย่างแรงกับการเขียนโปรแกรมผ่านสตริง เข้าใจข้อดีด้าน accessibility แต่ไม่ได้ตื่นเต้นเป็นพิเศษกับการใช้ element ID เป็นอีกชั้นหนึ่งของพฤติกรรมเว็บแอป

  • ไม่ควรทำสิ่งนี้โดยไม่มี API ที่ครบถ้วน แทนที่จะมีคำสั่งสัก 5 คำสั่ง มันดูเหมือนควรทำให้ความสามารถทั้งหมดของ JavaScript ใช้งานผ่าน HTML ได้ ซึ่งอาจกลายเป็นคำสั่งนับพันรายการ

  • ตอนแรกนึกว่าจะเป็นเรื่อง command and conquer ใน HTML

  • การปรับปรุงและขยาย HTML เป็นเรื่องดี แต่ยังต้องไปอีกไกล ทีม HTMX มีไอเดียดี ๆ อยู่บ้าง