Skip to content
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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Sometimes it is beneficial to build a component directly in the frontend. This are several reasons for this:
Sometimes it is beneficial to build a component directly in the frontend. There 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* 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.
* Lower latency/performance: If the responsiveness of a component is critical, it might be beneficial to build it in the frontend as interactions would not require a roundtrip to the server.

* 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Simpler: Sometimes it is just simpler to build a component in the frontend and Solara.
* Simpler: Sometimes it is just simpler to build a component in the frontend than in Python.

* 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
Vue is easy to use for non-front-end developers, and easy to learn. It is also the default option for the Solara.dev website itself. No frontend tools are needed (like webpack, etc.), and you can write your components directly in a template `.vue` file or as an inline string in python.


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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.
There are two main limitations to using Vue components:
* we are currently limited to Vue 2
* ipyvue has no support for ES modules, meaning some boilerplate code is required to load external libraries.


[Explore how to use a new Vue component in Solara, in our dedicated Howto](/documentation/advanced/howto/make-a-vue-component).
Copy link
Collaborator

Choose a reason for hiding this comment

The 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).
[Explore how to use a new Vue component in Solara, in our dedicated howto](/documentation/advanced/howto/make-a-vue-component).



### 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Another advantage is the ipyreact supports ESM modules, so you can directly use third party libraries without any boilerplate code.
Another advantage is ipyreact's support for ES modules, so you can directly use third party libraries without any boilerplate code.

This library also allows sharing of ESM modules between different widgets, and composition of widgets (i.e. having child widgets).
Copy link
Collaborator

Choose a reason for hiding this comment

The 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).
This library also allows sharing ES modules between different widgets, and composing widgets (i.e. having child widgets).


[Explore how to use a new React component in Solara, in our dedicated Howto (SOON)](/documentation/advanced/howto/make-a-react-component).
Copy link
Collaborator

Choose a reason for hiding this comment

The 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).
[Explore how to use a new React component in Solara, in our dedicated howto (COMING SOON)](/documentation/advanced/howto/make-a-react-component).


### 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
a lot of work. This will, however, give you the most control. The libraries mentioned above (ipyreact and ipyvue) are build on top of this.


[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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
AnyWidget support ESM modules, but has no native way to re use ESM modules between different widgets or composition of widgets.
AnyWidget supports ES modules, but has no native support for re-using ES modules between widgets or composing widgets.


[Explore how to use a new JS component in Solara, in our dedicated Howto (SOON)](/documentation/advanced/howto/make-a-javascript-component).
Copy link
Collaborator

Choose a reason for hiding this comment

The 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).
[Explore how to use a new JS component in Solara, in our dedicated howto (COMING SOON)](/documentation/advanced/howto/make-a-javascript-component).

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
If based on ["New Components"](/documentation/advanced/howto/new-components) you made the decision to write a Vue based component, this article will guide you through the process.


This howto is an extended version of [https://solara.dev/documentation/examples/general/vue_component]
Copy link
Collaborator

Choose a reason for hiding this comment

The 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]
This howto is an extended version of the [Vue Component example](/documentation/examples/general/vue_component).


[https://solara.dev/documentation/api/utilities/component_vue]

Comment on lines +7 to +8
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[https://solara.dev/documentation/api/utilities/component_vue]

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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
We will start by making a component that shows a button, which shows confetti when clicked. The component is meant to be use inside of a single application, and will fetch a 3rd party library from a CDN. The goal

is to get something work with the minimal amount of effort.

Our goal is to create a button, that when clicked, will show confetti.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Our goal is to create a button, that when clicked, will show confetti.


## 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.:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.:
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 using a bit of boilerplate code:


```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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// make sure it is loaded by waiting on the Promise
// make sure the package is loaded by waiting on the Promise

await this.loadedConfetti;
await new Promise((resolve) => setTimeout(resolve, 1000))
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
await new Promise((resolve) => setTimeout(resolve, 1000))

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).
Loading