IndexedDB 사용하기 - Web API

IndexedDB 사용하기
==============

IndexedDB는 사용자의 브라우저에 데이터를 영구적으로 저장할 수 있는 방법 중 하나입니다. IndexedDB를 사용하여 네트워크 상태에 상관없이 풍부한 쿼리 기능을 이용할 수 있는 웹 어플리케이션을 만들 수 있기 때문에, 여러분의 웹 어플리케이션은 온라인과 오프라인 환경에서 모두 동작할 수 있습니다.

이 문서에 대하여
---------

여러분은 이 튜토리얼에서 IndexedDB의 비동기 방식(asynchronous) API에 대해 훑어볼 수 있습니다. 만약 IndexedDB가 생소하다면, IndexedDB key characteristics and basic terminology (en-US) 를 먼저 읽어보는 것이 좋습니다.

IndexedDB API에 대한 참조(reference) 문서를 원한다면, IndexedDB API 항목과 하위 페이지를 보십시오. 이 문서에서는 IndexedDB에서 사용되는 객체의 종류와, 동기식(synchrounous), 비동기식(asynchronous) API에 대해서 기술하고 있습니다.

기본 패턴
-----

IndexedDB가 권장하는 기본 패턴은 다음과 같습니다:

1. 데이터베이스를 엽니다.
2. 객체 저장소(Object store)를 생성합니다.
3. 트랜젝션(Transaction)을 시작하고, 데이터를 추가하거나 읽어들이는 등의 데이터베이스 작업을 요청합니다.
4. DOM 이벤트 리스너를 사용하여 요청이 완료될때까지 기다립니다.
5. (요청 객체에서 찾을 수 있는) 결과를 가지고 무언가를 합니다.

그러면 이제, 이런 큰 개념을 익히면 더 구체적인 것을 할 수 있습니다.

저장소를 생성하고 구성하기
--------------

### Indexed DB 의 실험적인 버전을 사용해보기

접두어를 사용하는 브라우저에서 코드를 테스트하려는 경우 다음 코드를 사용할 수 있습니다.

```js
// In the following line, you should include the prefixes of implementations you want to test.
window.indexedDB =
  window.indexedDB ||
  window.mozIndexedDB ||
  window.webkitIndexedDB ||
  window.msIndexedDB;
// DON'T use "var indexedDB = ..." if you're not in a function.
// Moreover, you may need references to some window.IDB\* objects:
window.IDBTransaction =
  window.IDBTransaction ||
  window.webkitIDBTransaction ||
  window.msIDBTransaction;
window.IDBKeyRange =
  window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
// (Mozilla has never prefixed these objects, so we don't need window.mozIDB\*)
```

접두어를 사용하여 구현하는 것은 버그가 있거나 불완전하거나 이전 버전의 사양을 따르는 경우가 있습니다. 따라서 프로덕션 상태의 코드에선 사용하지 않는 것을 권장합니다. 제대로 지원하지 않는 브라우저를 지원하게 구현하여 실패하는 것보다 미지원 하는 것이 바람직할 수 있습니다.:

```js
if (!window.indexedDB) {
  window.alert(
    "Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.",
  );
}
```

### 데이터베이스 열기

우리는 밑의 프로그래밍 코드로 시작할 것입니다:

```js
// 내 데이터 베이스를 열도록 요청하자
var request = window.indexedDB.open("MyTestDatabase");
```

보셨나요? 데이터베이스 접속은 다른 operation 들과 비슷합니다 — 당신은 "요청(request)" 하면 됩니다.

open 요청은 데이터베이스를 즉시 열거나 즉시 트랜잭션을 시작하지 않습니다. `open()` 함수를 호출하면 이벤트로 처리한 결과(성공 상태)나 오류 값이 있는 `IDBOpenDBRequest` (en-US) 객체를 반환합니다. IndexedDB의 다른 비동기 함수 대부분은 결과 또는 오류가 있는 `IDBRequest` (en-US) 객체를 반환합니다. `open()` 함수의 결과는 `IDBDatabase` (en-US) 의 인스턴스입니다.

open 메소드의 두번째 매개 변수는 데이터베이스의 버전입니다. 데이터베이스의 버전은 데이터베이스 스키마를 결정합니다. 데이터베이스 스키마는 데이터베이스 안의 객체 저장소와 그것들의 구조를 결정합니다. 데이터베이스가 아직 존재하지 않으면, open operation에 의해 생성되고, 그 다음 `onupgradeneeded` 이벤트가 트리거되고 이 이벤트 안에서 데이터베이스 스키마를 작성합니다. 데이터베이스가 존재하지만 업그레이드 된 버전 번호를 지정하는 경우 `onupgradeneeded` 이벤트가 트리거되고 해당 핸들러에 업데이트된 스키마를 제공할 수 있습니다. 자세한 내용은 나중에 아래의 데이터베이스의 버전 업데이트와 `IDBFactory.open` (en-US) 페이지를 참조하십시오.

**경고:** **중요**: 버전 번호는 `unsigned long long` 숫자입니다. 이는 버전 번호는 매우 큰 정수가 되어야한다는 의미입니다. 또한 부동 소수점을 사용할 수 없다는 것을 의미합니다. 그렇지 않으면 가장 가까운 정수로 변환되어 트랜잭션이 시작되지 않거나 `upgradeneeded` 이벤트가 트리거 되지 않습니다. 예를 들어, 2.4와 같은 버전 번호를 사용하지 마십시오:
 `var request = indexedDB.open("MyTestDatabase", 2.4); // don't do this, as the version will be rounded to 2`

#### 제어 객체 생성

첫번째로 당신이 하려는 모든 요청에 대해 성공했을 때 그리고 에러가 발생했을 때 제어를 할 객체를 요청해야 됩니다:

```js
request.onerror = function (event) {
  // request.errorCode 에 대해 무언가를 한다!
};
request.onsuccess = function (event) {
  // request.result 에 대해 무언가를 한다!
};
```

`onsuccess()` 또는 `onerror()` 두 함수 중 어떤 함수가 호출될까요? 모든 것이 성공하면, success 이벤트 (즉, `type` 속성이`"success"` 로 설정된 DOM 이벤트)가 `request`를 `target`으로 발생합니다. 일단 실행되면, `request` 의 `onsuccess()` 함수는 success 이벤트를 인수로 트리거됩니다. 반면, 문제가 있는 경우, 오류 이벤트 (즉 `type` 속성이`"error"` 로 설정된 DOM 이벤트)는 `request`에서 발생합니다. 이 오류 이벤트를 인수로 `onerror()` 함수가 트리거됩니다.

The IndexedDB API는 오류 처리의 필요성을 최소화하도록 설계되었기 때문에 많은 오류 이벤트를 볼 수는 없을 것입니다. (적어도 API에 익숙하지 않은 경우). 그러나 데이터베이스를 여는 경우 오류 이벤트를 발생하는 몇 가지 일반적인 조건이 있습니다. 가장 많은 문제는 사용자가 웹 응용 프로그램에 데이터베이스를 만들 수 있는 권한을 주지 않기로 결정한 것입니다. IndexedDB의 주요 설계 목표 중 하나는 많은 양의 데이터를 오프라인에서 사용할 수 있도록 하는 것입니다. (각 브라우저에서 저장할 수 있는 저장 용량에 대한 자세한 내용은 Storage limits (en-US) 를 참조하십시오.)

일부 광고 네트워크나 악의적인 웹 사이트가 컴퓨터를 오염시키는 것을 브라우저는 원하지 않기 때문에 브라우저는 특정 웹 응용 프로그램이 처음으로 저장용 IndexedDB를 열려고 할 때 사용자에게 메시지를 보냅니다. 사용자가 액세스를 허용하거나 거부할 수 있습니다. 또한, 개인정보 보호 모드의 브라우저에서 IndexedDB 공간은 시크릿 세션이 닫힐 때까지 메모리 내에서만 지속됩니다. (Firefox의 개인정보 보호 브라우징 모드와 Chrome 의 시크릿 모드가 있지만, Firefox 의 경우 2015년 11월 현재 아직 미구현(Firefox bug 781982 참조)이므로, 개인정보 보호 브라우징 모드의 Firefox에서는 IndexedDB를 사용할 수 없습니다).

이제, 사용자가 데이터베이스 생성 요청을 허용하여 success 콜백을 트리거하는 success 이벤트를 받았다고 가정합니다; 그 다음은 무엇을 해야할까요? 이 요청은 `indexedDB.open()`을 호출하여 생성되었고, `request.result` 는 `IDBDatabase` 의 인스턴스이므로, 이후에 이것을 사용하기 위해 저장하기 원할 것은 확실합니다. 코드는 다음과 같이 할 수 있습니다:

```js
var db;
var request = indexedDB.open("MyTestDatabase");
request.onerror = function (event) {
  alert("Why didn't you allow my web app to use IndexedDB?!");
};
request.onsuccess = function (event) {
  db = request.result;
};
```

#### 에러 처리

위에서 언급한 바와 같이, 오류 이벤트는 버블링됩니다. 오류 이벤트는 오류를 생성한 request를 대상으로하며, 이벤트는 트랜잭션으로 버블링되고, 마지막으로 데이터베이스 객체로 버블링됩니다. 모든 요청에 에러 처리를 피하고 싶은 경우, 아래와 같이 하나의 오류 핸들러를 데이터베이스 객체에 추가하여 대신할 수 있습니다:

```js
db.onerror = function (event) {
  // Generic error handler for all errors targeted at this database's
  // requests!
  alert("Database error: " + event.target.errorCode);
};
```

데이터베이스를 열 때 자주 발생하는 오류 중 하나가 `VER_ERR`가 있습니다. 이는 디스크에 저장된 데이터베이스의 버전이 현재 코드에서 지정된 버전 번호보다 큼을 나타냅니다. 이 오류는 항상 오류 처리기에서 처리해야합니다.

### 데이터베이스의 버전 생성 또는 업데이트

새로운 데이터베이스를 만들거나 기존 데이터베이스의 버전 번호를 높일 때(데이터베이스 열기시, 이전 버전보다 높은 버전 번호를 지정하면), `onupgradeneeded` 가 트리거되고 `request.result`(즉, 아래의 예제 : `db`)에 설정된 `onversionchange` 이벤트 핸들러에 IDBVersionChangeEvent (en-US) 객체가 전달됩니다. `upgradeneeded` 이벤트 처리기에서 이 버전의 데이터베이스에 필요한 객체 저장소를 만들어야합니다:

```js
// This event is only implemented in recent browsers
request.onupgradeneeded = function (event) {
  // Save the IDBDatabase interface
  var db = event.target.result;

  // Create an objectStore for this database
  var objectStore = db.createObjectStore("name", { keyPath: "myKey" });
};
```

이 경우 데이터베이스에는 이전 버전의 데이터베이스에 있는 객체 저장소가 이미 있으므로, 이전 버전의 객체 저장소를 다시 만들 필요가 없습니다. 여러분은 새로운 객체 저장소를 만들거나 더 이상 필요하지 않은 이전 버전의 객체 저장소만 삭제하면 됩니다. 기존 객체 저장소를 변경(예, `keyPath`를 변경) 해아 하는 경우, 이전 객체 저장소를 삭제하고 새 옵션으로 다시 만들어야합니다. (이것은 객체 저장소의 정보를 삭제하니 주의하십시오! 해당 정보를 보존해야하는 경우 데이터베이스를 업그레이드하기 전에 해당 정보를 읽고 다른 곳에 저장해야 합니다.)

이미 존재하는 이름으로 객체 저장소를 만들려고 하면 (또는 존재하지 않는 객체 저장소를 삭제하려고 하면) 오류가 발생합니다.

`onupgradeneeded` 이벤트가 성공적으로 끝나면, 데이터베이스 열기 요청의 `onsuccess` 핸들러가 트리거 됩니다.

### 데이터베이스 구성

이제 데이터베이스를 구축합니다. IndexedDB는 테이블이 아닌 객체 저장소를 사용하며 하나의 데이터베이스는 여러 개의 객체 저장소를 포함할 수 있습니다. 값을 객체 저장소에 저장할 때마다 값은 키와 연관됩니다. 객체 저장소가 키 경로 (en-US) 또는 키 생성기 (en-US) 옵션의 사용 여부에 따라 키를 제공할 수 있는 여러 가지 방법이 있습니다.

다음 표는 키가 제공되는 다양한 방법을 보여줍니다:

| 키 경로 (`keyPath`) | 키 생성기 (`autoIncrement`) | Description |
| --- | --- | --- |
| No | No | 이 객체 저장소는 숫자 및 문자열과 같은 원시 값을 포함한 모든 종류의 값을 보유 할 수 있습니다. 새 값을 추가 할 때 마다 별도의 키 인수를 공급해야합니다. |
| Yes | No | 이 객체 저장소는 JavaScript 객체만 포함 할 수 있습니다. 객체에는 키 경로와 같은 이름의 속성이 있어야합니다. |
| No | Yes | 이 객체 저장소는 모든 종류의 값을 보유할 수 있습니다. 키가 자동으로 생성됩니다. 또한 특정 키를 사용하려는 경우 별도의 키 인수를 공급할 수 있습니다. |
| Yes | Yes | 이 객체 저장소는 JavaScript 객체만 포함 할 수 있습니다. 일반적으로 키가 자동으로 생성되고 생성된 키의 값은 키 경로와 동일한 이름을 가진 속성의 객체에 저장됩니다. 그러나 그러한 속성이 이미 존재하는 경우, 새로운 키를 생성하는 것이 아닌 속성의 값을 키로 사용됩니다. |

객체 저장소가 기본이 아닌 객체를 보유하고 있으면 객체 저장소에서 인덱스를 만들 수 있습니다. 인덱스를 사용하면 객체의 키가 아닌 저장된 객체의 속성 값을 사용하여 객체 저장소에 저장된 값을 검색할 수 있습니다.

또한, 인덱스에는 저장된 데이터에 대한 간단한 제약 조건을 적용 할 수 있는 기능이 있습니다. 인덱스를 작성할 때 고유(unique) 플래그를 설정하면, 인덱스는 인덱스의 키 경로에 대해 동일한 값을 갖는 두 개의 객체가 저장되지 않도록 보장합니다. 따라서 예를 들자면, 사람 집단을 보유하고 있는 객체 저장소가 있고, 두 사람이 동일한 email 주소를 갖지 못 한다는 것을 보장하려는 경우, 이를 강제하기 위해 고유(unique) 플래그 설정한 인덱스를 사용하면 됩니다.

이 부분은 혼란스러울 수도 있으므로 간단한 예제를 들어 설명하겠습니다. 먼저, 다음의 예에서 사용할 고객 데이터를 정의하겠습니다:

```js
// This is what our customer data looks like.
const customerData = [
  { ssn: "444-44-4444", name: "Bill", age: 35, email: "bill@company.com" },
  { ssn: "555-55-5555", name: "Donna", age: 32, email: "donna@home.org" },
];
```

물론, 모든 사람이 사회 보장 번호(ssn)를 가지지 않기 때문에 ssn을 기본 키(primary key)로 지정하지 않을겁니다. 그리고 나이를 저장하는 대신 주로 생년월일을 저장하겠지만, 이러한 점은 편의를 위해 무시하고 지나가겠습니다.

이제 자료를 저장하기 위해 indexedDB를 생성하는 과정을 보겠습니다:

```js
const dbName = "the\_name";

var request = indexedDB.open(dbName, 2);

request.onerror = function (event) {
  // Handle errors.
};
request.onupgradeneeded = function (event) {
  var db = event.target.result;

  // Create an objectStore to hold information about our customers. We're
  // going to use "ssn" as our key path because it's guaranteed to be
  // unique - or at least that's what I was told during the kickoff meeting.
  var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });

  // Create an index to search customers by name. We may have duplicates
  // so we can't use a unique index.
  objectStore.createIndex("name", "name", { unique: false });

  // Create an index to search customers by email. We want to ensure that
  // no two customers have the same email, so use a unique index.
  objectStore.createIndex("email", "email", { unique: true });

  // Use transaction oncomplete to make sure the objectStore creation is
  // finished before adding data into it.
  objectStore.transaction.oncomplete = function (event) {
    // Store values in the newly created objectStore.
    var customerObjectStore = db
      .transaction("customers", "readwrite")
      .objectStore("customers");
    customerData.forEach(function (customer) {
      customerObjectStore.add(customer);
    });
  };
};
```

