Skip to content

Commit

Permalink
Merge pull request #778 from plone/volto4dummies
Browse files Browse the repository at this point in the history
  • Loading branch information
pbauer authored Sep 30, 2023
2 parents ba60a96 + 3dbc702 commit 32f9910
Show file tree
Hide file tree
Showing 15 changed files with 2,321 additions and 1 deletion.
7 changes: 6 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mastering-plone/index
mastering-plone-5/index
voltohandson/index
voltoaddons/index
volto_customization/index
effective-volto/index
testing/index
theming/index
Expand Down Expand Up @@ -64,6 +65,10 @@ teaching/index

: Build custom Volto add-ons, explore more advanced Volto topics.

{doc}`volto_customization/index`

: Are you new to JavaScript development and eager to explore the world of Volto customization? Unlock the power of Volto, the modern React-based CMS framework for Plone, by joining our comprehensive half day training designed specifically for JavaScript beginners.

{doc}`effective-volto/index`

: Learn proven practices of Plone frontend development.
Expand Down Expand Up @@ -124,7 +129,7 @@ Because we began this practice in 2022, all previous trainings that have documen

#### Volto, React, and Javascript

- [Volto](https://2022.training.plone.org/volto/index.html)
- [Volto](https://2022.training.plone.org/volto/index.html)
- [Angular SDK for Plone](https://2022.training.plone.org/angular/index.html)
- [GatsbyJS](https://2022.training.plone.org/gatsby/index.html)
- [JavaScript For Plone Developers (up to Plone 5)](https://2022.training.plone.org/javascript/index.html)
Expand Down
Binary file added docs/volto_customization/_static/variations.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
81 changes: 81 additions & 0 deletions docs/volto_customization/blocks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
myst:
html_meta:
"description": "How to extend volto blocks"
"property=og:description": "How to extend volto blocks"
"property=og:title": "Extend volto blocks"
"keywords": "Volto, Training, Extend block"
---

# Extend volto blocks

There are various ways of extending Volto blocks.
Component shadowing (see last chapter) is a very basic to customize components in volto.
But it comes with its own problems like keeping the shadowed component up to date with latest fixes and features of newer Volto versions.
Instead of shadowing components we can:

- Change the block-config
- Extend blocks by adding new block-variations
- Write add schemaEnhancer to modify blocks schema

Let us first change the View of the teaser block which we already have in volto core by changing the block-configuration.
In our addon `volto-teaser-tutorial` we will step by step extend each component that we have in volto core.

The most simple customization is the View of the Teaser. The volto core teaser block configration (in `omelette/src/config/Blocks.jsx`) looks like:

```{code-block} js
teaser: {
id: 'teaser',
title: 'Teaser',
icon: imagesSVG,
group: 'common',
view: TeaserViewBlock,
edit: TeaserEditBlock,
restricted: false,
mostUsed: true,
sidebarTab: 1,
blockSchema: TeaserSchema,
dataAdapter: TeaserBlockDataAdapter,
variations: [
{
id: 'default',
isDefault: true,
title: 'Default',
template: TeaserBlockDefaultBody,
},
],
},
```

Every block in Volto has Edit and View components.
You can customize these individually by either shadowing or directly in the confuguration (`index.js` of your addon) like this:

```{code-block} js
import MyTeaserView from 'volto-teaser-tutorial/components/Blocks/Teaser/View'
const applyConfig = (config) => {
config.blocks.blocksConfig.teaser.view = MyTeaserView
return config;
}
export default applyConfig;
```

Of course we need to add our custom `MyTeaserView` component in our addon.
From the root of the project that is `src/addon/volto-teaser-tutorial/src/components/Blocks/Teaser/View.jsx`:

```{code-block} jsx
import React from 'react';
import TeaserBody from '@plone/volto/components/manage/Blocks/Teaser/Body';
import { withBlockExtensions } from '@plone/volto/helpers';
const TeaserView = (props) => {
return <TeaserBody {...props} extraProps={{ foo: 'bar' }} />;
};
export default withBlockExtensions(TeaserView);
```

Here, the View component renders a TeaserBody which will be a result of an active variation, we will come to that in later chapters.
204 changes: 204 additions & 0 deletions docs/volto_customization/custom_block.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
---
myst:
html_meta:
"description": "How to add a custom block"
"property=og:description": "How to add a custom block"
"property=og:title": "Volto Weather Block (custom block)"
"keywords": "Volto, Training, Custom block"
---

# Volto Weather Block (custom block)

Let's create a volto block that will display weather information for Eibar. For this we can use <a target="_blank" href="https://open-meteo.com/">Open-Meteo API</a>. Open-Meteo is an open-source weather API and offers free access for non-commercial use. No API key required.

Creating a basic block in Volto, involves several steps. Below, I'll outline the steps to create a Volto block that displays the weather forecast in Eibar.

1. **Setup Your Volto Project:** If you haven't already, set up a Volto project. You can use the instructions presented in [Installation -> Bootstrap a new Volto project](installation.md#bootstrap-a-new-volto-project) section.

2. **Create a New Block:** In your Volto project directory, navigate to the "src/components" folder and locate/create the "Blocks" directory. Create a new folder for your custom block; let's name it "Weather".

3. **Define the Block Schema:** Inside the "Weather" folder, create a "schema.js" file to define your block's schema. Here's a basic schema for our block needs:

```{code-block} js
export const weatherBlockSchema = (props) => {
return {
title: 'Weather Block',
description: 'Display weather information for location.',
fieldsets: [
{
id: 'default',
title: 'Default',
fields: ['latitude', 'longitude', 'location'],
},
],
properties: {
latitude: {
title: 'Latitude',
description:
'Enter the latitude of the location for which you want to display the weather (e.g., 43.1849).',
widget: 'text',
},
longitude: {
title: 'Longitude',
description:
'Enter the longitude of the location for which you want to display the weather (e.g., -2.4716).',
widget: 'text',
},
location: {
title: 'Location',
description:
'Enter the name of the location for which you want to display the weather (e.g., Eibar, Basque Country).',
widget: 'text',
},
},
required: ['latitude', 'longitude', 'location'],
};
};
export default weatherBlockSchema;
```

4. **Create the Block Component:** Inside the "Weather" folder, create a "View.jsx" file to define your block's React component. This component will make an API request to fetch the weather data and display it:

```{code-block} jsx
import React, { useEffect, useState } from 'react';
const View = (props) => {
const { data = {} } = props;
const location = data.location || 'Eibar, Basque Country';
const [weatherData, setWeatherData] = useState(null);
useEffect(() => {
const latitude = data.latitude || '43.1849'; // Default Eibar latitude if no latitude is provided
const longitude = data.longitude || '-2.4716'; // Default to longitude if no longitude is provided
const abortController = new AbortController(); // creating an AbortController
fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true&timezone=auto`,
{ signal: abortController.signal }, // passing the signal to the query
)
.then((response) => response.json())
.then((data) => {
setWeatherData(data);
})
.catch((error) => {
if (error.name === 'AbortError') return;
console.error('Error fetching weather data:', error);
throw error;
});
return () => {
abortController.abort(); // stop the query by aborting on the AbortController on unmount
};
}, [data.latitude, data.longitude]);
return (
<>
{weatherData ? (
<div>
<h2>Weather in {location}</h2>
<p>Temperature: {weatherData.current_weather.temperature} °C</p>
</div>
) : (
<p>Loading weather data...</p>
)}
</>
);
};
export default View;
```

You should also create a "Edit.jsx" file. The BlockDataForm component will transform the schema.js data into a usable sidebar.

```{code-block} jsx
import React, { useMemo } from 'react';
import { SidebarPortal } from '@plone/volto/components';
import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
import weatherBlockSchema from './schema';
import View from './View';
const Edit = (props) => {
const schema = useMemo(() => weatherBlockSchema(props), [props]);
return (
<>
<View {...props} mode="edit" />
<SidebarPortal selected={props.selected}>
<BlockDataForm
schema={schema}
title={schema.title}
onChangeField={(id, value) => {
props.onChangeBlock(props.block, {
...props.data,
[id]: value,
});
}}
onChangeBlock={props.onChangeBlock}
formData={props.data}
block={props.block}
/>
</SidebarPortal>
</>
);
};
export default Edit;
```

5. **Register the Block:** In your Volto project, locate the "components/index.js" file and add an the entries for your "Weather Block"

```{code-block} js
...
import WeatherEdit from './components/Blocks/Weather/Edit';
import WeatherView from './components/Blocks/Weather/View';
...
export { WeatherView, WeatherEdit };
```

We need to configure the project to make it aware of a new block by adding it to the object configuration that is located in "src/config.js". For that we need the 2 blocks components we created and a svg icon that will be displayed in the blocks chooser.

```{code-block} js
import WeatherEdit from './Edit';
import WeatherView from './View';
import worldSVG from '@plone/volto/icons/world.svg';
...
export default function applyConfig(config) {
...
config.blocks.blocksConfig.weather = {
id: 'weather',
title: 'Weather',
icon: worldSVG,
group: 'common',
edit: WeatherEdit,
view: WeatherView,
restricted: false,
mostUsed: false,
sidebarTab: 1,
blocks: {},
security: {
addPermission: [],
view: [],
},
};
...
return config;
};
...
```

6. **Use the Weather Block:** In Volto's Dexterity-based content types, create or edit a content type that includes the "Weather Block" in the allowedBlocks field. Then, create a content item and add the "Weather Block" to display the weather information for the location you specify.

Additionally, you may customize the UI and add more weather details based on the API's response data as needed.
Loading

0 comments on commit 32f9910

Please sign in to comment.