ProcessWire 2.5.5 – New module configuration

Module configuration got a lot nicer this week

ProcessWire 2.5.5 (dev) adds new module configuration options that will save module developers time. Of course, the old methods will continue to work, but we think there are some things you'll like better about the new methods. To get started, lets consider this simple test module that displays a message on every page load in the admin:

/site/modules/Test/Test.module


class Test extends WireData implements Module {
  public static function getModuleInfo() {
    return array(
     'title' => 'Module Test',
     'version' => 1,
     'summary' => 'Module for testing/demo purposes.',
     'autoload' => 'template=admin',
    );
  }
  public function ready() {
    if($this->fullname && !count($this->input->post)) {
      $msg = "Hi $this->fullname! ";
      $msg .= "Your age: $this->age. ";
      $msg .= "Favorite color: $this->color.";
      $this->message($msg);
    }
  }
}

That module has 3 configuration settings: fullname, age and color.

The way we used to do it

In the past, we would have implemented the module configuration like this below, as part of the Test.module file. It's a bit long, so just skip to the end of this next code sample if you want.

/site/modules/Test/Test.module


class Test extends WireData implements Module, ConfigurableModule {
  public static function getModuleInfo() {
    return array(
     'title' => 'Module Test',
     'version' => 1,
     'summary' => 'Module for testing/demo purposes.',
     'autoload' => 'template=admin',
    );
  }
  static protected $defaults = array(
    'fullname' => '',
    'color' => 'blue',
    'age' => 40
  );
  public function __construct() {
    // populate defaults, which will get replaced with actual
    // configured values before the init/ready methods are called
    $this->setArray(self::$defaults);
  }
  public function ready() {
    if($this->fullname && !count($this->input->post)) {
      $msg = "Hi $this->fullname! ";
      $msg .= "Your age: $this->age. ";
      $msg .= "Favorite color: $this->color.";
      $this->message($msg);
    }
  }
  public static function getModuleConfigInputfields(array $data) {
    $inputfields = new InputfieldWrapper();
    $data = array_merge(self::$defaults, $data);

    $f = wire('modules')->get('InputfieldText');
    $f->attr('name', 'fullname');
    $f->label = 'Full Name';
    $f->required = true;
    $f->attr('value', $data['fullname']);
    $inputfields->add($f);

    $f = wire('modules')->get('InputfieldSelect');
    $f->attr('name', 'color');
    $f->label = 'Favorite Color';
    $f->options = array(
      'red' => 'Red',
      'green' => 'Green',
      'blue' => 'Blue'
    );
    $f->attr('value', $data['color']);
    $inputfields->add($f);

    $f = wire('modules')->get('InputfieldInteger');
    $f->attr('name', 'age');
    $f->label = 'Your Age';
    $f->attr('value', $data['age']);
    $inputfields->add($f);

    return $inputfields;
}

That really adds a lot of verbosity to our simple module, doesn't it? Some things to note above:

  • We had to add a ConfigurableModule interface to our class definition.
  • We had to define a static getModuleConfigInputfields() method for configuration (an inconvenience).
  • We had to manually define and manage our default values in the static self::$defaults variable (a pain).
  • We have to manually populate values to our configuration fields (also a pain).

In the end, this method is reliable and works just fine… but is just not as nice as it could be. With ProcessWire growing, we need something nicer…

Enter the new ModuleConfig class

Now we have new options to define module configuration. We use a separate configuration file and class which is named the same as the module but with "Config" appended to the end of it. This new class simply extends the new ModuleConfig class included with the core. This ModuleConfig class can save us a lot of work!

Go back and look at the first code sample on this page, which shows the nice and clean "Test" module, before it got bogged down with all the configuration code. That clean and simple version is exactly what the module would look like when we use a ModuleConfig file. It needs no configuration-specific code, nor does it need to implement ConfigurableModule. It can simply just refer to it's configuration values with $this->fieldName without any preparation. Here's what the the ModuleConfig file looks like:

/site/modules/Test/TestConfig.php


class TestConfig extends ModuleConfig {
  public function getDefaults() {
    return array(
      'fullname' => '',
      'color' => 'blue',
      'age' => 40,
    );
  }
  public function getInputfields() {
    $inputfields = parent::getInputfields();

    $f = $this->modules->get('InputfieldText');
    $f->attr('name', 'fullname');
    $f->label = 'Full Name';
    $f->required = true;
    $inputfields->add($f);

    $f = $this->modules->get('InputfieldSelect');
    $f->attr('name', 'color');
    $f->label = 'Favorite Color';
    $f->options = array(
      'red' => 'Red',
      'green' => 'Green',
      'blue' => 'Blue'
    );
    $inputfields->add($f);

    $f = $this->modules->get('InputfieldInteger');
    $f->attr('name', 'age');
    $f->label = 'Your Age';
    $inputfields->add($f);

    return $inputfields;
  }
}

Note that we are implementing two methods here: getDefaults() and getInputfields(). The getDefaults() method just returns an associative array of field name and default value. The getInputfields() method returns a group of Inputfields. There are some benefits here versus the previous static method:

  • Default values get automatically populated to our module (we don't need to do that manually in a __construct() method).
  • We don't have to set the value attribute of any Inputfields… ModuleConfig takes care of that for us automatically.
  • No more static methods or variables interfering with the way we usually access the API in modules.
  • We don't have to do anything to tell ProcessWire that the module is configurable other than adding a file in the same directory named ModuleNameConfig.php, where you replace ModuleName with the actual module name. Note however that you will need to click the Modules > Refresh button in the admin in order for ProcessWire to notice it the first time.

Using an array to define module configuration

Another nice thing about the new ModuleConfig class is that it gives you another option for defining your module configuration. You can define it entirely as an array. Admittedly, I prefer to define things programatically, but some have asked for this array option in the past. So here it is. And I have to admit, now that I see it in action it's growing on me–I like how little code it takes to define module configuration:

/site/modules/Test/TestConfig.php


class TestConfig extends ModuleConfig {
  public function __construct() {
    $this->add(array(
      array(
        'name' => 'fullname',
        'label' => 'Full Name',
        'type' => 'text',
        'required' => true,
        'value' => '',
      ),
      array(
        'name' => 'color',
        'type' => 'select',
        'label' => 'Favorite Color',
        'options' => array(
          'red' => 'Red',
          'green' => 'Green',
          'blue' => 'Blue'
        ),
        'value' => 'blue',
      ),
      array(
        'name' => 'age',
        'type' => 'integer',
        'label' => 'Your Age',
        'value' => 40
      )
    ));
  }
}

That's all there is to it! Some things to mention about the above:

  • The entire configuration is defined in an array sent to the $this->add() method (a method of ModuleConfig). Each field has it's own array defining properties of the field. The properties are exactly the same properties you populate when doing it programatically (as in the previous example).
  • The "type" property is the only exception, as you don't see that when defining it programatically. This is where you tell it what type of field it can be. The value can be any Inputfield module name, or you can omit the "Inputfield" part at the beginning, and just specify the remainder, i.e. "text" rather than "InputfieldText".
  • Our default values are defined in this array as well. See the "value" property. This is a nice benefit, keeping everything together.
  • Not shown, but if you wanted to define a fieldset, then you would make the type "fieldset" and give it a "children" property which is itself an array like the above. You can nest as far as you'd like.

Alright… you guys that wanted arrays for defining Inputfields–I have to admit this is most definitely a good thing. So as a bonus (stepping outside module configuration for a moment), you can now use these arrays anywhere that you use Inputfields. Simply pass an array like the above to any InputfieldWrapper, InputfieldForm, or InputfieldFieldset via the add() method, and it will set it all up for you, i.e $form->add(array( ... ));

Here's the module configuration screen that all of the above examples produced:

Comments

  • SiNNuT

    SiNNuT

    • 10 years ago
    • 35

    Great stuff. Maybe change the post title to reflect the correct version, 2.4.5 instead of 2.5.5

  • Jens Martsch

    Jens Martsch

    • 8 years ago
    • 10

    @ryan This should be added to the blog entry.

  • ryan

    ryan

    • 10 years ago
    • 33

    Thanks guys. @Sinnut: why 2.4.5? the version is correct at 2.5.5

  • Bernhard

    Bernhard

    • 8 years ago
    • 31

    if anybody wonders how to set the "attr" value of the field via array syntax:

    array(
    'name' => 'yourfield',
    'type' => 'text',
    'attr' => array('autocomplete' => 'off')
    )

    • Rudy Affandi

      Rudy Affandi

      • 8 years ago
      • 10

      How do you pass options to asmSelect using this new method?

    • Martin Gartner

      Martin Gartner

      • 6 years ago
      • 50

      Great stuff! But why is this information buried in an old, hard to find blog post and not somewhere in the docs?

     

    PrevIntroducing ProcessWire ListerPro

    ListerPro enables you to find, filter, list and manipulate pages based on any of their properties. It is a truly unique, powerful and easy-to-use tool that will significantly enhance the way you work with ProcessWire. More 

    Latest news

    • ProcessWire Weekly #549
      In the 549th issue of ProcessWire Weekly we’re going to check out the latest core updates, highlight one older yet still very relevant third party module, and more. Read on!
      Weekly.pw / 17 November 2024
    • Custom Fields Module
      This week we look at a new ProFields module named Custom Fields. This module provides a way to rapidly build out ProcessWire fields that contain any number of subfields/properties within them.
      Blog / 30 August 2024
    • Subscribe to weekly ProcessWire news

    “Indeed, if ProcessWire can be considered as a CMS in its own right, it also offers all the advantages of a CMF (Content Management Framework). Unlike other solutions, the programmer is not forced to follow the proposed model and can integrate his/her ways of doing things.” —Guy Verville, Spiria Digital Inc.