컴포넌트/필드 컴포넌트

Display (디스플레이)

VanillaFront 2026. 9. 14. 18:07

Va.Display — 읽기 전용 값 표시

입력 대신 값을 표시만 하는 필드. 다른 폼 필드와 시각적 규격을 맞춰 정렬된 값 표시가 필요할 때 사용합니다. <input readonly>보다 유연해서 HTML 콘텐츠까지 담을 수 있습니다.

  • 클래스: Va.Display  va_component.js:9557
  • short name: display
  • 상속: Va.PureField (Input과 형제)
  • DOM 태그: <div> (Input과 달리 <input>이 아님)
  • isContainer: true
  • 베이스 CSS: va-display


1. 기본 사용

{
    tagName: 'display',
    text: '홍길동',
    style: { width: '200px' }
}
  • 입력 불가한 값 표시
  • 다른 폼 필드와 같은 크기·정렬로 배치 가능
  • 편집 없이 데이터 조회 화면에 어울림

2. Display의 특징

Display는 처음부터 readonly: true가 강제되어 있고 (this.readonly = true), 값 입력 로직 대신 텍스트/HTML 표시에 특화되어 있습니다.

항목Va.Input (readonly)Va.Display

DOM 태그 <input> <div>
HTML 콘텐츠 ✓ (innerHTML 지원)
선택 가능 텍스트 선택 텍스트 선택
키 이벤트 발생 발생 (읽기 전용)
상속 PureField PureField
기본 readonly false true 강제
컴포넌트 목적 편집 가능 필드 표시 전용

한 줄 요약: "폼 안 라벨 없는 읽기 전용 값 표시 필드."


3. 주요 속성

Display 전용

속성설명

text 표시할 순수 텍스트
innerHTML HTML 포함 콘텐츠 (내부 _sanitize 적용)

text와 innerHTML이 모두 있으면 text 우선.

PureField 상속

readonly (기본 true), disabled, size, appearance, stopPropagation 등.

⚠️ readonly가 강제로 true — 옵션으로 readonly: false를 넘겨도 무시됨. Display는 본래 표시 전용.


4. 이벤트

Display는 자체 이벤트가 매우 적음 — properties/events 배열 spread만 하고 특별한 이벤트 등록 없음.

PureField의 표준 이벤트(focus, blur, click 등)는 상속되지만, 편집 관련 이벤트(change 등)는 실질적 의미가 없음.


5. 메서드

값 관리 (오버라이드)

메서드설명

setValue(value) 값 세팅 — innerHTML과 element.textContent 둘 다 설정
getValue() element.textContent 반환

⚠️ setValue()의 특이한 구현 (va_component.js:9601-9604):

setValue(value){
    this.innerHTML = value;
    this.element.textContent = value;   // ← element 전체를 textContent로 덮음
}

이 코드가 element 전체 자식을 지움 — fieldWrapper, fieldElement, focusLine 등이 사라져 텍스트만 남게 될 수 있음. 이후 getValue()가 그 텍스트만 반환하므로 표면적으론 동작하지만 내부 구조 훼손 위험. text 옵션이나 update() 경로가 더 안전.

권장: setValue()보다는 option.text / option.innerHTML을 옵션으로 지정하거나, 값 변경이 필요하면 component.text = value; component.update();.


6. 내부 구조

<div elname="element" class="va-display" tag-name="display" field="true">
  <div elname="fieldWrapper" class="field-wrapper">
    <div elname="field" style="border:0px">홍길동</div>      ← 값 표시 영역
    <div elname="focusLine" class="focus-line"></div>
  </div>
</div>

핵심:

  • <input> 대신 <div> 를 fieldElement로 사용 (PureField 생성자에 'div' 전달)
  • border:0 강제 — 다른 폼 필드와 구분 없이 자연스럽게 보임
  • text는 textContent, innerHTML은 innerHTML로 세팅

7. text vs innerHTML

두 옵션은 처리 경로가 다름:

text — 순수 텍스트

{ tagName: 'display', text: '<b>강조</b>' }
// 화면: <b>강조</b>   (그대로 보임, HTML 파싱 안 됨)
  • containerElement.textContent로 세팅
  • HTML 태그가 있어도 텍스트로만 표시
  • XSS 걱정 없음

innerHTML — HTML 포함

{ tagName: 'display', innerHTML: '<b>강조</b>' }
// 화면: 강조 (bold 스타일 적용)
  • fieldElement.innerHTML로 세팅 (Va._sanitize 적용)
  • HTML 태그 렌더링
  • XSS 방지를 위해 sanitize 통과

서버에서 받은 사용자 입력 텍스트는 text로 표시하는 게 안전.


8. 언제 쓰나

Display가 맞을 때

  • 상세 조회 화면의 값 표시 (편집 없음)
  • 폼과 정렬을 맞춰야 하는 읽기 전용 값
  • HTML 콘텐츠 표시 (링크 포함, 강조 등)
  • 리포트·요약 화면
  • 계산 결과 표시 (다른 필드의 값을 조합)

다른 걸 쓸 때

  • 편집 가능 텍스트 → Va.Input
  • 라벨 붙은 표시 → Va.DisplayField
  • 그냥 텍스트 표시 (폼 정렬 필요 없음) → <div> / Va.Div / Va.Span
  • 긴 문서형 텍스트 → Va.Textarea + readonly

