Now.js Framework Documentation

Now.js Framework Documentation

DropdownPanel

TH 15 Dec 2025 08:52

DropdownPanel

ภาพรวม

DropdownPanel คือ singleton panel สำหรับแสดง dropdown content ใน Now.js Framework ใช้แชร์ระหว่าง components เช่น Calendar และ ColorPicker

ใช้เมื่อ:

  • ต้องการ dropdown floating panel
  • ต้องการ share panel ระหว่าง components
  • ต้องการ avoid z-index issues ใน modals
  • ต้องการ popup positioning

ทำไมต้องใช้:

  • ✅ Singleton pattern (one panel for all)
  • ✅ Attached to document.body (no overflow issues)
  • ✅ Smart positioning (auto-adjust to viewport)
  • ✅ Keyboard support (Escape to close)
  • ✅ Click outside to close
  • ✅ Scroll/resize repositioning

การใช้งานพื้นฐาน

Show Panel

const panel = DropdownPanel.getInstance();

// Show with HTML content
panel.show(targetElement, '<div class="picker">Content here</div>');

// Show with DOM element
const content = document.createElement('div');
content.innerHTML = '<p>My content</p>';
panel.show(targetElement, content);

// With options
panel.show(targetElement, content, {
  align: 'right',
  offsetY: 10,
  onClose: () => console.log('Panel closed')
});

Hide Panel

const panel = DropdownPanel.getInstance();
panel.hide();

Options

panel.show(targetElement, content, {
  // Alignment relative to target
  align: 'left',    // 'left', 'right', 'center'

  // Offset from target
  offsetY: 5,       // Vertical offset (px)
  offsetX: 0,       // Horizontal offset (px)

  // Callback when closed
  onClose: () => {}
});

API อ้างอิง

รับ singleton instance

Returns: DropdownPanel

const panel = DropdownPanel.getInstance();

panel.show(targetElement, content, options?)

แสดง panel

Parameter Type Description
targetElement HTMLElement Element ที่จะ position ถัดจาก
content HTMLElement/string Content (element หรือ HTML string)
options.align string 'left', 'right', 'center'
options.offsetY number Vertical offset (default: 5)
options.offsetX number Horizontal offset (default: 0)
options.onClose function Callback เมื่อปิด

panel.hide()

ซ่อน panel

panel.isOpen()

ตรวจสอบว่า panel เปิดอยู่หรือไม่

Returns: boolean

panel.setContent(content)

เปลี่ยน content ขณะที่แสดงอยู่

Parameter Type Description
content HTMLElement/string New content

panel.getContentContainer()

รับ content container element

Returns: HTMLElement

ทำลาย singleton instance

เหตุการณ์

Event เมื่อเกิด Detail
dropdown:show Panel แสดง {target}
dropdown:hide Panel ซ่อน {target}
const panel = DropdownPanel.getInstance();
panel.panel.addEventListener('dropdown:show', (e) => {
  console.log('Opened for:', e.detail.target);
});

การจัดรูปแบบ CSS

/* Dropdown panel */
.dropdown-panel {
  position: fixed;
  display: none;
  background: white;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
  z-index: 1000;
  overflow: auto;
}

.dropdown-panel-content {
  padding: 8px;
}

/* Animation */
.dropdown-panel {
  animation: dropdown-fade-in 0.15s ease-out;
}

@keyframes dropdown-fade-in {
  from {
    opacity: 0;
    transform: translateY(-8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

ตัวอย่างการใช้งานจริง

Color Picker

function showColorPicker(inputElement) {
  const panel = DropdownPanel.getInstance();

  const picker = document.createElement('div');
  picker.className = 'color-picker-grid';
  picker.innerHTML = `
    <div class="color" style="background: #ff0000" data-color="#ff0000"></div>
    <div class="color" style="background: #00ff00" data-color="#00ff00"></div>
    <div class="color" style="background: #0000ff" data-color="#0000ff"></div>
  `;

  picker.addEventListener('click', (e) => {
    const color = e.target.dataset.color;
    if (color) {
      inputElement.value = color;
      inputElement.style.backgroundColor = color;
      panel.hide();
    }
  });

  panel.show(inputElement, picker, {
    onClose: () => {
      // Cleanup if needed
    }
  });
}

Date Picker

function showDatePicker(inputElement, currentDate) {
  const panel = DropdownPanel.getInstance();

  const calendar = CalendarManager.createCalendarElement({
    date: currentDate,
    onSelect: (date) => {
      inputElement.value = Utils.date.format(date, 'YYYY-MM-DD');
      panel.hide();
    }
  });

  panel.show(inputElement, calendar, {
    align: 'left',
    offsetY: 4
  });
}

Context Menu

function showContextMenu(event, items) {
  event.preventDefault();

  const panel = DropdownPanel.getInstance();

  const menu = document.createElement('ul');
  menu.className = 'context-menu';

  items.forEach(item => {
    const li = document.createElement('li');
    li.textContent = item.label;
    li.addEventListener('click', () => {
      item.action();
      panel.hide();
    });
    menu.appendChild(li);
  });

  // Create virtual target at click position
  const virtualTarget = document.createElement('span');
  virtualTarget.style.cssText = `
    position: fixed;
    left: ${event.clientX}px;
    top: ${event.clientY}px;
  `;
  document.body.appendChild(virtualTarget);

  panel.show(virtualTarget, menu, {
    onClose: () => virtualTarget.remove()
  });
}

ข้อควรระวัง

⚠️ 1. Singleton - มีได้แค่ panel เดียว

// ❌ เปิด 2 panels พร้อมกัน
panel.show(element1, content1);
panel.show(element2, content2); // อันแรกถูกปิด

// ✅ เข้าใจว่ามีได้แค่ 1 panel
// ถ้าต้องการหลาย popup ให้ใช้ DialogManager

⚠️ 2. Target Element ต้องมีอยู่

// ❌ Target ไม่มีอยู่จริง
panel.show(null, content);

// ✅ ตรวจสอบก่อน
if (targetElement) {
  panel.show(targetElement, content);
}

⚠️ 3. Clean up เมื่อไม่ใช้

// onClose callback สำหรับ cleanup
panel.show(target, content, {
  onClose: () => {
    // Cleanup resources
    disposeResources();
  }
});

เอกสารที่เกี่ยวข้อง