Cornerstone Custom Elements

This article was last updated on the January 2, 2017.

Cornerstone offers an API allowing you to introduce new elements. This article will be an in-depth tour of how that works. All the code examples to followed are summarized in this sample extension. It’s a great starting point for new plugins that will be registering Cornerstone elements.

Download Sample Extension

You can explore how existing Cornerstone elements are created by looking in the /includes/elements folder of the plugin. Keep in mind that files in the _alternate folder are using our old API, and will eventually be moved to the new one.

Anatomy of a Cornerstone Element

To get starting with the API, we’ll cover how the simplest form of an element works. Along the way we’ll link to other sections of the documentation where concepts are more fleshed out. An element can be simplified to two parts:

  1. Definition
  2. Output

The definition provides Cornerstone with the information required to show your element in the element library and shortcode generator. It also tells the page builder how the element should behave.

The output is used in two places. First, it will provide the final generated markup (usually a shortcode) to be placed in the page when a user saves. Second, it will output the markup used for displaying the element in the preview window.

That’s all you need to create an element (not a very interesting one). To look a layer deeper, your element will a set of default values and controls to manipulate them. Let’s take a closer look. Using the sample extension as an example, let’s examine the file structure, and which files relate to our two main parts:

my-element
  controls.php (Definition)
  defaults.php (Definition)
  definition.php (Definition)
  shortcode.php (Output)

These files are what you might expect to see from a typical element. definition.php is the only mandatory file, as you could structure your definition to get the controls, defaults, and shortcodes by other means. We’ll cover this more later.

Base Definition

Each element needs a definition.php file, which should contain a single class named after your element. You can use any class name you want. From there the only function you need to declare is ui which should return an array with a localized title.

class My_Element {

  public function ui() {
    return array(
      'title'       => __( 'My Element', 'my-extension' ),
    );
  }
  
}

This code is enough for Cornerstone to add your element to the Element Library. There are many other things we can do in this class, but we’ll cover those in their related areas to follow, along with a special section for advanced element defintions.

Defaults

Next we’ll look at defaults.php. This file will contain a list of properties that you would like to make available in your output handler (most often a shortcode). This file will simply return an array of key and value pairs for your defaults. When a new element is added, it will use the values set here until the user overrides them via controls. For example:

return array(
  'id'          => '',
  'class'       => '',
  'style'       => '',
  'color'       => '#000',
  'orientation' => 'horizontal'
);

You must provide a default for each control mapped to an element. If you don’t want to use a defaults.php file, you can declare your defaults directly on your definition class in a defaults function. For example:

class My_Element {

  ...

  public function defaults() {
    return array(
      'id'               => '',
      'class'            => '',
      'style'            => '',
    );
  }

}

Controls

To finish defining our basic element we’ll provide some controls in controls.php. Here’s a snippet of what that may look like:

return array(

  'content' => array(
    'type' => 'text',
    'ui' => array(
      'title' => __( 'Text', 'my-extension' ),
      'tooltip' => __( 'Optional tooltip. Shown when hovering over the control label', 'my-extension' )
),
  'context' => 'content',
  'suggest' => 'This is the default text.'
  ),
  'color' => array(
    'type' => 'color',
      'ui' => array(
        'title' => __( 'Text Color', 'my-extension' ),
    'tooltip' => __( 'Optional tooltip. Shown when hovering over the control label', 'my-extension' ),
  )
  ),

  'orientation' => array(
    'type' => 'choose',
    'ui' => array(
      'title' => __( 'Orientation', 'my-extension' ),
    ),
    'options' => array(
      'columns' => '2',
      'choices' => array(
        array( 'value' => 'vertical',   'tooltip' => __( 'Vertical', 'my-extension' ),   'icon' => fa_entity( 'arrows-v' ) ),
        array( 'value' => 'horizontal', 'tooltip' => __( 'Horizontal', 'my-extension' ), 'icon' => fa_entity( 'arrows-h' ) ),
      )
    )

  )

);