이전에 적었듯이, `onupgradeneeded` 는 데이터베이스의 구조를 변경할 수 있는 곳에 위치해야합니다. 이 이벤트 안에서 객체 저장소를 만들거나 삭제하고, 인덱스를 만들거나 지울 수 있습니다.

 객체 저장소는 `createObjectStore()`를 한번 호출함으로써 생성됩니다. 이 메소드는 저장소의 이름과 파라미터 객체를 인자로 받습니다. 파라미터 객체는 선택적으로 사용할 수 있지만, 이는 중요한 설정들을 정의하고 만들고자하는 객체 저장소의 타입을 정의하기 때문에 매우 중요합니다. 이번 예시에서, 우리는 객체 저장소의 이름을 "customers"로 짓고 개별 객체들이 유일하게 저장되도록 만들어주는 특성인 `keyPath`를 정의합니다. 그리고 사회 보장 번호(ssn)는 고유함이 보장되기 때문에, `keyPath`로 예제의 ssn 프로퍼티를 사용하며 ssn은 `objectStore` 에 저장되는 모든 객체에 반드시 포함되어야 합니다.
 우리는 또한 저장된 객체의 `name` 프로퍼티를 찾기 위한 인덱스 "name"을 요청합니다.
 또한 `createObjectStore()`, `createIndex()` 도 생성하려는 인덱스의 종류를 결정하는 선택적인 객체인 `options` 을 인자로 받습니다. `name` 프로퍼티가 없는 객체를 추가할 수는 있지만, 이 경우 그 객체는 "name" 인덱스에 나타나지 않습니다.

이제 우리는 저장된 customer 객체를 가져오기 위해 `ssn` 을 이용하여 객체 저장소로부터 바로 가져오거나, 인덱스에서 그들의 이름을 이용해 가져올 수 있습니다. 이 과정이 어떻게 이루어지는지 배우기 위해, using an index 섹션을 확인할 수 있습니다.

### 키 생성기 사용하기

객체 저장소를 생성할 때 `autoIncrement` 플래그를 설정함으로써 키 생성기를 활성화할 수 있습니다. 기본값으로 이 플래그는 설정되지 않습니다.

키 생성기가 활성화되면, 객체 저장소에 값을 추가할 때 키가 자동으로 추가됩니다. 처음 생성되면 키 생성기의 값은 항상 1로 설정되고, 새로 생성되는 키는 기본적으로 이전 키에서 1을 더한 값이 됩니다. 키 생성기의 값은 트랜잭션이 취소되는 등 데이터베이스 작업이 복구되는게 아닌 한 절대 작아지지 않습니다. 그래서 레코드를 지우거나 객체 저장소의 모든 레코드를 지우더라도 해당 객체 저장소의 키 생성기에는 영향을 끼치지 않습니다.

우리는 아래와 같이 키 생성기와 함께 객체 저장소를 생성할 수 있습니다:

```js
// Open the indexedDB.
var request = indexedDB.open(dbName, 3);

request.onupgradeneeded = function (event) {
  var db = event.target.result;

  // Create another object store called "names" with the autoIncrement flag set as true.
  var objStore = db.createObjectStore("names", { autoIncrement: true });

  // Because the "names" object store has the key generator, the key for the name value is generated automatically.
  // The added records would be like:
  // key : 1 => value : "Bill"
  // key : 2 => value : "Donna"
  customerData.forEach(function (customer) {
    objStore.add(customer.name);
  });
};
```

키 생성기와 관련된 보다 많은 정보는 "W3C Key Generators" 를 참고해 주십시오.

데이터 추가, 검색 및 제거
---------------

새 데이터베이스에서 작업을 하기전에, 트랜잭션을 시작할 필요가 있습니다. 트랜잭션은 데이터베이스 객체 단위로 작동하므로 트랜잭션을 사용할 객체 저장소를 지정해줘야합니다. 트랜잭션에 들어오고 나면, 자료가 있는 객체 저장소에 접근할 수 있고 요청을 만들 수 있습니다. 다음으로, 데이터베이스에 변경점을 만들지, 혹은 읽기만 할지 결정해야합니다. 트랜잭션은 다음의 3가지 모드가 있습니다: `readonly`, `readwrite`, 그리고 `versionchange`.

객체 저장소나 인덱스를 만들거나 삭제하는 작업을 포함하여 데이터베이스의 "schema"나 구조를 변경하기 위해서 트랜잭션은 반드시 `versionchange` 여야 합니다. 이 트랜잭션은 `IDBFactory.open` (en-US) 메소드를 `version` 과 함께 호출할 경우 시작됩니다.

이미 존재하는 객체 저장소의 레코드를 읽기 위해서 트랜잭션은 `readonly` 혹은 `readwrite` 모드이면 됩니다. 이미 존재하는 객체 저장소에 변경점을 기록하기 위해서는 트랜잭션이 반드시 `readwrite` 모드여야합니다. 특정 트랜잭션은 `IDBDatabase.transaction` (en-US) 으로 열 수 있습니다. 이 메소드는 접근하고 싶은 객체 저장소들의 배열로 정의된 범위인 `storeNames`와 트랜잭션의 모드`mode` (`readonly` 혹은 `readwrite`), 2개의 인자를 받습니다. 이 메소드는 객체 저장소에 접근할 수 있는 `IDBIndex.objectStore` (en-US) 메소드를 포함한 트랜잭션 객체를 반환합니다. 모드가 지정되지 않는다면 기본적으로 트랜잭션은 `readonly` 모드로 열립니다.

**참고:** As of Firefox 40, IndexedDB transactions have relaxed durability guarantees to increase performance (see Firefox bug 1112702.) Previously in a `readwrite` transaction `IDBTransaction.oncomplete` (en-US) was fired only when all data was guaranteed to have been flushed to disk. In Firefox 40+ the `complete` event is fired after the OS has been told to write the data but potentially before that data has actually been flushed to disk. The `complete`event may thus be delivered quicker than before, however, there exists a small chance that the entire transaction will be lost if the OS crashes or there is a loss of system power before the data is flushed to disk. Since such catastrophic events are rare most consumers should not need to concern themselves further. If you must ensure durability for some reason (e.g. you're storing critical data that cannot be recomputed later) you can force a transaction to flush to disk before delivering the `complete` event by creating a transaction using the experimental (non-standard) `readwriteflush` mode (see `IDBDatabase.transaction` (en-US).

트랜잭션에서 적합한 범위와 모드를 사용함으로써 자료 접근을 빠르게 할 수 있습니다. 여기 두개의 팁이 있습니다:

* 범위를 지정할 때, 필요한 객체 저장소만 정하십시오. 이 방식은 겹치지 않는 범위의 트랜잭션들을 동시에 처리할 수 있게 해줍니다.
* `readwrite` 모드는 필요할때만 사용하십시오. 겹친 범위에 대해 `readonly` 트랜잭션은 동시에 실행될 수 있지만, `readwrite` 트랜잭션은 객체 저장소에 오직 한개만 작동할 수 있습니다. 더 자세한 내용은 Basic Concepts (en-US) 글에 있는 *transactions (en-US)* 정의를 참고하십시오.
### 데이터베이스에 데이터 추가

데이터베이스를 만들었다면, 데이터를 추가하고 싶을겁니다. 데이터 추가는 이렇게 할 수 있습니다:

```js
var transaction = db.transaction(["customers"], "readwrite");
// Note: Older experimental implementations use the deprecated constant IDBTransaction.READ\_WRITE instead of "readwrite".
// In case you want to support such an implementation, you can just write:
// var transaction = db.transaction(["customers"], IDBTransaction.READ\_WRITE);
```

`transaction()` 함수는 2개(1개는 옵션)의 인자를 받고 트랜잭션 객체를 반환합니다. 첫번째 인자는 트랜잭션이 확장될 객체 저장소의 목록입니다. 모든 객체 저장소에 대해 트랜잭션을 확장하고 싶다면 빈 배열을 넘겨줄 수 있습니다. 두번째 인자로 아무것도 넘기지 않는다면, 읽기 전용 트랜잭션이 반환됩니다. 쓰기 작업을 하고 싶다면, `readwrite` 플래그를 넘겨줘야 합니다.

계속해서 트랜잭션을 사용하기 위해서는 트랜잭션의 생애 주기에 대해 이해할 필요가 있습니다. 트랜잭션은 이벤트 루프(Event loop)와 매우 밀접하게 연관되어 있습니다. 만일 당신이 트랜잭션을 만들어 놓고 사용하지 않은 채 이벤트 루프로 돌아가게 된다면 트랜잭션은 비활성화 상태가 됩니다. 트랜잭션을 활성화 상태로 유지하는 유일한 방법은 트랜잭션에 그것을 요청하는 것 뿐입니다. 요청이 완료되면 DOM 이벤트가 발생하며, 트랜잭션에 대한 요청이 성공했다는 가정 하에 해당 요청에 대한 콜백(Callback)이 일어나기 전까지 트랜잭션의 수명을 연장할 수 있습니다. 만일 당신이 트랜잭션에 대한 연장 요청 없이 이벤트 루프로 돌아가려 한다면 트랜잭션은 곧 비활성화가 된 후, 계속해서 비활성 상태를 유지할 것입니다. 트랜잭션에 대한 요청이 계속되는 한 트랜잭션은 활성화 상태를 유지합니다. 트랜잭션의 생애 주기는 매우 단순하지만 당신이 이에 적응하는 데에는 다소 시간이 걸릴 수 있습니다. 다른 예제 몇 개를 더 확인하는 것을 권장합니다. 만일 당신의 프로그램에서 `TRANSACTION_INACTIVE_ERR` 라는 에러 코드가 나타나기 시작한다면, 뭔가 잘못되기 시작한 것입니다.

트랜잭션은 다음 세 개의 DOM 이벤트를 받을 수 있습니다: `error`, `abort`, 그리고 `complete`. 우리는 `error` 이벤트가 어떻게 버블링되는지에 대해 이미 이야기했으며, 이에 따라서 트랜잭션은 요청으로부터 발생하는 모든 에러 이벤트를 받아들입니다. 여기서 더욱 애매한 점은 에러를 처리하는 가장 기본적인 방식이 에러가 발생한 트랜잭션을 취소하는 것이라는 점입니다. 당신이 우선 `stopPropagation()`을 이용해 에러를 처리하고 난 후에 다른 행동을 하려고 해도, 전체 트랜잭션은 이미 롤백된 상황일 것입니다. 이러한 디자인은 당신이 에러 처리에 대해서 생각하도록 강제하지만, 만일 잘 정제된 에러 핸들링을 모든 트랜잭션에 만드는 것이 너무 번거롭다면 당신은 데이터베이스에 항상 캐치올 에러 핸들러(catchall error handler)를 추가하도록 설정할 수 있습니다. 당신이 만약 에러를 제어하지 않았거나 트랜잭션에서 `abort()`를 호출할 경우, 트랜잭션은 롤백되고 `abort` 이벤트는 취소될 것입니다. 반면, 트랜잭션에 대한 모든 요청이 정상적으로 처리되었을 경우엔 `complete` 이벤트를 반환합니다. 만일 당신이 매우 많은 데이터베이스 작업을 수행하고 있다면, 데이터베이스에 대한 개별 요청을 추적하는것보단 트랜잭션을 추적하는 것이 정신 건강에 이로울 것입니다.

이제 당신이 트랜잭션을 만들었고, 당신은 그 트랜잭션을 통해 오브젝트 스토어에 접근해야 하는 상황이라고 가정해 봅시다. 트랜잭션은 당신이 트랜잭션을 만들 때 지정한 오브젝트 스토어만 사용할 수 있게 해줍니다. 그러고 나면 당신은 원하는 모든 데이터를 그곳에 추가할 수 있습니다.

```js
// Do something when all the data is added to the database.
transaction.oncomplete = function (event) {
  alert("All done!");
};

transaction.onerror = function (event) {
  // Don't forget to handle errors!
};

var objectStore = transaction.objectStore("customers");
for (var i in customerData) {
  var request = objectStore.add(customerData[i]);
  request.onsuccess = function (event) {
    // event.target.result == customerData[i].ssn
  };
}
```

호출에서 `add()`까지의 과정에서 생성된 트랜잭션의 `result`는 추가된 데이터의 키와 같습니다. 따라서 이 경우, 오브젝트 스토어가 `ssn` 속성을 키 패스로 사용하기 때문에 추가된 데이터의 `ssn` 속성과 같은 값을 가질 것입니다. `add()` 함수를 사용할 때 같은 키를 가진 데이터가 데이터베이스 안에 존재하지 않아야 한다는 점에 주의하십시오. 만일 당신이 이미 존재하는 데이터를 수정하고 싶거나, 혹은 이미 데이터가 있건 말건 상관 없다면 문서 아래쪽의 Updating an entry in the database 부분에서 소개하는 `put()` 함수를 사용하십시오.

### 데이터베이스로부터 데이터를 지우기

데이터 삭제는 아주 익숙한 그 방식대로 하시면 됩니다:

```js
var request = db
  .transaction(["customers"], "readwrite")
  .objectStore("customers")
  .delete("444-44-4444");
request.onsuccess = function (event) {
  // It's gone!
};
```

### 데이터베이스로부터 데이터 가져오기

이제 데이터베이스 안에 정보도 들어 있겠다, 몇 가지 방법을 통해 정보를 가져와 봅시다. 가장 먼저, 가장 단순한 `get()`을 사용해 봅시다. 데이터를 가져오기 위해 당신은 키를 제공해야합니다. 이렇게:

```js
var transaction = db.transaction(["customers"]);
var objectStore = transaction.objectStore("customers");
var request = objectStore.get("444-44-4444");
request.onerror = function (event) {
  // Handle errors!
};
request.onsuccess = function (event) {
  // Do something with the request.result!
  alert("Name for SSN 444-44-4444 is " + request.result.name);
};
```

"단순히" 가져오는 것 치고는 코드가 좀 많군요. 당신이 데이터베이스 수준에서 에러를 제어하고 있다면, 아래와 같이 간략화할 수도 있습니다:

```js
db
  .transaction("customers")
  .objectStore("customers")
  .get("444-44-4444").onsuccess = function (event) {
  alert("Name for SSN 444-44-4444 is " + event.target.result.name);
};
```

어떻게 작동하는지 보이시나요? 오브젝트 스토어가 하나 뿐이기 때문에, 당신의 트랜잭션에서 필요한 오브젝트 스토어들의 리스트를 보낼 필요 없이 그냥 이름만 String으로 보내면 됩니다. 또한, 당신은 데이터베이스에서 읽기만 했기 때문에, `"readwrite"` 트랜잭션은 필요하지 않습니다. `transaction()`을 특정한 모드 설정 없이 그냥 부를 경우엔 기본적으로 `"readonly"` 트랜잭션이 됩니다. 또 하나 신비한 점은 당신은 요청한 오브젝트를 따로 특정한 변수에 저장하지 않았다는 점입니다. DOM 이벤트는 요청을 대상으로 할 때 이벤트를 사용하여 `result`의 값을 불러올 수 있습니다.

범위 제한과 모드 설정에 따라 데이터 접근 속도를 빠르게 할 수 있다는 점을 주목하십시오. 여기 몇개의 팁이 있습니다:

* 범위(scope)를 정의할 때, 당신이 필요로 하는 오브젝트 스토어만 지정하십시오. 이렇게 하면, 범위가 중복되지 않는 한 여러 개의 트랜잭션을 동시에 실행할 수 있습니다.
* `readwrite` 모드는 반드시 필요할 때만 사용하십시오. `readonly` 모드는 범위가 중복되더라도 동시에 실행 가능하지만, `readwrite` 모드는 한 오브젝트 스토어에 대해 동시에 하나밖에 실행할 수 없습니다. 더욱 자세한 정보는, 기본 개념서의 트랜잭션의 정의 항목(transactions in the Basic Concepts article (en-US))을 참조하십시오.
### 데이터베이스의 내용을 업데이트하기

이제 우리는 몇 개의 데이터를 꺼냈습니다. 이 데이터를 업데이트하고 다시 IndexedDB에 집어넣는 것은 매우 간단합니다. 이전 예제를 약간 업데이트해 봅시다:

```js
var objectStore = db
  .transaction(["customers"], "readwrite")
  .objectStore("customers");
var request = objectStore.get("444-44-4444");
request.onerror = function (event) {
  // Handle errors!
};
request.onsuccess = function (event) {
  // Get the old value that we want to update
  var data = event.target.result;

  // update the value(s) in the object that you want to change
  data.age = 42;

  // Put this updated object back into the database.
  var requestUpdate = objectStore.put(data);
  requestUpdate.onerror = function (event) {
    // Do something with the error
  };
  requestUpdate.onsuccess = function (event) {
    // Success - the data is updated!
  };
};
```

이제 우리는 `objectStore`를 만들고 사회보장번호(SSN)가 (`444-44-4444`)인 고객 레코드를 요청했습니다. 그리고 우리는 그 결과를 변수(`data`)에 넣고, 이 객체의 `age` 속성을 업데이트하여, 두 번째 요청(`requestUpdate`)을 만들어 고객 레코드를 다시 `objectStore`에 집어넣어 이전 값을 덮어썼습니다.

**참고:** 이 때 우리는 `readwrite` 모드를 사용해야 합니다. 우리가 지금 한 것은 단순히 데이터를 읽어오는 게 아니라, 다시 쓰는 것이기 때문입니다.

### 커서 사용하기

`get()`을 사용하려면 검색하려는 키를 알고 있어야 합니다. 객체 저장소(object store)의 모든 값을 탐색하려면 커서를 사용할 수 있습니다. 아래는 커서를 사용하는 방법입니다:

```js
var objectStore = db.transaction("customers").objectStore("customers");

objectStore.openCursor().onsuccess = function (event) {
  var cursor = event.target.result;
  if (cursor) {
    alert("Name for SSN " + cursor.key + " is " + cursor.value.name);
    cursor.continue();
  } else {
    alert("No more entries!");
  }
};
```

`openCursor()` 함수는 몇 가지 인자를 받습니다. 첫 번째로, 아래에서 곧 다룰 키 범위 객체(key range object)를 사용하여 검색할 항목의 범위를 제한할 수 있습니다. 두 번째로, 반복할 방향을 지정할 수 있습니다. 위 예제에서는 오름차순으로 모든 객체를 반복합니다. 커서들에 대한 성공 콜백함수는 약간 특이합니다. 커서 자체가 이벤트의 `결과` 입니다 (위 예에서는 축약 표현을 사용하므로 `event.target.result`입니다). 그런 다음 실제 키와 값을 커서 객체의 `key`와 `value` 속성에서 찾을 수 있습니다. 계속 진행하려면 커서에서 continue()를 호출해야 합니다. 데이터의 끝에 도달했거나 `openCursor()` 요청과 일치하는 항목이 없는 경우에도 여전히 성공 콜백이 호출되지만 `result` 속성은 `undefined`입니다.

커서를 사용하는 일반적인 패턴 중 하나는 객체 저장소의 모든 객체를 검색하여 array에 추가하는 것입니다. 다음과 같이 작성할 수 있습니다:

```js
var customers = [];

objectStore.openCursor().onsuccess = function (event) {
  var cursor = event.target.result;
  if (cursor) {
    customers.push(cursor.value);
    cursor.continue();
  } else {
    alert("Got all customers: " + customers);
  }
};
```

**참고:** **Note**: Mozilla는 이런 경우를 처리하기 위해 getAll()을 구현했습니다(그리고 함께 구현한 `getAllKeys()`는 현재 about:config의 `dom.indexedDB.experimental` 설정 뒤에 숨겨져 있습니다.). 이들은 IndexedDB 표준은 아니기 때문에 추후 사라질 수 있습니다. 하지만 우리는 이것들이 유용하다고 생각하기 때문에 포함시켰습니다. 다음 코드는 위 코드와 정확히 같이 동작합니다.:`js objectStore.getAll().onsuccess = function(event) { alert("Got all customers: " + event.target.result); };` 커서의 `value` 속성을 살펴보는 것은 성능상의 비용이 발생합니다. 왜냐하면 해당 객체는 지연 생성되기 때문입니다. 예를 들어 `getAll()` 을 사용할 때, Gecko는 모든 객체를 한 번에 생성합니다. 만약 키 각각을 보는 것에만 관심이 있다면, 커서를 사용하는 것이 `getAll()`을 사용하는 것 보다 훨씬 효율적입니다. 반면에 객체 저장소의 모든 객체 배열을 가져오려는 경우에는 `getAll()`을 사용하세요.

### index 사용하기

고객 데이터를 저장할 때 사회보장번호(SSN)를 키로 사용하는 것은 논리적입니다. 왜냐하면 SSN은 각 개인을 고유 식별하기 때문입니다. (이것이 사생활 보호에 좋은 생각인지는 이 아티클의 범위를 벗어난 다른 문제입니다.) 그러나 이름으로 고객을 찾아야 하는 경우 데이터베이스의 모든 SSN을 반복하여 올바른 SSN을 찾아야 합니다. 이러한 방식으로 검색하면 매우 느리기 때문에 인덱스를 대신 사용할 수 있습니다.

```js
// 먼저, request.onupgradeneeeded로 index를 생성해주세요:
// objectStore.createIndex("name", "name");
// 그렇지 않을 경우 DOMException이 발생합니다.

var index = objectStore.index("name");

index.get("Donna").onsuccess = function (event) {
  alert("Donna's SSN is " + event.target.result.ssn);
};
```

"name"은 고유하지 않기 때문에 `name`값이 `"Donna"`로 설정된 항목이 하나 이상 있을 수 있습니다. 이 경우 항상 가장 낮은 키 값인 결과 하나만 얻게 됩니다.

특정 `name`값을 가진 모든 항목에 액세스해야 하는 경우 cursor를 사용할 수 있습니다. 인덱스들 마다 두 가지 다른 종류의 cursor를 열 수 있습니다. 일반적인 커서는 인덱스 속성을 객체 저장소의 객체에 매핑합니다. 그리고 키 커서는 객체를 객체 저장소에 저장하기 위해 사용된 키에 인덱스를 매핑합니다. 이러한 차이점은 다음과 같습니다.:

```js
// 일반적인 커서를 사용해서 고객 레코드 전체를 가져오기
index.openCursor().onsuccess = function (event) {
  var cursor = event.target.result;
  if (cursor) {
    // cursor.key는 "Bill"과 같은 이름이며, cursor.value는 객체 전체를 의미합니다.
    alert(
      "Name: " +
        cursor.key +
        ", SSN: " +
        cursor.value.ssn +
        ", email: " +
        cursor.value.email,
    );
    cursor.continue();
  }
};

// 키 커서를 사용해서 고객 레코드 객체 키를 가져오기
index.openKeyCursor().onsuccess = function (event) {
  var cursor = event.target.result;
  if (cursor) {
    // cursor.key는 "Bill"과 같은 이름이며, cursor.value는 사회보장번호(SSN)입니다.
    // 저장된 객체의 나머지 부분을 직접적으로 가져올 방법은 없습니다.
    alert("Name: " + cursor.key + ", SSN: " + cursor.primaryKey);
    cursor.continue();
  }
};
```

### 커서들의 범위와 방향을 특정하기

`openCursor()` 또는 `openKeyCursor()`의 첫 번째 인자로 사용할 수 있는 키 범위 객체(key range object)를 사용하여 커서에서 볼 값의 범위를 제한할 수 있습니다. 단일 키만 허용하도록 하는 key range를 만들거나 하한 또는 상한값이 있는 key range를 만들 수 있습니다. 또는 하한 및 상한 값이 모두있는 key range를 만들 수 있습니다. 범위는 "closed"(즉, key range가 주어진 값까지 포함)거나 "open"(즉, key range가 주어진 값은 포함하지 않음)일 수 있습니다. 다음은 key range가 작동하는 방식입니다:

```js
// "Donna"만을 조회
var singleKeyRange = IDBKeyRange.only("Donna");

// "Bill"을 포함한, "Bill" 이후 모든 값을 조회
var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill");

// "Bill"을 제외한, "Bill" 다음 모든 값을 조회
var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true);

// "Donna"를 제외한, 이전 모든 값을 조회
var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true);

// "Donna"를 제외한, "Bill"과 "Donna" 사이 모든 값을 조회
var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true);

// 위 키 범위 중 하나를 사용하려면, openCursor()/openKeyCursor()에 첫 번째 인자로 넘겨주세요.
index.openCursor(boundKeyRange).onsuccess = function (event) {
  var cursor = event.target.result;
  if (cursor) {
    // 조회된 값으로 무언가 수행한다.
    cursor.continue();
  }
};
```

Sometimes you may want to iterate in descending order rather than in ascending order (the default direction for all cursors). Switching direction is accomplished by passing `prev` to the `openCursor()` function:

```js
objectStore.openCursor(boundKeyRange, "prev").onsuccess = function (event) {
  var cursor = event.target.result;
  if (cursor) {
    // Do something with the entries.
    cursor.continue();
  }
};
```

If you just want to specify a change of direction but not constrain the results shown, you can just pass in null as the first argument:

```js
objectStore.openCursor(null, "prev").onsuccess = function (event) {
  var cursor = event.target.result;
  if (cursor) {
    // Do something with the entries.
    cursor.continue();
  }
};
```

Since the "name" index isn't unique, there might be multiple entries where `name` is the same. Note that such a situation cannot occur with object stores since the key must always be unique. If you wish to filter out duplicates during cursor iteration over indexes, you can pass `nextunique` (or `prevunique` if you're going backwards) as the direction parameter. When `nextunique` or `prevunique` is used, the entry with the lowest key is always the one returned.

