Contribute to Docs Contribute to Docs

Custom Blocks

This article covers how to implement custom blocks for Piranha. If you want to read more about the standard blocks included, please refer to Blocks.

Blocks are the content pieces that the Administrators can use when designing the main content body. They main difference between Fields and Blocks is that blocks are intended to be more visual in their presentation than fields to give editors a clear understanding of the content they are creating.

Reserved Names

When naming your components, whether it's fields, blocks or block groups you should stay clear of the names already used by the internal components of the Piranha manager.

  • block-group-horizontal
  • block-group-vertical
  • block-group
  • folder-item
  • generic-block
  • page-item
  • pagecopy-item
  • post-archive
  • region
  • sitemap-item

Block initialization

After all of the block fields have been initialized additional initialization can be performed on the block itself for loading or preparing data based on the values of the fields.

Init
// Synchronous Init method
void Init(...);

// Asyncronous Init method
Task Init(...);

Initializes the block for client use. This is called when the block is loaded and is used for loading additional resources.

The method supports Dependency Injection which means that any service registered in the DI Container can be added as an parameter. This is useful when loading custom data from your application.

Manager Init
// Synchronous Init method
void InitManager(...);

// Asyncronous Init method
Task InitManager(...);

Initializes the block when it's loaded from within the manager. Just like the regular Init method it supports Dependency Injection so that you can use any service registered in the DI Container.

Creating a Block

A block is in essence a class that inherits from the abstract base class Piranha.Extend.Block. A block can contain one or several fields, it can actually contain data that is not a field as well, but only properties that derive from Piranha.Extend.IField will be saved in the database. All blocks need to be marked with the BlockType attribute.

As an example, let's take a look at how the Quote block is implemented.

using Piranha.Extend;
using Piranha.Extend.Fields;

[BlockType(Name = "Quote", Category = "Content",
    Icon = "fas fa-quote-right", Component = "quote-block")]
public class QuoteBlock : Block
{
    /// <summary>
    /// Gets/sets the text body.
    /// </summary>
    public TextField Body { get; set; }
}

Generic Blocks

All of the standard blocks included in Piranha has their own Vue component rendering them in the manager, but sometime you might just want to add a block that is not that visual and just gives the editor a form with the available fields in it.

By omitting the Component property of the BlockTypeAttribute Piranha will automatically handle the rendering of the block the same way it would with a region. Let's for example say we wanted an Author block that we could add at the bottom of our Posts.

using Piranha.Extend;
using Piranha.Extend.Fields;

[BlockType(Name = "Author", Category = "Content", Icon = "fas fa-use")]
public class AuthorBlock : Block
{
    public StringField Name { get; set; }

    public ImageField Image { get; set; }

    public HtmlField Description { get; set; }
}

The edit model for the block used for the manager interface is different in the way that instead of sending the actual AuthorBlock as the model it sends an array of the fields together with meta-data for rendering. Please note that it's completely possible to use this edit model for your custom block components as well, by setting the IsGeneric property together with a Component.

[BlockType(Name = "Author", Category = "Content", Icon = "fas fa-use",
    Component = "author-block", IsGeneric = true)]

Block Configuration

In order to register your block you need to mark it with the BlockType attribute like in the above example. This has the following properties.

Name
public string Name { get; set; }

This is the name that is shown in the manager interface when editing.

Category
public string Category { get; set; }

The name of category the Block will be grouped under in the manager interface.

Icon
public string Icon { get; set; }

Css class for rendering the Block icon in the manager interface. The manager interface uses the free icon package from font awesome.

IsUnlisted
public bool IsUnlisted { get; set; }

If you're developing a block that is supposed to be used within a Block Group you should set this to true. This will prevent it from being shown elsewhere when adding Blocks.

Component
public string Component { get; set; }

The name of the global Vue component that should be used when rendering the block in the manager.

Register The Block

All available blocks has to be registered in the singleton Piranha.App after the app has been initialized.

Piranha.App.Init(api);

// Register custom blocks
Piranha.App.Blocks.Register<MyCustomBlock>();

Create The Manager Component

The manager interface is based on Vue.js and assumes that all blocks are registered as global components. As these components are written in Javascript you need to register your custom resources in the manager. For more information about this, please refer to Resources in the section Manager Extensions.

A note on block templates

The root element of the block template needs to have the class block-body for the manager interface to be able to collapse it in the edit view.

Let's again take a look at how the component for the Quote block is implemented:

/*global
    piranha
*/

Vue.component("quote-block", {
    props: ["uid", "toolbar", "model"],
    methods: {
        onBlur: function (e) {
            this.model.body.value = e.target.innerText;
        }
    },
    computed: {
        isEmpty: function () {
            return piranha.utils.isEmptyText(this.model.body.value);
        }
    },
    template:
        "<div class='block-body' :class='{ empty: isEmpty }'>" +
        "  <i class='fas fa-quote-right quote'></i>" +
        "  <p class='lead' contenteditable='true' spellcheck='false' " +
        "    v-html='model.body.value' v-on:blur='onBlur'></p>" +
        "</div>"
});

Component Properties

All field components get three properties from the main edit views in the manager interface.

uid

A globally unique id that can be used in the component when rendering more complex views. This field is generated at runtime and will be different each time for every field.

toolbar

The id of the container that toolbars should be positioned inside, for example when using the Tiny MCE editor.

model

The model value from the block instance.