Getting the attributes of a custom content type in a custom block

I've been trying to get the attributes of a custom content type in a custom block. The problem is that I don't receive the attributes, and I don't understand how I can pass these attributes to the block.
I specified the attributes for the person content type (image, name, email, position, entrance, tags) and tried to log them in the isotopeView (console.log(data)), but the data attribute only contains the title and description of the person content type. I figured out that I can add attributes with the "selectedItemAttrs" property in isotopeSchema.js, but I can't just add any attribute, only selected, and there is no documentation about which attributes can be added.

Backend:
person.py

from plone import schema
from plone.autoform import directives
from plone.dexterity.content import Container
from plone.supermodel import model
from plone.namedfile.field import NamedBlobImage
from plone.schema.email import Email
from z3c.form.browser.checkbox import CheckBoxFieldWidget
from zope.interface import implementer

class IPerson(model.Schema):
    """Dexterity-Schema for Person"""

    image = NamedBlobImage(
        title='Image',
        description='Portrait of the person',
        required=True,
    )

    name = schema.TextLine(
        title='Name',
        description='Name of the person',
        required=True,
    )

    email = Email(
        title='Email',
        description='Email of the person',
        required=False,
    )

    position = schema.TextLine(
        title='Position',
        description='Position of the person',
        required=False,
    )

    entrance = schema.Int(
        title='Entrance',
        description='Year of employment',
        required=False,
    )

    directives.widget(tags=CheckBoxFieldWidget)
    tags = schema.Set(
        title='Tags',
        value_type=schema.Choice(
            values=['Medienproduktion', 'Vorstufe', 'Redaktion', 'Sekretariat'],
        ),
        required=True,
    )

@implementer(IPerson)
class Person(Container):
    """Person instance class"""

person.xml

<?xml version="1.0"?>
<object name="person" meta_type="Dexterity FTI" i18n:domain="plone"
   xmlns:i18n="http://xml.zope.org/namespaces/i18n">
 <property name="title" i18n:translate="">Person</property>
 <property name="description" i18n:translate=""></property>
 <property name="icon_expr">string:${portal_url}/document_icon.png</property>
 <property name="factory">person</property>
 <property name="add_view_expr">string:${folder_url}/++add++person</property>
 <property name="link_target"></property>
 <property name="immediate_view">view</property>
 <property name="global_allow">True</property>
 <property name="filter_content_types">True</property>
 <property name="allowed_content_types"/>
 <property name="allow_discussion">False</property>
 <property name="default_view">view</property>
 <property name="view_methods">
  <element value="view"/>
 </property>
 <property name="default_view_fallback">False</property>
 <property name="add_permission">cmf.AddPortalContent</property>
 <property name="klass">herme_backend.content.person.Person</property>
 <property name="schema">herme_backend.content.person.IPerson</property>
 <property name="behaviors">
  <element value="plone.dublincore"/>
  <element value="plone.namefromtitle"/>
  <element value="plone.versioning" />
 </property>
 <property name="model_source"></property>
 <property name="model_file"></property>
 <property name="schema_policy">dexterity</property>
 <alias from="(Default)" to="(dynamic view)"/>
 <alias from="edit" to="@@edit"/>
 <alias from="sharing" to="@@sharing"/>
 <alias from="view" to="(selected layout)"/>
 <action title="View" action_id="view" category="object" condition_expr=""
    description="" icon_expr="" link_target="" url_expr="string:${object_url}"
    visible="True">
  <permission value="View"/>
 </action>
 <action title="Edit" action_id="edit" category="object" condition_expr=""
    description="" icon_expr="" link_target=""
    url_expr="string:${object_url}/edit" visible="True">
  <permission value="Modify portal content"/>
 </action>
</object>

types.xml

<?xml version="1.0" encoding="utf-8"?>
<object meta_type="Plone Types Tool" name="portal_types" >
  <object meta_type="Dexterity FTI" name="person" />
</object>

Frontend:
isotopeSchema.js

import config from '@plone/volto/registry';

const DEFAULT_TAGS = [
    ['Medienproduktion', 'Medienproduktion'],
    ['Vorstufe', 'Vorstufe'],
    ['Redaktion', 'Redaktion'],
]

const isotopeItemSchema = (props) => {
    const { intl } = props;

    return {
      title: 'Item',
      addMessage: 'Add item',
      fieldsets: [
        {
          id: 'default',
          title: 'Default',
          fields: ['href'],
        },
      ],
  
      properties: {
        href: {
          title: 'Source',
          widget: 'object_browser',
          mode: 'link',
          selectedItemAttrs: ['Title', 'Description', 'image', 'name', 'email', 'position', 'entrance', 'tags' ],
          allowExternals: false,
          selectableTypes: ['Person'],
        },
      },
      required: [],
    };
};

export const IsotopeSchema = (props) => {
    const default_tags = 
        config.blocks?.blocksConfig?.listing?.default_tags ||
        DEFAULT_TAGS;
    return {
        title: 'People',
        fieldsets: [
            {
                id: 'default',
                title: 'Default',
                fields: ['columns', 'default_tags'],
            },
        ],

        properties: {
            columns: {
                widget: 'object_list',
                title: 'People',
                schema: isotopeItemSchema,
            },
            default_tags: {
                title: 'Tags',
                widget: 'select',
                isMulti: true,
                choices: default_tags,
                noValueOption: true,
            },
        },
        required: [],
    };
};

export default IsotopeSchema;

isotopeView.jsx

import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Helmet } from '../../../../../omelette/src/helpers/Helmet/Helmet';