```js
index.openKeyCursor(null, "nextunique").onsuccess = function (event) {
  var cursor = event.target.result;
  if (cursor) {
    // Do something with the entries.
    cursor.continue();
  }
};
```

Please see "IDBCursor Constants" for the valid direction arguments.

Version changes while a web app is open in another tab
------------------------------------------------------

When your web app changes in such a way that a version change is required for your database, you need to consider what happens if the user has the old version of your app open in one tab and then loads the new version of your app in another. When you call `open()` with a greater version than the actual version of the database, all other open databases must explicitly acknowledge the request before you can start making changes to the database. Here's how it works:

```js
var openReq = mozIndexedDB.open("MyTestDatabase", 2);

openReq.onblocked = function (event) {
  // If some other tab is loaded with the database, then it needs to be closed
  // before we can proceed.
  alert("Please close all other tabs with this site open!");
};

openReq.onupgradeneeded = function (event) {
  // All other databases have been closed. Set everything up.
  db.createObjectStore(/\* ... \*/);
  useDatabase(db);
};

openReq.onsuccess = function (event) {
  var db = event.target.result;
  useDatabase(db);
  return;
};

function useDatabase(db) {
  // Make sure to add a handler to be notified if another page requests a version
  // change. We must close the database. This allows the other page to upgrade the database.
  // If you don't do this then the upgrade won't happen until the user close the tab.
  db.onversionchange = function (event) {
    db.close();
    alert("A new version of this page is ready. Please reload!");
  };

  // Do stuff with the database.
}
```

You should also listen for `VersionError` errors to handle the situation where already opened apps may initiate code leading to a new attempt to open the database, but using an outdated version.

Security
--------

IndexedDB uses the same-origin principle, which means that it ties the store to the origin of the site that creates it (typically, this is the site domain or subdomain), so it cannot be accessed by any other origin.

Third party window content (e.g. `<iframe>` (en-US) content) can access the IndexedDB store for the origin it is embedded into, unless the browser is set to never accept third party cookies (see Firefox bug 1147821.)

Warning about browser shutdown
------------------------------

When the browser shuts down (because the user chose the Quit or Exit option), the disk containing the database is removed unexpectedly, or permissions are lost to the database store, the following things happen:

1. Each transaction on every affected database (or all open databases, in the case of browser shutdown) is aborted with an `AbortError`. The effect is the same as if `IDBTransaction.abort()` (en-US) is called on each transaction.
2. Once all of the transactions have completed, the database connection is closed.
3. Finally, the `IDBDatabase` (en-US) object representing the database connection receives a `close` event. You can use the `IDBDatabase.onclose` (en-US) event handler to listen for these events, so that you know when a database is unexpectedly closed.

The behavior described above is new, and is only available as of the following browser releases: Firefox 50, Google Chrome 31 (approximately).

Prior to these browser versions, the transactions are aborted silently, and no `close` event is fired, so there is no way to detect an unexpected database closure.

Since the user can exit the browser at any time, this means that you cannot rely upon any particular transaction to complete, and on older browsers, you don't even get told when they don't complete. There are several implications of this behavior.

First, you should take care to always leave your database in a consistent state at the end of every transaction. For example, suppose that you are using IndexedDB to store a list of items that you allow the user to edit. You save the list after the edit by clearing the object store and then writing out the new list. If you clear the object store in one transaction and write the new list in another transaction, there is a danger that the browser will close after the clear but before the write, leaving you with an empty database. To avoid this, you should combine the clear and the write into a single transaction.

Second, you should never tie database transactions to unload events. If the unload event is triggered by the browser closing, any transactions created in the unload event handler will never complete. An intuitive approach to maintaining some information across browser sessions is to read it from the database when the browser (or a particular page) is opened, update it as the user interacts with the browser, and then save it to the database when the browser (or page) closes. However, this will not work. The database transactions will be created in the unload event handler, but because they are asynchronous they will be aborted before they can execute.

In fact, there is no way to guarantee that IndexedDB transactions will complete, even with normal browser shutdown. See Firefox bug 870645. As a workaround for this normal shutdown notification, you might track your transactions and add a `beforeunload` event to warn the user if any transactions have not yet completed at the time of unloading.

At least with the addition of the abort notifications and `IDBDatabase.onclose` (en-US), you can know when this has happened.

Locale-aware sorting
--------------------

Mozilla has implemented the ability to perform locale-aware sorting of IndexedDB data in Firefox 43+. By default, IndexedDB didn't handle internationalization of sorting strings at all, and everything was sorted as if it were English text. For example, b, á, z, a would be sorted as:

* a
* b
* z
* á

which is obviously not how users want their data to be sorted — Aaron and Áaron for example should go next to one another in a contacts list. Achieving proper international sorting therefore required the entire dataset to be called into memory, and sorting to be performed by client-side JavaScript, which is not very efficient.

This new functionality enables developers to specify a locale when creating an index using `IDBObjectStore.createIndex()` (en-US) (check out its parameters.) In such cases, when a cursor is then used to iterate through the dataset, and you want to specify locale-aware sorting, you can use a specialized `IDBLocaleAwareKeyRange` (en-US).