It’s just another array returned from the file. The first level will be a key corresponding to a property in defaults.php. From there you specify a type, ui, and fill in any options the control may need. The controls themselves are covered more in-depth in the Mapping Controls section.

Output

Now that the definition is complete, let’s put some output together. We can do this in shortcode.php Here’s a very simple example that can show how this all works:

<p class="my-element <?php echo $orientation; ?>" style="color: <?php echo $text_color; ?>">
  <?php echo $content ?>
</p>

Notice there isn’t a WordPress add_shortcode call, or any other boilerplate. shortcode.php is a straightforward template file, with access to all your control values as PHP variables. Cornerstone handles all the boilerplate behind the scenes. There may be cases where you need to work with existing shortcodes instead of having Cornestone furnish them. See Advanced Element Definitions for more information on this.

Once shortcode.php is in place, you can revisit the page builder and see your element change in real time as you manipulate the controls. When saving the page, a shortcode will be added to the page with the appropriate values.

Mapping Element Controls

A control map is an associative array where the key indicates which “default” should be used, and the value is a list of properties defining the control. The only requirements are a type, and a ui which should contain at least a title. You can also declare options to pass flags and parameters to more clearly specify your control’s appearance and behavior. For example:

'default' => array(
  'type' => '',
  'ui => array(
    'title' => '',
  ),
  'options' => array()
)

At this level of the control definition, you will also see context and suggest paired together. This is preperatory work in the API for an upcoming Cornerstone feature where users will be able to set the default values of element controls globally. If you set 'context' => 'content', you can prevent users from editing controls that may not make sense to have a global value for. suggest can be used to provide a default value in that case.

The ui property supports title and tooltip string values which you should localize. It also supports a boolean value for divider which you can declare to add a line below your control for organizational purposes. Now we’ll get into the control types, and their supported options.

Regarding output: Cornerstone controls are stored different than they are outputted. For example, the dimensions control is stored as an array of the 4 values, with a fifth value to indicate if the array is linked. Like this:

array( '5px', '0px', '5px', '0px', 'unlinked' )

Before being used in output (like a shortcode) Cornerstone will “transform” the data. At this point, you won’t have an array, but a string with a reduced form, that is useful in CSS:

5px 0px

Keep this in mind while using controls.

Choose

Type: choose

Description: The choose control allows you to show a group of buttons with text or icon depictions of what your control does.

Available Options:

  • columns Integer up to 5.
  • choices List of choices available.
    • icon Should be a Font Awesome HTML entitiy
    • label Text value to display under the icon
    • tooltip Shown on hover by the browser
    • value Required. What will be stored in this control, and available as it’s value in your element output

Example:

'orientation' => array(
        'type' => 'choose',
        'ui' => array(
            'title' => __( 'Orientation', 'my-extension' ),
      'tooltip' => __( 'Choose to display the heading vertically or horizonatally', 'my-extension' ),
      'divider' => true
        ),
        'options' => array(
            'divider' => true,
      'columns' => '2',
      'choices' => array(
        array( 'value' => 'vertical',   'tooltip' => __( 'Vertical', 'my-extension' ),   'icon' => fa_entity( 'arrows-v' ) ),
        array( 'value' => 'horizontal', 'tooltip' => __( 'Horizontal', 'my-extension' ), 'icon' => fa_entity( 'arrows-h' ) ),
      )
    )
  ),

Color

Type: color

Description: Cornerstone’s very own “Huebert” color picker, featuring alpha transparency. The value will be an RGBA color.

Available Options:

  • output_format Defaults to hsl. Can be: hsl, rgb, hsv. Will always include an apha channel.

Example:

'heading_color' => array(
    'type' => 'color',
    'ui' => array(
        'title'   => __( 'Heading Color', 'my-extension' )
    )
),

Date

Type: date

Description: A date picker. The output is provide as {date}|{date_format} for convenint access to both the raw date, and the user selected format.

