# DModal API를 사용하여 Discourse에서 모달 창(팝업/다이얼로그) 렌더링하기

**URL:** https://meta.discourse.org/t/using-the-dmodal-api-to-render-modal-windows-aka-popups-dialogs-in-discourse/268304
**Category:** Developer Guides
**Tags:** code
**Created:** [7월 3, 2023, 9:52오전 UTC](https://meta.discourse.org/t/using-the-dmodal-api-to-render-modal-windows-aka-popups-dialogs-in-discourse/268304 "2023-07-03T09:52:06Z")
**Posts on this page:** 2
**Page:** 1

<div class="post-metadata">

### Author: ![Discourse](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/discourse/32/148734_2.png) [@Discourse](https://meta.discourse.org/u/Discourse)
#### Post date: [7월 3, 2023, 9:52오전 UTC](https://meta.discourse.org/t/using-the-dmodal-api-to-render-modal-windows-aka-popups-dialogs-in-discourse/268304/1 "2023-07-03T09:52:06Z")

</div>

Discourse 3.1.0.beta6에는 완전히 새로운 `<DModal>` 컴포넌트 기반 API가 포함되어 있습니다. `DModal`은 [UI 키트](https://meta.discourse.org/t/-/411319)의 일부이며 `discourse/ui-kit/d-modal`에서 가져옵니다.

> ℹ 이는 이제 비추천(deprecated)된 기존 컨트롤러 기반 API를 대체합니다. 기존 API를 사용하는 모달이 있는 경우, 마이그레이션 가이드를 [여기](https://meta.discourse.org/t/converting-modals-from-legacy-controllers-to-new-dmodal-component-api/268057)에서 확인하세요.

## 모달 렌더링

모달은 Handlebars 템플릿에 `<DModal>` 컴포넌트를 포함하여 렌더링됩니다. 이미 적합한 템플릿이 없다면 [https://meta.discourse.org/t/using-plugin-outlet-connectors-from-a-theme-or-plugin/32727을](https://meta.discourse.org/t/using-plugin-outlet-connectors-from-a-theme-or-plugin/32727%EC%9D%84) 확인하세요.

간단한 모달은 다음과 같은 형태를 가질 수 있습니다:

```hbs
<DButton
  @translatedLabel="Show Modal"
  @action={{fn (mut this.modalIsVisible) true}}
/>

{{#if this.modalIsVisible}}
  <DModal @title="My Modal" @closeModal={{fn (mut this.modalIsVisible) false}}>
    Hello world, this is some content in a modal
  </DModal>
{{/if}}

```

> ℹ 여기서는 [`mut` 헬퍼](https://api.emberjs.com/ember/release/classes/Ember.Templates.helpers/methods/mut)가 hbs 전용 방식으로 값을 설정하는 데 사용됩니다. `modalIsVisible`을 다른 표준 Ember 메서드를 사용하여 설정할 수도 있습니다.

이 예제는 다음과 같은 간단한 모달을 생성합니다:

 ![SCR-20230614-mxwd](https://global.discourse-cdn.com/meta/original/4X/4/0/b/40be9a7c253202136882269ba77d47ad86bd6d35.png)

## 컴포넌트로 래핑하기

더 많은 복잡성을 도입하기 전에, 새로운 모달을 자체 컴포넌트 정의에 래핑하는 것이 일반적으로 가장 좋습니다. `<DModal>` 관련 코드를 새로운 `<MyModal />` 컴포넌트 안으로 이동해 보겠습니다.

```gjs
// components/my-modal.gjs
<template>
  <DModal @title="My Modal" @closeModal={{@closeModal}}>
    Hello world, this is some content in a modal
  </DModal>
</template>

```

이 `.gjs` 파일을 클래스 기반 컴포넌트로 업그레이드하면 더 복잡한 로직과 상태를 도입할 수 있습니다.

새로운 컴포넌트를 활용하려면 호출 지점을 업데이트하여 해당 컴포넌트를 참조하고 `@closeModal` 인자를 전달하도록 하세요.

```hbs
<DButton
  @translatedLabel="Show Modal"
  @action={{fn (mut this.modalIsVisible) true}}
/>

{{#if this.modalIsVisible}}
  <MyModal @closeModal={{fn (mut this.modalIsVisible) false}} />
{{/if}}

```

## 푸터 추가하기

많은 모달에는某种의 행동 유도(call-to-action)가 있습니다. Discourse에서는 이러한 요소들이 모달 하단에 위치하는 경향이 있습니다. 이를 가능하게 하기 위해 `DModal`에는 내부에 콘텐츠가 렌더링될 수 있는 여러 '이름 지정 블록(named blocks)'이 있습니다. 여기서는 푸터에 두 개의 버튼(그중 하나는 표준 `DModalCancel` 버튼)을 포함하도록 업데이트된 예제입니다.

```hbs
<DModal @title="My Modal" @closeModal={{@closeModal}}>
  <:body>
    Hello world, this is some content in a modal
  </:body>
  <:footer>
    <DButton class="btn-primary" @translatedLabel="Submit" />
    <DModalCancel @close={{@closeModal}} />
  </:footer>
</DModal>

```

 ![SCR-20230614-njze](https://global.discourse-cdn.com/meta/original/4X/f/1/1/f11e6f7ffe100c91695d7463b4ce64c61ef9bf83.png)

## hbs가 아닌 컨텍스트에서 모달 렌더링

이상적으로는 위에서 시연한 선언적 기법을 사용하여 Ember 템플릿 내에서 `<DModal>` 인스턴스를 렌더링해야 합니다. 사용 사례에서 이것이 불가능한 경우, `modal` 서비스를 주입하고 `modal.show()`를 호출하여 수행할 수 있습니다.

위에서 설명한 대로 모달을 자체 컴포넌트로 래핑했는지 확인하세요. 그런 다음 `showModal`에 컴포넌트 클래스의 참조를 전달하여 모달을 트리거합니다:

```js
import MyModal from "discourse/components/my-modal";

// (관련 위치에 modal 서비스를 주입)

// 모달을 열 때마다 이 호출을 추가하세요.
// `@closeModal` 인자가 컴포넌트에 자동으로 전달됩니다.
this.modal.show(MyModal);

// 선택적으로 '`model`' 매개변수를 전달합니다. `@model`로 컴포넌트에 전달됩니다.
// 여기에는 데이터와 모달이 사용할 동작/콜백을 포함할 수 있습니다.
this.modal.show(MyModal, {
  model: { topic: this.topic, someAction: this.someAction },
});

// `modal.show()`는 Promise를 반환하므로, 닫힐 때까지 기다릴 수 있습니다.
// `@closeModal` 동작에 전달된 데이터로 resolve됩니다.
const result = await this.modal.show(MyModal);

```

## 더 많은 커스터마이징!

`<DModal>`에는 여러 이름 지정 블록과 인자가 있습니다.

### 인자(Arguments)

| Arg | Purpose |
| --- | --- |
| `@closeModal` | 닫기 UI가 표시되려면 필수입니다. |
| `@title` | `<h1 id="discourse-modal-title">`을 렌더링하고 `aria-labelledby`를 연결합니다. |
| `@subtitle` | 제목 아래에 표시되는 작은 텍스트입니다. |
| `@flash` / `@flashType` | 모달 상단의 인라인 알림(`DFlashMessage`)입니다. |
| `@hideHeader`, `@hideFooter` | 전체 영역을 숨깁니다. |
| `@headerClass`, `@bodyClass` | 헤더/바디 래퍼에 추가할 클래스입니다. |
| `@dismissable` | `@closeModal`이 설정되어 있으면 기본적으로 true입니다. Esc / 배경 클릭 / X를 비활성화합니다. |
| `@autofocus` | 기본적으로 true입니다. `dTrapTab`을 통해 첫 번째 포커스 가능한 요소에 자동으로 포커스를 맞춥니다. |
| `@submitOnEnter` | 기본적으로 true입니다. 포커스가 폼 / textarea / select-kit에 있지 않으면 Enter 키가 `.d-modal__footer .btn-primary`을 클릭합니다. |
| `@beforeClose` | `async ({ initiatedBy }) => boolean`. `false`를 반환하면 닫기 동작을 취소합니다(예: 수정된 폼 확인). |
| `@hidden` | 키보드 처리를 일시 중지합니다. 중첩된 모달이 위에 있을 때 사용됩니다. |
| `@tagName` | `"div"`(기본값) 또는 `"form"`. 네이티브 제출(submit)이 작동하도록 폼에는 `"form"`을 사용하세요. |

### 블록(Blocks)

| Block | Position | When to use |
| --- | --- | --- |
| default / `:body` | Main content area | 기본 영역 |
| `:aboveHeader` | Very top, before header | 거의 필요하지 않습니다; 제목 바 위에 있어야 하는 콘텐츠(예: 배너)에 사용됩니다. |
| `:headerAboveTitle` | Inside header, before title | 존재하지만 미사용입니다. 거의 필요하지 않습니다. |
| `:belowModalTitle` | Inside `.d-modal__title`, after the `<h1>` | 보조 메타 정보를 위한 훌륭한 위치입니다. |
| `:headerBelowTitle` | Inside header, after title block | 헤더의 일부인 탭, 서브 내비게이션 또는 검색 입력 필드에 사용됩니다. |
| `:headerPrimaryAction` | Right side of header on **mobile only** | X 닫기 버튼을 기본 동작(예: “저장”)으로 대체합니다. 또한 왼쪽에 “취소” 버튼을 자동으로 렌더링하고 헤더에 `.--has-primary-action`을 추가합니다. |
| `:belowHeader` | Between header and body | 스크롤 가능한 바디 외부에 위치하여 고정(sticky) 표시되는 영구적인 서브 헤더 콘텐츠(예: 검색 바)에 사용됩니다. |
| `:aboveFooter` | Between body and footer | `@hideFooter`이 설정되면 억제됩니다. 푸터와 관련되었지만 푸터 외부에 있는 콘텐츠에 사용됩니다. 또한 드뭅니다. |
| `:footer` | Bottom action bar | 기본 및 보조 버튼. 여기의 첫 번째 `.btn-primary`은 Enter 키가 트리거하는 버튼입니다. |
| `:belowFooter` | After the footer | 거의 필요하지 않습니다; `@hideFooter`을 무시합니다. 경계가 있는 푸터 영역 외부의 상태 텍스트에 유용합니다. |

출처: 인자에 대한 [인터랙티브 스타일 가이드](https://meta.discourse.org/styleguide/organisms/modal) 및 이름 지정 블록에 대한 [d-modal 템플릿 구현](https://github.com/discourse/discourse/blob/main/frontend/discourse/app/ui-kit/d-modal.gjs).

### CSS

`.d-modal` 클래스를 앵커로 사용하여 코어를 오버라이드하고 레거시 `.modal` 선택자를 피하세요.

사용 가능한 4개의 수정자(modifiers):

- .`--large`는 **최대**  **너비** 를 800px으로 설정합니다 (데스크톱 전용)
- .`--max`는 **최대**  **너비** 를 90vw로 설정합니다 (데스크톱 전용)
- .`has-search`는 **고정**  **높이** (80vh)를 설정합니다: 결과 길이에 따른 높이 변화를 피하기 위해 검색/필터 시스템이 있는 모달에 intended (데스크톱 전용)
- `.--stacked`는 푸터 버튼을 스택 방식으로 설정합니다 (모바일 전용)

* * *

이 문서는 버전 관리됩니다 - 변경 사항을 [github](https://github.com/discourse/discourse/blob/main/docs/developer-guides/docs/03-code-internals/12-dmodal-api.md)에서 제안하세요.

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [12월 27, 2023, 6:11오전 UTC](https://meta.discourse.org/t/using-the-dmodal-api-to-render-modal-windows-aka-popups-dialogs-in-discourse/268304/4 "2023-12-27T06:11:48Z")

</div>

게시글이 새 주제로 분리되었습니다: [head\_tag에서 모달을 표시할 수 있나요?](https://meta.discourse.org/t/can-i-show-a-modal-from-head-tag/289836)