`IDBIndex` (en-US) has also had new properties added to it to specify if it has a locale specified, and what it is: `locale` (returns the locale if any, or null if none is specified) and `isAutoLocale` (returns `true` if the index was created with an auto locale, meaning that the platform's default locale is used, `false` otherwise.)

**참고:** This feature is currently hidden behind a flag — to enable it and experiment, go to about:config and enable `dom.indexedDB.experimental`.

Full IndexedDB example
----------------------

### HTML Content

```html
<script
 type="text/javascript"
 src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>

<h1>IndexedDB Demo: storing blobs, e-publication example</h1>
<div class="note">
  <p>Works and tested with:</p>
  <div id="compat"></div>
</div>

<div id="msg"></div>

<form id="register-form">
  <table>
    <tbody>
      <tr>
        <td>
          <label for="pub-title" class="required"> Title: </label>
        </td>
        <td>
          <input type="text" id="pub-title" name="pub-title" />
        </td>
      </tr>
      <tr>
        <td>
          <label for="pub-biblioid" class="required">
            Bibliographic ID:<br />
            <span class="note">(ISBN, ISSN, etc.)</span>
          </label>
        </td>
        <td>
          <input type="text" id="pub-biblioid" name="pub-biblioid" />
        </td>
      </tr>
      <tr>
        <td>
          <label for="pub-year"> Year: </label>
        </td>
        <td>
          <input type="number" id="pub-year" name="pub-year" />
        </td>
      </tr>
    </tbody>
    <tbody>
      <tr>
        <td>
          <label for="pub-file"> File image: </label>
        </td>
        <td>
          <input type="file" id="pub-file" />
        </td>
      </tr>
      <tr>
        <td>
          <label for="pub-file-url">
            Online-file image URL:<br />
            <span class="note">(same origin URL)</span>
          </label>
        </td>
        <td>
          <input type="text" id="pub-file-url" name="pub-file-url" />
        </td>
      </tr>
    </tbody>
  </table>

  <div class="button-pane">
    <input type="button" id="add-button" value="Add Publication" />
    <input type="reset" id="register-form-reset" />
  </div>
</form>

<form id="delete-form">
  <table>
    <tbody>
      <tr>
        <td>
          <label for="pub-biblioid-to-delete">
            Bibliographic ID:<br />
            <span class="note">(ISBN, ISSN, etc.)</span>
          </label>
        </td>
        <td>
          <input
 type="text"
 id="pub-biblioid-to-delete"
 name="pub-biblioid-to-delete" />
        </td>
      </tr>
      <tr>
        <td>
          <label for="key-to-delete">
            Key:<br />
            <span class="note">(for example 1, 2, 3, etc.)</span>
          </label>
        </td>
        <td>
          <input type="text" id="key-to-delete" name="key-to-delete" />
        </td>
      </tr>
    </tbody>
  </table>
  <div class="button-pane">
    <input type="button" id="delete-button" value="Delete Publication" />
    <input
 type="button"
 id="clear-store-button"
 value="Clear the whole store"
 class="destructive" />
  </div>
</form>

<form id="search-form">
  <div class="button-pane">
    <input
 type="button"
 id="search-list-button"
 value="List database content" />
  </div>
</form>

<div>
  <div id="pub-msg"></div>
  <div id="pub-viewer"></div>
  <ul id="pub-list"></ul>
</div>
```

### CSS Content

```css
body {
  font-size: 0.8em;
  font-family: Sans-Serif;
}

form {
  background-color: #cccccc;
  border-radius: 0.3em;
  display: inline-block;
  margin-bottom: 0.5em;
  padding: 1em;
}

table {
  border-collapse: collapse;
}

input {
  padding: 0.3em;
  border-color: #cccccc;
  border-radius: 0.3em;
}

.required:after {
  content: "\*";
  color: red;
}

.button-pane {
  margin-top: 1em;
}

#pub-viewer {
  float: right;
  width: 48%;
  height: 20em;
  border: solid #d092ff 0.1em;
}
#pub-viewer iframe {
  width: 100%;
  height: 100%;
}

#pub-list {
  width: 46%;
  background-color: #eeeeee;
  border-radius: 0.3em;
}
#pub-list li {
  padding-top: 0.5em;
  padding-bottom: 0.5em;
  padding-right: 0.5em;
}

#msg {
  margin-bottom: 1em;
}

.action-success {
  padding: 0.5em;
  color: #00d21e;
  background-color: #eeeeee;
  border-radius: 0.2em;
}

.action-failure {
  padding: 0.5em;
  color: #ff1408;
  background-color: #eeeeee;
  border-radius: 0.2em;
}

.note {
  font-size: smaller;
}

.destructive {
  background-color: orange;
}
.destructive:hover {
  background-color: #ff8000;
}
.destructive:active {
  background-color: red;
}
```

### JavaScript Content

```js
(function () {
  var COMPAT\_ENVS = [
    ["Firefox", ">= 16.0"],
    [
      "Google Chrome",
      ">= 24.0 (you may need to get Google Chrome Canary), NO Blob storage support",
    ],
  ];
  var compat = $("#compat");
  compat.empty();
  compat.append('<ul id="compat-list"></ul>');
  COMPAT\_ENVS.forEach(function (val, idx, array) {
    $("#compat-list").append("<li>" + val[0] + ": " + val[1] + "</li>");
  });

  const DB\_NAME = "mdn-demo-indexeddb-epublications";
  const DB\_VERSION = 1; // Use a long long for this value (don't use a float)
  const DB\_STORE\_NAME = "publications";

  var db;

  // Used to keep track of which view is displayed to avoid uselessly reloading it
  var current_view_pub_key;

  function openDb() {
    console.log("openDb ...");
    var req = indexedDB.open(DB\_NAME, DB\_VERSION);
    req.onsuccess = function (evt) {
      // Better use "this" than "req" to get the result to avoid problems with
      // garbage collection.
      // db = req.result;
      db = this.result;
      console.log("openDb DONE");
    };
    req.onerror = function (evt) {
      console.error("openDb:", evt.target.errorCode);
    };

    req.onupgradeneeded = function (evt) {
      console.log("openDb.onupgradeneeded");
      var store = evt.currentTarget.result.createObjectStore(DB\_STORE\_NAME, {
        keyPath: "id",
        autoIncrement: true,
      });

      store.createIndex("biblioid", "biblioid", { unique: true });
      store.createIndex("title", "title", { unique: false });
      store.createIndex("year", "year", { unique: false });
    };
  }

  /\*\*
 \* @param {string} store\_name
 \* @param {string} mode either "readonly" or "readwrite"
 \*/
  function getObjectStore(store\_name, mode) {
    var tx = db.transaction(store_name, mode);
    return tx.objectStore(store_name);
  }

  function clearObjectStore(store\_name) {
    var store = getObjectStore(DB\_STORE\_NAME, "readwrite");
    var req = store.clear();
    req.onsuccess = function (evt) {
      displayActionSuccess("Store cleared");
      displayPubList(store);
    };
    req.onerror = function (evt) {
      console.error("clearObjectStore:", evt.target.errorCode);
      displayActionFailure(this.error);
    };
  }

  function getBlob(key, store, success\_callback) {
    var req = store.get(key);
    req.onsuccess = function (evt) {
      var value = evt.target.result;
      if (value) success\_callback(value.blob);
    };
  }

  /\*\*
 \* @param {IDBObjectStore=} store
 \*/
  function displayPubList(store) {
    console.log("displayPubList");

    if (typeof store == "undefined")
      store = getObjectStore(DB\_STORE\_NAME, "readonly");

    var pub_msg = $("#pub-msg");
    pub_msg.empty();
    var pub_list = $("#pub-list");
    pub_list.empty();
    // Resetting the iframe so that it doesn't display previous content
    newViewerFrame();

    var req;
    req = store.count();
    // Requests are executed in the order in which they were made against the
    // transaction, and their results are returned in the same order.
    // Thus the count text below will be displayed before the actual pub list
    // (not that it is algorithmically important in this case).
    req.onsuccess = function (evt) {
      pub_msg.append(
        "<p>There are <strong>" +
          evt.target.result +
          "</strong> record(s) in the object store.</p>",
      );
    };
    req.onerror = function (evt) {
      console.error("add error", this.error);
      displayActionFailure(this.error);
    };

    var i = 0;
    req = store.openCursor();
    req.onsuccess = function (evt) {
      var cursor = evt.target.result;

      // If the cursor is pointing at something, ask for the data
      if (cursor) {
        console.log("displayPubList cursor:", cursor);
        req = store.get(cursor.key);
        req.onsuccess = function (evt) {
          var value = evt.target.result;
          var list_item = $(
            "<li>" +
              "[" +
              cursor.key +
              "] " +
              "(biblioid: " +
              value.biblioid +
              ") " +
              value.title +
              "</li>",
          );
          if (value.year != null) list_item.append(" - " + value.year);

          if (
            value.hasOwnProperty("blob") &&
            typeof value.blob != "undefined"
          ) {
            var link = $('<a href="' + cursor.key + '">File</a>');
            link.on("click", function () {
              return false;
            });
            link.on("mouseenter", function (evt) {
              setInViewer(evt.target.getAttribute("href"));
            });
            list_item.append(" / ");
            list_item.append(link);
          } else {
            list_item.append(" / No attached file");
          }
          pub_list.append(list_item);
        };

        // Move on to the next object in store
        cursor.continue();

        // This counter serves only to create distinct ids
        i++;
      } else {
        console.log("No more entries");
      }
    };
  }

  function newViewerFrame() {
    var viewer = $("#pub-viewer");
    viewer.empty();
    var iframe = $("<iframe />");
    viewer.append(iframe);
    return iframe;
  }

  function setInViewer(key) {
    console.log("setInViewer:", arguments);
    key = Number(key);
    if (key == current_view_pub_key) return;

    current_view_pub_key = key;

    var store = getObjectStore(DB\_STORE\_NAME, "readonly");
    getBlob(key, store, function (blob) {
      console.log("setInViewer blob:", blob);
      var iframe = newViewerFrame();

      // It is not possible to set a direct link to the
      // blob to provide a mean to directly download it.
      if (blob.type == "text/html") {
        var reader = new FileReader();
        reader.onload = function (evt) {
          var html = evt.target.result;
          iframe.load(function () {
            $(this).contents().find("html").html(html);
          });
        };
        reader.readAsText(blob);
      } else if (blob.type.indexOf("image/") == 0) {
        iframe.load(function () {
          var img_id = "image-" + key;
          var img = $('<img id="' + img_id + '"/>');
          $(this).contents().find("body").html(img);
          var obj_url = window.URL.createObjectURL(blob);
          $(this)
            .contents()
            .find("#" + img_id)
            .attr("src", obj_url);
          window.URL.revokeObjectURL(obj_url);
        });
      } else if (blob.type == "application/pdf") {
        $("\*").css("cursor", "wait");
        var obj_url = window.URL.createObjectURL(blob);
        iframe.load(function () {
          $("\*").css("cursor", "auto");
        });
        iframe.attr("src", obj_url);
        window.URL.revokeObjectURL(obj_url);
      } else {
        iframe.load(function () {
          $(this).contents().find("body").html("No view available");
        });
      }
    });
  }

  /\*\*
 \* @param {string} biblioid
 \* @param {string} title
 \* @param {number} year
 \* @param {string} url the URL of the image to download and store in the local
 \* IndexedDB database. The resource behind this URL is subjected to the
 \* "Same origin policy", thus for this method to work, the URL must come from
 \* the same origin as the web site/app this code is deployed on.
 \*/
  function addPublicationFromUrl(biblioid, title, year, url) {
    console.log("addPublicationFromUrl:", arguments);

    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    // Setting the wanted responseType to "blob"
    // http://www.w3.org/TR/XMLHttpRequest2/#the-response-attribute
    xhr.responseType = "blob";
    xhr.onload = function (evt) {
      if (xhr.status == 200) {
        console.log("Blob retrieved");
        var blob = xhr.response;
        console.log("Blob:", blob);
        addPublication(biblioid, title, year, blob);
      } else {
        console.error(
          "addPublicationFromUrl error:",
          xhr.responseText,
          xhr.status,
        );
      }
    };
    xhr.send();

    // We can't use jQuery here because as of jQuery 1.8.3 the new "blob"
    // responseType is not handled.
    // http://bugs.jquery.com/ticket/11461
    // http://bugs.jquery.com/ticket/7248
    // $.ajax({
    // url: url,
    // type: 'GET',
    // xhrFields: { responseType: 'blob' },
    // success: function(data, textStatus, jqXHR) {
    // console.log("Blob retrieved");
    // console.log("Blob:", data);
    // // addPublication(biblioid, title, year, data);
    // },
    // error: function(jqXHR, textStatus, errorThrown) {
    // console.error(errorThrown);
    // displayActionFailure("Error during blob retrieval");
    // }
    // });
  }

  /\*\*
 \* @param {string} biblioid
 \* @param {string} title
 \* @param {number} year
 \* @param {Blob=} blob
 \*/
  function addPublication(biblioid, title, year, blob) {
    console.log("addPublication arguments:", arguments);
    var obj = { biblioid: biblioid, title: title, year: year };
    if (typeof blob != "undefined") obj.blob = blob;

    var store = getObjectStore(DB\_STORE\_NAME, "readwrite");
    var req;
    try {
      req = store.add(obj);
    } catch (e) {
      if (e.name == "DataCloneError")
        displayActionFailure(
          "This engine doesn't know how to clone a Blob, " + "use Firefox",
        );
      throw e;
    }
    req.onsuccess = function (evt) {
      console.log("Insertion in DB successful");
      displayActionSuccess();
      displayPubList(store);
    };
    req.onerror = function () {
      console.error("addPublication error", this.error);
      displayActionFailure(this.error);
    };
  }

  /\*\*
 \* @param {string} biblioid
 \*/
  function deletePublicationFromBib(biblioid) {
    console.log("deletePublication:", arguments);
    var store = getObjectStore(DB\_STORE\_NAME, "readwrite");
    var req = store.index("biblioid");
    req.get(biblioid).onsuccess = function (evt) {
      if (typeof evt.target.result == "undefined") {
        displayActionFailure("No matching record found");
        return;
      }
      deletePublication(evt.target.result.id, store);
    };
    req.onerror = function (evt) {
      console.error("deletePublicationFromBib:", evt.target.errorCode);
    };
  }

  /\*\*
 \* @param {number} key
 \* @param {IDBObjectStore=} store
 \*/
  function deletePublication(key, store) {
    console.log("deletePublication:", arguments);

    if (typeof store == "undefined")
      store = getObjectStore(DB\_STORE\_NAME, "readwrite");

    // As per spec http://www.w3.org/TR/IndexedDB/#object-store-deletion-operation
    // the result of the Object Store Deletion Operation algorithm is
    // undefined, so it's not possible to know if some records were actually
    // deleted by looking at the request result.
    var req = store.get(key);
    req.onsuccess = function (evt) {
      var record = evt.target.result;
      console.log("record:", record);
      if (typeof record == "undefined") {
        displayActionFailure("No matching record found");
        return;
      }
      // Warning: The exact same key used for creation needs to be passed for
      // the deletion. If the key was a Number for creation, then it needs to
      // be a Number for deletion.
      req = store.delete(key);
      req.onsuccess = function (evt) {
        console.log("evt:", evt);
        console.log("evt.target:", evt.target);
        console.log("evt.target.result:", evt.target.result);
        console.log("delete successful");
        displayActionSuccess("Deletion successful");
        displayPubList(store);
      };
      req.onerror = function (evt) {
        console.error("deletePublication:", evt.target.errorCode);
      };
    };
    req.onerror = function (evt) {
      console.error("deletePublication:", evt.target.errorCode);
    };
  }

  function displayActionSuccess(msg) {
    msg = typeof msg != "undefined" ? "Success: " + msg : "Success";
    $("#msg").html('<span class="action-success">' + msg + "</span>");
  }
  function displayActionFailure(msg) {
    msg = typeof msg != "undefined" ? "Failure: " + msg : "Failure";
    $("#msg").html('<span class="action-failure">' + msg + "</span>");
  }
  function resetActionStatus() {
    console.log("resetActionStatus ...");
    $("#msg").empty();
    console.log("resetActionStatus DONE");
  }

  function addEventListeners() {
    console.log("addEventListeners");

    $("#register-form-reset").click(function (evt) {
      resetActionStatus();
    });

    $("#add-button").click(function (evt) {
      console.log("add ...");
      var title = $("#pub-title").val();
      var biblioid = $("#pub-biblioid").val();
      if (!title || !biblioid) {
        displayActionFailure("Required field(s) missing");
        return;
      }
      var year = $("#pub-year").val();
      if (year != "") {
        // Better use Number.isInteger if the engine has EcmaScript 6
        if (isNaN(year)) {
          displayActionFailure("Invalid year");
          return;
        }
        year = Number(year);
      } else {
        year = null;
      }

      var file_input = $("#pub-file");
      var selected_file = file_input.get(0).files[0];
      console.log("selected\_file:", selected_file);
      // Keeping a reference on how to reset the file input in the UI once we
      // have its value, but instead of doing that we rather use a "reset" type
      // input in the HTML form.
      //file\_input.val(null);
      var file_url = $("#pub-file-url").val();
      if (selected_file) {
        addPublication(biblioid, title, year, selected_file);
      } else if (file_url) {
        addPublicationFromUrl(biblioid, title, year, file_url);
      } else {
        addPublication(biblioid, title, year);
      }
    });

    $("#delete-button").click(function (evt) {
      console.log("delete ...");
      var biblioid = $("#pub-biblioid-to-delete").val();
      var key = $("#key-to-delete").val();

      if (biblioid != "") {
        deletePublicationFromBib(biblioid);
      } else if (key != "") {
        // Better use Number.isInteger if the engine has EcmaScript 6
        if (key == "" || isNaN(key)) {
          displayActionFailure("Invalid key");
          return;
        }
        key = Number(key);
        deletePublication(key);
      }
    });

    $("#clear-store-button").click(function (evt) {
      clearObjectStore();
    });

    var search_button = $("#search-list-button");
    search_button.click(function (evt) {
      displayPubList();
    });
  }

  openDb();
  addEventListeners();
})(); // Immediately-Invoked Function Expression (IIFE)
```

Test the online live demoSee also
--------

Further reading for you to find out more information if desired.

### Reference

* IndexedDB API Reference
* Indexed Database API Specification
* IndexedDB interface files in the Firefox source code
### Tutorials and guides

* Databinding UI Elements with IndexedDB
* IndexedDB — The Store in Your Browser
### Libraries

* localForage: A Polyfill providing a simple name:value syntax for client-side data storage, which uses IndexedDB in the background, but falls back to WebSQL and then localStorage in browsers that don't support IndexedDB.
* dexie.js: A wrapper for IndexedDB that allows much faster code development via nice, simple syntax.
* ZangoDB: A MongoDB-like interface for IndexedDB that supports most of the familiar filtering, projection, sorting, updating and aggregation features of MongoDB.
* JsStore: A simple and advanced IndexedDB wrapper having SQL like syntax.


CanvasCaptureMediaStreamTrack.requestFrame() - Web API

CanvasCaptureMediaStreamTrack.requestFrame()
============================================

The `CanvasCaptureMediaStreamTrack` (en-US) 의 메서드 **`requestFrame()`** 은 canvas에서 프레임을 캡처하여 stream으로 보낼 것을 요청한다. 렌더링 타이밍과 프레임 캡처를 섬세하게 제어해야하는 애플리케이션은 `requestFrame()` 을 사용해 프레임을 캡처할 시기를 직접 지정할 수 있다.

프레임의 자동 캡처를 방지하려면 `requestFrame()` 이 호출될 때만 프레임이 캡처되도록 `captureStream()` (en-US) 메서드에 값을 0으로 지정하세요.

구문
--

```js
stream.requestFrame();
```

### Return 값

`undefined`

참고 사항
-----

현재 사양에는 canvas가 origin-clean 이 아닌 경우 예외를 발생시키지 않는다는 점을 지적하는 문제가 플래그로 표시되어 있다. 이것은 앞으로 바뀔 수 있기 때문에 계획을 미리 세우고 `SecurityError` (적용될 수 있는 구체적인 오류는 사양에 언급되어 있지 않지만, 유력한 후보임)와 같은 예외사항을 지켜보는 것이 좋다.

예시
--

```js
// 캡처할 canvas 요소 찾기
var canvasElt = document.getElementsByTagName("canvas")[0];

// stream 가져오기
var stream = canvasElt.captureStream(25); // 25 FPS

// canvas의 현재 상태를 프레임으로 stream에 보내기
stream.getVideoTracks()[0].requestFrame();
```

명세
--

| Specification |
| --- |
| Media Capture from DOM Elements # dom-canvascapturemediastreamtrack-requestframe |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

더 보기
----

* `CanvasCaptureMediaStream` (en-US), the interface it belongs to.
* `<canvas>`


CanvasCaptureMediaStreamTrack.canvas - Web API

CanvasCaptureMediaStreamTrack.canvas
====================================

The `CanvasCaptureMediaStreamTrack` (en-US) \*\*`canvas`\*\*읽기 전용 속성은 프레임이 캡쳐되는`HTMLCanvasElement` 를 반환한다.

구문
--

```js
var elt = stream.canvas;
```

### 값

`캡처되는 프레임의 원본인 canvas를 나타내는 HTMLCanvasElement` .

예시
--

```js
// 캡처할 canvas 요소 찾기
var canvasElt = document.getElementsByTagName("canvas")[0];

// stream 얻기
var stream = canvasElt.captureStream(25); // 25 FPS

// stream 처리하기
...

// stream과 연결된 canvas 가져오기
var canvas = stream.canvas;
```

명세
--

| Specification |
| --- |
| Media Capture from DOM Elements # dom-canvascapturemediastreamtrack-canvas |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

더 보기
----

* `HTMLCanvasElement.captureStream()` (en-US) to create a stream to capture a given canvas element.
* `<canvas>`


AudioWorkletProcessor.parameterDescriptors (static getter) - Web API

AudioWorkletProcessor.parameterDescriptors (static getter)
==========================================================

**실험적:** **이 기능은 실험적인 기능입니다.**
프로덕션 환경에서 사용하기 전에 브라우저 호환성 표를 주의 깊게 확인하세요.

`AudioWorkletProcessor`로부터 파생된 클래스의 **`parameterDescriptors`** 읽기 전용 속성은 *static getter* 인데, 이는 `AudioParamDescriptor` 기반 객체의 iterable을 반환합니다.

이 속성은 `AudioWorkletProcessor` 인터페이스의 일부가 아니지만, 만약 정의된다면, 이것은 `AudioWorkletProcessor` 생성자에 의해 연관된 `AudioWorkletNode`의 `parameters` 속성에서 사용자 정의 `AudioParam` 객체들의 리스트를 생성하기 위해 내부적으로 호출됩니다.

getter를 정의하는 것은 선택 사항입니다.

구문
--

```js
AudioWorkletProcessorSubclass.parameterDescriptors;
```

### Value

`AudioParamDescriptor` 기반 객체들의 iterable. 이 객체들의 속성은 다음과 같습니다.

`name`

`AudioParam` 의 이름을 나타내는 `DOMString`. 이 이름 아래에서 `AudioParam` 은 이 노드의 `parameters` 속성에서 사용 가능하고, 이 이름 아래에서 `AudioWorkletProcessor.process` 메서드는 이 `AudioParam` 의 계산된 값을 획득할 것입니다.

`automationRate` Optional

이 `AudioParam` 의 자동화 비율을 나타내는 `"a-rate"`, 또는 `"k-rate"` 문자열. 기본값은 `"a-rate"` 입니다.

`minValue` Optional

`AudioParam` 의 최소 값을 나타내는 `float`. 기본 값은 `-3.4028235e38` 입니다.

`maxValue` Optional

`AudioParam` 의 최대 값을 나타내는 `float`. 기본 값은 `3.4028235e38` 입니다.

`defaultValue` Optional

`AudioParam` 의 초기 값을 나타내는 `float`. 기본값은 `0` 입니다.

예제
--

어떻게 static `parameterDescriptors` getter를 사용자 정의 `AudioWorkletProcessor` 에 추가하는지를 보여주는 예제 코드를 보시려면 `AudioWorkletNode.parameters` 를 참고해 보세요.

명세서
---

| Specification |
| --- |
| Web Audio API # audioworkletprocess-callback-parameters |

브라우저 호환성
--------

{{Compat}}같이 보기
-----

* Web Audio API
* Web Audio
API 사용하기


AudioWorkletProcessor() - Web API

AudioWorkletProcessor()
=======================

**`AudioWorkletProcessor()`** 생성자는 `AudioWorkletNode`의 근본적인 오디오 프로세싱 메커니즘을 나타내는 새로운 `AudioWorkletProcessor` 객체를 생성합니다.

구문
--

**참고:** `AudioWorkletProcessor` 와 이것으로부터 파생된 클래스들은
 사용자에 의해 제공된 코드로부터 직접적으로 초기화될 수 없습니다.
 대신, 이것들은 오직 연관된 `AudioWorkletNode`들의 생성에 의해서만 내부적으로 생성됩니다.

```js
new AudioWorkletProcessor(options);
```

### 매개변수

`options`

`AudioWorkletNode constructor`에 *options* 매개변수로서 전달되고 structured clone algorithm에 통과되는 객체. 사용 가능한 속성들은 다음과 같습니다:

`numberOfInputs` Optional

`numberOfInputs` 속성을 초기화할 값. 기본 값은 1입니다.

`numberOfOutputs` Optional

`numberOfOutputs` 속성을 초기화할 값. 기본 값은 1입니다.

`outputChannelCount` Optional

각 출력에 대한 채널의 수를 정의하는 **배열**. 예를 들자면, *outputChannelCount: [n, m]* 은 첫번째 출력의 채널의 수를 *n* 으로, 두번째 출력을 *m* 으로 명시합니다. 배열의 길이는 반드시 `numberOfOutputs` 와 일치해야 합니다.

`parameterData` Optional

`key` 가 사용자 정의 파라미터의 이름이고 `value` 가 이것의 초기 값인 채로, (이 노드의 `parameters` 속성에 있는) 이 노드의 사용자 정의 `AudioParam`의 초기 값들을 포함하는 객체.

`processorOptions` Optional

근본적인 `AudioWorkletProcessor`의 사용자 정의 초기화에 사용될 수 있는 모든 추가적인 데이터

처음 두 개의 속성에 대해 기본 값이 있어서, `AudioWorkletNode constructor`에 전달되는 *options* 객체가 없을지라도, 노드에 의해 `AudioWorkletProcessor` 생성자에 전달된 *options* 객체는 존재할 것이고 최소한 `numberOfInputs` 과 `numberOfOutputs` 을 가질 것이라는 데 주목하세요.

### 반환 값

새롭게 생성된 `AudioWorkletProcessor` 인스턴스.

예제
--

이 예제에서 우리는 `AudioWorkletNode constructor`에 사용자 정의 옵션들을 전달하고 어떻게 그것들의 structured
clone이 우리의 `AudioWorkletProcessor` 생성자에 전달되는지 관찰합니다.

우선, 우리는 사용자 정의 `AudioWorkletProcessor`를 정의하고 등록할 필요가 있습니다. 이 작업은 별도의 파일에서 이루어져야 한다는 것에 주의하세요.

```js
// test-processor.js
class TestProcessor extends AudioWorkletProcessor {
  constructor(options) {
    super();
    console.log(options.numberOfInputs);
    console.log(options.processorOptions.someUsefulVariable);
  }
  process(inputs, outputs, parameters) {
    return true;
  }
}

registerProcessor("test-processor", TestProcessor);
```

다음으로, 메인 스크립트 파일에서 우리는 프로세서를 로드하고, `AudioWorkletNode` 에 프로세서의 이름과 *options* 객체를 전달하며 `AudioWorkletNode` 의 인스턴스를 생성합니다.

*options* 객체에서 우리는 `someUsefulVariable` 키 아래에서 `Map` 인스턴스와 함께 `processorOptions` 를 전달합니다. 우리는 `numberOfInputs` 을 전달하지 않았고 어떻게 이것이 기본 값을 얻게 되는지 관찰해 봅시다.

```js
const audioContext = new AudioContext();
await audioContext.audioWorklet.addModule("test-processor.js");
const testNode = new AudioWorkletNode(audioContext, "test-processor", {
  processorOptions: {
    someUsefulVariable: new Map([
      [1, "one"],
      [2, "two"],
    ]),
  },
});
```

콘솔 출력은 다음과 같을 것입니다:

```js
> 1 // AudioWorkletNode options.numberOfInputs가 기본 값으로 설정됨
> Map(2) {1 => "one", 2 => "two"} // someUsefulVariable 아래의 복제된 map
```

명세
--

| Specification |
| --- |
| Web Audio API # dom-audioworkletprocessor-audioworkletprocessor |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보기
-----

* `AudioWorkletNode` 인터페이스


AudioWorkletProcessor.process - Web API

AudioWorkletProcessor.process
=============================

`AudioWorkletProcessor`에서 파생된 클래스의 **`process()`** 메서드는 오디오 프로세서 worklet에 대한 오디오 프로세싱 알고리즘을 구현합니다.

비록 이 메서드가 `AudioWorkletProcessor` 인터페이스의 일부가 아니긴 하지만, `AudioWorkletProcessor` 의 모든 구현은 반드시 `process()` 메서드를 제공해야 합니다.

이 메서드는 오디오 렌더링 스레드에서 프로세서의 해당하는 `AudioWorkletNode`를 통해 향해지고 있는 (또한 렌더링 quantum으로도 알려져 있는) 각 오디오 블럭에 대해 한 번 동기적으로 호출됩니다. 다른 말로 하자면, 여러분의 프로세서가 조작할 새로운 오디오 블럭이 준비될 때마다, `process()` 함수가 그렇게 하기 위해 호출됩니다.

**참고:**

 현재, 오디오 데이터 블럭은 항상 128 프레임
 길이입니다—즉, 그것들은 입력들의 채널의 각각에 대해
 128개의 32비트 부동점 샘플을 포함합니다. 그러나, 오디오 블럭의 크기가 상황에 따라
 달라지게 할 수 있도록 명세를 개정할 준비가 이미 되어 있습니다
 (예를 들자면, 만약 오디오 하드웨어나 CPU 활용이
 더 큰 블럭 크기에서 더욱 효율적인 경우). 그러므로,
 여러분은 특정한 크기를 추정하기보다는 **항상 반드시 샘플 배열의 크기를 확인** 해야 합니다.

 이 크기는 심지어 시간에 따라 바뀌게 허용될지도 모르므로,
 여러분은 절대로 단지 첫번째 블럭만을 보고 샘플 버퍼가 항상 같은 크기일 것이라고 추정해서는 안 됩니다.

구문
--

```js
var isActivelyProcessing = audioWorkletProcessor.process(
  inputs,
  outputs,
  parameters,
);
```

### 매개변수

`inputs`

노드에 연결된 *inputs* 의 배열인데, 이것의 각각의 원소는 결국 *channel* 들의 배열입니다. 각 *channel* 은 128개의 샘플을 포함하고 있는 `Float32Array`입니다. 예를 들자면, `inputs[n][m][i]` 는 *n* 번째 입력, 그 입력의 *m* 번째 채널, 그 채널의 *i* 번째 샘플에 접근할 것입니다.

각 샘플 값은 `[-1 .. 1]` 의 범위 내에 있습니다.

*inputs* 의 수와 따라서 그 배열의 길이는 노드의 생성에 고정되어 있습니다 (`AudioWorkletNode`를 참고하세요). 만약 노드의 *n* 번째 입력에 연결된 활성화된 노드가 없다면, `inputs[n]` 는 빈 배열일 것입니다 (0개의 입력 채널 사용 가능).

각 입력의 *channel* 들의 수는 `channelCount`와 `channelCountMode` 속성에 따라 다양할 수 있습니다.

`outputs`

구조적으로 `inputs` 매개변수와 유사한 *outputs* 의 배열. 이것은 `process()` 메서드의 실행 중에 채워지도록 의도되었습니다. 각 출력 채널은 기본적으로 0으로 채워집니다 — 출력 배열이 수정되지 않는 한 프로세서는 무음을 출력할 것입니다.

`parameters`

문자열 키와 `Float32Array` 값들을 포함하고 있는 객체. `parameterDescriptors` getter를 사용해 정의된 각각의 사용자 정의 `AudioParam`에 대해, 이 객체의 키는 그 `AudioParam`의 `name` 이고, 그 값은 `Float32Array`입니다. 배열의 값들은 예정된 자동화 이벤트를 고려함으로써 계산됩니다.

만약 이 매개변수의 자동화 비율이`"a-rate"`라면, 배열은 128개의 값 — 현재 오디오 블럭에 있는 각 프레임에 대해 하나 — 을 포함할 것입니다. 만약 현재 블럭에 의해 표현된 시간 중에 발생한 자동화가 없다면, 128개의 동일한 값 대신 배열은 모든 블럭에 대해 변함없는 하나의 값을 포함할 것입니다.

만약 자동화 비율이 `"k-rate"`라면, 배열은 하나의 값을 포함할 것인데, 이는 128 프레임 각각에 대해 사용될 것입니다.

### 반환 값

사용자 에이전트의 내부 로직이 노드를 끄는 것이 안전하다고 다르게 판단할 지라도 `AudioWorkletNode`를 활성화된 채로 유지하게 할 것인지 말 것인지를 나타내는 Boolean 값.

반환된 값은 프로세서가 `AudioWorkletProcessor`와 이것을 소유하는 노드의 생애 주기 정책에 영향을 끼칠 수 있게 합니다. 만약 반환 값과 노드 상태의 조합이 브라우저로 하여금 노드를 멈추게 결정한다면, `process()`는 다시 호출되지 않을 것입니다.

`true` 를 반환하는 것은 Web Audio API가 노드를 계속 살려두게 하는 반면, `false` 를 반환하는 것은 만약 노드가 새로운 오디오 데이터를 생성하는 것도 아니고 처리되고 있는 이것의 입력을 통해 데이터를 받고 있는 것도 아니라면 브라우저가 노드를 제거할 수 있게 합니다.

세 가지 가장 흔한 유형의 오디오 노드는:

1. 출력의 소스. 이런 노드를 구현하는 `AudioWorkletProcessor`는 이것이 출력을 생산하는 한 `process` 메서드에서 `true` 를 반환해야 합니다. 이 메서드는 이것이 더 이상 출력을 생산하지 않을 것이라는 것을 알게 되지마자 `false` 를 반환해야 합니다. 예시로, `AudioBufferSourceNode`를 생각해 보십시오 — 이런 노드 뒤의 프로세서는 버퍼가 재생되는 동안 `process` 메서드에서 `true` 를 반환해야 하고, 버퍼 재생이 끝났을 때 `false` 반환을 시작해야 합니다 (같은 `AudioBufferSourceNode`에서 다시 `play` 를 호출할 방법은 없습니다).
2. 입력을 변형시키는 노드. 이런 노드를 구현하는 프로세서는 활동 중인 입력 노드들의 존재와 그 노드에 대한 참조들이 garbage-collected 될 수 있는지를 결정할 수 있게 허용하도록 `process` 메서드에서 `false` 를 반환해야 합니다. 이 동작을 하는 노드의 예시는 `GainNode` (en-US)입니다. 연결된 입력과 유지되는 참조가 없어지자마자, gain은 더 이상 무엇에도 적용되지 않을 수 있으므로, 이것은 안전하게 garbage-collected 될 수 있습니다.
3. 입력을 변형시키나 소위 *tail-time* 을 가지고 있는 노드 — 이는 이것이 심지어 입력이 끊기거나 활동이 없는 후일지라도 (0개의 채널을 생산) 몇 번에 대해 출력을 생산할 것임을 의미합니다. 이런 노드를 구현하는 프로세서는 `process` 메서드에서, 0개의 채널을 포함하는 입력이 발견되자마자 시작하며, *tail-time* 의 기간 동안 `true` 를 반환해야 합니다.

**참고:**

`return` 문의 부재는 이 메서드가 `undefined` 를 반환할 것임을 의미하고, 이것은 falsy 값이므로, 이것은 `false`를 반환하는 것과도 같습니다.
 명시적인 `return` 문을 누락하는 것은 여러분의 노드에서 감지하기 어려운 문제를 야기할 수도 있습니다.

### 예외

`process()` 메서드는 유저에 의해 구현되므로, 이것은 아무 것이나 발생시킬 수 있습니다. 만약 uncaught error가 발생된다면, 노드는 `onprocessorerror` 이벤트를 발생시킬 것이고 이것의 생애 주기의 나머지 동안 무음을 출력할 것입니다.

예제
--

이 예제에서 우리는 프로세서의 첫번째 출력에 백색소음을 출력하는 `AudioWorkletProcessor` 를 생성할 것입니다. gain은 `customGain` 파라미터에 의해 제어될 수 있습니다.

```js
class WhiteNoiseProcessor extends AudioWorkletProcessor {
  process(inputs, outputs, parameters) {
    // 첫번째 출력을 취합니다
    const output = outputs[0];
    // 각 채널을 gain에 의해 곱해진 무작위 값으로 채웁니다
    output.forEach((channel) => {
      for (let i = 0; i < channel.length; i++) {
        // 각 샘플에 대해 무작위 값을 생성합니다
        // Math.random의 범위는 [0; 1)입니다; 우리는 [-1; 1]이 필요합니다
        // 이것은 정확한 1을 포함하지 않을 것이지만 단순성을 위해 지금으로선 괜찮습니다
        channel[i] =
          (Math.random() \* 2 - 1) \*
          // 배열은 자동화가 존재하는지와
          // 자동화가 k-rate인지 a-rate인지에 따라
          // 1개 또는 128개의 값을 포함할 수 있습니다
          (parameters["customGain"].length > 1
            ? parameters["customGain"][i]
            : parameters["customGain"][0]);
      }
    });
    // 이것은 자신만의 출력을 생성하는 소스 노드이므로, 만약 우리가
    // 메인 스레드에서 이것에 대해 어떠한 참조도 가지고 있지 않는다면
    // 이것이 뜻하지 않게 garbage-collected되지 않도록 우리는 true를 반환할 것입니다
    return true;
  }
  // process 메서드에서 사용되는 customGain 파라미터를 정의합니다
  static get parameterDescriptors() {
    return [
      {
        name: "customGain",
        defaultValue: 1,
        minValue: 0,
        maxValue: 1,
        automationRate: "a-rate",
      },
    ];
  }
}
```

명세서
---

| Specification |
| --- |
| Web Audio API # process |

브라우저 호환성
--------

브라우저에서 제공하는 메서드가 아니라 클라이언트 코드로 작성해야 하는 콜백 메서드입니다.

같이 보기
-----

* Web Audio API 사용하기


AudioWorkletProcessor.port - Web API

AudioWorkletProcessor.port
==========================

**실험적:** **이 기능은 실험적인 기능입니다.**
프로덕션 환경에서 사용하기 전에 브라우저 호환성 표를 주의 깊게 확인하세요.

`AudioWorkletProcessor` 인터페이스의 읽기 전용 **`port`** 속성은 연관된 `MessagePort` (en-US)를 반환합니다. 이것은 프로세서와 프로세서가 속한 `AudioWorkletNode` 사이에서 소통하기 위해 사용될 수 있습니다.

**참고:** 채널의 다른 쪽 끝에 있는 포트는
 노드의 `port` 속성 아래에서 사용 가능합니다.

구문
--

```js
AudioWorkletProcessorInstance.port;
```

### 값

`AudioWorkletProcessor` 와 연관된 `AudioWorkletNode` 를 연결하고 있는 `MessagePort` (en-US) 객체.

예제
--

`AudioWorkletNode.port`에서 예제 코드를 볼 수 있습니다.

명세
--

| Specification |
| --- |
| Web Audio API # dom-audioworkletprocessor-port |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보기
-----

* Web Audio API
* Web Audio
API 사용하기


Blob.type - Web API

Blob.type
=========

`Blob` 객체의 **`type`** 속성은 데이터의 MIME 유형을 반환합니다.

값
-

파일의 MIME 유형을 나타내는 `DOMString`. 유형을 알아낼 수 없는 경우 빈 문자열입니다.

예제
--

이 예제는 사용자가 선택한 모든 파일 각각에 대해, 허용된 이미지 파일 유형 중 하나인지 검사합니다.

### HTML

```html
<input type="file" id="input" multiple />
<output id="output">이미지 파일 선택...</output>
```

```
output {
  display: block;
  margin-top: 16px;
}
```

### JavaScript

```js
// 우리 애플리케이션에서는 GIF, PNG, JPEG 이미지만 허용
const allowedFileTypes = ["image/png", "image/jpeg", "image/gif"];

const input = document.getElementById("input");
const output = document.getElementById("output");

input.addEventListener("change", (event) => {
  const files = event.target.files;

  if (files.length === 0) {
    output.innerText = "이미지 파일 선택...";
    return;
  }

  if (Array.from(files).every((file) => allowedFileTypes.includes(file.type))) {
    output.innerText = "모든 파일 사용 가능!";
  } else {
    output.innerText = "이미지 파일만 선택하세요.";
  }
});
```

### 결과

명세
--

| Specification |
| --- |
| File API # dfn-type |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보기
-----

* `Blob`
* 웹 애플리케이션에서 파일 사용하기


Blob() - Web API

Blob()
======

**`Blob()`** 생성자는 새로운 `Blob` 객체를 반환합니다. 반환하는 블롭의 데이터는 `array` 매개변수에 제공한 배열을 이어붙인 값입니다.

구문
--

```js
new Blob(array);
new Blob(array, options);
```

### 매개변수

`array`

`ArrayBuffer`, `ArrayBufferView` (en-US), `Blob`, `USVString` 또는 이 값들을 혼합해 구성한 `Array`입니다. `Blob`의 데이터를 구성하게 됩니다. `USVString`은 UTF-8로 인코딩됩니다.

`options` Optional

다음 속성을 지정할 수 있는 `BlobPropertyBag` 객체입니다.

`type` Optional

블롭에 저장할 데이터의 MIME 유형입니다. 기본 값은 빈 문자열(`''`)입니다.

`endings` Optional
비표준

데이터가 텍스트일 때, 개행 문자(`\n`)를 어떻게 해석할지 지정합니다. 기본 값인 `transparent`는 개행 문자를 바꾸지 않고 블롭 데이터로 복사합니다. `native`를 지정하면 호스트 시스템 컨벤션에 맞춰서 변환합니다.

### 반환 값

지정한 데이터를 담은 새로운 `Blob` 객체.

예제
--

```js
const array = ['<a id="a"><b id="b">hey!</b></a>']; // 하나의 DOMString을 포함한 배열
const blob = new Blob(array, { type: "text/html" }); // 생성한 블롭
```

명세
--

| Specification |
| --- |
| File API # constructorBlob |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


Blob.size - Web API

Blob.size
=========

`Blob` 인터페이스의 **`size`** 속성은 `Blob` 또는 `File`의 크기를 바이트 단위로 반환합니다.

값
-

`Blob` 또는 `File`과 같은 `Blob` 기반 객체의 데이터 크기를 바이트 단위로 나타낸 숫자입니다.

예제
--

이 예제는 `file` 유형의 `<input>` (en-US) 요소를 통해 사용자가 선택한 여러 파일을 순회하면서, 각각의 파일 이름과 바이트 크기를 출력합니다.

### HTML

```html
<input type="file" id="input" multiple />
<output id="output">파일 선택...</output>
```

```
output {
  display: block;
  margin-top: 16px;
}
```

### JavaScript

```js
const input = document.getElementById("input");
const output = document.getElementById("output");

input.addEventListener("change", (event) => {
  output.innerText = "";

  for (const file of event.target.files) {
    output.innerText += `${file.name}의 크기는 ${file.size} 바이트입니다.\n`;
  }
});
```

### 결과

명세
--

| Specification |
| --- |
| File API # dfn-size |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보기
-----

* `Blob`
* 웹 애플리케이션에서 파일 사용하기


Geolocation API 사용하기 - Web API

Geolocation API 사용하기
====================

**Secure context:** This feature is available only in secure contexts (HTTPS), in some or all supporting browsers.

Geolocation API는 사용자의 현재 위치를 가져오는 API로, 지도에 사용자 위치를 표시하는 등 다양한 용도로 사용할 수 있습니다. 이 글에서는 Geolocation API의 기초 사용법을 설명합니다.

geolocation 객체
--------------

Geolocation API는 `navigator.geolocation` 객체를 통해 사용할 수 있습니다.

`geolocation` 객체가 존재하는 경우 위치 정보 서비스를 지원하는 것입니다. 객체의 존재 여부는 다음과 같이 알아낼 수 있습니다.

```js
if ("geolocation" in navigator) {
  /\* 위치정보 사용 가능 \*/
} else {
  /\* 위치정보 사용 불가능 \*/
}
```

### 현재 위치 가져오기

사용자의 현재 위치는 `getCurrentPosition()` 메서드를 호출해서 가져올 수 있습니다. 이 메서드는 사용자의 위치를 탐지하는 비동기 요청을 시작하고, 위치 관련 하드웨어에 최신 정보를 요청하며, 위치를 알아낸 후 주어진 콜백 함수를 호출합니다. 선택적으로, 오류가 발생하면 호출할 콜백을 두 번째 매개변수로 지정할 수 있습니다. 또 다른 선택 사항인 세 번째 매개변수는 위치 정보의 최대 수명, 요청의 최대 대기시간, 고정밀 위치정보 여부 등의 옵션을 담은 객체입니다.

**참고:** `getCurrentPosition()`의 기본 설정에서는 정밀도가 낮더라도 최대한 빠르게 응답을 반환하므로, 정확도보다 속도가 중요한 상황에서 유용합니다. 예를 들어, GPS 기능을 가진 장비는 보정 과정에 수 분이 걸릴 수도 있으므로 그동안 IP 위치와 Wi-Fi 등 정확하지 않은 출처에 기반한 위치 정보를 반환할 수 있습니다.

```js
navigator.geolocation.getCurrentPosition((position) => {
  doSomething(position.coords.latitude, position.coords.longitude);
});
```

위의 예제는 사용자 위치가 확인되면 `doSomething()` 함수를 실행합니다.

### 현재 위치 추적하기

장치의 이동이나 위치 정밀도 향상으로 인해 위치 정보가 바뀔 때 호출할 콜백 함수를 `watchPosition()` 메서드로 설정할 수 있습니다. 이 메서드의 매개변수는 `getCurrentPosition()`과 같습니다. 이 콜백은 여러 번 호출될 수 있으므로 브라우저가 사용자의 움직임에 따라 위치를 업데이트하거나, 고정밀 위치 기술을 적용해 보다 정밀한 위치를 표시할 수 있습니다. `getCurrentPosition()`과 마찬가지로 선택 사항인 오류 콜백 역시 여러 번 호출될 수 있습니다.

**참고:** `getCurrentPosition()`을 먼저 호출하지 않아도 `watchPosition()`을 사용할 수 있습니다.

```js
const watchID = navigator.geolocation.watchPosition((position) => {
  doSomething(position.coords.latitude, position.coords.longitude);
});
```

`watchPosition()` 메서드는 위치 추적 요청을 식별할 수 있는 고유 숫자를 반환합니다. 이 숫자를 `clearWatch()` 메서드에 전달하면 해당 위치 추적을 종료할 수 있습니다.

```js
navigator.geolocation.clearWatch(watchID);
```

### 응답 미세 조정

`getCurrentPosition()`과 `watchPosition()` 둘 다 성공 콜백, 실패 콜백, 그리고 옵션 객체를 받을 수 있습니다.

이 옵션 객체로는 고정밀도 활성화 여부, 위치 정보의 최대 수명(수명이 끝나기 전에는 이전에 반환했던 위치 정보를 저장했다가, 같은 요청을 또 받으면 그대로 반환합니다), 그리고 위치 정보 요청의 응답을 기다릴 최대 대기시간을 지정할 수 있습니다.

옵션 객체를 사용한 `watchPosition`의 호출 예시는 다음과 같습니다.

```js
function success(position) {
  doSomething(position.coords.latitude, position.coords.longitude);
}

function error() {
  alert("죄송합니다. 위치 정보를 사용할 수 없습니다.");
}

const options = {
  enableHighAccuracy: true,
  maximumAge: 30000,
  timeout: 27000,
};

const watchID = navigator.geolocation.watchPosition(success, error, options);
```

위치 표현
-----

사용자의 위치는 `GeolocationPosition` 객체 인스턴스와, 그 안의 `GeolocationCoordinates` 객체 인스턴스로 표현됩니다.

`GeolocationPosition`은 오직 두 가지, `GeolocationCoordinates` 인스턴스를 가진 `coords` 속성과, 위치 정보의 기록 시점을 타임스탬프(Unix 시간, 밀리초)로 나타내는 `timestamp` 속성만 갖습니다.

`GeolocationCoordinates` 인스턴스는 여러 속성을 갖지만, 그중 가장 많이 쓰게 될 두 가지는 지도 위에 위치를 표시할 때 필요한 `latitude`와 `longitude`입니다. 따라서 대부분의 `Geolocation` 성공 콜백은 아래와 같이 꽤 간단한 형태입니다.

```js
function success(position) {
  const latitude = position.coords.latitude;
  const longitude = position.coords.longitude;

  // 위도와 경도를 사용해 작업 수행
}
```

그러나 `GeolocationCoordinates` 객체에서 고도, 속도, 장치의 방향, 위경도와 고도의 오차범위 등 다른 다양한 정보도 가져올 수 있습니다.

오류 처리
-----

`getCurrentPosition()` 또는 `watchPosition()`에 오류 콜백을 제공한 경우, 콜백은 첫 번째 매개변수로 `GeolocationPositionError` (en-US) 객체를 받습니다. 해당 객체는 오류의 유형을 나타내는 `code` 속성과, 사람이 읽을 수 있는 형태로 오류 코드의 뜻을 설명한 `message` 속성을 갖습니다.

다음 형태로 사용할 수 있습니다.

```js
function errorCallback(error) {
  alert(`ERROR(${error.code}): ${error.message}`);
}
```

예제
--

다음 예제는 Geolocation API를 사용해 사용자의 위경도를 가져오는 데 성공하면, 사용자의 위치로 향하는 `openstreetmap.org` 링크를 생성하고 하이퍼링크에 지정합니다.
```

body {
  padding: 20px;
  background-color: #ffffc9;
}

button {
  margin: 0.5rem 0;
}
```

### HTML

```html
<button id="find-me">내 위치 보기</button><br />
<p id="status"></p>
<a id="map-link" target="\_blank"></a>
```

### JavaScript

```js
function geoFindMe() {
  const status = document.querySelector("#status");
  const mapLink = document.querySelector("#map-link");

  mapLink.href = "";
  mapLink.textContent = "";

  function success(position) {
    const latitude = position.coords.latitude;
    const longitude = position.coords.longitude;

    status.textContent = "";
    mapLink.href = `https://www.openstreetmap.org/#map=18/${latitude}/${longitude}`;
    mapLink.textContent = `위도: ${latitude} °, 경도: ${longitude} °`;
  }

  function error() {
    status.textContent = "현재 위치를 가져올 수 없음";
  }

  if (!navigator.geolocation) {
    status.textContent = "브라우저가 위치 정보를 지원하지 않음";
  } else {
    status.textContent = "위치 파악 중…";
    navigator.geolocation.getCurrentPosition(success, error);
  }
}