Available Options:

  • choose_format Allow the user to choose a date format. Defaults to true
  • default_format Set which format should be selected by default. Defaults to Do MMMM YYYY
  • available_formats An array of formats available with this date picker. Defaults to a handful of commonly used formats. For more info, see the source file: /includes/classes/controls/class-control-date.php
  • delimiter Set the delimeter used when storing the date and format.

Example:

'date' => array(
    'type'    => 'date',
    'ui' => array(
        'title' => __( 'Date', 'my-extension' ),
    ),
    'options' => array(
        'choose_format' => false
    )
),

Dimensions

Type: dimensions

Description: Allow the users to define four units of CSS, optionally linking their values.

Available Options:

  • lock Force a value in a particular field. For example, Cornerstone rows offer top/bottom margin, but lock in left/right as auto. See the code example for

Example:

'custom_margin' => array(
    'type' => 'dimensions',
    'ui' => array(
        'title'   => __( 'Margin', 'my-extension' )
    ),
    'options' => array( 'lock' => array( 'left' => 'auto', 'right' => 'auto' ) )
),

Editor

Type: editor

Description: The WordPress TinyMCE editor. Regretfully, the WordPress editor itself can be quite fragile, so it’s a stripped down version that is loaded without additional plugins. It’s [wasn’t designed] to work in a live environment. We were able to workaround that limitation in Cornerstone, but removing this control and replacing it with something more robust is under consideration by our development team.

Available Options:

  • expandable Defaults to true. Allows the editor to be expanded, showing a larger version covering the preview area.

Example:

'content' => array(
    'type'    => 'editor',
    'context' => 'content',
    'suggest' => __( 'Click to inspect, then edit as needed.', 'my-extension' ),
    ),

Icon Choose

Type: icon-choose

Description: Allows the user to select an icon from the Font Awesome library.

Available Options:

  • expandable Defaults to true. Allows the icon selection area to be expanded, showing a larger version covering the preview area.

Example:

'icon' => array(
    'type' => 'icon-choose',
    'ui' => array(
        'title' => __( 'Icon', 'my-extension' ),
    ),
    'context' => 'content',
    'suggest' => 'lightbulb-o',
  ),

Image

Type: image

Description: A simple image control. Currently images that are assigned are stored by their URL (not attachment ID).

Available Options: None

Example:

'image' => array(
    'type' => 'image',
    'ui' => array(
        'title' => __( 'Image', 'my-extension' ),
        'tooltip' => __( 'Choose an image to display above your content.', 'my-extension' ),
    )
  ),

Multi Choose

Type: multi-choose

Description: Multi-choose controls can be mapped exactly like Choose controls. The difference is that users will be able to have multiple choices active as once. The value of the control will be an array containing strings for each active choice.

Available Options:

  • columns Integer up to 5.
  • choices List of choices available.
    • icon Should be a Font Awesome HTML entitiy
    • label Text value to display under the icon
    • tooltip Shown on hover by the browser
    • value Required. What will be stored in this control, and available as it’s value in your element output

Example:

'features' => array(
        'type' => 'multi-choose',
        'ui' => array(
            'title'   => __( 'Extra Features', 'my-extension' )
        ),
        'options' => array(
      'columns' => '4',
      'choices' => array(
        array( 'value' => 'option1', 'icon' => fa_entity( 'bolt' ) ),
        array( 'value' => 'option2', 'icon' => fa_entity( 'camera' ) ),
        array( 'value' => 'option3', 'icon' => fa_entity( 'flask' ) ),
        array( 'value' => 'option4', 'icon' => fa_entity( 'leaf' ) ),
      )
    ),
    ),

Number

Type: number

Description: Provides a number input allowing users to easily increment/decrement their selection.

Available Options: None

Example:

Select

Type: select

Description: The traditional browser select. Provides a dropdown with a list of options.

Available Options:

  • choices List of choices available.
    • value Required. What will be stored in this control, and available as it’s value – label Text to display for this value
    • disabled Defaults to false. Set as true to prevent the item from being selected. Useful for programatically generated lists, such as choosing an entity from your plugin to display.

