JavaScript

JavaScript

Bootstrap의 JavaScript 플러그인을 사용하는 방법을 정리한 문서예요. 개별 포함부터 모듈 사용, 이벤트, 프로그래매틱 API, jQuery 사용까지 다룬답니다.

출처: 문서

본문

개별 포함 또는 한 번에 (Individual or compiled)

플러그인은 개별로(Bootstrap의 개별 js/dist/*.js 사용) 포함하거나, bootstrap.js 혹은 minify된 bootstrap.min.js(둘 다 포함하지는 마세요)로 한 번에 모두 포함할 수 있어요.

번들러(Webpack, Parcel, Vite…)를 사용한다면 UMD로 준비된 /js/dist/*.js 파일을 사용할 수 있어요.

JavaScript 프레임워크와의 사용 (Usage with JavaScript frameworks)

Bootstrap CSS는 어떤 프레임워크에서도 사용할 수 있지만, Bootstrap JavaScript는 DOM을 완전히 알고 있다고 가정하는 React, Vue, Angular 같은 JavaScript 프레임워크와는 완전히 호환되지 않아요. Bootstrap과 프레임워크가 같은 DOM 요소를 변경하려 할 수 있고, 그 결과 드롭다운이 "열린" 상태에 갇히는 것 같은 버그가 발생할 수 있어요.

이런 유형의 프레임워크를 사용하는 사람에게는 Bootstrap JavaScript 대신 프레임워크 전용 패키지를 사용하는 게 더 나은 대안이에요. 가장 인기 있는 옵션 중 일부는 다음과 같아요.

직접 해 보세요! React, Next.js, React Bootstrap으로 Bootstrap을 사용하는 소스 코드와 동작 데모를 twbs/examples 저장소에서 다운로드할 수 있어요. StackBlitz에서 예시를 열어 볼 수도 있어요.

Bootstrap을 모듈로 사용하기 (Using Bootstrap as a module)

직접 해 보세요! twbs/examples 저장소에서 Bootstrap을 ES 모듈로 사용하는 소스 코드와 동작 데모를 다운로드할 수 있어요. StackBlitz에서 예시를 열어 볼 수도 있어요.

우리는 ESM으로 빌드된 Bootstrap 버전(bootstrap.esm.js와 bootstrap.esm.min.js)을 제공해요. 대상 브라우저가 지원한다면 Bootstrap을 브라우저에서 모듈로 사용할 수 있게 해 주죠.

<script type="module">
  import { Toast } from 'bootstrap.esm.min.js'

  Array.from(document.querySelectorAll('.toast'))
    .forEach(toastNode => new Toast(toastNode))
</script>

JS 번들러와 비교하면, 브라우저에서 ESM을 사용할 때는 모듈 이름 대신 전체 경로와 파일명을 사용해야 해요. 브라우저의 JS 모듈에 대해 더 읽어 보세요. 그래서 위에서 'bootstrap' 대신 'bootstrap.esm.min.js'를 사용하는 거예요. 그런데 여기에 우리 Popper 의존성이 또 문제를 복잡하게 만들어요. Popper를 아래처럼 JavaScript로 import하거든요.

import * as Popper from "@popperjs/core"

이대로 그냥 실행하면 콘솔에 다음과 같은 오류가 보일 거예요.

Uncaught TypeError: Failed to resolve module specifier "@popperjs/core". Relative references must start with either "/", "./", or "../".

이 문제를 해결하려면 importmap을 사용해서 임의의 모듈 이름을 완전한 경로로 매핑할 수 있어요. 대상 브라우저가 importmap을 지원하지 않는다면 es-module-shims 프로젝트를 사용해야 해요. Bootstrap과 Popper에서 이렇게 동작해요.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
    <title>Hello, modularity!</title>
  </head>
  <body>
    <h1>Hello, modularity!</h1>
    <button id="popoverButton" type="button" class="btn btn-primary btn-lg" data-bs-toggle="popover" title="ESM in Browser" data-bs-content="Bang!">Custom popover</button>

    <script async src="https://cdn.jsdelivr.net/npm/es-module-shims@1/dist/es-module-shims.min.js" crossorigin="anonymous"></script>
    <script type="importmap">
    {
      "imports": {
        "@popperjs/core": "https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/esm/popper.min.js",
        "bootstrap": "https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.esm.min.js"
      }
    }
    </script>
    <script type="module">
      import * as bootstrap from 'bootstrap'

      new bootstrap.Popover(document.getElementById('popoverButton'))
    </script>
  </body>
</html>

의존성 (Dependencies)

일부 플러그인과 CSS 컴포넌트는 다른 플러그인에 의존해요. 플러그인을 개별로 포함한다면 문서에서 이런 의존성을 꼭 확인하세요.

우리의 드롭다운, 팝오버, 툴팁은 Popper에도 의존해요.

데이터 속성 (Data attributes)

거의 모든 Bootstrap 플러그인은 데이터 속성만으로(우리가 JavaScript 기능을 사용하는 데 선호하는 방법) HTML만으로 활성화하고 구성할 수 있어요. 한 요소에는 한 세트의 데이터 속성만 사용하세요(예: 같은 버튼에서 툴팁과 모달을 동시에 트리거할 수는 없어요).

옵션은 데이터 속성이나 JavaScript로 전달할 수 있으므로, data-bs-animation="{value}"처럼 data-bs-에 옵션 이름을 붙일 수 있어요. 데이터 속성으로 옵션을 전달할 때는 옵션 이름의 대소문자를 "camelCase"에서 "kebab-case"로 바꿔야 해요. 예를 들어 data-bs-customClass="beautifier" 대신 data-bs-custom-class="beautifier"를 사용하세요.

Bootstrap 5.2.0부터 모든 컴포넌트는 JSON 문자열로 간단한 컴포넌트 구성을 담을 수 있는 실험 전용 예약 데이터 속성 data-bs-config를 지원해요. 요소에 data-bs-config='{"delay":0, "title":123}'와 data-bs-title="456" 속성이 있으면 최종 title 값은 456이 되고, 개별 데이터 속성이 data-bs-config에 주어진 값을 덮어써요. 또한 기존 데이터 속성도 data-bs-delay='{"show":0,"hide":150}'처럼 JSON 값을 담을 수 있어요.

최종 구성 객체는 data-bs-config, data-bs-, js object가 합쳐진 결과인데, 가장 마지막에 주어진 키-값이 나머지를 덮어써요.

선택자 (Selectors)

우리는 DOM 요소를 조회할 때 성능상의 이유로 네이티브 querySelector와 querySelectorAll 메서드를 사용해요. 그래서 유효한 선택자를 사용해야 해요. collapse:Example 같은 특수 선택자를 사용한다면 반드시 이스케이프하세요.

이벤트 (Events)

Bootstrap은 대부분의 플러그인 고유 동작에 대해 커스텀 이벤트를 제공해요. 일반적으로 부정사(infinitive)와 과거분사(past participle) 형태로 제공되는데, 부정사(예: show)는 이벤트 시작 시에, 과거분사 형태(예: shown)는 동작 완료 시에 트리거돼요.

모든 부정사 이벤트는 preventDefault() 기능을 제공해요. 이를 통해 동작이 시작되기 전에 실행을 멈출 수 있어요. 이벤트 핸들러에서 false를 반환해도 자동으로 preventDefault()가 호출돼요.

const myModal = document.querySelector('#myModal')

myModal.addEventListener('show.bs.modal', event => {
  return event.preventDefault() // stops modal from being shown
})

프로그래매틱 API (Programmatic API)

모든 생성자는 선택적 옵션 객체나 아무것도 받지 않아요(아무것도 받지 않으면 기본 동작으로 플러그인을 초기화해요).

const myModalEl = document.querySelector('#myModal')
const modal = new bootstrap.Modal(myModalEl) // initialized with defaults

const configObject = { keyboard: false }
const modal1 = new bootstrap.Modal(myModalEl, configObject) // initialized with no keyboard

특정 플러그인 인스턴스를 얻고 싶다면 각 플러그인이 getInstance 메서드를 노출해요. 예를 들어 요소에서 직접 인스턴스를 가져오려면:

bootstrap.Popover.getInstance(myPopoverEl)

이 메서드는 요청한 요소에 인스턴스가 초기화되지 않았다면 null을 반환해요.

혹은 getOrCreateInstance를 사용해서 DOM 요소와 연결된 인스턴스를 가져오거나, 초기화되지 않았다면 새로 만들 수 있어요.

bootstrap.Popover.getOrCreateInstance(myPopoverEl, configObject)

인스턴스가 초기화되지 않은 경우, 두 번째 인자로 선택적 구성 객체를 받아 사용할 수 있어요.

생성자의 CSS 선택자 (CSS selectors in constructors)

getInstance와 getOrCreateInstance 메서드 외에도 모든 플러그인 생성자는 첫 번째 인자로 DOM 요소나 유효한 CSS 선택자를 받을 수 있어요. 우리 플러그인은 단일 요소만 지원하기 때문에 플러그인 요소는 querySelector 메서드로 찾아요.

const modal = new bootstrap.Modal('#myModal')
const dropdown = new bootstrap.Dropdown('[data-bs-toggle="dropdown"]')
const offcanvas = bootstrap.Offcanvas.getInstance('#myOffcanvas')
const alert = bootstrap.Alert.getOrCreateInstance('#myAlert')

비동기 함수와 전환 (Asynchronous functions and transitions)

모든 프로그래매틱 API 메서드는 비동기이며, 전환이 시작되면 끝나기 전에 호출자에게 돌아가요. 전환이 완료된 후에 동작을 실행하려면 해당 이벤트를 수신하면 돼요.

const myCollapseEl = document.querySelector('#myCollapse')

myCollapseEl.addEventListener('shown.bs.collapse', event => {
  // Action to execute once the collapsible area is expanded
})

또한 전환 중인 컴포넌트에 대한 메서드 호출은 무시돼요.

const myCarouselEl = document.querySelector('#myCarousel')
const carousel = bootstrap.Carousel.getInstance(myCarouselEl) // Retrieve a Carousel instance

myCarouselEl.addEventListener('slid.bs.carousel', event => {
  carousel.to('2') // Will slide to the slide 2 as soon as the transition to slide 1 is finished
})

carousel.to('1') // Will start sliding to the slide 1 and returns to the caller
carousel.to('2') // !! Will be ignored, as the transition to the slide 1 is not finished !!

dispose 메서드

hide() 직후에 dispose 메서드를 사용하는 게 맞아 보일 수 있지만, 그렇게 하면 잘못된 결과가 나와요. 문제가 있는 사용 예시는 다음과 같아요.

const myModal = document.querySelector('#myModal')
myModal.hide() // it is asynchronous

myModal.addEventListener('shown.bs.hidden', event => {
  myModal.dispose()
})

기본 설정 (Default settings)

플러그인의 Constructor.Default 객체를 수정해서 플러그인의 기본 설정을 바꿀 수 있어요.

// changes default for the modal plugin's `keyboard` option to false
bootstrap.Modal.Default.keyboard = false

메서드와 속성 (Methods and properties)

모든 Bootstrap 플러그인은 다음 메서드와 정적 속성을 노출해요.

메서드 설명
dispose 요소의 모달을 파괴해요. (DOM 요소에 저장된 데이터를 제거해요)
getInstance DOM 요소와 연결된 모달 인스턴스를 가져올 수 있는 정적(Static) 메서드예요.
getOrCreateInstance DOM 요소와 연결된 모달 인스턴스를 가져오거나, 초기화되지 않았다면 새로 만들 수 있는 정적(Static) 메서드예요.
정적 속성 설명
NAME 플러그인 이름을 반환해요. (예: bootstrap.Tooltip.NAME)
VERSION 플러그인의 생성자 VERSION 속성으로 각 Bootstrap 플러그인의 버전에 접근할 수 있어요. (예: bootstrap.Tooltip.VERSION)

샌티타이저 (Sanitizer)

우리 툴팁과 팝오버 컴포넌트는 구성에 따라 페이지에 임의의 HTML을 렌더링할 수 있어요. 크로스 사이트 스크립팅(XSS) 공격을 막기 위해, 이 컴포넌트들은 HTML을 받는 모든 옵션을 페이지에 렌더링하기 전에 내장 콘텐츠 샌티타이저로 소독해요. 콘텐츠 샌티타이제이션은 기본적으로 활성화되어 있어요.

기본적으로 허용되는 태그와 속성은 다음과 같아요. 명시적으로 허용되지 않은 태그나 속성은 샌티타이제이션 중에 제거돼요.

js/src/util/sanitizer.js

const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i

export const DefaultAllowlist = {
  // Global attributes allowed on any supplied element below.
  '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
  a: ['target', 'href', 'title', 'rel'],
  area: [],
  b: [],
  br: [],
  col: [],
  code: [],
  dd: [],
  div: [],
  dl: [],
  dt: [],
  em: [],
  hr: [],
  h1: [],
  h2: [],
  h3: [],
  h4: [],
  h5: [],
  h6: [],
  i: [],
  img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],
  li: [],
  ol: [],
  p: [],
  pre: [],
  s: [],
  small: [],
  span: [],
  sub: [],
  sup: [],
  strong: [],
  u: [],
  ul: []
}

이런 고급 옵션을 사용할 때는 주의하세요. 자세한 내용은 OWASP의 Cross Site Scripting Prevention Cheat Sheet를 참고하세요. 콘텐츠 샌티타이제이션을 비활성화하거나 수정해서만 발생하는 취약점은 Bootstrap의 보안 모델 범위에 포함되지 않아요.

기본 allowList에 새 값을 추가할 수 있어요.

const myDefaultAllowList = bootstrap.Tooltip.Default.allowList

// To allow table elements
myDefaultAllowList.table = []

// To allow td elements and data-bs-option attributes on td elements
myDefaultAllowList.td = ['data-bs-option']

// You can push your custom regex to validate your attributes.
// Be careful about your regular expressions being too lax
const myCustomRegex = /^data-my-app-[\w-]+/
myDefaultAllowList['*'].push(myCustomRegex)

또한 샌티타이저를 DOMPurify 같은 전용 라이브러리로 교체할 수도 있어요.

const yourTooltipEl = document.querySelector('#yourTooltip')
const tooltip = new bootstrap.Tooltip(yourTooltipEl, {
  sanitizeFn(content) {
    return DOMPurify.sanitize(content)
  }
})

jQuery 선택적 사용 (Optionally using jQuery)

Bootstrap 5에서 jQuery는 필요 없지만, jQuery로 우리 컴포넌트를 사용하는 것은 여전히 가능해요. Bootstrap이 window 객체에서 jQuery를 감지하면 모든 컴포넌트를 jQuery의 플러그인 시스템에 추가해요. 그래서 이렇게 할 수 있어요.

// to enable tooltips with the default configuration
$('[data-bs-toggle="tooltip"]').tooltip()

// to initialize tooltips with given configuration
$('[data-bs-toggle="tooltip"]').tooltip({
  boundary: 'clippingParents',
  customClass: 'myClass'
})

// to trigger the `show` method
$('#myTooltip').tooltip('show')

다른 컴포넌트도 마찬가지예요.

충돌 없음 (No conflict)

때로는 Bootstrap 플러그인을 다른 UI 프레임워크와 함께 사용해야 할 때가 있어요. 이런 상황에서는 네임스페이스 충돌이 가끔 발생할 수 있어요. 이런 경우, 값을 되돌리고 싶은 플러그인에 .noConflict를 호출할 수 있어요.

const bootstrapButton = $.fn.button.noConflict() // return $.fn.button to previously assigned value
$.fn.bootstrapBtn = bootstrapButton // give $().bootstrapBtn the Bootstrap functionality

Bootstrap은 Prototype이나 jQuery UI 같은 서드파티 JavaScript 라이브러리를 공식적으로 지원하지 않아요. .noConflict와 네임스페이스가 지정된 이벤트가 있음에도 불구하고, 직접 고쳐야 하는 호환성 문제가 있을 수 있어요.

jQuery 이벤트 (jQuery events)

Bootstrap은 jQuery가 window 객체에 있고 <body>에 data-bs-no-jquery 속성이 설정되지 않았다면 jQuery를 감지해요. jQuery가 발견되면 Bootstrap은 jQuery의 이벤트 시스템 덕분에 이벤트를 발생시켜요. 그래서 Bootstrap 이벤트를 수신하려면 addEventListener 대신 jQuery 메서드(.on, .one)를 사용해야 해요.

$('#myTab a').on('shown.bs.tab', () => {
  // do something...
})

JavaScript 비활성화 (Disabled JavaScript)

Bootstrap의 플러그인은 JavaScript가 비활성화되었을 때 특별한 폴백이 없어요. 이런 경우의 사용자 경험을 신경 쓴다면 <noscript>를 사용해서 상황(그리고 JavaScript를 다시 활성화하는 방법)을 사용자에게 설명하고, 자체 커스텀 폴백을 추가하세요.

더 알아보기 (Learn more)