document.querySelector("#find-me").addEventListener("click", geoFindMe);
```

### 결과


Range.selectNodeContents() - Web API

Range.selectNodeContents()
==========================

**`Range.selectNodeContents()`** 는 `Node` (en-US)의 내용을 포함하는 `Range`를 설정합니다.

부모 `Node` (en-US) 의 start, end 값이 Reference가 됩니다. `startOffset` 은 0이고, `endOffset` 은 Child Node의 수 혹은 Reference Node에 포함되어 있는 Child Node의 수가 됩니다.

구문
--

```js
range.selectNodeContents(referenceNode);
```

### 매개변수

`referenceNode`
`Range` 로 선택될 내용의 `Node` (en-US)
예제
--

```js
range = document.createRange();
referenceNode = document.getElementsByTagName("div")[0];
range.selectNodeContents(referenceNode);
```

명세
--

| Specification |
| --- |
| DOM Standard # dom-range-selectnodecontents |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보기
-----

* The DOM interfaces index (en-US)


Node.replaceChild() - Web API

Node.replaceChild()
===================

**`Node.replaceChild()`** 메소드는 특정 부모 노드의 한 자식 노드를 다른 노드로 교체합니다.

Syntax
------

```js
replacedNode = parentNode.replaceChild(newChild, oldChild);
```

* `newChild` 는 `oldChild` 를 교체할 새로운 노드입니다. 만약 이미 `DOM` 안에 존재한다면 가장 먼저 제거됩니다.
* `oldChild` 는 이미 존재하는, 교체될 자식 노드입니다.
* `replacedNode` 는 교체된 노드입니다. `oldChild` 와 동일한 노드입니다.
Example
-------

```js
// <div>
// <span id="childSpan">foo bar</span>
// </div>

