ProcessWire 3.0.152 core updates

This week we have some major improvements to our core date/time Inputfield, as well as a new ability to specify your own custom classes for Page objects. Read on for all the details and examples.

Date/Time input improvements

ProcessWire 3.0.152 adds some pretty major updates to the core InputfieldDatetime module. Since the beginning this module has been responsible for handling all date inputs in ProcessWire, as well as pretty much anything running in ProcessWire that can collect dates (like FormBuilder). It has always used a standard text input combined with a jQuery UI date picker.

The jQuery UI date picker has been (and continues to be) a great solution, but for a long time I’ve thought ProcessWire would benefit from more options when it came to input of dates (and/or times). As good as the jQuery UI date picker is, it’s Javascript based and comes with the overhead of jQuery UI. Especially when it comes to front-end use, you may not want to have to load jQuery UI just to provide a single date input on a form. In addition, a Javascript date-picker isn’t as handy on mobile clients as it is on desktop clients.

HTML5 date and time inputs

The HTML5 date and time input types didn’t even exist when InputfieldDatetime was originally written. But now they’ve been around for quite awhile and have quite good support in browsers. In particular, the major mobile browsers provide really impressive input capabilities when using the HTML5 date input type. So adding support for the HTML5 date and time input types was one of the priorities with this update, and that’s what we did.

You may have also heard of the HTML5 “datetime-local” input type… Rather than adding that, I instead added the ability to use “date” and “time” inputs alongside each other as one of the input type options (like the screenshot above). The reason for this is that browser support for HTML5 datetime-local is not nearly as universal as support for HTML5 date and time input types, as far as I understand it.

Something to note is that if you specify a value of 1 or higher for the "Step seconds for time input setting", then you also gain the ability to specify seconds as well — this is just the way the HTML5 time input works.

Date input with day, month and year selects

In addition to the HTML5 date/time input types, we’ve also gained the ability to select dates with independent Month, Day and Year <select> elements, alongside each other. I personally find this to be the simplest, most direct way to input a date. Since it requires nothing more than just 3 regular HTML select elements, it is also perhaps the most portable, efficient and consistent means of collecting dates across all browsers and platforms. You can choose what order you want it to display them in (YMD, MDY, DMY) and you can also choose whether you want it to use full month names or 3-letter abbreviations. In addition, PHP translates the month names (or abbreviations) for the current language locale, automatically.

There are 2 downsides to selects for date inputs that we accounted for:

  1. It can be possible to select an invalid date like (February 30th).
  2. Year selection is always limited to options presented in the select.

Both of these issues were solved with a little bit of Javascript. For #1, we create a Javascript Date object to make sure that the selected date is valid. It is the “day” selection that can make a date invalid, so if an invalid date is selected, the day selection is reset. Meaning, it’s not possible to retain an invalid date selection. Of course, this is all validated server-side too.

To solve #2, two things were done: First, you can specify the range of years that you want to be selectable when configuring the field. Secondly, you can make it possible to select outside of that range. For instance, if your default selectable year range was 1930 to 2030, there are also selectable options for “< 1930” (first selectable option) and “> 2030” (last selectable option). Selecting either will increase the number of selectable options in that direction by 100 years.

The config screen above mentions "and optionally time" — I didn't manage to finish that part this week, but it should hopefully have that by PW 3.0.153. Likewise for the ability to select just month and year (no day), or day and month (no year).

New ability to specify custom Page classes

This week ProcessWire gained a pretty useful ability to specify your own custom PHP classes for Page objects on per-template basis. And it’s super simple to setup. But before we get into the details, here’s how to get it going…

Getting started

  1. Edit your /site/config.php file and add $config->usePageClasses = true;
  2. Create new directory /site/classes/
  3. Create a new file in that directory: /site/classes/DefaultPage.php and paste in the following:
<?php namespace ProcessWire;

class DefaultPage extends Page {}

Now, all pages in your site that were previously “Page” objects are now “DefaultPage” objects. Just to prove it to yourself, make sure you’ve got debug mode enabled ($config->debug = true), and go into your admin and click the “Debug Mode Tools” link (bottom right corner). In the debug tools, click the first accordion item (“Pages Loaded”) and note all the pages that indicate their “type” as “DefaultPage”.

