-
Clientside Callbacksplotly dash 2023. 12. 13. 00:38
개요
dash 에서 javascript 로 작성된 라이브러리를 사용하기 위해 dash 의 Clientside Callback 을 활용 할 수 있다.
데모 페이지
https://wefree-tistory.fly.dev/javascript
Clientside Callback 동작
사용할려는 javascript library 를 다운 받아 src/assests 디렉토리 하위에 저장해 둔다. (Dash 에서 자동으로 import 해 줌)
dash 의 Clientside Callback 은 @callback 대신에 clientside_callback 을 사용하는데, 다음과 같이 동작한다.
- (python) dash.Input(id, property) 로 callback 이 trigger 될 조건 지정
- (javascript) javascript function 코드를 python string 으로 작성하는데, 이 javascript function 이 리턴하는 값이 dash.Output(id, property) 을 업데이트 한다.
- javascript function 에서 Promise 로 리턴되어도 좋다. (dash 도움말 참고)
- javascript library 가 callback 함수 기반이면, Promise 로 만들어 리턴되도록 하자. (참고)
문제 상황
javascript Sortable library 를 Clientside Callbacks 을 이용해 dash 에 포함시킬려고 하니, 아래와 같은 문제가 있었다.
- dash 의 Clientside Callback 은 기본적으로 dash 에서 만든 Html element (or component) 가 dash.Input() 으로 들어가야 하는데, drag & drop 할 element 를 Sortable library 에서 만들고 이벤트도 다 처리 하도록 되어 있다. 아래는 Sortable library 사용법이다.
<ul id="items"> <li>item 1</li> <li>item 2</li> <li>item 3</li> </ul> let el = document.getElementById('items'); Sortable.create(el, { animation: 150, onEnd: function (/**Event*/evt) { ... } });
- 그리고 Sortable.create() 는 한번만 호출 되어야 한다.
해결 방안
- dash 는 기본적으로 app 시작시 모든 callback 이 한번씩 호출 되므로, dummy element 에 Clientside Callback 을 걸어 Sortable.create() 는 한번만 호출되도록 한다.
from dash import Dash, html, dcc, Output, Input, State, callback from dash import clientside_callback clientside_callback( ''' (value) => { Sortable.create(items, { animation: 150, onEnd: (evt) => { const msg = [evt.oldIndex.toString(), evt.newIndex.toString()] console.log(msg); sessionStorage.setItem("session", JSON.stringify(msg)); document.getElementById("hidden_sync_button").click(); } }); } ''', Output("dummy-element", "style"), Input("dummy-element", "value"), )
- javascript 로 작성된 onEnd 에서 이벤트를 받는데, 이벤트 결과를 dash 로 전달해 python 코드로 가공할 수 있도록 해야 한다. 이를 위해 브라우저 Session Storage 에 이벤트 결과를 저장하고, dash 의 dcc.Store() 와 연동 될 수 있도록 한다. layout 에 dcc.Store() 를 추가한다. dcc.Store() 는 데이터를 json 으로 저장하고 읽기 때문에 javascript 에서 저장하거나(sessionStorage.setItem) 읽을 때(sessionStorage.getItem)도 이에 맞추어야 한다.
app = Dash(__name__) app.layout = html.Div( [ dcc.Store(id='session', storage_type='session'), # 목적에 따라 storage_type='memory' 로 변경 dcc.Textarea(id="my-sortable-output"), html.Button("Sync", id="hidden_sync_button", hidden=True), html.Ul( [ html.Li("item 1"), html.Li("item 2"), html.Li("item 3") ], id="items", ) ]
- clientside_callback() 에서 호출되는 sessionStorage.setItem() 에 의해 dcc.Store(id='session') 이 trigger 되거나, sessionStorage.setItem() 에서 저장한 데이터를 dcc.Store 를 이용해 읽을 수 있으면 좋겠지만.. client 와 react 간의 sync 이슈 때문에 그렇게는 되지 않는다. (이것 때문에 고생했다.ㅠ) - 참고
- 위의 문제 때문에 onEnd: sessionStorage.setItem() 에서 저장한 데이터를 다시 dcc.Store(id='session') 에 저장하는 프로세스가 필요했다. 이를 위해 아래와 같이 꼼수를 썼다. - 참고
- javascript 로 작성된 onEnd 에서 sessionStorage.setItem() 으로 데이터를 저장하면서 dash element (hidden_sync_button) 에 click event 를 발생 시켰다.
코드: document.getElementById("hidden_sync_button").click(); - dash 에서는 hidden_sync_button click() 이벤트가 발생하면, clientside_callback 을 통해 sessionStorage.setItem() 으로 저장한 데이터를 sessionStorage.getItem() 으로 읽어 dcc.Store(id='session') 에 저장되도록 한다.
- javascript 로 작성된 onEnd 에서 sessionStorage.setItem() 으로 데이터를 저장하면서 dash element (hidden_sync_button) 에 click event 를 발생 시켰다.
clientside_callback( ''' (n_clicks) => { let json = sessionStorage.getItem("session"); return JSON.parse(json); } ''', Output("session", "data"), Input("hidden_sync_button", "n_clicks"), )
- 최종적으로 dcc.Store() 에 저장된 데이터는 아래처럼 사용할 수 있다.
@callback( Output("my-sortable-output", "value"), Input("session", "modified_timestamp"), State("session", "data"), prevent_initial_call=True ) def action(_, data): data = data or ["0", "0"] return ' -> '.join(data)
주의 사항
dash 의 Clientside Callback 이용시 python string 으로 javascript 코드를 넣는 대신에, 따로 javascript 파일을 만들어 활용할 수 있다. 이때 외부 라이브러리를 사용할 필요가 있는데, 따로 만든 javascript 파일에서 외부 라이브러리를 import 하면 정의한 function 을 찾을 수 없다는 에러가 발생한다. 이를 위해 다음과 같이 개발한다.
- 외부 라이브러리(예: Sortable.min.js) 파일을 다운로드 후 src/assests 디렉토리 하위에 저장
- 로컬 개발 환경에서 개발 편의를 위해 npm 으로 사용하려는 외부 라이브러리 인스톨(예: npm install sortablejs)
- 이제 외부 라이브러리에 대한 code auto-complete 이 가능
- (다시 강조하지만) clientside callback 을 위해 따로 만든 javascript 파일에서 외부 라이브러리를 import 하면 안된다.
결론
왠만한 javascript 라이브러리는 모두 dash 에서 쉽게 사용 가능할 듯?
'plotly dash' 카테고리의 다른 글
Callback Gotchas & Form submit (0) 2024.11.30 html.Div 에 간단한 데이터 저장하기 (0) 2023.12.15 admin (multi-page) 화면 개발 (0) 2023.07.15 Advanced Callback (0) 2023.06.15 Interval (0) 2022.06.14