// 텅빈 요소 노드를 하나 생성합니다.
// ID도, 속성도, 컨텐츠도 없습니다.
var sp1 = document.createElement("span");

// 'newSpan'이란 id 속성을 부여합니다.
sp1.id = "newSpan";

// 새로운 요소를 위한 컨텐츠를 생성합니다.
var sp1_content = document.createTextNode("new replacement span element.");

// 컨텐츠를 생성한 요소에 붙입니다.
sp1.appendChild(sp1_content);

// DOM에 존재하던, 교체되야할 노드를 참조합니다.
var sp2 = document.getElementById("childSpan");
var parentDiv = sp2.parentNode;

// 이미 존재하던 sp2 노드를 새로운 span 요소인 sp1으로 교체합니다.
parentDiv.replaceChild(sp1, sp2);

// 결과:
// <div>
// <span id="newSpan">new replacement span element.</span>
// </div>
```

Specification
-------------

* DOM Level 1 Core: replaceChild
* DOM Level 2 Core: replaceChild
* DOM Level 3 Core: replaceChild
See also
--------

* `Node.removeChild` (en-US)


Node.normalize() - Web API

Node.normalize()
================

**`Node.normalize()`** 메소드는 지정된 노드와 하위 트리의 모든 노드를 "정규화된" 형태로 놓습니다. 정규화된 하위 트리의 텍스트 노드는 비어있지 않으며 인접한 텍스트 노드도 존재하지 않습니다.

문법
--

```js
element.normalize();
```

예제
--

```js
var wrapper = document.createElement("div");

wrapper.appendChild(document.createTextNode("Part 1 "));
wrapper.appendChild(document.createTextNode("Part 2 "));

