Object-oriented dash components

The dccType is basically a component factory that allows for a simpler and less error-prone syntax for generating callbacks and referencing components in an object-oriented manner

import dash
from dash.dependencies import Input, Output, State, Event
import dash_core_components as dcc
import dash_html_components as html

class dccType(object):
	def __init__(self, divtype, component_property = 'value', string = str, **kwargs):
		self.divtype = divtype
		self.kwargs = kwargs
		self.component_property = component_property
		self.string = string

	def div(self, *args, **kwargs):
		div_dict = {}
		for k,v in self.kwargs.items():
		   try:
			  div_dict[k] = v(*args)
		   except:
			  div_dict[k] = v
		for k,v in kwargs.items():
		   div_dict[k] = v
		return self.divtype(**div_dict)

	def input(self, *args, **kwargs):
		component_property = kwargs.pop('component_property', self.component_property)
		try:
			return Input(self.id(*args), component_property)
		except:
			return Input(self.kwargs['id'], component_property)

	def output(self, *args, **kwargs):
		component_property = kwargs.pop('component_property', self.component_property)
		try:
			return Output(self.id(*args), component_property)
		except:
			return Output(self.kwargs['id'], component_property)


	def state(self, *args, **kwargs):
		component_property = kwargs.pop('component_property', self.component_property)
		try:
			return State(self.id(*args), component_property)
		except:
			return State(self.kwargs['id'], component_property)

	def event(self, *args, **kwargs):
		component_property = kwargs.pop('component_property', self.component_property)
		try:
			return Event(self.id(*args), component_property)
		except:
			return Event(self.kwargs['id'], component_property)

	def id(self, *args):
		return self.kwargs['id'](*args)
  


app = dash.Dash()
app.css.append_css({'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'})
app.config['suppress_callback_exceptions']=False

greeting = dccType(
	html.Div,
	id = 'an-id-you-never-need-to-type-again',
	children = ['Hello World'],
	component_property = 'children')


greeting_options = dccType(
	dcc.Dropdown,
	id = 'dropdown-id-blah-blah',
    options=[
            {'label': 'Hi there!', 'value': 'Hi there!'},
            {'label': 'Hello Cleveland!', 'value': 'Hello Cleveland!'},
            {'label': 'Hi San Francisco!', 'value': 'Hi San Francisco!'}
        ],
    value='Hi there!', #the default component_property is 'value'
	)



### Lambdas for component properties allows for more component reuse
greeting_style = dccType(
	dcc.Dropdown,
	id = lambda prefix: '{}-style'.format(prefix),
	options = [
		dict(label = 'white', value = 'white'),
		dict(label = 'red', value = 'red'),
		dict(label = 'blue', value = 'blue')
	],
	value =  'white')


options_outer = dccType(
	html.Div,
	children = [greeting_options.div(), 
				greeting_style.div('background'),
				greeting_style.div('text')],
	className = 'two columns')

app.layout = html.Div(children=[greeting.div(), options_outer.div()])


@app.callback(greeting.output(), [greeting_options.input()])
def update_greeting(dropdown_value):
	return dropdown_value

@app.callback(
	greeting.output(component_property = 'style'), 
	[greeting_style.input('background'), greeting_style.input('text')])
def update_greeting_style(bgcolor, textcolor):
	return dict(background = bgcolor, color = textcolor)
	

if __name__ == '__main__':
    app.run_server(debug=True, port = 8000)
3 Likes

This is from a couple years ago now, but I think it is a great idea!

There is a nice project from AlgorithmHub which takes this idea a step or two further (including an installable module on PyPI):


https://dash-building-blocks.readthedocs.io/en/latest/motivation.html
This version does include a Store class implemented using the hidden Div hack, which is no longer necessary thanks to the the recent addition into the Dash Core Components library:
https://dash.plotly.com/dash-core-components/store

In any case, I would love to see something like this incorporated into the core library someday! Together with the new callback_context, it makes for a much cleaner API.

I have implemented a similar component system for our projects, and it’s very benefical especially when building reusable components for multi-page apps. It would be great if at least .input(), .output() and .state() -methods like in Dash-building blocks would be added to the official Dash BaseComponent. Also, a method like is_triggered() would be valuable for checking if a certain component has been triggered in a callback.

This is pretty cool, thanks for sharing @dstarkebaum and @psip!

Another useful thing here would be namespacing component IDs: https://github.com/plotly/dash/issues/637

@psip - Any chance there is any code that you could share? Either of your current implementation or your dream API :smiley_cat: Curious to see what you might imagine an API for this to look like.