Awesome. Thanks @xavArtley.
I have a feeling that the roboto
fonts have to be included to be really be “material”.
Awesome. Thanks @xavArtley.
I have a feeling that the roboto
fonts have to be included to be really be “material”.
__javascript__ = [
"https://unpkg.com/react@17/umd/react.development.js",
"https://unpkg.com/react-dom@17/umd/react-dom.development.js",
"https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js",
]
__css__ = ["https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"]
a more advanced example:
from panel.reactive import ReactiveHTML
import panel as pn
import param
import time
class MaterialButton(ReactiveHTML):
clicks = param.Number(default=0)
_open = param.Event()
_template = """
<div id="materialbutton"></div>
"""
_scripts = {
'render': """
const {
Button,
colors,
createTheme ,
CssBaseline,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Icon,
MuiThemeProvider,
Typography,
withStyles,
} = MaterialUI;
const theme = createTheme ({
palette: {
primary: {
light: colors.purple[300],
main: colors.purple[500],
dark: colors.purple[700],
},
secondary: {
light: colors.green[300],
main: colors.green[500],
dark: colors.green[700],
},
},
});
const styles = theme => ({
root: {
textAlign: 'center',
paddingTop: theme.spacing(1),
},
icon: {
marginRight: theme.spacing(1),
},
});
class Index extends React.Component {
state = {
open: false,
};
handleClose = () => {
this.setState({
open: false,
});
};
handleClick = () => {
this.setState({
open: true,
});
};
componentDidMount = () => {
state.reactElem = this;
};
render() {
const { classes } = this.props;
const { open } = this.state;
return React.createElement(MuiThemeProvider, {
theme: theme,
ref: this.myRef
}, React.createElement("div", {
className: classes.root
}, React.createElement(CssBaseline, null), React.createElement(Dialog, {
open: open,
onClose: this.handleClose
}, React.createElement(DialogTitle, null, "Super Secret Password"), React.createElement(DialogContent, null, React.createElement(DialogContentText, null, "1-2-3-4-5")), React.createElement(DialogActions, null, React.createElement(Button, {
color: "primary",
onClick: this.handleClose
}, "OK"))), React.createElement(Typography, {
variant: "subtitle1",
gutterBottom: true
}, "Material-UI"), React.createElement(Typography, {
variant: "h6",
gutterBottom: true
}, "example project"), React.createElement(Button, {
variant: "contained",
color: "secondary",
onClick: () => {
data.clicks = data.clicks + 1
this.handleClick()
}
}, React.createElement(Icon, {
className: classes.icon
}, "fingerprint"), "Super Secret Password")));
}
}
const App = withStyles(styles)(Index);
ReactDOM.render(React.createElement(App, null), materialbutton)
console.log(data._open)
""",
"clicks": """
console.log("Clicked")
""",
"_open": """
if (data._open)
state.reactElem.handleClick()
data._open = false
"""
}
__javascript__ = [
"https://unpkg.com/react@17.0.2/umd/react.development.js",
"https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js",
"https://unpkg.com/@material-ui/core@4.12.3/umd/material-ui.development.js",
"https://unpkg.com/babel-standalone@latest/babel.min.js"
]
__css__ = ["https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap", "https://fonts.googleapis.com/icon?family=Material+Icons"]
pn.extension()
@xavArtley So cool! Thank you! let me learn more about your code. It is really exited to find out this can be work!
I feel excited about this new feature, but since I am really a beginner, I have a lot of additional questions in this scope.
At first, how do I get the error when I am using ReactiveHTML?
Here, I am trying to get the value from the Material-ui TextField. However, it seems that I found an error so the text field is not shown. I am expecting my error is on value
and onChange
because without those I could get the TextField.
class MaterialTextField(ReactiveHTML):
value = param.String(default='')
name = param.String()
def __init__(self, **params):
if not "name" in params:
params["name"]=''
super().__init__(**params)
_template = """
<div id="textfield">
</div>
"""
_dom_events = {'textfield': ['change']}
_scripts = {
'render': """
class Index extends React.Component {
state = {
myValue: '',
};
_handleChange = (e) => {this.setState({
myValue: e.target.value
})
};
render() {
return React.createElement(
MaterialUI.TextField,
{label:data.name,
variant:"filled",
value: this.state.myValue,
onChange: this._handleChange
}
)
}
};
const App = Index;
ReactDOM.render(React.createElement(App, null), textfield)
""",
}
__javascript__ = [
"https://unpkg.com/react@17.0.2/umd/react.development.js",
"https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js",
"https://unpkg.com/@material-ui/core@4.12.3/umd/material-ui.development.js",
"https://unpkg.com/babel-standalone@latest/babel.min.js"
]
__css__ = ["https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap", "https://fonts.googleapis.com/icon?family=Material+Icons"]
pn.extension()
Edit0: my bad, I just realize the problem in my code is mainly because of ;
are missing. Now the code above could show the TextField. However, it is not assigned to param.value
.
You will have to play with ref and inputRef to connect React elements with ReactiveHTML params:
from panel.reactive import ReactiveHTML
import panel as pn
import param
import time
class MaterialTextField(ReactiveHTML):
value = param.String(default='')
name = param.String()
def __init__(self, **params):
if not "name" in params:
params["name"]=''
super().__init__(**params)
_template = """
<div id="textfield"></div>
"""
_scripts = {
'render': """
class TextInput extends React.Component {
handleChange = (e) => {
data.value = e.target.value
}
render() {
const {defaultValue} = this.props
return React.createElement(
MaterialUI.TextField,
{
label:data.name,
variant:"filled",
defaultValue: defaultValue,
onChange: this.handleChange,
inputRef: (el) => {this.textfield = el}
},
)
}
}
ReactDOM.render(React.createElement(TextInput, {ref: (el) => {state.el = el}, defaultValue: data.value}), textfield)
""",
"value": """
state.el.textfield.value = data.value
"""
}
__javascript__ = [
"https://unpkg.com/react@17.0.2/umd/react.development.js",
"https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js",
"https://unpkg.com/@material-ui/core@4.12.3/umd/material-ui.development.js",
"https://unpkg.com/babel-standalone@latest/babel.min.js"
]
__css__ = ["https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap", "https://fonts.googleapis.com/icon?family=Material+Icons"]
pn.extension()
Thanks @xavArtley for your revision! it is really helpful! I think I will have another additional questions, but I will try harder first.
Hi all,
Now I am trying to do use Material-ui Autocomplete (React Autocomplete component - Material-UI), which is one of my main target to use material-ui.
Really sorry, but still I could not get it correctly without any help.
This is my code now.
class MaterialAutocomplete(ReactiveHTML):
value = param.String(default='')
options = param.List(default=[])
name = param.String()
def __init__(self, **params):
if not "name" in params:
params["name"]=''
super().__init__(**params)
_template = """
<div id="autocomplete">
</div>
"""
_dom_events = {'autocomplete': ['change']}
_scripts = {
'render': """
const {
Autocomplete,
TextField,
} = MaterialUI;
class AutoComplete extends React.Component {
handleChange = (e) => {
data.value = e.target.value
}
render() {
return React.createElement(
Autocomplete,
{
options: data.options,
renderInput: React.createElement(
TextField, {
label:data.name,
variant:"filled",
defaultValue: defaultValue,
onChange: this.handleChange,
inputRef: (el) => {this.textfield = el}
},
)
}
)
}
}
ReactDOM.render(React.createElement(
AutoComplete,
{ref: (el) => {state.el = el},
defaultValue: data.value}
),
autocomplete)
""",
"_update_value": """
state.el.textfield.value = data.value
"""
}
__javascript__ = [
"https://unpkg.com/react@17.0.2/umd/react.development.js",
"https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js",
"https://unpkg.com/@material-ui/core@4.12.3/umd/material-ui.development.js",
"https://unpkg.com/babel-standalone@latest/babel.min.js",
"https://cdn.jsdelivr.net/npm/@material-ui/lab@4.0.0-alpha.60/index.min.js"
]
__css__ = [
"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap",
"https://fonts.googleapis.com/icon?family=Material+Icons"
]
pn.extension()
It seems it is not possible to use material-ui autocomplete with react CDN (reactjs - How to use material-ui's Autocomplete component when using a react CDN - Stack Overflow). However, I am not sure if this is the main issue of the present code (I believe there are a lot of problem in this code).
Thanks in advance.
Any comments or suggestions are greatly appreciated.
Arifin
PS: When using Jupyterlab, the controls panel is shrinking but could recovered if I changed tab. I also got this behavior when I use the materialui from panel gallery (Materialui — Panel 0.12.1 documentation)
Thanks to @Marc, the issue is up here (MaterialUI Gallery Example does not look nice · Issue #2680 · holoviz/panel · GitHub)
Autocomplete is not include in the umd version of the official cdn. So you won’t be able to use it.
You can try may be this one material-ui-w-autocomplete - npm
Thanks @xavArtley for you quick comment. I tried to use one that you have suggested it but still not showing anything. Please kindly let me know if you have any other suggestions.
class MaterialAutocomplete(ReactiveHTML):
value = param.String(default='', allow_None=True)
options = param.List(default=[])
label = param.String()
def __init__(self, **params):
if not "name" in params:
params["name"]=''
super().__init__(**params)
_template = """
<div id="autocomplete">
</div>
"""
_scripts = {
'render': """
function _extends() {
_extends =
Object.assign ||
function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
const {
Autocomplete,
TextField
} = MaterialUI;
class ReactiveAutoComplete extends React.Component {
constructor(props) {
super(props)
this.state = {
value: data.value,
options: data.options,
label: data.label
}
}
render() {
return React.createElement(Autocomplete, {
value: this.state.value,
options: this.state.options,
renderInput: params => React.createElement(TextField, _extends({}, params, {
label:this.state.label,
variant:"filled",
})),
onChange: (_, value) => {
this.setState({value: value})
data.value = value
},
})
}
}
ReactDOM.render(
React.createElement(ReactiveAutoComplete, {ref: (el) => state.el=el}),
autocomplete
)
""",
"value": """
state.el.setState({value: data.value})
""",
"options": """
state.el.setState({options: data.options})
""",
"label": """
state.el.setState({label: data.label})
"""
}
__javascript__ = [
"https://unpkg.com/react@17.0.2/umd/react.development.js",
"https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js",
"https://unpkg.com/material-ui-w-autocomplete@4.9.8/material-ui-w-autocomplete.development.js",
]
__css__ = [
"https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap",
"https://fonts.googleapis.com/icon?family=Material+Icons"
]
pn.extension()
I think Material UI is a limit of the CDN use.
It may be more interesting to use bokeh Custom Extension to wrap Material UI
Edit : Actually not that easy to wrap React components into a Bokeh extension
Could be interesting to see a Vue example of bootstrap or material components in ReactiveHTML one day to see how they play together.
@xavArtley thank you for your solutions! I am sorry for my lack of knowledges and experiences. By the way, do you use other tools to catch some error when building the rendering scripts?
@xavArtley , I saw an error when the autocomplete value
becomes None
(when the text field is empty or x button is used) although value = param.String(default='', allow_None=True)
. Is it a bug in ReactiveHTML
?
Traceback (most recent call last):
File "/envs/lib/python3.6/site-packages/pyviz_comms/__init__.py", line 325, in _handle_msg
self._on_msg(msg)
File "/envs/lib/python3.6/site-packages/panel/viewable.py", line 273, in _on_msg
doc.unhold()
File "/envs/lib/python3.6/site-packages/bokeh/document/document.py", line 669, in unhold
self._trigger_on_change(event)
File "/envs/lib/python3.6/site-packages/bokeh/document/document.py", line 1180, in _trigger_on_change
self._with_self_as_curdoc(event.callback_invoker)
File "/envs/lib/python3.6/site-packages/bokeh/document/document.py", line 1198, in _with_self_as_curdoc
return f()
File "/envs/lib/python3.6/site-packages/bokeh/util/callback_manager.py", line 161, in invoke
callback(attr, old, new)
File "/envs/lib/python3.6/site-packages/panel/reactive.py", line 301, in _comm_change
self._process_events({attr: new})
File "/envs/lib/python3.6/site-packages/panel/reactive.py", line 262, in _process_events
self.param.set_param(**self_events)
File "/envs/lib/python3.6/site-packages/param/parameterized.py", line 1526, in set_param
self_._batch_call_watchers()
File "/envs/lib/python3.6/site-packages/param/parameterized.py", line 1665, in _batch_call_watchers
self_._execute_watcher(watcher, events)
File "/envs/lib/python3.6/site-packages/param/parameterized.py", line 1627, in _execute_watcher
watcher.fn(*args, **kwargs)
File "/envs/lib/python3.6/site-packages/panel/param.py", line 522, in link
widget.param.set_param(**updates)
File "/envs/lib/python3.6/site-packages/param/parameterized.py", line 1526, in set_param
self_._batch_call_watchers()
File "/envs/lib/python3.6/site-packages/param/parameterized.py", line 1665, in _batch_call_watchers
self_._execute_watcher(watcher, events)
File "/envs/lib/python3.6/site-packages/param/parameterized.py", line 1627, in _execute_watcher
watcher.fn(*args, **kwargs)
File "/envs/lib/python3.6/site-packages/panel/reactive.py", line 252, in _param_change
self._apply_update(events, msg, model, ref)
File "/envs/lib/python3.6/site-packages/panel/reactive.py", line 206, in _apply_update
self._update_model(events, msg, root, model, doc, comm)
File "/envs/lib/python3.6/site-packages/panel/reactive.py", line 219, in _update_model
model.update(**msg)
File "/envs/lib/python3.6/site-packages/bokeh/core/has_props.py", line 441, in update
setattr(self, k, v)
File "/envs/lib/python3.6/site-packages/bokeh/core/has_props.py", line 298, in __setattr__
super().__setattr__(name, value)
File "/envs/lib/python3.6/site-packages/bokeh/core/property/descriptors.py", line 552, in __set__
self._internal_set(obj, value, setter=setter)
File "/envs/lib/python3.6/site-packages/bokeh/core/property/descriptors.py", line 784, in _internal_set
value = self.property.prepare_value(obj, self.name, value)
File "/envs/lib/python3.6/site-packages/bokeh/core/property/bases.py", line 350, in prepare_value
raise ValueError(f"failed to validate {obj_repr}.{name}: {error}")
ValueError: failed to validate TextInput(id='1006', ...).value: expected a value of type str, got None of type NoneType
I suppose it’s a bug. It’s more a question for @philippjfr
I only use chrome dev tools to debug scritps.
I use a console.log or debugger statement to know where to insert break points
I use a lot Babel · The compiler for next generation JavaScript to convert jsx into js
Actually I can’t reproduce your error
I got the error when I serve it or when I also show the controls()
. If I set m.value=None
, I got nothing when I restart m
on the other cell.
Looks like a bug, may be there is already an issue on github or you can report it.
You can work around the error by changing the onChange callback:
onChange: (_, value) => {
this.setState({value: value})
data.value = value != null ? value : ""
},
and not allowing None in the value parameter