// 이 때, wrapper.childNodes.length === 2
// wrapper.childNodes[0].textContent === "Part 1 "
// wrapper.childNodes[1].textContent === "Part 2 "

wrapper.normalize();

// 이제, wrapper.childNodes.length === 1
// wrapper.childNodes[0].textContent === "Part 1 Part 2 "
```

명세
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-node-normalize① |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

함께 보기
-----

* `Text.splitText`


Node.textContent - Web API

Node.textContent
================

`Node` (en-US) 인터페이스의 **`textContent`** 속성은 노드와 그 자손의 텍스트 콘텐츠를 표현합니다.

**참고:** `textContent`와 `HTMLElement.innerText`가 자주 혼동되곤 하지만, 두 속성에는 몇 가지 중요한 차이점이 있습니다.

구문
--

```js
let text = someNode.textContent;
someOtherNode.textContent = string;
```

### 값

문자열 또는 `null`.

설명
--

`textContent`의 값은 상황에 따라 다릅니다.

* 노드가 `document` 또는 Doctype이면 `null`을 반환합니다.

**참고:** 전체 문서의 모든 텍스트와 CDATA 데이터를 얻으려면 `document.documentElement.textContent`를 사용하세요.
* 노드가 CDATA 구획 (en-US), 주석, 처리 명령 (en-US), 텍스트 노드면 노드 내의 텍스트, 즉 `Node.nodeValue` (en-US)를 반환합니다.
* 다른 노드 유형에 대해서는 주석과 처리 명령을 제외한 모든 자식 노드의 `textContent`를 병합한 결과를 반환합니다. 자식이 없는 경우 빈 문자열입니다.

노드의 `textContent`를 설정하면, 노드의 모든 자식을 주어진 문자열로 이루어진 하나의 텍스트 노드로 대치합니다.

### `innerText`와의 차이점

비록 `Node.textContent`와 `HTMLElement.innerText`의 이름이 유사하긴 하지만, 중요한 차이가 있으므로 헷갈리지 마세요.

* `textContent`는 `<script>`와 `<style>` 요소를 포함한 모든 요소의 콘텐츠를 가져옵니다. 반면 `innerText`는 "사람이 읽을 수 있는" 요소만 처리합니다.
* `textContent`는 노드의 모든 요소를 반환합니다. 그에 비해 `innerText`는 스타일링을 고려하며, "숨겨진" 요소의 텍스트는 반환하지 않습니다.
    + 또한, `innerText`의 CSS 고려로 인해, innerText 값을 읽으면 최신 계산값을 반영하기 위해 리플로우가 발생합니다. (리플로우 계산은 비싸므로 가능하면 피해야 합니다)
* Internet Explorer 기준, `innerText`를 수정하면 요소의 모든 자식 노드를 제거하고, 모든 자손 텍스트 노드를 영구히 파괴합니다. 이로 인해, 해당 텍스트 노드를 이후에 다른 노드는 물론 같은 노드에 삽입하는 것도 불가능합니다.
### `innerHTML`과의 차이점

`Element.innerHTML`는 이름 그대로 HTML을 반환합니다. 간혹 innerHTML을 사용해 요소의 텍스트를 가져오거나 쓰는 경우가 있지만, HTML로 분석할 필요가 없다는 점에서 `textContent`의 성능이 더 좋습니다.

이에 더해, `textContent`는 XSS 공격의 위험이 없습니다.

예제
--

다음과 같은 HTML 조각에서...

```html
<div id="divA">This is <span>some</span> text!</div>
```

`textContent`를 사용해 요소의 텍스트 콘텐츠를 가져오거나...

```js
let text = document.getElementById("divA").textContent;
// The text variable is now: 'This is some text!'
```

텍스트 내용을 설정할 수 있습니다.

```js
document.getElementById("divA").textContent = "This text is different!";
// The HTML for divA is now:
// <div id="divA">This text is different!</div>
```

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

명세
--

| Specification |
| --- |
| DOM Standard # dom-node-textcontent |

참고
--

* `HTMLElement.innerText`
* `Element.innerHTML`


Node.previousSibling - Web API

Node.previousSibling
====================

`Node.previousSibling` 은 읽기전용 속성이며 현재 호출하는 노드가 속해 있는 부모의 `childNodes` 목록에서 특정 자식 노드를 리턴하거나 `childNodes` 목록의 첫번째 노드일 경우 Null값을 리턴합니다.`

Syntax
------

```js
previousNode = node.previousSibling;
```

Example
-------

```js
// <a><b1 id="b1"/><b2 id="b2"/></a>

alert(document.getElementById("b1").previousSibling); // null
alert(document.getElementById("b2").previousSibling.id); // "b1"
```

Notes
-----

 Gecko 기반 브라우저는 소스 마크업에서 공백을 나타내기 위해 문서 내에 텍스트 노드를 삽입합니다.
 그러므로 예를 들어 `Node.firstChild`나 `Node.previousSibling`을 통해서 얻은 노드는 작성자가 얻으려 한 실제 요소와는
 달리 공백 텍스트 노드를 참조할 지도 모릅니다.

 더 많은 정보는 Whitespace in the DOM (en-US)과
 W3C DOM 3 FAQ: Why are some Text nodes empty?를 보세요.

To navigate the opposite way through the child nodes list use Node.nextSibling (en-US).

Specification
-------------

* DOM Level 1 Core: previousSibling
* DOM Level 2 Core: previousSibling
* DOM Level 3 Core: previousSibling


element.firstChild - Web API

element.firstChild
==================

요약
--

트리에서 노드의 첫 번째 자식이나 `null`(노드가 자식이 없으면)을 반환합니다.

구문
--

```js
childNode = node.firstChild;
```

`childNode`는 자식이 있으면 `node`의 첫 번째 자식 참조이고 없으면 `null`입니다.

예시
--

이 예는 `firstChild`의 쓰임과 공백 노드가 어떻게 이 프로퍼티 사용을 방해하는 지 실제로 보입니다. Gecko DOM에서 공백 처리에 관한 더 많은 정보는 Notes 절을 보세요.

```html
<p id="para-01">
  <span>First span</span>
</p>

<script type="text/javascript">
 var p01 = document.getElementById("para-01");
 alert(p01.firstChild.nodeName);
</script>
```

위에서, alert은 텍스트 노드가 여는 태그 <p>의 끝과 여는 태그 <span> 사이에 공백을 유지하여 삽입되었기 때문에 '#text'를 보입니다. space 하나에서 여럿, return, tab 등 **어떤** 공백이든 #text 노드에 삽입되게 됩니다.

다른 #text 노드는 닫는 태그 </span>과 닫는 태그 </p> 사이에 삽입됩니다.

이 공백을 소스에서 제거하면, #text 노드는 삽입되지 않고 span 요소가 paragraph의 첫 번째 자식이 됩니다.

```html
<p id="para-01"><span>First span</span></p>

<script type="text/javascript">
 var p01 = document.getElementById("para-01");
 alert(p01.firstChild.nodeName);
</script>
```

이제 alert은 'SPAN'을 보입니다.

주의
--

 Gecko 기반 브라우저는 소스 마크업에서 공백을 나타내기 위해 문서 내에 텍스트 노드를 삽입합니다.
 그러므로 예를 들어 `Node.firstChild`나 `Node.previousSibling`을 통해서 얻은 노드는 작성자가 얻으려 한 실제 요소와는
 달리 공백 텍스트 노드를 참조할 지도 모릅니다.

 더 많은 정보는 Whitespace in the DOM (en-US)과
 W3C DOM 3 FAQ: Why are some Text nodes empty?를 보세요.

명세
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-node-firstchild① |


element.childNodes - Web API

element.childNodes
==================

요약
--

**childNodes**는 주어진 요소의 자식 노드 모음(
 *collection*
 )을 반환합니다.

구문과 값
-----

```js
var ndList = elementNodeReference.childNodes;
```

*ndList*는 현재 요소의 자식인 노드 개체의 순서 있는 모음입니다. 요소가 자식이 없으면, *ndList*는 아무 노드도 포함하지 않습니다.

*ndList*는 childNodes의 노드 목록을 저장하는 변수입니다. 그 목록은 NodeList 형입니다. childNodes 속성은 읽기 전용입니다.

예시
--

```js
// parg는 <p> 요소 개체 참조
if (parg.hasChildNodes()) {
  // 그래서, 먼저 개체가 찼는 지(자식 노드가 있는 지) 검사
  var children = parg.childNodes;
  for (var i = 0; i < children.length; i++) {
    // children[i]로 각 자식에 무언가를 함
    // 주의: 목록은 유효해(live), 자식 추가나 제거는 목록을 바꿈
  }
}

// This는 노드에서 모든 자식을 제거하는 한 방법
// box는 자식 있는 요소 개체 참조
while (box.firstChild) {
  //목록은 유효해서(LIVE) 호출마다 재배열(re-index)함
  box.removeChild(box.firstChild);
}
```

명세
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-node-childnodes① |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보기
-----

* `Node.firstChild`
* `Node.lastChild`
* `Node.nextSibling`
* `Node.previousSibling`
* `Element.children`


Node.ownerDocument - Web API

Node.ownerDocument
==================

**`Node.ownerDocument`** 읽기 전용 속성은 이 node 의 최상위 document 객체를 반환합니다.

Syntax
------

```js
document = element.ownerDocument;
```

* `document` 는 element 의 부모 `document` 객체입니다.
Example
-------

```js
// given a node "p", get the top-level HTML child
// of the document object

var d = p.ownerDocument;
var html = d.documentElement;
```

Notes
-----

이 속성에 의해 반환된`document` 객체는 실제 HTML 문서의 모든 child 노드들이 생성되는 메인 객체입니다. 이 속성이 document 그 자체 노드에서 사용될 경우, 결과는 `null` 이 됩니다.

명세서
---

| Specification |
| --- |
| DOM Standard # ref-for-dom-node-ownerdocument① |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


Node.appendChild() - Web API

Node.appendChild()
==================

**`Node.appendChild()`** 메소드는 한 노드를 특정 부모 노드의 자식 노드 리스트 중 마지막 자식으로 붙입니다. 만약 주어진 노드가 이미 문서에 존재하는 노드를 참조하고 있다면 `appendChild()` 메소드는 노드를 현재 위치에서 새로운 위치로 이동시킵니다. (문서에 존재하는 노드를 다른 곳으로 붙이기 전에 부모 노드로 부터 지워버릴 필요는 없습니다.)

이것은 한 노드가 문서상의 두 지점에 동시에 존재할 수 없다는 것을 의미합니다. 그래서 만약 노드가 이미 부모를 가지고 있다면 우선 삭제되고 새로운 위치로 이동합니다.

`Node.cloneNode()` (en-US) 메소드는 노드가 새로운 부모의 밑으로 붙기 전에 노드를 복제합니다. `cloneNode 메소드로 만들어진 복사된 노드들은 자동적으로 문서에 적용되지 않는다는 것에 주의하세요`.

이 메소드는 서로 다른 문서로 노드를 이동시키진 못합니다. 만약 노드를 다른 문서로 이동시키고 싶다면 `document.importNode()` 메소드를 사용하셔야 합니다.

Syntax
------

```js
var aChild = element.appendChild(aChild);
```

이동한 자식 노드를 반환합니다.

Example
-------

```js
// 새로운 단락 요소를 생성하고 문서에 있는 바디 요소의 끝에 붙입니다.
var p = document.createElement("p");
document.body.appendChild(p);
```

명세서
---

| Specification |
| --- |
| DOM Standard # dom-node-appendchild |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `Node.removeChild()` (en-US)
* `Node.replaceChild()`
* `Node.insertBefore()` (en-US)
* `Node.hasChildNodes()`


Node.nextSibling - Web API

Node.nextSibling
================

읽기 전용 속성인 **`Node.nextSibling`** 은 부모의 `childNodes` 목록에서 지정된 노드 바로 다음에 있는 노드를 반환하거나 지정된 노드가 해당 목록의 마지막 노드이면 `null` 값을 반환합니다.

Syntax
------

```js
nextNode = node.nextSibling;
```

Notes
-----

 Gecko 기반 브라우저는 소스 마크업에서 공백을 나타내기 위해 문서 내에 텍스트 노드를 삽입합니다.
 그러므로 예를 들어 `Node.firstChild`나 `Node.previousSibling`을 통해서 얻은 노드는 작성자가 얻으려 한 실제 요소와는
 달리 공백 텍스트 노드를 참조할 지도 모릅니다.

 더 많은 정보는 Whitespace in the DOM (en-US)과
 W3C DOM 3 FAQ: Why are some Text nodes empty?를 보세요.

Example
-------

```html
<div id="div-01">Here is div-01</div>
<div id="div-02">Here is div-02</div>

<script type="text/javascript">
 var el = document.getElementById("div-01").nextSibling,
 i = 1;

 console.log("Siblings of div-01:");

 while (el) {
 console.log(i + ". " + el.nodeName);
 el = el.nextSibling;
 i++;
 }
</script>

/************************************************** 로드될 때 다음과 같이 콘솔에
기록됩니다. : Siblings of div-01 1. #text 2. DIV 3. #text 4. SCRIPT
**************************************************/
```

위의 예에서 `#text` 노드는 태그 사이의 마크 업 에서 공백이 발생하는 DOM에 삽입되며 ( 즉 요소의 닫기 태그와 다음 태그의 열기 태그 사이에 있습니다 ) `document.write` 구문에 의해 삽입 된 요소간에 공백이 생성되지 않습니다.

`nextSibling` 을 사용하여 DOM을 탐색 할 때, DOM에 텍스트 노드를 포함시킬 수 있어야 합니다. 노트 섹션의 리소스를 참조하세요.

Specification
-------------

* DOM Level 1 Core: nextSibling
* DOM Level 2 Core: nextSibling
See also
--------

* `Element.nextElementSibling` (en-US)


Node.hasChildNodes() - Web API

Node.hasChildNodes()
====================

**`Node.hasChildNodes()`** 메소드는 현재 노드(`Node` (en-US))에게 자식노드(child nodes (en-US))가 있는지를 `Boolean` 값으로 반환합니다.

구문
--

```js
node.hasChildNodes();
```

예시
--

아래 예시에서는 "foo"라는 id를 가진 요소를 찾고 "foo" 요소에게 자식노드가 있으면 첫번째 자식노드를 제거해줍니다.

```js
var foo = document.getElementById("foo");

if (foo.hasChildNodes()) {
  foo.removeChild(foo.childNodes[0]);
}
```

명세
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-node-haschildnodes① |

같이 보기
-----

* `Node.childNodes`
* `Node.hasAttributes`


element.lastChild - Web API

element.lastChild
=================

요약
--

**lastChild**는 노드의 마지막 자식을 반환합니다.

구문과 값
-----

```js
last_child = element.lastChild;
```

반환되는 `last_child`는 노드입니다. 노드의 부모가 요소이면, 자식은 보통 요소 노드, 텍스트 노드, 주석 노드입니다.

예시
--

```js
var tr = document.getElementById("row1");
corner_td = tr.lastChild;
```

주의
--

자식 요소가 없으면 `null`을 반환합니다.

명세
--

| Specification |
| --- |
| DOM Standard # ref-for-dom-node-lastchild① |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


Node.contains() - Web API

Node.contains()
===============

**`Node.contains()`** 메소드는 주어진 인자가 node 의 자손인지, 아닌지에 대한 `Boolean` 값을 리턴합니다.

Syntax
------

```js
node.contains(otherNode);
```

Example
-------

이 함수는 요소가 페이지의 body 안에 있는지 검사합니다. `contains` 는 포괄적이므로 node 가 body 자기 자신일 경우에도 true 가 반환됩니다. 만약 이걸 원하지 않는 경우에는 node 가 body 자기 자신인지 검사하여 `false` 를 반환하여 버리면 됩니다.

```js
function isInPage(node) {
  return node === document.body ? false : document.body.contains(node);
}
```

명세서
---

| Specification |
| --- |
| DOM Standard # ref-for-dom-node-contains① |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `Node.compareDocumentPosition` (en-US)
* `Node.hasChildNodes`


Server-Sent Events 사용하기 - Web API

Server-Sent Events 사용하기
=======================

Server-Sent Events를 사용하는 웹 애플리케이션 개발은 매우 간단하다. 웹 애플리케이션으로 스트림 이벤트를 보내는 약간의 코드가 서버 상에 필요하지만, 웹 애플리케이션 측은 웹 소켓에서 이벤트를 다루는 방식과 거의 차이가 없다.

서버에서 이벤트 받기
-----------

Server-Sent Event API는 `EventSource` (en-US) 인터페이스에 포함돼 있다. 이벤트를 전달 받기 위해서 서버로 접속을 시작하려면 우선, 이벤트를 생성하는 서버측 스크립트를 URI로 지정하여 새로운 `EventSource` (en-US) 객체를 생성한다. 예를 들어:

```js
var evtSource = new EventSource("ssedemo.php");
```

