# Create a new widget

# Tailor package

There are three kind of packages :

  • Starter (old: Basic)
  • Premium
  • Tailor

All new widgets not included in the list of basic and premium packages will be named "Tailor".

Please refer to this list before creating some new.

# Starting a new tailor

Go to minotaure-theme/widgets

cd htdocs/content/themes/minotaure-theme/widgets
1

In your repository widgets create a new folder named tailor without any caps and a subfolder for your widget.

In this exemple, we are going to create a simple accordion widget.

mdkir tailor

cd tailor

mdkir accordion

cd accordion
1
2
3
4
5
6
7

Then create 3 files :

  • Register.php
  • m_form.twig
  • m_widget.twig
touch Register.php
touch m_form.twig
touch m_widget.twig
1
2
3

You may have the following structure

├──minotaure-theme
|  ├──widgets
|  |  ├──tailor
|  |  |  ├── accordion
|  |  |  |   └── Register.php
|  |  |  |   └── m_form.twig
|  |  |  |   └── m_widget.twig
1
2
3
4
5
6
7

# Register.php

This file manages the connexion between wordpress datas, back-end view and front-end view. This is the most important file in a widget, without it nothing can work.

The code uses the php classes, google it if classes are a mistery to you.

# Create a new class

First, a class needs a namespace to be called

TIP

There is an only and unique way to name a widget namespace in minotaure-theme :

namespace Be\Minotaure\Widgets\Tailor\Your_widget_name
1

Your class will be called Register, this a standard for all widgets.

<?php

namespace Be\Minotaure\Widgets\Tailor\Accordion;

class Register extends \WP_Widget
{

}
?>

1
2
3
4
5
6
7
8
9
10

# __construct()

Now, we are going to create a constructor with some Minotaure standards

 public function __construct()
  {
    parent::__construct(
      'm_accordion',
      __("Accordion", M_TD)
    );

    $this->default = [
      'title' => '',
      'text' => '',
    ];
  }
1
2
3
4
5
6
7
8
9
10
11
12
  • m_accordion is the id of your widget. A widget ID is always prefixed by m_. It is the diminutive of minotaure

  • Accordion is the title of the widget in the widgets panel.

  • $this->default contains all inputs you want in your widget

# form()

The form function is used to get datas from the database to your widget form. The function needs to pass a variable named $instance, it contains all datas already registered.

public function form($instance)
{

}
1
2
3
4

It is time to merge $this->default previously defined and $instance in case there is data.

public function form($instance)
{
    $data = array_merge($this->default, $instance, [
        'widget' => $this
    ]);
}
1
2
3
4
5
6

The user may have to create several blocks in their accordion ,so we need to merge the widget and future sections.

public function form($instance)
{
    $data = array_merge($this->default, $instance, [
        'widget' => $this,
        'sections' => ''
    ]);
}
1
2
3
4
5
6
7

All text from an input type text must be decoded before being sent in the form input. Decoding not including editor type.

public function form($instance)
{
    $data = array_merge($this->default, $instance, [
        'widget' => $this,
        'sections' => ''
    ]);

    $data['title'] = html_entity_decode($data['title']);
}
1
2
3
4
5
6
7
8
9

It will also be necessary to create the sections with the data received and to configure them. Then push them in the $dataarray. The Section calls come from a helper class in Minotaure. This class is not autoload so we need to call it just after the namespace.

use Be\Minotaure\Widgets\Core\Sections;
1

Sections::create((array) $data);

Sections::set((array) $sectionsCreated, (array) $this)


public function form($instance)
{
    $data = array_merge($this->default, $instance, [
        'widget' => $this,
        'sections' => ''
    ]);

    $data['title'] = html_entity_decode($data['title']);

    $newSections = Sections::create($data);
    $data['sections'] = Sections::set($newSections, $this);
}
1
2
3
4
5
6
7
8
9
10
11
12

Last but not least, we need to return this datas to our future form. Same as Section class, we need to load SetView class.

use Be\Minotaure\Widgets\Core\SetView;
1

SetView::form((string) view.path, (array) $datas);


