HTML, CSS, JavaScript를 사용하여 Tulip에서 나만의 위젯을 제작하세요
Users on Professional plans and above.
Please allow Outgoing access to Custom Widgets. See Networking requirements for a Tulip Cloud Deployment to learn more.* *.tulip-custom-widgets.com
앱으로 드래그 앤 드롭하는 항목을 위젯이라고 합니다. 여기에는 버튼, 이미지, 입력 상자 및 도구 모음에서 사용할 수 있는 기타 모든 요소가 포함됩니다.

사용자 정의 위젯을 사용하면 직접 코드를 작성하여 나만의 위젯을 만들 수 있으며, 이를 앱에 드래그 앤 드롭하여 상호작용할 수 있습니다. 이 기능을 통해 Tulip 사용자는 Tulip에서 가능한 범위를 더욱 넓힐 수 있습니다.
이 기능에 대한 개요는 다음 동영상을 참조하세요:
Tulip 라이브러리에서 사용 가능한 사용자 정의 위젯을 확인해 보세요.
사용자 정의 위젯 기본 사항
사용자 정의 위젯 관리 화면은 계정 설정 페이지에서 찾을 수 있습니다. 이곳에서 위젯을 생성하고 삭제할 수 있습니다. 사용자 정의 위젯 편집기를 사용하면 위젯 코드를 작성하고, 프로프(Props)와 이벤트(Events)를 생성하며, 위젯을 미리 볼 수 있습니다. 아래는 프로프와 이벤트가 무엇인지 설명하는 다이어그램입니다:

프로프(Props): 프로프는 사용자 정의 위젯과 Tulip 애플리케이션 간에 공유되는 데이터입니다. 프로프는 애플리케이션 편집기에서 노출되며, 앱 편집자가 해당 프로프와 연결할 변수, 테이블 레코드 또는 기타 정보를 선택할 수 있게 해줍니다.

이벤트: 이벤트는 위젯이 애플리케이션으로 보낼 수 있는 신호입니다. 이벤트를 통해 애플리케이션은 트리거로 응답할 수 있으며, 이벤트에는 관련 정보가 포함될 수 있습니다.

사용자 정의 위젯은 앱 편집기의 ‘사용자 정의(Custom)’ 드롭다운 메뉴를 통해 액세스할 수 있습니다.

위젯 만들기
Only users with access to account settings (i.e. Account Owners) or users with specific permission to access the custom widget page can use the custom widget editor.
위젯 편집기 화면은 네 개의 섹션으로 나뉩니다. 왼쪽 하단 섹션에 작성한 코드는 미리보기 섹션을 클릭하면 해당 섹션에 표시됩니다. 테스트를 위해 미리보기 섹션에서 Prop 값을 직접 변경할 수 있습니다.