Example:

'this_and_that' => array(
  'type' => 'select',
  'ui' => array(
    'title' => __( 'Pick Something', 'my-extension' ),
    'tooltip' => __( 'This affects that.', 'my-extension' ),
  ),
  'options' => array(
    'choices' => array(
      array( 'value' => 'first',   'label' => __( 'First', 'my-extension' ) ),
      array( 'value' => 'second',  'label' => __( 'Second', 'my-extension' ) ),
      array( 'value' => 'third',   'label' => __( 'Third', 'my-extension' ) ),
      array( 'value' => 'fourth',  'label' => __( 'Fourth', 'my-extension' ) ),
    )
  )
),

Sortable

Type: sortable

Description: Sortables can be used to establish a one time parent/child relationship between elements. This is covered more in-depth later on in the Sortables section of this article.

Available Options:

  • element required. Set the name of what will be used as the child element.
  • newTitle When a new item is added, you can determine what the title is. This should be a formattable string containing %s which will become a number.
  • floor Set a minimum amount of items in this sortable group. It will not let users delete past this amount.
  • capacity Sets the maximum amount of items allowed.
  • title_field Sets which field of the child element should be pulled through to use as the title on the sortable item control.

Example:

'elements' => array(
        'type'    => 'sortable',
        'options' => array(
            'element' => 'my-sortable-element-item',
            'newTitle' => __( 'Item %s', csl18n() ),
            'floor'   => 1,
      'capacity' => 4,
      'title_field' => 'heading'
    ),
    'context' => 'content',
    'suggest' => array(
        array( 'heading' => __( 'First Item', csl18n() ) ),
        array( 'heading' => __( 'Second Item', csl18n() ) ),
      )
    ),

Additional considerations:

  • When using sortables, you must set the key to elements for it to work properly.
  • Use the suggest field to declare some default child elements with specific values.
  • Look at the Cornerstone native Pricing Tables, Block Grid, and Icon List for more examples.

Text

Type: text

Description: A single line text input.

Available Options:

  • placeholder Accepts a string to show as a placeholder when the field is empty.
  • monospace Useful for code related fiends. Set to true if you would the field styled with a monospace font.

Example:

'heading' => array(
    'type'    => 'text',
    'ui' => array(
        'title'   => __( 'Heading ', 'my-extension' ),
    ),
    'options' => array(
        'monospace' => true
    ),
    'context' => 'content',
    'suggest' => __( 'Heading', 'my-extension' ),
),

Textarea

Type: textarea

Description: Multiline text input.

Available Options:

  • placeholder Accepts a string to show as a placeholder when the field is empty. Useful when you don’t want to use suggest which actually populates the field.
  • monospace Useful for code related fiends. Set to true if you would the field styled with a monospace font.
  • expandable Defaults to true. Allows the textarea to be expanded, showing a larger version covering the preview area.
  • htmlhint Set to true to enable HTML hinting. An error message will be triggered if the field contains broken HTML such as mismatched tags.

Example:

'content' => array(
    'type'    => 'textarea',
    'context' => 'content',
    'suggest' => __( 'Click to inspect, then edit as needed.', 'my-extension' ),
),

Title

Type: title

Description: Behaves identically as the text control, but offers a more prominent presentation. This is often used with sortable controls.

Available Options: None

Example:

'heading' => array(
    'type'    => 'title',
    'context' => 'content',
    'suggest' => __( 'Sortable Item', 'my-extension' ),
),

Toggle

Type: toggle