You’ve now got full control over the class used for Page objects and can add methods or properties, or override existing ones. Of course, with a little more effort, you can also do that with hooks already. This DefaultPage example is a good way to introduce the custom Page classes, but where I think they are most useful is in specifying custom classes per template. So once you are done looking at the DefaultPage example, go ahead and delete your DefaultPage.php file (unless you want to keep it) and let’s get into some more details.

A more practical example

Let’s say that my site has a blog in it and I want each of my blog post $page objects to have some extra methods that I can use both when listing multiple blog posts, and when rendering single entire blog posts. One example would be a byline() method that combines the post author and post date in a single, consistent line, ready to output.

ProcessWire’s class loader looks for classes in the format [TemplateName]Page where the "TemplateName" portion is the template name converted to CamelCase. Because my blog post template is named blog-post, ProcessWire will look for a class named BlogPostPage. If the class is present, ProcessWire will use it rather than Page. The filename that the class is stored in mirrors the class name. So ProcessWire will look for a file named /site/classes/BlogPostPage.php with a class in it named BlogPostPage. Here it is with my new byline method, along with a new summary() method I also added, that returns a preview of the body copy:

<?php namespace ProcessWire;

class BlogPostPage extends Page {

  public function byline() {
    $date = wireDate('Y/m/d', $this->published);
    $name = $this->get('createdUser')->name;
    return "Posted on $date by $name";
  }

  public function summary() {
    return $this->wire('sanitizer')->truncate($this->body, 300);
  }
}

Now when I’m listing blog posts, I can use my new byline() and summary() methods:

<?php foreach($pages->find('template=blog-post, sort=-published') as $post): ?>
  <div class='blog-post'>
    <h2><?=$post->title?></h2>
    <p class='byline'>
      <?=$post->byline()?>
    </p>
    <p>
      <?=$post->summary()?>
      <a href='<?=$post->url?>'>Read more</a>
    </p>
  </div>
<?php endforeach; ?>

Anywhere else that my blog post pages appear, those methods I added will be available. This example is a bit contrived to keep it simple and just scratches the surface of how custom Page classes can be useful, but hopefully gives a taste of what’s possible here — you’ve now got a whole lot more control over ProcessWire with this addition.

Custom Page classes vs. hooks

Our examples here could also be accomplished with hooks, and hooks are a great way to do it. But there are a few benefits to using custom classes that I can identify so far:

  • When it comes to adding new methods or properties to a Page, custom classes are lower overhead than hooks since they accomplish this with PHP constructs rather than code logic, making them more efficient.

  • Your IDE doesn’t know what PW hooks are, but it does recognize PHP classes and knows how to use them for type hinting and documentation.

  • For overriding existing methods or properties, custom classes give you a lot more control since you can literally override anything, rather than just existing hookable methods.

  • Your custom classes can use PHP object inheritance, enabling you to maintain Page objects that build upon one another. For instance, your BlogPostPage class could extend your DefaultPage class rather than the Page class, and so on.

  • Hooks are exclusive to ProcessWire, whereas class inheritance is just standard PHP, and likely familiar to more people than hooks.

What about custom Page classes like User, Role, Permission?

ProcessWire has actually had the ability to support custom Page classes since the beginning, but it has been hidden in Advanced mode and not very straightforward to use, so it’s been rarely used (other than in the core). It was accomplished with each template’s “pageClass” property, which was how we’ve had Page objects that are actually User objects, and so on. This property could be edited on the “System” tab of any Template (Setup > Templates), but that System tab only appears if you’ve got Advanced mode enabled ($config->advanced=true).

In addition, ProcessWire gave you no way to provide the actual custom class file, so it wasn’t all that practical or straightforward to use for anything outside of the core… indeed it wasn’t really meant for use outside of the core. But the new custom Page class ability added this week is very much intended for front-end use outside of the core, and it’s a whole lot simpler to get going with.

Since there is that existing core way to specify page classes, you might be wondering if that could collide with the new ability? They actually work together. Let’s take the “user” template as an example. It has a built-in custom page class of “User”. If you want to provide your own custom class for users, then you must extend the existing User class rather than Page (in /site/classes/UserPage.php):

<?php namespace ProcessWire;
class UserPage extends User { … }