이벤트를 생성하는 스크립트가 다른 도메인에 존재할 경우엔 URI와 옵션 딕셔너리를 모두 지정하여 새로운 `EventSource` (en-US) 객체를 생성한다. 클라이언트 스크립트가 example.com에 있는 경우라면:

```js
var evtSource = new EventSource("//api.example.com/ssedemo.php", {
  withCredentials: true,
});
```

이벤트 소스를 생성 했다면 `message` 이벤트에 대한 핸들러를 등록하여 서버로부터의 메시지 수신을 시작할 수 있다.

```js
evtSource.onmessage = function (e) {
  var newElement = document.createElement("li");
  var eventList = document.getElementById("list");

  newElement.innerHTML = "message: " + e.data;
  eventList.appendChild(newElement);
};
```

위 코드는 입력 메시지(즉, `event` 필드를 갖고 있지 않은 서버로부터의 알림)를 수신하여 그 메시지의 텍스트를 document의 HTML에 있는 목록에 추가한다.

또는 `addEventListener()`를 사용하여 이벤트를 기다릴 수도 있다.

```js
evtSource.addEventListener("ping", function (event) {
  const newElement = document.createElement("li");
  const time = JSON.parse(event.data).time;
  newElement.textContent = "ping at " + time;
  eventList.appendChild(newElement);
});
```

앞서 소개한 코드와 비슷하지만 `event` 필드에 "ping"이 설정된 메시지가 서버로부터 보내졌을 때만 자동으로 호출된다는 점이 다르다.

>
> **Warning:** When **not used over HTTP/2**, SSE suffers from a limitation to the maximum number of open connections, which can be especially painful when opening multiple tabs, as the limit is *per browser* and is set to a very low number (6). The issue has been marked as "Won't fix" in Chrome and Firefox. This limit is per browser + domain, which means that you can open 6 SSE connections across all of the tabs to `www.example1.com` and another 6 SSE connections to `www.example2.com` (per Stackoverflow). When using HTTP/2, the maximum number of simultaneous *HTTP streams* is negotiated between the server and the client (defaults to 100).
>
>
>

서버에서의 이벤트 송신
------------

이벤트를 송신하는 서버 사이드의 스크립트는 MIME 타입 `text/event-stream`을 사용해 응답할 필요가 있다. 각 알림은 두 개의 줄 바꿈으로 끝나는 텍스트 블럭으로 전송된다. 이벤트 스트림의 형식에 관한 자세한 내용은 Event stream format을 참고한다.

다음은 우리가 사용하고 있는 PHP 코드 예다.

```php
date\_default\_timezone\_set("America/New\_York");
header("Content-Type: text/event-stream\n\n");

$counter = rand(1, 10);
while (1) {
  // "ping" 이벤트를 초당 송신

  echo "event: ping\n";
  $curDate = date(DATE\_ISO8601);
  echo 'data: {"time": "' . $curDate . '"}';
  echo "\n\n";

  // 간단한 메시지를 랜덤 간격으로 송신

  $counter--;

  if (!$counter) {
    echo 'data: This is a message at time ' . $curDate . "\n\n";
    $counter = rand(1, 10);
  }

  ob\_end\_flush();
  flush();
  sleep(1);
}
```

 위 코드는 이벤트 타입이 "ping"인 이벤트를 초당 생성한다. 각 이벤트 데이터는 이벤트가 생성된 시각의 ISO 8601 형식의 타입스탬프를 포함하는 JSON 객체다. 또, 랜덤한 간격으로 간단한 메시지(이벤트타입 없는)를 송신한다.
 The loop will keep running independent of the connection status, so a check is included
 to break the loop if the connection has been closed (e.g. client closes the page).

에러 핸들링
------

문제가 발생한 경우(네크워크 타임아웃이나 접근 제약 (en-US)과 관련한 문제)에는 오류 이벤트를 생성한다. `EventSource` 갹채에 `onerror` 콜백을 등록하면 에러를 프로그램으로 대처할 수 있다.

```js
evtSource.onerror = function (e) {
  alert("EventSource failed.");
};
```

이벤트 스트림 닫기
----------

기본적으로는 클라이언트와 서버 사이의 연결이 닫히면 연결이 재시작된다. 연결은 `.close()` 메서드로 종료한다.

```js
evtSource.close();
```

이벤트 스트림 형식
----------

이벤트 스트림은 텍스트 데이터의 단순한 스트림으로 UTF-8을 사용하여 인코딩 해야만 한다. 이벤트 스트림 내부 메시지는 두 개의 줄바꿈 문자로 구분된다. 행 선두에 있는 콜론은 본질적으로 주석으로 나타내며 무시된다.

**참고:** **노트:** 주석 행은 연결이 타임아웃 되는 것을 방지하기 위해 사용할 수 있다. 서버는 연결을 유지하기 위해 정기적으로 주석을 송신할 수 있다.

각 메시지는 필드를 나열한 하나 이상의 텍스트 행으로 구성된다. 각 필드는 "필드명, 그 다음 콜론, 이어서 필드의 값에 해당하는 텍스트 데이터"로 나타낸다.

### 필드

다음과 같은 필드명이 사양에 정리돼 있다.

`event`

이벤트 타입이다. 이 필드가 지정돼 있는 경우, 이벤트는 브라우저 내에서 이벤트명에 해당하는 이벤트 리스너로 전달된다. 웹 사이트의 소스 코드에서는 이름이 붙여진 이벤트를 받기 위해서 `addEventListener()`를 사용한다. 메시지에서 이벤트 명이 지정되 있지 않은 경우는 `onmessage` 핸들러가 호출된다.

`data`

메시지의 데이터 필드다. `EventSource`가 `data:`로 시작된다. 복수의 연속된 행을 전달 받은 경우에는 그것을 연결해 각 항목의 사이에 개행 문자를 삽입한다. 이때, 마지막의 줄바꿈은 제외된다.

`id`

메시지의 데이터 필드다. `EventSource`가 data:로 시작된다. 복수의 연속된 행을 전달 받은 경우에는 그것을 연결해 각 항목의 사이에 개행 문자를 삽입한다. 이때, 마지막의 줄바꿈은 제외된다.

`retry`

이벤트 송신을 시도할 때에 사용하는 재연결 시간(reconnection time)이다. 이 값은 정수여야 하며 재연결 시간을 밀리초 단위로 지정한다. 정수가 아닌 값이 지정되면 이 필드는 무시된다.

이 필드명 외의 다른 필드는 모두 무시된다.

**참고:** **노트:** 행에 콜론이 포함되지 않으면 행 전체가 필드명으로 인식되며 값은 빈문자열로 취급한다.

### 예

#### 데이터만 있는 메시지

아래 예에서는 세 개의 메시지가 송신되고 있다. 최초의 메시지는 콜론 문자로 시작되고 있다. 주석이다. 앞서 설명한 바와 같이 코멘트는 메시지가 정기적으로 송신되지 않을 가능성이 있을 경우 킵얼라이브 용으로 사용할 수 있다.

두 번째 메시지는 값이 "some text"인 data 필드를 갖고있다. 세 번째 메시지는 값이 "another message\nwith two lines"인 data 필드를 갖고 있다. 값에 줄 바꿈 문자가 있음을 주의하라.
```

: this is a test stream

data: some text

data: another message
data: with two lines
```

#### 이름이 있는 이벤트

아래 예에서는 이름이 있는 이벤트를 몇개 송신하고 있다. 각각의 이벤트는 `event` 필드로 지정된 이벤트 명을 갖고 있고 또, 클라이언트에서 필요한 데이터를 포함하는 적절한 JSON 문자열을 값으로 갖는 `data` 필드도 있다. 물론 `data` 필드는 임의의 문자열 데이터를 가질 수 있다. 꼭 JSON 일 필요는 없다.
```

event: userconnect
data: {"username": "bobby", "time": "02:33:48"}

event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}

event: userdisconnect
data: {"username": "bobby", "time": "02:34:23"}

event: usermessage
data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."}
```

#### 조합형

이름 없는 메시지 또는, 이름이 있는 이벤트만 사용해야 하는 경우는 없다. 그리고 이것을 하나의 이벤트 스트림 내에서 혼합해 표현할 수 있다.
```

event: userconnect
data: {"username": "bobby", "time": "02:33:48"}

data: Here's a system message of some kind that will get used
data: to accomplish some task.

event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}
```

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


Element: children property - Web API

Element: children property
==========================

읽기 전용인 **`children`** 속성은 호출된 요소의 모든 자식 `요소` (en-US)를 포함하는 동적인 `HTMLCollection`을 반환합니다.

`Element.children`은 요소 노드만을 포함합니다. 텍스트 및 주석 노드와 같이 요소가 아닌 노드를 포함한 모든 자식 노드를 가져오기 위해서는 `Node.childNodes`를 사용합니다.

값
-

`HTMLCollection`은 `node`의 자식인 DOM 요소의 동적이고 정렬된 컬렉션입니다. 컬렉션의 `item()` 메서드 혹은 JavaScript 배열 형식 표기법을 사용하여 컬렉션의 개별 자식 노드에 접근할 수 있습니다.

요소에 요소 자식이 없으면 `children`은 `length`가 `0`인 빈 목록입니다.

예제
--

```js
const myElement = document.getElementById("foo");
for (const child of myElement.children) {
  console.log(child.tagName);
}
```

명세서
---

| Specification |
| --- |
| DOM Standard # ref-for-dom-parentnode-children① |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보기
-----

* `Node.childNodes`


HTMLElement: input 이벤트 - Web API

HTMLElement: input 이벤트
======================

Baseline 2023
-------------

Newly availableSince March 2023, this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.

* Learn more
* See full compatibility
* Report feedback
**`input`** 이벤트는 `<input>` (en-US), `<select>` (en-US), `<textarea>` (en-US) 요소의 `value`가 바뀔 때 발생합니다.

`input`은 또한 `contenteditable`이나 `designMode`가 활성화된 요소에서도 발생합니다. `contenteditable`과 `designMode`에서의 이벤트 대상은 '편집 호스트'가 됩니다. 이 두 특성이 여러 특성에 적용된 경우, 편집할 수 없는 부모를 둔 가장 가까운 조상 요소이 편집 호스트입니다.

`type=checkbox` 또는 `type=radio`인 `<input>` 요소에서는 HTML Living Standard 명세에 따라 사용자가 컨트롤을 토글할 때마다 `input` 이벤트가 발생해야 합니다. 하지만 역사적으로 이 명세가 항상 맞는 것은 아니었습니다. 호환성을 먼저 확인하세요. 이 요소들에서는 `change` 이벤트로 대체하는 방법도 있습니다.

`<textarea>` (en-US)와 텍스트 입력을 허용하는 `<input>` (en-US)(`type=text`, `type=tel`, ...)에서의 이벤트 인터페이스는 `InputEvent`고, 나머지 요소에서는 `Event`입니다.

**참고:** `input` 이벤트는 요소의 `value`가 변할 때마다 발생합니다. `change` 이벤트는 엔터 키를 누르거나 리스트에서 옵션을 선택하는 등 변경점을 '반영'해야 발생한다는 점에서 다릅니다.

구문
--

이벤트 이름을 `addEventListener()` 등의 메서드에 제공하거나, 이벤트 처리기 속성을 사용하세요.

```js
addEventListener("input", (event) => {});

oninput = (event) => {};
```

이벤트 유형
------

일반 `Event`.

예제
--

이 예제에서는 `<input>` (en-US) 요소의 값을 바꿀 때마다 기록을 남깁니다.

### HTML

```html
<input placeholder="텍스트 입력" name="name" />
<p id="values"></p>
```

### JavaScript

```js
const input = document.querySelector("input");
const log = document.getElementById("values");

input.addEventListener("input", updateValue);

function updateValue(e) {
  log.textContent = e.target.value;
}
```

### 결과

명세서
---

| Specification |
| --- |
| UI Events # event-type-input |
| HTML Standard # handler-oninput |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보기
-----

* 관련 이벤트
    + `beforeinput` (en-US)
    + `change`
    + `invalid` (en-US)


Element.getElementsByTagName() - Web API

Element.getElementsByTagName()
==============================

**Element.getElementsByTagName()** 메서드는 제공된 태그 네임(tag name)을 가지는 엘리먼트의 유효한 `HTML 컬랙션`를 반환합니다. 엘리먼트가 특정된 범위를 검색하는 경우, 그 자신의 엘리먼트는 제외하고, 특정된 엘리먼트의 하위에 있는 서브트리가 검색됩니다. 반환된 리스트가 *live* 상태이면, 리스트는 DOM 트리와 함께 자동적으로 업데이트 되는 것을 의미합니다. 결과적으로, 동일한 엘리먼트와 인자값으로 **Element.getElementsByTagName()** 을 여러번 호출할 필요성은 없어집니다.

HTML 문서 내에서 HTML 엘리먼트가 호출되면, getElementsByTagName는 진행하기 전에 그 인자들을 소문자로 변환합니다. 따라서, 카멜케이스로 이루어진 SVG 엘리먼트를 HTML문서의 서브트리 내에서 매칭하고자 하는 경우에는 `Element.getElementsByTagNameNS()` (en-US)을 사용해야 합니다.

Element.getElementsByTagName는 특정된 엘리먼트의 하위에 있는 엘리먼트 만을 검색하는 것으로 한정된 것을 제외하고는 `Document.getElementsByTagName()`와 실질적으로 유사합니다.

문법
--

```js
elements = element.getElementsByTagName(tagName);
```

* `elements` is a *live* `HTMLCollection` of found elements in the order they appear in the sub-tree. If no elements were found, the `HTMLCollection` is empty.
* 요소들의 서브트리안에서 나타나는 순서대로 발견된 요소들은 live `HTMLCollection` 입니다.
* `element` is the element from where the search should start. Note that only the descendants of this element are included in the search, but not the element itself.
* 검색이 시작되어야 하는 요소에서부터 요소들이라고 할 수 있습니다. 요소 그 자체가 아니라 요소들에서 파생된 요소만 검색에 포함된다는 것을 알아두세요.
* `tagName` is the qualified name to look for. The special string `"*"` represents all elements. For compatibility with XHTML, lower-case should be used.
* tagName은 둘러볼 만한 가치가 있는 이름이다.특별한 문자열인 "\*"은 모든 요소들을 대표합니다. XHTML과의 호환성을 위해서, 소문자가 사용되어야 합니다.
Example 예제
----------

```js
// check the alignment on a number of cells in a table.
var table = document.getElementById("forecast-table");
var cells = table.getElementsByTagName("td");
for (var i = 0; i < cells.length; i++) {
  var status = cells[i].getAttribute("data-status");
  if (status == "open") {
    // grab the data
  }
}
```

Specifications 명세
-----------------

| Specification |
| --- |
| DOM Standard # dom-element-getelementsbytagname |

Browser compatibility 브라우저 호환성
------------------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


blur - Web API

blur
====

`blur` 이벤트는 엘리먼트의 포커스가 해제되었을때 발생합니다. 이 이벤트와 `focusout` (en-US) 이벤트의 가장 다른점은 `focusout` (en-US) 은 이벤트 버블링이 발생합니다.

General info
------------

Specification

DOM L3

Interface
`FocusEvent` (en-US)
Bubbles

No

Cancelable

No

Target

Element

Default Action

None.

>
> **Note:** 이 이벤트가 처리될때 `Document.activeElement`의 값이 브라우저마다 다릅니다 (Firefox bug 452307): IE10은 이 값을 포커스가 옮겨가는 엘리먼트에 추가하지만, Firefox와 크롬은 도큐먼트의 `body` 에 추가합니다.
>
>
>

Properties
----------

| Property | Type | Description |
| --- | --- | --- |
| `target` 읽기 전용 | `EventTarget` | Event target (DOM element) |
| `type` 읽기 전용 | `DOMString` | The type of event. |
| `bubbles` 읽기 전용 | `Boolean` | Whether the event normally bubbles or not. |
| `cancelable` 읽기 전용 | `Boolean` | Whether the event is cancellable or not. |
| `relatedTarget` 읽기 전용 | `EventTarget` (DOM element) | null |

이벤트 위임
------

이 이벤트에 이벤트 위임을 적용하는 방법은 두가지가 있습니다 : 브라우저가 지원한다면 `focusout` 이벤트를 사용하거나, `addEventListener` (en-US)의 "useCapture" 파라미터를 `true`로 설정하세요:

### HTML Content

```html
<form id="form">
  <input type="text" placeholder="text input" />
  <input type="password" placeholder="password" />
</form>
```

### JavaScript Content

```js
var form = document.getElementById("form");
form.addEventListener(
  "focus",
  function (event) {
    event.target.style.background = "pink";
  },
  true,
);
form.addEventListener(
  "blur",
  function (event) {
    event.target.style.background = "";
  },
  true,
);
```

#### 결과

명세서
---

| Specification |
| --- |
| UI Events # event-type-blur |
| HTML Standard # handler-onblur |

브라우저 호환성
--------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

같이 보기
-----

* The `HTMLElement.blur()` (en-US) method
* Related events: `focus` (en-US), `focusin` (en-US), `focusout` (en-US)
* This event on `Window` targets: `blur` (en-US) event
* Focusing: focus/blur