Description: Like a checkbox, the toggle lets you control on/off states. It will be transformed to string "true" and “false”` when shortcodes are being saved for the page content, but inside Cornerstone’s shortcode handlers they will be available as booleans.

Available Options: None

Example:

'some_feature' => array(
    'type'    => 'toggle',
    'ui' => array(
        'title'   => __( 'Enabled', 'my-extension' ),
        'tooltip' => __( 'Enables the feature.', 'my-extension' ),
    ),
)

Mixins

Cornerstone has many controls available in the form of mixins. If the controls you need are pretty standard, you can pull in pre-existing definitions. You can explore the available control mixins in /includes/config/controls/mixins.php

You’ll notice by default, every element will have id, class, and style available. Cornerstone automatically provides these. We can change this by adding a new common entry in our controls array. Cornerstone looks for common and uses it to setup mixins. For example, this will remove the id and style controls:

'common' => array( '!id', '!style' ),

And this will add padding, border, and text_align controls.

'common' => array( 'padding', 'border', 'text_align' ),

Many of these mixins run sugar behind the scenes. For example, margin and padding will append to the style property inline styles. text_align will append a helper class to the class property.

Advanced Element Definitions

The Anatomy of a Cornerstone Element section introduced us to how element definitions work, but there is quite a bit more they are capable that we can cover here.

UI

A title is the only required part of the UI definition. We’ll start with a code example containing all the optional fields, then cover the specifics on each one.

public function ui() {
    return array(
      'title'       => __( 'My First Element', 'my-extension' ),
      'autofocus' => array(
            'heading' => 'h4.my-first-element-heading',
            'content' => '.my-first-element'
        ),
        'icon_group' => 'my-extension'
    );
}
  • title Required. Sets the localized name for your element, displayed throughout the page builder.
  • autofocus This is an array of control names paired with jQuery selectors. When a click is detected for a given selector, Cornerstone will attempt to automatically bring focus to the associated control when the Inspector opens.
  • icon_group Set to the SVG file you registered in an icon map. This is covered in the Plugin Integration article.

Using your own shortcode

Often you may want to use an existing shortcode instead of having Cornerstone dynamically register one for you. To do this, first add this function to your definition class:

public function register_shortcode() {
    return false;
}

Next we need to tell Cornerstone what shortcode to use. You can specify it with the $shortcode_name property.

public $shortcode_name = 'my_shortcode';

When saving and previewing content, Cornerstone will now pass the control data as attributes to your shortcode directly.

Preserving Content

By default, WordPress will perform several formatting routines on content. Sometimes you may want to avoid this, such as not adding <p></p> tags, altering whitespace, or affecting code. This is possible by adding the $preserve_content property to your class.

public $preserve_content = true;

Manipulating Shortcode Attributes

You can add a function on your definition class to filter how attributes are passed through from the page builder into the shortcodes. This will determine how they are saved into the page content. To do this, add the update_build_shortcode_atts function to your definition class:

public function update_build_shortcode_atts( $atts ) {

        // This allows us to manipulate attributes that will be assigned to the shortcode
        // Here we will inject a background-color into the style attribute which is
        // already present for inline user styles
        if ( !isset( $atts['style'] ) ) {
            $atts['style'] = '';
        }


        if ( isset( $atts['background_color'] ) ) {
            $atts['style'] .= ' background-color: ' . $atts['background_color'] . ';';
            unset( $atts['background_color'] );
        }

        return $atts;

    }

Sortables

Once you’ve mapped your sortable control, you’ll need to ensure the proper flags are setup on the element definitions. First, add a flags function to both the parent and child sortables.

public function flags() {
    return array(
    
    );
}

Next, add the corresponding flags to each element.

  • child This must be set to true on elements that are going to be used as children in a sortable. It shouldn’t be set on any other form of element.
  • dynamic_child Set to true on parent elements if you want the child elements to be individually inspectable in the page builder. Depending on how your elements are built, this may not be possible. For example, our block grid element doesn’t use this because of how it needs to render.

You may also want the child shortcode to depend on attributes from the parent. For this to work, the above described update_build_shortcode_atts function offers an optional $parent argument. For example:

public function update_build_shortcode_atts( $atts, $parent ) {

    if ( ! is_null( $parent ) ) {
        $atts['linked'] = $parent['linked'];
    }

    return $atts;

}

We also advise reviewing the sample extension to see sortables in action.