const IsotopeView = ({ data }) => {
    console.log(data);
    // console.log(data.columns[0].href[0].title);
    // init one ref to store the future isotope object
    var isotope;

    if (typeof window !== "undefined") {
        isotope = window.Isotope;
    }

    // store the filter keyword in a state  
    const [filterKey, setFilterKey] = useState('*')

    // initialize an Isotope object with configs
    useEffect(() => {
        console.log("handle it");
        isotope.current = new Isotope('.grid', {
            itemSelector: '.grid-item',
            sortBy: 'random',
        })
        // cleanup
        return () => isotope.current.destroy()
    }, [])

    // handling filter key change
    useEffect(() => {
      filterKey === '*'
          ? isotope.current.arrange({filter: `*`})
          : isotope.current.arrange({filter: `.${filterKey}`})
    }, [filterKey])

    const handleFilterKeyChange = key => () => setFilterKey(key)

    return(
        <>
            <Helmet>
                <script src="https://unpkg.com/isotope-layout@3/dist/isotope.pkgd.min.js"></script>
            </Helmet>
            <div id="filters" className="button-group filter-button-group">
                <button className="filterBtn is-checked" onClick={handleFilterKeyChange('*')}>Alle</button>
                {data.default_tags.map((filter) => (
                    <button className="filterBtn" onClick={handleFilterKeyChange(filter)}>{ filter }</button>
                ))}
            </div>

            <div className='grid'>
                <div className='grid-item Medienproduktion'><p>Medienproduktion</p></div>
                <div className='grid-item Vorstufe'><p>Vorstufe</p></div>
                <div className='grid-item Redaktion'><p>Redaktion</p></div>
            </div>
        </>
   );
}; 

IsotopeView.propTypes = {
    data: PropTypes.objectOf(PropTypes.any).isRequired,
};

export default IsotopeView;

isotopeEdit.jsx

import React from "react";
import PropTypes from 'prop-types';
import IsotopeData from "./IsotopeData";
import SidebarPortal from "../../../../../omelette/src/components/manage/Sidebar/SidebarPortal";

const IsotopeEdit = React.memo((props) => {
    const { selected, onChangeBlock, block, data } = props;

    return(
        <>
        <p>This is the filter cards edit</p>
        <SidebarPortal selected={selected}>
            <IsotopeData 
             {...props}
             data={data}
             block={block}
             onChangeBlock={onChangeBlock}
            />
        </SidebarPortal>
        </>
    )
})

export default IsotopeEdit;

isotopeData.jsx

import React from 'react';
import { IsotopeSchema } from './IsotopeSchema';
import BlockDataForm from '../../../../../omelette/src/components/manage/Form/BlockDataForm';

const IsotopeView = (props) => {
  const { block, data, onChangeBlock } = props;
  const schema = IsotopeSchema({ ...props });
  return (
    <BlockDataForm
      schema={ schema }
      title={ schema.title }
      onChangeField={(id, value) =>  {
        onChangeBlock(block, {
          ...data,
          [id]: value,
        });
      }}
      formData={ data }
      block={ block } 
    />
  );
};
export default IsotopeView;

See if there's another block prop, properties (sometimes it's also metadata for the "block in block" scenario).

@tiberiuichim properties and / or metadata don't contain my attributes. In the view that I created for the person content type, I can log all my attributes. But in the isotope view, I receive just the @id and title attribute of the person.

IsotopeView.jsx

PersonView.jsx

I don't understand what isotopeView is and how it gets the information on the Persons.

If understand your construction right, you are building a block 'isotope'.

const IsotopeView = ({ data }) => {
    console.log(data);

means that the code wants to use props.data. If you are interested in the content data (of the person instance), then props.properties or as @tiberiu mentioned props.metadata shoud give you the needed data. You can first log all props with console.log(props).

The data in a blocks view like in const IsotopeView = ({ data }) => { is just the blocks data defined by the blocks schema IsotopeSchema.

But I think it's worth to think about, if you really want the user to set references in a block. Or if you maybe better create a RelationsField on the content type person. Then you have the relations machinery and tools available: You can query, inspect and manage it far easier than if you have the references just deep in blocks data.

@ksuess I tried this but when i log props.properties and look at items the item don't contain my fields (image, name, email, position, entrance, tags). They just contain the default attributes. When I log props.metadata it is undefined.

I'm new to Plone so I don't know anything about relations, but I will have a look at that. Thank you.

I am not sure about what you are building here.
You have a content type Person and what I did not get before is that you have also a content type Isotope. Right?
Now you want a view for a Person.
This view shall show the contained Isotopes or shall it show Isotopes selected by the user in columns field (RelationField)?
And are you working with blocks? It seems so, because you are providing edit and view components of a block.
I think some more information about the data model and use case would help to help.

@ksuess So basically, I want to create a team site. On this team site, I want to implement the isotope.js framework. I need to show some filter buttons and the team members. For the implementation of isotopes, I created the isotope block. In the isotope edit, I need to select the filters (which is working). The next step would be to add the people with their information. For that, I created the content type person, which contains these information: name, image, email, position, entrance and tags. So I would create a person for every team member. Then I want to select in the isotope edit each person with the object browser. And I was expecting that I get all the information of a person item in the isotope view, so I could add a div with the name, image, etc .. My problem now is that when I select the person in the isotope edit, I don't receive the information I added in the content type.

I normaly work with umbraco so I think that I don't realy understand how the structure of plone/volto works. I'm really sorry for this mess :grimacing:

This is how it should look like in the end:

This is the isotope edit:
image

This is how a person looks like:

I think you do not want the editor user to select persons manually. Instead you want in the view component of the block to request once all items of type Person in a useEffect hook. Then you have all persons available with useSelector.
See 34. The Sponsors Component – Mastering Plone 6 Development — Plone Training 2023 documentation

@ksuess

That's it. Thank you so much, it is now working. :star_struck:

1 Like