public function form($instance)
{
    $data = array_merge($this->default, $instance, [
        'widget' => $this,
        'sections' => ''
    ]);

    $data['title'] = html_entity_decode($data['title']);

    $newSections = Sections::create($data);
    $data['sections'] = Sections::set($newSections, $this);

    SetView::form('tailor.accordion', $data);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Now, you will be able to disable your widget in the dashboard and see the widget button in the panel. But you need a form and save all data from inputs.

# update()

Update function get all the data from your form, sanitize them before being sent to the database. The function need to pass your $instance and $old_instance, it contains all datas already registered and new datas.

public function update($instance, $old_instance)
{
}

1
2
3
4

First get and update sections in the DB

public function update($instance, $old_instance)
{
     $sections = Sections::get($instance);
     newSections = Sections::update($sections);
}
1
2
3
4
5

Then do the same with the data. For security purposes it is essential to validate data before sending them in the db. We need a two class Validator. One for all input, the second one only for editors.

use Be\Minotaure\Plugin\Facades\Validator;
use Be\Minotaure\Widgets\Core\Validator;
1
2

Oops both have the same class name, let's rename one of them.

use Be\Minotaure\Plugin\Facades\Validator;
use Be\Minotaure\Widgets\Core\Validator as ValidatorEditor;

1
2
3

Then use it and merge sections with array of data before returning it. All type of input need a different validation. Read the complete list here

public function update($instance, $old_instance)
{
     $sections = Sections::get($instance);
     $newSections = Sections::update($sections);

     $data = Validator::multiple($instance, [
        'title' => ['textfield'],
        'text' => ValidatorEditor::editor(),
    ]);

    return array_merge($data, $newSections);
}
1
2
3
4
5
6
7
8
9
10
11
12

Now the form is visible and is able to update data.

# widget()

Widget function call data and send it to the front-end. The function need to pass $args from Composer and $instance.

public function widget($args, $instance)
{
}
1
2
3

First merge $args, $this->default, $instance together and decode all text input type.

public function widget($args, $instance)
{
    $data = array_merge($args, $this->default, $instance);
    $data['title'] = html_entity_decode($data['title']);
}
1
2
3
4
5

Then we need to get sections in our instance and parse them if there has, to finally add them to array $data

public function widget($args, $instance)
{
    $data = array_merge($args, $this->default, $instance);
    $data['title'] = html_entity_decode($data['title']);

     $sections = Sections::get($instance);
    if (!empty($sections)) $sections = Sections::parse($sections);
    $data['sections'] = $sections;
}
1
2
3
4
5
6
7
8
9

The very last, we call the widget view.


SetView::view((string) view.path, (array) $datas);


public function widget($args, $instance)
{
    $data = array_merge($args, $this->default, $instance);
    $data['title'] = html_entity_decode($data['title']);

     $sections = Sections::get($instance);
    if (!empty($sections)) $sections = Sections::parse($sections);
    $data['sections'] = $sections;

    SetView::widget('tailor.accordion', $data);
}
1
2
3
4
5
6
7
8
9
10
11

# m_form.twig

Previously we were using php, now we will continue with twig language. React manage for you a big part of the widget instance. You only need to create a form to send data.

Most of HTML input are already created with right classes, id, etc…

# Create new input

In the Register.php, we define two inputs :

  • title
  • text

Title is a simple input text but Text is a complete editor.

First create the title

<fieldset>
{% include 'm_components/form/input/text.twig' with {
    label : "title",
    value: title,
    title_text: __("Title", constant('M_TD')),
    class: "widefat"
} %}
</fieldset>
1
2
3
4
5
6
7
8

(string) label: The ID of the input in this specific instance, must be the same than the value and in register file.

(var) value: Call the variable sent to $data

(string) title_text : Label of the input (client side)

(string) class : Add input class

Then create the editor

<fieldset>
  {% include 'm_components/form/input/text.twig' with {
    label : "title",
    value: title,
    title_text: __("Title", constant('M_TD')),
    class: "widefat"
  } %}
  {% include 'm_components/form/input/editor.twig' with { 
      label: "text", 
      value: text, 
      title_text:__("Description", constant('M_TD')), 
      class: "text-text" 
    } %}
</fieldset>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

See all inputs available here

Now we are going to create a div for the sections registered

<div class="repeater-wrapper">
{% if sections %}
    <div class="mt-repeater-hidden">
        <div class="mt-repeater-hidden-inputs">
            {{ sections|raw }}
        </div>
    </div>
{% endif %}
</div>
1
2
3
4
5
6
7
8
9

and a div to create the repeater

<div class="mt-repeater-main-container repeater-container-columns">
      "Input":{
        "label": "Titre",
        "name": "title"
      },
      "Editor":{
        "label": "Texte",
        "name": "text"
      }
</div>
1
2
3
4
5
6
7
8
9
10

(string) "Input" : the name of the function to call

(string) "label": Label of the input

(string) "name" : name and id of the input

See all inputs available for the repeater here

# m_widget.twig

All variable stocked in the $data are available at the same name. In Register.php, we previously defined :

  • title
  • text
  • sections

First create a condition to show the widget only if there is one of those variables.

{% if sections or title or text %}

{% endif %}
1
2
3

Then create widget section. All global class for a widget must be prefixed by a m_. You also need to add a data named m-pack="tailor".

{% if sections or title or text %}
<section class="m_accordion" m-pack="tailor">

</section>
{% endif %}
1
2
3
4
5

Now you can construct your html.

{% if sections or title or text %}
<section class="m_panel m_accordion" m-pack="tailor">

  {% include 'm_components/widgets/headline.twig' with {title: title, text: text} only %}

  {% if sections %}
  <div class="m_details">
    {% for section in sections %}
     <div class="m_details-item">
      <h2 class="m_details-item__head">{{section.title|raw}}</h2>
      <div class="m_details-item__body">
        {{fn.apply_filters('the_content', fn.html_entity_decode(section.text))|raw}}
      </div>
    </div>
    {% endfor %}
    </div>
  {% endif %}

</section>
{% endif %}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

WARNING

Texts need to be escape for special characters. Use |raw

Editor needs to be decoded and escaped. Use fn.html_entity_decode(section.text))|raw

Last Updated: 4/14/2020, 11:57:40 AM