日本語と英語で検索しても全然ロクな文献がヒットしなかったので、検索ひっかかるように併記しておきます
Adding a 'Select All' and 'Remove All' Button to a dcc.CheckList.
達成したいこと
- 複数個のチェックボックスを、別に用意したボタンによって全選択・全解除する
結果だけ知りたい方は下へスクロールしてください
やったこと
dcc.CheckListのvalueをcallbackで制御する
チェックリストとボタンだけのさっぱりした環境で調査
当初のソース / at first
python {ファイル名.py} access localhost:{任意のポート}
で起動できます
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table
from dash.dependencies import Input, Output, State
from flask import Flask, request
server = Flask(__name__)
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets, server=server)
app.title = 'checklist-test'
selected_key = None
checklists = dcc.Checklist(
id='checklist-states',
options=[
{'label': 'New York City', 'value': 'NYC'},
{'label': 'Montréal', 'value': 'MTL'},
{'label': 'San Francisco', 'value': 'SF'}
],
value=['MTL', 'SF']
)
app.layout = html.Div(id='main',children=[
html.H1(children='チェックリストのテスト'),
dcc.Location(id='location', refresh=False),
html.Div(className='main-block', children=[checklists]),
html.Div(className='second', children=[
html.Button('全選択', id='filter-check-button', className='filter_button', n_clicks=0),
html.Button('全解除', id='filter-remove-button', className='filter_button', n_clicks=0)
])
])
@app.callback(
[Output('checklist-states', 'value')],
[Input('filter-check-button', 'n_clicks'),
Input('filter-remove-button', 'n_clicks')]
)
def remove_check(all_check, all_remove):
if all_check:
return ['NYC', 'MTL', 'SF']
if all_remove:
return []
if __name__ == '__main__':
app.run_server(host='0.0.0.0', debug=True)
CheckListのvalueへ任意のリストを返却すればいけるっしょ!と思ったが違う
全選択ボタンを押下したとき return ['NYC', 'MTL', 'SF']
dash.exceptions.InvalidCallbackReturnValue: Invalid number of output values for ..checklist-states.value...
Expected 1 got 3
全解除ボタンを押下したとき return []
dash.exceptions.InvalidCallbackReturnValue: Invalid number of output values for ..checklist-states.value...
Expected 1 got 0
どうも勝手に皮むきするっぽいので応急処置
if all_check:
return [['NYC', 'MTL', 'SF']]
if all_remove:
return [[]]
動くもののエラーは出る
どうもボタンがロードされる時?に一度実行されるっぽく、もろもろが未定義な状態で返り値を渡してしまうためのエラーっぽい
dash.exceptions.InvalidCallbackReturnValue: The callback ..checklist-states.value.. is a multi-output.
Expected the output type to be a list or tuple but got None.
クリック以外で呼ばれたときを明示的に対処する
checklistのvalueをStateを使って読み込む
callbackにState
@app.callback(
[Output('checklist-states', 'value')],
[Input('filter-check-button', 'n_clicks'),
Input('filter-remove-button', 'n_clicks')],
[State('checklist-states', 'value')] # callbackが発生したときに値を取得する
)
def update_check(all_check, all_remove, value_checking):
どっちもNoneなら元々valueに入ってる値を返す
皮むき対策も忘れずに
if all_check:
return [['NYC', 'MTL', 'SF']]
if all_remove:
return [[]]
if all_check is None and all_remove is None:
return [value_checking] # 皮むき対策
ボタンを何回もクリックする
n_clicksがダメっぽい
いけた!と思ったらクリック1発目しか機能しない
調べてみるとn_clickはクリック回数をカウントするという機能らしく、クリックすると0,1,2,3,4...と値が増えていくらしい
現在Dashには、そもそもクリックされたことだけを検知する機能は実装されていないらしい
代わりに クリックしたunixtimestamp(ミリ秒まで)を取得する n_clicks_timestampというものが用意されているので、それを使用する
n_clicks_timestampを使う
イベントが発生したタイミングと今が1秒以内ならオッケーという処理にしておく
if not all_check is None:
if (time.time() * 1000 - all_check) < 1000:
return [['NYC', 'MTL', 'SF']]
if not all_remove is None:
if (time.time() * 1000 - all_remove) < 1000:
return [[]]
if all_check is None and all_remove is None:
return [checking]
結果
𝐬𝐮𝐜𝐜𝐞𝐬𝐬
最終的なソース / in the end
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table
from dash.dependencies import Input, Output, State
from flask import Flask, request
import time
server = Flask(__name__)
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets, server=server)
app.title = 'checklist-test'
selected_key = None
checklists = dcc.Checklist(
id='checklist-states',
options=[
{'label': 'New York City', 'value': 'NYC'},
{'label': 'Montréal', 'value': 'MTL'},
{'label': 'San Francisco', 'value': 'SF'}
],
value=['MTL', 'SF']
)
app.layout = html.Div(id='main',children=[
html.H1(children='チェックリストのテスト'),
dcc.Location(id='location', refresh=False),
html.Div(className='main-block', children=[checklists]),
html.Div(className='second', children=[
html.Button('全選択', id='filter-check-button', className='filter_button'),
html.Button('全解除', id='filter-remove-button', className='filter_button')
])
])
@app.callback(
[Output('checklist-states', 'value')],
[Input('filter-check-button', 'n_clicks_timestamp'),
Input('filter-remove-button', 'n_clicks_timestamp')],
[State('checklist-states', 'value')]
)
def update_check(all_check, all_remove, checking):
if not all_check is None:
if (time.time() * 1000 - all_check) < 1000:
return [['NYC', 'MTL', 'SF']]
if not all_remove is None:
if (time.time() * 1000 - all_remove) < 1000:
return [[]]
if all_check is None and all_remove is None:
return [checking]
if __name__ == '__main__':
app.run_server(host='0.0.0.0', debug=True)