9. 흔한 조합 예시

// 표준
{
    tagName: 'display',
    text: '홍길동',
    style: { width: '200px' }
}

// HTML 콘텐츠
{
    tagName: 'display',
    innerHTML: '<b>VIP</b> 고객'
}

// 링크 포함
{
    tagName: 'display',
    innerHTML: '<a href="/detail/123">상세 보기</a>'
}

// 폼 안 다른 필드와 정렬 (DisplayField 아닌 이유는 라벨 불필요)
{
    tagName: 'div',
    layout: 'ds-flex fd-row gap-s',
    tags: [
        { tagName: 'inputField', label: '이름' },
        { tagName: 'display', text: '(자동 생성)' }
    ]
}

// 계산 결과 표시
mounted() {
    const total = this.getRef('a').getValue() + this.getRef('b').getValue();
    this.getRef('sumDisplay').text = total;
    this.getRef('sumDisplay').update();
}

// 서버 응답 표시
onLoad(view, ok, res) {
    if (ok) {
        view.getRef('userName').innerHTML = res.data.userName;
        view.getRef('userName').update();
    }
}

10. 실전 예 — 주문 상세 조회

class OrderDetail extends Va.View {
    async mounted() {
        const res = await OrderService.get(this, { orderId: '12345' });
        if (res.result) {
            const order = res.data.info;
            this.getRef('orderId').text = order.orderId;
            this.getRef('orderId').update();
            this.getRef('custName').text = order.custName;
            this.getRef('custName').update();
            this.getRef('amount').text = order.amount.toLocaleString() + '원';
            this.getRef('amount').update();
            this.getRef('status').innerHTML =
                `<span style="color:${order.status === 'DONE' ? 'green' : 'orange'}">${order.statusName}</span>`;
            this.getRef('status').update();
        }
    }

    config() {
        return {
            tagName: 'page',
            tags: [{
                tagName: 'panel',
                tags: [
                    { tagName: 'h2', innerHTML: '주문 상세' },
                    {
                        tagName: 'div',
                        layout: 'ds-flex fd-column gap-s',
                        tags: [
                            {
                                tagName: 'div',
                                layout: 'ds-flex fd-row gap-s',
                                tags: [
                                    { tagName: 'div', innerHTML: '주문번호:', style: { width: '100px' } },
                                    { tagName: 'display', ref: 'orderId' }
                                ]
                            },
                            {
                                tagName: 'div',
                                layout: 'ds-flex fd-row gap-s',
                                tags: [
                                    { tagName: 'div', innerHTML: '고객명:', style: { width: '100px' } },
                                    { tagName: 'display', ref: 'custName' }
                                ]
                            },
                            {
                                tagName: 'div',
                                layout: 'ds-flex fd-row gap-s',
                                tags: [
                                    { tagName: 'div', innerHTML: '금액:', style: { width: '100px' } },
                                    { tagName: 'display', ref: 'amount' }
                                ]
                            },
                            {
                                tagName: 'div',
                                layout: 'ds-flex fd-row gap-s',
                                tags: [
                                    { tagName: 'div', innerHTML: '상태:', style: { width: '100px' } },
                                    { tagName: 'display', ref: 'status' }
                                ]
                            }
                        ]
                    }
                ]
            }]
        };
    }
}

핵심:

  • 폼과 시각적 규격을 맞춘 값 표시
  • 상태는 HTML 콘텐츠로 색상 강조
  • 편집 불가능 (조회 전용)

실무에선 대개 DisplayField로 라벨과 함께 감싸는 게 편함.


11. 알아두면 좋을 주의사항

  1. readonly: true 강제 — 옵션으로 편집 가능 만들 수 없음.
  2. <input> 아닌 <div> 기반 — 폼 서브밋에 값 참여 안 됨. 데이터 전송은 별도 처리 필요.
  3. setValue() 내부 구조 훼손 위험  element.textContent = value가 자식 요소 다 지움. 안전한 방법은 component.text = value; component.update().
  4. text vs innerHTML 차이 — HTML 렌더링 여부. XSS 조심.
  5. innerHTML은 _sanitize 통과 — 위험한 태그(예: <script>)는 제거되지만 완벽한 방어는 아님.
  6. 이벤트 매우 제한적 — 편집 이벤트는 실질 의미 없음.
  7. getValue()는 textContent — HTML 태그 제거된 순수 텍스트.
  8. 정렬 목적으로 사용 — 라벨 없이 다른 필드와 나란히 두려면 유용.
  9. 라벨 필요하면 DisplayField — 폼 안 정식 필드 관행에 맞춤.
  10. change 이벤트 없음 — 값이 프로그램적으로 바뀌어도 이벤트 발생 안 함. 필요하면 세팅 후 명시적 로직.

12. display vs input readonly vs div 선택

상황추천

폼 안 값 표시 (라벨 없이) display
폼 안 값 표시 (라벨 함께) displayField
HTML 콘텐츠 표시 display + innerHTML
편집 가능 필드로 표시만 잠금 input + readonly: true
폼 관계없는 일반 텍스트 <div> / Va.Div
긴 문서 스크롤 표시 textarea + readonly

 


참고