프로프(Props) 및 이벤트(Events)와 상호작용하기 위한 특수 함수가 있습니다.
프로프 값 가져오기
It is not guaranteed that the prop being used by a widget will load before that widget loads, so all props associated with Table record fields, Aggregations, or any other dynamic value should include logic to support cases where they are null at the point the widget is loaded. See this section for details on triggering logic when a prop changes.
// 프로프 값 가져오기 getValue('My Prop');``// 프로프를 변수에 저장 let myVariable = getValue('My Prop');
Prop이 변경될 때 특정 작업 수행
// 프로프 값이 변경될 때마다 특정 작업을 수행 getValue('My Prop', (internalVariable) => { doSomething(internalVariable);});
Prop 값 설정하기
//텍스트 프로퍼티의 값을 'hello!'로 설정setValue('My Text Prop', 'hello!');
// 객체 내 항목의 값 설정setValue('My Object Prop', { 'Key inside object': 'new value' });```
### 이벤트 발생
```**이벤트 발생** fireEvent('event', *payload*);
// 페이로드 없이 이벤트 발생fireEvent('My Event');
//페이로드를 포함한 이벤트 발생fireEvent('My Event', myVariable);```
다음 다이어그램은 앱 내 사용자 정의 위젯을 통해 정보가 흐르는 과정을 보여줍니다. 입력 변수 데이터는 prop에 저장됩니다. 값이 변경되면 이벤트가 발생하고, 트리거가 데이터를 출력 변수에 저장합니다.
{height="" width=""}
## 위젯 빌드 데모
{height="576" width="1024"}
## 앱 가져오기/내보내기
이 기능에 대한 자세한 내용은 [‘가져오기 및 내보내기’를](/r230/docs/import-export) 참조하십시오.
### 내보내기
1. 사용자 지정 위젯 개요에서 내보내려는 위젯 옆에 있는 **점 세 개 메뉴** 를 선택합니다.
{height="" width=""}
1. **'내보내기'를** 선택합니다.
{height="" width="800"}
### 가져오기
1. 사용자 지정 위젯 개요 화면에서 상단의 **점 세 개 메뉴를** 선택합니다.
{height="" width="800"}
1. **'가져오기'를** 선택합니다.
{height="" width="350"}
1. .json 형식의 사용자 정의 위젯 파일을 찾습니다.
## 외부 라이브러리 활성화
외부 라이브러리는 사용자 정의 위젯의 기능을 더욱 확장해 줍니다. 외부 라이브러리는 JavaScript에서 직접 HTML을 작성하는 수작업을 대신 처리합니다. 외부 라이브러리를 사용하려면 해당 라이브러리를 사용하려는 각 위젯에서 활성화해야 합니다.
1. 위젯 내에서 페이지 오른쪽 상단의 **점 세 개 메뉴를** 클릭합니다.
{height="" width=""}
1. **'외부 라이브러리 활성화'를** 선택하세요.
{height="" width="250"}
1. 활성화하려는 **확장** 프로그램을 선택하세요.
{height="" width=""}
각 확장 프로그램의 기능에 대한 기본적인 설명은 다음과 같습니다:
* **jQuery** - HTML 요소를 보다 효율적으로 선택하고 조작할 수 있게 해줍니다.
* **D3 -** 데이터 시각화의 표준으로, 학습 곡선은 가파르지만 유연성이 매우 뛰어납니다.
* **Google Visualization -** 이름에서 알 수 있듯이 시각화를 수행하는 데 훌륭한 도구입니다.
* **Lodash -** Lodash는 자바스크립트 데이터 유형을 다루는 작업을 더 쉽게 만들어 주는 수많은 도구를 제공합니다.
* **ChartJS -** 온라인 자료가 풍부하며, 가능한 한 가장 간단한 그래프 작성 라이브러리입니다.
* **Moment -** Moment는 날짜와 시간을 다루는 데 필요한 수많은 도구를 제공합니다.
## 런타임 버전
새로운 런타임에서 사용자 정의 위젯이 로드되는 방식이 업데이트되었습니다( [‘사용자 정의 위젯 개선 사항’](/r230/docs/custom-widget-improvements) 참조). 이 변경 사항은 `getValue()`를 통해 가져오는 속성과 같이 위젯의 속성에 안정적으로 접근할 수 있는 시점에 영향을 미칩니다.
위젯이 사용하는 런타임 버전을 확인하려면:
* **사용자 정의 위젯 목록** 페이지나 편집기에서 위젯 이름 옆에 **'Legacy'** 배지가 표시되어 있다면, 해당 위젯은 기존 런타임을 사용합니다. 배지가 없다면, 위젯은 새로운 런타임을 사용합니다.
* 사용자 정의 위젯 정보 사이드 패널의 **‘런타임’** 섹션을 확인하세요.
* 사용자 정의 위젯의 **권한 설정에서** **‘사용자 정의 위젯 런타임’** 토글이 켜져 있다면, 해당 위젯은 새로운 런타임을 사용하고 있는 것입니다.
:::(Info) (NOTE)
By default, newly created Custom Widgets use the old runtime.
:::
사용자 정의 위젯의 런타임 버전을 전환하려면 **권한 설정에서** **‘사용자 정의 위젯 런타임’** 토글을 사용하십시오.{height="" width=""}
## 예시
다음은 위에서 설명한 기능들을 보여주는 [Tulip](https://tulip.co/library/) 라이브러리의 몇 가지 사용자 정의 위젯입니다:
### 간단한 타이머 위젯
[간단한 타이머 위젯](https://library.tulip.co/apps/simple-timer-widget)
앱 워크플로우 진행 중 경과 시간을 추적할 수 있는 사용자 친화적인 타이머 위젯입니다. 운영자는 앱 인터페이스에서 직접 타이머를 시작, 중지 및 재설정할 수 있어 작업이나 프로세스의 소요 시간을 측정하는 데 유용합니다.
{height="" width=""}
### 방사형 게이지 위젯
[방사형 게이지 위젯](https://library.tulip.co/apps/radial-gauge-widget)
이 위젯은 사용자 정의가 가능한 방사형 게이지를 통해 수치 값을 시각적으로 표현합니다. 기계 속도, 온도 또는 진행 상황과 같은 지표를 빠르고 이해하기 쉬운 형식으로 표시하는 데 이상적입니다.
{height="" width=""}
### 타임라인 위젯
[타임라인 위젯](https://library.tulip.co/apps/timeline-widget)
수평 타임라인을 따라 이벤트나 단계를 표시하여, 사용자가 프로세스 단계, 완료된 작업 또는 시간 기반 데이터를 한눈에 파악할 수 있도록 도와줍니다.
{height="" width=""}
## 모범 사례
### 1. 로드 시 실행되는 위젯 코드로 인한 의도하지 않은 부작용을 방지하세요
Tulip은 위젯이 렌더링될 때마다(Player뿐만 아니라 사용자 정의 위젯 편집기, 앱 편집기, 스테이션의 실시간 미리보기에서도) 위젯의 실제 코드를 실행합니다. 이를 통해 위젯의 정확하고 실시간적인 미리보기를 확인할 수 있습니다. 스크립트의 최상위 수준에 있는 부수적 효과를 일으키는 코드(예: API 호출, 테이블 쓰기, 알림)는 위젯이 렌더링될 때마다 실행되며, 여기에는 편집기에서 위젯이 다시 열리거나 미리보기가 새로 고쳐질 때마다 포함됩니다.
실제 환경에 영향을 미치는 모든 작업은 로드 시 자동으로 실행하는 대신, 명시적인 트리거(예: 버튼 누르기, 사용자 동작에 대한 `fireEvent` 호출, Prop 변경) 뒤에 배치하십시오.
```// 올바른 예: 앱이 명시적으로 요청할 때만 실행됩니다 getValue('TriggerIncrement', (shouldIncrement) => { if (shouldIncrement) { fetch("https://api.example.com/increment-counter", { method: "POST" }); }});
// 나쁨: 편집기 및 미리보기 모드를 포함하여 위젯이 로드될 때마다 실행됨fetch("https://api.example.com/increment-counter", { method: "POST" });```위젯이 데이터를 자동으로 로드해야 하는 경우, 반복적인 로드로 인해 원치 않는 결과가 발생하지 않도록 읽기 전용 또는 이뎀포텐트 엔드포인트를 사용하십시오.
### 2. 실제 부수 효과를 유발하지 않고 반복적으로 테스트하세요
실제 부수 효과가 있는 프로덕션 API를 호출하는 위젯을 개발할 때는, 반복 개발이 완료될 때까지 테스트 또는 스테이징 엔드포인트를 지정한 다음, 게시하기 전에 프로덕션 엔드포인트로 전환하십시오. 이렇게 하면 위젯을 편집하거나 미리 볼 때 발생하는 중복 레코드, 알림 또는 기타 부수 효과를 방지할 수 있습니다.
게시 후에도 프로덕션 환경에서 반드시 수행되어야 하는 작업의 경우, 로딩 타이밍에 의존하기보다는 위젯 자체의 UI에 확인 단계를 포함시키세요. 가시적인 동작(예: 버튼 클릭)을 요구함으로써, 위젯을 보는 사용자가 효과가 발생하는 시점을 제어할 수 있게 됩니다.
```// 위젯 자체가 몇 번 렌더링되었는지와 관계없이,// 실제 동작이 실행되기 전에 명시적인 클릭이 필요합니다.document.getElementById('submitBtn')?.addEventListener('click', () => { fetch("https://api.example.com/submit-order", { method: "POST" });});
3. 프로프 관리 - 프로프 값 유효성 검사
사용자 정의 위젯이 데이터를 안정적으로 처리하도록 하세요. 프로프는 위젯으로 정보를 전달하는 방법이지만, 그 값은 변경될 수 있거나 초기에는 undefined일 수 있습니다. 프로프에 정해진 유형이 있더라도 초기에는 undefined일 수 있으며, 이로 인해 사용자 정의 위젯 코드에서 오류가 발생할 수 있습니다. 올바른 패턴을 따르면 오류를 방지하고 위젯을 더욱 견고하게 만들 수 있습니다.
아래 예제에서는 getValue의 두 번째 매개변수로 콜백 함수를 사용합니다. 이 콜백은 사용자 정의 위젯이 업데이트된 prop 값을 수신할 때마다 실행됩니다.
프로퍼티의 초기 값이 undefined일 수 있고 나중에 업데이트된 값을 수신할 수 있으므로, 업데이트된 값을 수신하는 즉시 구성을 적용해야 합니다. 콜백 함수 없이 getValue를 사용하면 코드 실행 시점의 프로퍼티 값이 반환됩니다.
getValue('config', function(config) { if (config) { applyConfiguration(config); }});
다음 예제는 프로퍼티의 초기 값이 undefined일 수 있으므로 권장되지 않습니다:const config = getValue('config');applyConfiguration(config);
4. 이벤트 처리 - 데이터 유효성 검사
이벤트 데이터에는 유형이 있습니다. 데이터 유형이 잘못된 이벤트를 발생시켜도 사용자 정의 위젯 코드의 실행이 중단되거나 오류가 발생하지는 않지만, fireEvent를 통해 전송되는 데이터가 유효하고 올바른 유형인지 확인하기 위해 자체적인 검증 로직을 구현하는 것이 좋은 관행입니다.
이러한 관행은 예기치 않은 동작을 방지하고 코드의 예측 가능성을 높여줍니다.
if (data != null && typeof data === 'string') { fireEvent(eventName, data);} else { // 필요에 따라 유효하지 않은 데이터에 대한 처리를 수행합니다.}
5. 비동기 프로퍼티 액세스를 위해 콜백 함수 사용
setValue()를 호출하면 프로퍼티 업데이트는 비동기적으로 이루어집니다. 즉, 사용자 정의 위젯이 iframe 내에서 메인 애플리케이션과 통신하기 때문에 업데이트가 즉시 반영되지 않습니다. setValue() 요청은 전송되지만, 코드는 변경 사항이 적용될 때까지 기다리지 않고 계속 실행됩니다. 바로 뒤에 getValue()를 사용하려고 하면 이전 값이 반환될 가능성이 높습니다.
가장 최신 데이터를 얻으려면 항상 콜백 함수와 함께 getValue()를 사용하세요. 콜백은 프로퍼티가 성공적으로 업데이트된 경우에만 실행되므로, 최신 정보를 기반으로 작업할 수 있습니다.
다음 예제는 이 모범 사례를 보여줍니다:```// 올바른 예: 프로퍼티의 값을 비동기적으로 가져오기const newConfig = { setting: 'value' };setValue('config', newConfig);
// 프로퍼티 업데이트가 완료되면 콜백 함수가 실행됩니다.getValue('config', function(updatedConfig) { console.log('프로퍼티가 업데이트되었습니다. 새로운 구성은 다음과 같습니다:', updatedConfig); // 최신 값이 보장된 상태에서 작업을 수행합니다. applyConfiguration(updatedConfig);});```
다음 예제는 피해야 할 방법을 보여줍니다:```// 잘못된 예: 예상대로 작동하지 않을 수 있습니다const newConfig = { setting: 'value' };setValue('config', newConfig);
// 이 경우 새로운 값이 아닌 기존 값이 콘솔에 출력될 가능성이 높습니다.const currentConfig = getValue('config'); ```
6. API 호출 시 지원되는 헤더 사용
사용자 정의 위젯에서 외부 API 호출을 수행할 때는 CORS(Cross-Origin Resource Sharing) 문제를 방지하기 위해 HTTP 헤더를 주의해서 사용해야 합니다. 사용자 정의 위젯은 특정 보안 제한이 적용된 iframe 환경에서 실행됩니다.
명시적으로 지원되는 헤더만 사용하십시오. Content-Type이나 기타 사용자 정의 헤더와 같이 지원되지 않는 헤더를 설정하면 CORS 오류가 발생하여 API 호출이 작동하지 않을 수 있습니다.
다음 예제는 이 모범 사례를 보여줍니다:// 올바른 예: 사용자 정의 헤더가 없는 간단한 API 호출fetch("https://akhatri.bulb.cloud/api/v3/w/1/tables", { headers: { Authorization: "<API 키>", },}) .then((response) => response.json()) .then((data) => console.log(data)) .catch((error) => console.error("오류:", error));
다음 예제는 피해야 할 사항을 보여줍니다:// 잘못된 예: Content-Type 헤더를 설정하면 CORS 문제가 발생합니다fetch("https://api.example.com/data", { headers: { Authorization: "<API 키>", "Content-Type": "application/json", // 이 경우 CORS 오류가 발생합니다 },})
If you encounter CORS errors, first remove any custom headers you've set (especially Content-Type) and test with minimal headers. The iframe security model restricts which headers can be set from custom widgets.
7. 런타임에 따라 위젯 초기화
자세한 내용은 런타임 버전을 참조하십시오.
새로운 런타임
새로운 런타임에서 src 속성은 빈 셸 HTML 파일을 반환합니다. 이 셸 파일은 로드되면 상위 앱에 메시지를 보냅니다. 상위 앱은 사용자 정의 위젯 코드, 유틸리티 함수(예: getValue()), 그리고 iframe에 삽입할 초기 prop 값을 전송합니다.
이 때문에 빈 셸이 로드될 때, 즉 위젯 코드가 주입되기 전에 DOMContentLoaded 및 window.onload 이벤트가 발생합니다. 이러한 이벤트에 연결된 핸들러는 등록 시기가 너무 늦습니다. 이벤트가 이미 발생했기 때문에 콜백은 절대 실행되지 않습니다. 아래 예제에서 볼 수 있듯이, 해당 핸들러 내의 코드는 아무런 오류 메시지 없이 건너뜁니다:
document.addEventListener('DOMContentLoaded', function() { // SHELL이 로드될 때, 사용자 코드 삽입 전에 발생 // 사용자 코드는 아직 삽입되지도 않았습니다! const myProp = getValue('myProp'); // 오류: getValue가 정의되지 않았습니다});
custom widgets 에서는 DOMContentLoaded나 window.onload를 사용할 필요가 없습니다. Tulip은 타사 라이브러리, 사용자 HTML, JavaScript 순으로 리소스를 로드합니다. JavaScript가 실행될 때쯤이면 DOM은 이미 사용 가능한 상태입니다. 아래 예제에서 볼 수 있듯이, 로드 이벤트를 기다릴 필요 없이 DOM 요소에 직접 접근할 수 있습니다:
// 직접 호출해도 안전합니다. 이 코드가 실행될 때쯤이면 DOM은 이미 로드된 상태입니다.const btn = document.getElementById('my-btn');
구형 런타임
구형 런타임에서는 srcDoc 속성이 getValue() 와 같은 유틸리티 함수 및 초기 prop 값과 함께 사용자 정의 위젯 코드를 iframe에 삽입합니다. 위젯 코드는 처음부터 문서의 일부이므로, iframe 콘텐츠가 로드되는 즉시 getValue() 와 prop을 사용할 수 있습니다.그 결과, 다음 예제에서 볼 수 있듯이 구형 런타임에서는 DOMContentLoaded 와 window.onload가 예상대로 작동합니다:
document.addEventListener('DOMContentLoaded', function() { // 사용자의 HTML이 준비되면 발생 // 프로퍼티는 이미 사용 가능 const myProp = getValue('myProp'); // 즉시 작동});
{{snippet.원하는 정보를 찾으셨나요?}}

