-
Notifications
You must be signed in to change notification settings - Fork 146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs: how to add new (frontend) components #847
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,60 @@ | ||||||||||||
# Making new components | ||||||||||||
|
||||||||||||
When starting with solara, you will always create a `Page` component. This `Page` component will be using components provided by solara. | ||||||||||||
|
||||||||||||
When your project grows, you might build your own reusable components [as described in the fundamentals](https://solara.dev/documentation/getting_started/fundamentals/components). | ||||||||||||
|
||||||||||||
Depending on your needs, this may not be sufficient, and there are several approaches to creating new components: | ||||||||||||
|
||||||||||||
## Build a Pure Python component using ipyvuetify | ||||||||||||
|
||||||||||||
Most components in Solara itself are build on top of [Vuetify](https://v2.vuetifyjs.com/), using the [ipyvuetify](/documentation/advanced/understanding/ipyvuetify) Python binding. | ||||||||||||
|
||||||||||||
If there is a component in [Vuetify](https://v2.vuetifyjs.com/) that you want to use that is not (yet) exposed in Solara directly, you can access | ||||||||||||
all the components via [ipyvuetify](/documentation/advanced/understanding/ipyvuetify). | ||||||||||||
|
||||||||||||
|
||||||||||||
## Build a Frontend component | ||||||||||||
|
||||||||||||
Sometimes it is beneficial to build a component directly in the frontend. This are several reasons for this: | ||||||||||||
|
||||||||||||
* Lower latency/performance: If a component is very performance-critical, it might be beneficial to build it in the frontend as it does not require a roundtrip to the server. | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
* Direct access to the DOM: If you need to interact with the DOM directly, it might be beneficial to build a component in the frontend. | ||||||||||||
* Simpler: Sometimes it is just simpler to build a component in the frontend and Solara. | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
* Use of third-party libraries: If you want to use a third-party library that is not available in Solara, you can build a component in the frontend. | ||||||||||||
|
||||||||||||
|
||||||||||||
### Using Vue | ||||||||||||
|
||||||||||||
In solara itself, we use Vue to write new frontend components. The main reason is that we build Solara on top of [Vuetify](https://v2.vuetifyjs.com/), which is a Vue component library. | ||||||||||||
|
||||||||||||
Vue is easy to use for non-front-end developers, and it is easy to learn and is the default option for the Solara dev itself. No frontend tools are needed (like webpack, etc.), and you can write your components directly in file or inline string in python. | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
The downside of using Vue, is that we are currently limited to Vue 2, and our ipyvue library has no support for ESM modules, introducing a bit of boilerplate code | ||||||||||||
to load external libraries. | ||||||||||||
Comment on lines
+33
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
[Explore how to use a new Vue component in Solara, in our dedicated Howto](/documentation/advanced/howto/make-a-vue-component). | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
|
||||||||||||
### Using React | ||||||||||||
|
||||||||||||
Since the release of [ipyreact](https://github.com/widgetti/ipyreact/) we can now also very easily write write ReactJS components for Solara. | ||||||||||||
ReactJS has the advantage that its model is quite similar to Solara itself, and it is very popular in the frontend world with a ton of third | ||||||||||||
party libraries available. | ||||||||||||
|
||||||||||||
Another advantage is the ipyreact supports ESM modules, so you can directly use third party libraries without any boilerplate code. | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
This library also allows sharing of ESM modules between different widgets, and composition of widgets (i.e. having child widgets). | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
[Explore how to use a new React component in Solara, in our dedicated Howto (SOON)](/documentation/advanced/howto/make-a-react-component). | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
### Using Javascript | ||||||||||||
|
||||||||||||
If you prefer to use pure JavaScript (actually TypeScript), you can [write you own widget](https://github.com/jupyter-widgets/widget-ts-cookiecutter), but this requires | ||||||||||||
a lot of work. This will give you however, the most flexibility. The underlying libraries mentioned above (ipyreact and ipyvue) are build on top of this. | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
[AnyWidget](https://anywidget.dev/) has a similar solution to ipyvue and ipyreact, but is targeted at pure JavaScript. It's a well documented project, and in most | ||||||||||||
cases a better alternative to writing your own ipywidget from scratch. | ||||||||||||
|
||||||||||||
AnyWidget support ESM modules, but has no native way to re use ESM modules between different widgets or composition of widgets. | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
[Explore how to use a new JS component in Solara, in our dedicated Howto (SOON)](/documentation/advanced/howto/make-a-javascript-component). | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,196 @@ | ||||||
# Making a Vue based component | ||||||
|
||||||
If based on [./make-components](./make-components) you made the decision to write a vue based component, this article will guide you through the process. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
This howto is an extended version of [https://solara.dev/documentation/examples/general/vue_component] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
[https://solara.dev/documentation/api/utilities/component_vue] | ||||||
|
||||||
Comment on lines
+7
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
We will first start with a component which is meant to be use inside of a single application, which will fetch the 3rd party library from a CDN. The goal | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
is to get something work with the minimal amount of effort. | ||||||
|
||||||
Our goal is to create a button, that when clicked, will show confetti. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
## The skeleton setup. | ||||||
|
||||||
First, we create our minimal `button-confetti.vue` file which includes a very simple button with a custom label and a click event handler. | ||||||
```vue | ||||||
<template> | ||||||
<button class="button-confetti" @click="click({extra: 'foo'}) "> | ||||||
\{\{ label \}\} | ||||||
</button> | ||||||
</template> | ||||||
``` | ||||||
|
||||||
|
||||||
To expose this vue component to Solara, we use [the component_vue decorator](/documentation/api/utilities/component_vue). | ||||||
|
||||||
|
||||||
```python | ||||||
import solara | ||||||
from typing import Callable | ||||||
|
||||||
@solara.component_vue("button-confetti.vue") | ||||||
def ButtonConfetti( | ||||||
label: str = "Default label", | ||||||
event_click: Callable = None, | ||||||
): | ||||||
... | ||||||
|
||||||
@solara.component | ||||||
def Page(): | ||||||
ButtonConfetti(label="Click me", event_click=print) | ||||||
``` | ||||||
|
||||||
This simple app will show a button with the label "Click me" and when clicked, it will print `{'extra': 'foo'}` to the console. [<img src="https://py.cafe/logos/pycafe_logo.png" alt="PyCafe logo" width="24" height="24"> Run and edit this example at PyCafe](https://py.cafe/maartenbreddels/solara-howto-component-vue-A). | ||||||
|
||||||
|
||||||
## Loading the confetti library. | ||||||
|
||||||
Now that we have our skeleton setup, we can add the confetti library. We will use the [`canvas-confetti`](https://www.npmjs.com/package/canvas-confetti) library, which is available on a CDN. We will add this to our `button-confetti.vue` file with unfortunately a bit of boilerplate code to load the library.: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
```vue | ||||||
<template> | ||||||
<button class="button-confetti" @click="clickHandler"> | ||||||
{{label}} | ||||||
</button> | ||||||
</template> | ||||||
<script> | ||||||
module.exports = { | ||||||
created() { | ||||||
console.log("button-confetti: created"); | ||||||
this.loadedConfetti = this.loadConfetti(); | ||||||
}, | ||||||
mounted() { | ||||||
console.log("button-confetti: mounted"); | ||||||
}, | ||||||
watch: { | ||||||
label() { | ||||||
console.log("button-confetti: label changed"); | ||||||
} | ||||||
}, | ||||||
methods: { | ||||||
clickHandler() { | ||||||
if(this.click) { | ||||||
this.click({extra: 'foo'}) | ||||||
} | ||||||
this.showConfetti(); | ||||||
}, | ||||||
async showConfetti() { | ||||||
// make sure it is loaded by waiting on the Promise | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
await this.loadedConfetti; | ||||||
await new Promise((resolve) => setTimeout(resolve, 1000)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm guessing this should either be removed or somehow commented on?
Suggested change
|
||||||
confetti(); | ||||||
}, | ||||||
/* begin boilerplate */ | ||||||
async loadConfetti() { | ||||||
let confettiUrl = "https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"; | ||||||
require.config({ | ||||||
map: { | ||||||
'*': { | ||||||
'confetti': confettiUrl, | ||||||
} | ||||||
} | ||||||
}) | ||||||
await new Promise((resolve, reject) => { | ||||||
require(['confetti'], (confettiModule) => { | ||||||
resolve(confettiModule); | ||||||
}, reject) | ||||||
}); | ||||||
}, | ||||||
/* end boilerplate */ | ||||||
}, | ||||||
} | ||||||
</script> | ||||||
<style id="button-confetti"> | ||||||
.button-confetti { | ||||||
background-color: lightblue; | ||||||
margin: 10px; | ||||||
padding: 4px; | ||||||
border: 1px solid black; | ||||||
} | ||||||
</style> | ||||||
``` | ||||||
|
||||||
[<img src="https://py.cafe/logos/pycafe_logo.png" alt="PyCafe logo" width="24" height="24"> Run and edit this example at PyCafe](https://py.cafe/maartenbreddels/solara-howto-component-vue-B). | ||||||
|
||||||
|
||||||
## Triggering the confetti from the Python side | ||||||
|
||||||
Although we have a working button showing out confetti, this is triggered by a button in our vue template. There are situations where we want to trigger the confetti from the Python side. We can do this by making our | ||||||
Vue component respond to its argument in the `watch` section. We also remove any vue from out template so that our vue template becomes a non-visual component. | ||||||
|
||||||
Our Python code simply passes an integer (named `trigger`) to the Vue component. When this integer changes, the confetti should be shown. | ||||||
```python | ||||||
import solara | ||||||
from typing import Callable | ||||||
|
||||||
@solara.component_vue("confetti.vue") | ||||||
def Confetti(trigger: int): | ||||||
... | ||||||
|
||||||
@solara.component | ||||||
def Page(): | ||||||
trigger = solara.use_reactive(0) | ||||||
def on_click(): | ||||||
trigger.value += 1 | ||||||
with solara.Row(): | ||||||
solara.Button("Confetti", on_click=on_click, color="primary") | ||||||
Confetti(trigger=trigger.value) | ||||||
``` | ||||||
|
||||||
Our vue component | ||||||
```vue | ||||||
<template> | ||||||
</template> | ||||||
<script> | ||||||
module.exports = { | ||||||
created() { | ||||||
console.log("confetti: created"); | ||||||
this.loadedConfetti = this.loadConfetti(); | ||||||
}, | ||||||
mounted() { | ||||||
console.log("confetti: mounted"); | ||||||
}, | ||||||
watch: { | ||||||
trigger(value) { | ||||||
console.log("confetti: trigger value changed to", value); | ||||||
this.showConfetti(); | ||||||
} | ||||||
}, | ||||||
methods: { | ||||||
clickHandler() { | ||||||
if(this.click) { | ||||||
this.click({extra: 'foo'}) | ||||||
} | ||||||
this.showConfetti(); | ||||||
}, | ||||||
async showConfetti() { | ||||||
// make sure it is loaded by waiting on the Promise | ||||||
await this.loadedConfetti; | ||||||
confetti(); | ||||||
}, | ||||||
/* begin boilerplate */ | ||||||
async loadConfetti() { | ||||||
let confettiUrl = "https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"; | ||||||
require.config({ | ||||||
map: { | ||||||
'*': { | ||||||
'confetti': confettiUrl, | ||||||
} | ||||||
} | ||||||
}) | ||||||
await new Promise((resolve, reject) => { | ||||||
require(['confetti'], (confettiModule) => { | ||||||
resolve(confettiModule); | ||||||
}, reject) | ||||||
}); | ||||||
}, | ||||||
/* end boilerplate */ | ||||||
}, | ||||||
} | ||||||
</script> | ||||||
|
||||||
``` | ||||||
|
||||||
[<img src="https://py.cafe/logos/pycafe_logo.png" alt="PyCafe logo" width="24" height="24"> Run and edit this example at PyCafe](https://py.cafe/maartenbreddels/solara-howto-component-vue-C). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.