How to get nested blocks working in the panel

Hi there,

I want to create a custom block that has nested blocks inside it. Here is a simplified blueprint:

name: My Custom Block
fields:
  headline:
    type: writer
    marks: 
      - bold
  content_blocks:
    type: blocks
    fieldsets:
      - heading
      - text
      - video

Now I want to create an editable preview for the panel. My problem is, that I don’t know how to properly use the <k-blocks> element. I want it to function exactly like the root blocks inside a page. Here is what I have so far:

panel.plugin('diverently/my-custom-block', {
    blocks: {
        content: {
            template: `
                <div>
                    <k-writer
                        :inline="true"
                        :marks="['bold']"
                        :placeholder="field('headline').placeholder"
                        :value="content.headline"
                        @input="update({ headline: $event })"
                    />
                    <div>
                        <div v-for="item in content.content_blocks">
                            // What goes here?
                        </div>
                    </div>
                </div>
            `
        }
    }
})

Can someone help me with this?

Best regards,
Robert

See Nested blocks | Kirby CMS

Yeah, thanks, I saw that one. I guess you’re talking about this part specifically:

template: `
  <div @dblclick="open" class="faq">
    <h2>{{ content.headline }}</h2>
    <div v-html="content.text"></div>
    <dl v-for="item in content.blocks">
      <dt v-html="item.content.question"></dt>
      <dd v-html="item.content.answer"></dt>
    </dl>
  </div>
`

But this has only one nested (custom) block type. In my case there will be different block types possible, some default and some custom ones. Also the example from that article isn’t editable inside the preview.

Is there a way to have nested blocks be editable? And is it possible to do that without having to rebuild the default previews?

I’m trying to do the same thing. Did you ever figure this out?

The only other related post I found online also doesn’t offer any solution.

Not tested, but maybe you can use the block component? Kirby Panel

Hi @gmpreussner, no unfortunately I never figured this out. Now the client has to use the form in the slide over panel. Maybe some day :slight_smile:

This works for me as a very basic outline, using the example blocks from above (without the updating part, that is. Maybe it helps as a starting point:

name: My Custom Block
preview: mycustomblock
wysiwyg: true
fields:
  headline:
    type: writer
    marks:
      - bold
  content_blocks:
    type: blocks
panel.plugin('my/customblock', {
    blocks: {
        mycustomblock: {
            template: `
                <div>
                    <k-writer
                        :inline="true"
                        :marks="['bold']"
                        :placeholder="field('headline').placeholder"                        
                        :value="content.headline"
                        @input="update({ headline: $event })"
                    />
                    <div>
                        <div v-for="item in content.content_blocks">
                        <k-block :type="item.type" :content="item.content"></k-block>
                        </div>
                    </div>
                </div>
            `
        }
    }
})

I’m having trouble accessing field properties of nested blocks. I’m trying something along the line of

disclosure.yml :

name: field.blocks.disclosure.name
label: Disclosure
icon: bars

fields:
  details:
    label: Details
    type: writer
    placeholder: Add more details…

accordion.yml :

name: field.blocks.accordion.name
icon: map
fields:
  items:
    label: Items
    type: blocks
    fieldsets:
      - disclosure

index.js :


panel.plugin("my-custom/blocks", {
	blocks: {
		accordion: {
			computed: {
				items() {
					return this.content.items || {}
				},
			},
			methods: {
				detailsField( item ) {
					return item.field( "details" );
				},
			},
			template: `
				<div class="k-block-type-my-custom-accordion-item"
					v-for = "(item, index) in items"
					:key = "index"
				>
					<p>
						<k-writer
							ref = "details"
							:placeholder = "detailsField( item ).placeholder || 'Add some details'"
							:value = "item.content.details"
							@input = "updateItem( content, index, 'details', $event )"
						/>
					</p>
				</div>
			`
		}
	}
}

where this part

:placeholder = detailsField( item ).placeholder

fails.

I cannot find the documentation for the function

item.field()

i could dig deep into this._props.fieldset.tabs.content.fields but that doesn’t seem right.

Edit: This works:

computed: {
	disclosureFields() {
		return this.fieldset.tabs.content.fields.items.fieldsets.disclosure.tabs.content.fields
	},
},

and then use

:placeholder = "disclosureFields.details.placeholder

But is this the correct approach?

Just wanted to point that I had a challenge with the exact same use case described by @RobertC and that this answer allowed me to do exactly what I wanted to do and was stuck trying to implement.

I don’t think it is documented as clearly as it is here anywhere else though. I have not seen the part with the following in the different documentation pages and examples provided. In the “custom block library” shared with the docs there is no example of a custom block containing nested blocks that are standard blocks defined by the fieldsets and not custom blocks themselves. (not sure if I’m crystal clear here). If there actually is, I missed it.

For future reference here is an example block I was working on when stumbled on the issue:

boxedfeature.yml :

# A block with 2 parts: one side is content (different fields possible, default and custom ones) with a background color, and the other side is an image
name: Boxed Feature
fields:
  contents:
    type: blocks
    fieldsets:
      - heading
      - text
      - list
      - button # a custom block
  illustration:
    type: files
    multiple: false
  background:
    type: color
    width: 1/2
    mode: options
    options: 
      "#625c51": "Dark background"
      "#E5E2DA": "Light background"

index.js for that block:

panel.plugin("hb4/boxedfeature", {
  blocks: {
    boxedfeature: {
      computed: {
        image() {
          return this.content.illustration[0] || {};
        }
      },
      template: `
        <div class="boxed-feature" :style="'background-color:' + this.content.background">
          <div class="boxed-feature__grid">
            <div class="boxed-feature__content">
                <!-- here goes the k-block element with a for loop described by @texnixe -->
                <div v-for="item in content.contents">
                <k-block :type="item.type" :content="item.content"></k-block>
                </div>
            </div>
            <figure>
              <img class="boxed-feature__img" :src="image.url" alt="Feature Image">
            </figure>
          </div>
        </div>
      `,
    },
  },
});

And finally here is what it looks like:

It can be made better but it is functional. Thanks thanks @RobertC for opening this question and @texnixe for answering it!

Coming back to this project, obviously my code lacks any way of saving the content in the index.js.
Any suggestion on how to make this work so that the individual blocks contents inside this custom block are saved when edited?

I tried several things but the content is never saved it seems. Any help?