If your UserPage extended Page instead, then ProcessWire would not use it. But since our example above extends User (rather than Page) now all of our User objects in ProcessWire are UserPage objects! Whatever we add to our UserPage class is now present on all of our User objects.

Expanding this feature

Currently you have to enable this capability on your own with the $config->usePageClasses = true; setting, and then create the /site/classes/ directory and add your custom class files in there. I think this option should be disabled by default for existing installations, but have also been thinking that perhaps the option should be enabled already for new installations (depending on site profile). So I may update one or more of our site profiles to have it enabled and demonstrate this feature.

In addition, when enabled, I also think there’s potential value in ProcessWire maintaining the phpdoc documentation for them automatically based upon what fields you add to each template. Robin S.’s recent AutoTemplateStubs module does this, but the difference here is that the classes are the actual ones that PW uses (for runtime execution) rather than purely for documentation purposes.

The reality is that this type of documentation, inspection, code hinting and completion is a real game changer once you start using it. So I think a natural next step for custom Page classes is for ProcessWire to be able to automatically document them too. But having PHP write/manipulate PHP files that are actually used at runtime can be kind of a shady thing, so maybe it instead provides the phpdoc to copy/paste, or maybe we leave this to 3rd party modules like AutoTemplateStubs, which already work quite well. I’m not yet sure, but will look at it more.

Lastly, if there is interest, I thought we also might provide ability to provide custom classes for other objects in PW such as custom PageArrays. But as things are now, I think these custom Page classes open up a whole lot of new possibilities in ProcessWire. The next time you need to extend or modify something Page related in PW, consider trying out these new custom Page classes as a nice alternative to hooks.

That’s all for this week. Thanks for reading and I hope you enjoy this week’s updates. As always, be sure to also read the ProcessWire Weekly for the latest ProcessWire news and updates.

Comments

  • Pete

    Pete

    • 4 years ago
    • 81

    As usual, you release a useful new function just as I have a need for it - mind reading at its best 😄 Custom page classes are going to keep my code a lot tidier for something I'm working on at the moment 👍🏻

  • thetuningspoon

    thetuningspoon

    • 4 years ago
    • 41

    Great to see more attention paid to custom Page classes. We've been using this (the Advanced Mode feature) for years now in our web applications and couldn't live without it.

  • Sam

    Sam

    • 4 years ago
    • 12

    Does have a page methode or property has a higher precedence, compared to a method added via hook?

    • ryan

      ryan

      • 4 years ago
      • 43

      That's correct, it would have a higher precedence than a hook. Also, if you wanted to make a new method that you added hookable, you can also do that. You would just change "public function methodName()" to "public function ___methodName()" (3 underscores prepended to the method name). Then, every time you call $page->methodName(); any "before" hooks would execute before the method call, and any "after" hooks would execute after it.

      • Sam

        Sam

        • 4 years ago
        • 43

        That’s great. Hadn’t thought about other hooks for custom class methods. But hooking sometimes can make things a lot easier. Thanks!

 

PrevProcessWire 3.0.151 core updates

2

This week we’ve got a couple of really useful API-side improvements to the core in 3.0.151, among other updates. First we’ll take a look at a new $config setting that lets you predefine image resize settings, enabling you to isolate them from code that outputs them, not unlike how you isolate CSS… More 

NextProcessWire 3.0.153 core updates

This latest version of the core on the dev branch focuses on comments field updates, significant refactoring/improvements to ProcessWire’s core Template class and PagesLoader class (which is used by the $pages API variable), and we introduce a useful new $pages API method. More 

Latest news

  • ProcessWire Weekly #520
    In the 520th issue of ProcessWire Weekly we'll check out some of the latest additions to the ProcessWire module's directory, share some highlights from the latest weekly update from Ryan, and more. Read on!
    Weekly.pw / 27 April 2024
  • ProFields Table Field with Actions support
    This week we have some updates for the ProFields table field (FieldtypeTable). These updates are primarily focused on adding new tools for the editor to facilitate input and management of content in a table field.
    Blog / 12 April 2024
  • Subscribe to weekly ProcessWire news

“The end client and designer love the ease at which they can update the website. Training beyond how to log in wasn’t even necessary since ProcessWire’s default interface is straightforward.” —Jonathan Lahijani