Jump to content

New blog post: Introducing the Custom Fields Module


ryan
 Share

Recommended Posts

This week we introduce a new module named Custom Fields. This module provides a way to rapidly build out ProcessWire fields that contain any number of subfields/properties within them. No matter how simple or complex your needs are, Custom Fields makes your job faster and easier. Not only does this post introduce Custom Fields, but also documents how to use them and includes numerous examples— 

https://processwire.com/blog/posts/custom-fields-module/

  • Like 10
  • Thanks 5
Link to comment
Share on other sites

@ryan Understood. I suppose I would rephrase the question with a scenario. Let's say I install the module, will it or could it auto-generate definitions of existing fields?

I hope I'm not missing something obvious. And I will read the post in detail, but we have to head out for the evening 🙂

Link to comment
Share on other sites

@Jim Bailie If I understand the question correctly, you want to convert several regular ProcessWire fields into a single Custom Field? There isn't an automated way to do that. I suppose there could be though, as they are using all the same configuration properties. 

  • Like 1
Link to comment
Share on other sites

@ryan, thanks for creating this cool module!

A couple of questions:

1. Do the subfields have an ID of some sort separate to the name, such that a subfield can be renamed without losing the existing data stored for the subfield?

2. What happens to stored data if a subfield is removed from the defining file? Does the existing data for the subfield persist in the database, or does the fieldtype detect the removal and do some cleanup to remove the stored data?

Also, I think there's a typo in the blog post.

// multi-selection
foreach($page->custom_field->countries as $value) {
  $label = $page->field->label('countries', $value);
  echo "<li>$value: $label</li>"; // i.e. "USA: United States"
}

...should be...

// multi-selection
foreach($page->custom_field->countries as $value) {
  $label = $page->custom_field->label('countries', $value);
  echo "<li>$value: $label</li>"; // i.e. "USA: United States"
}

 

  • Like 6
Link to comment
Share on other sites

@Robin S These property/subfield definitions are in files rather than the database, so there are no database-style IDs. Or, you can think of the property names as the IDs. There is already is a to-do note in the module to add support for property aliases, so that you can rename properties without having to convert data. That's not in this v1 beta version, but likely will be in the next one. That will enable you to rename properties when/if the need arises. But you'll still have to update your own code that refers to any of those names, as would be the case with any other field. 

When it comes to deleting properties, the no-longer-needed data would be cleaned up whenever it is saved. This is like any other Fieldtype that encodes multiple properties/subfields together (Textareas is one example, Combo is another, depending on the chosen storage method). If you regularly need to rename and delete these kinds of things after development of a site, regular old ProcessWire fields (without subfields) are hard to beat. But either way, you still have to consider your own code that's referring to those fields. 

Thanks, I will correct the typo! 

  • Like 5
Link to comment
Share on other sites

@ryan The module sounds very cool. Does it support MariaDB too or only MySQL?

Quote

1. Do the subfields have an ID of some sort separate to the name, such that a subfield can be renamed without losing the existing data stored for the subfield?

A storage_name option could be useful to be able to rename the field in the api, but keep the values in the database.

 

Edited by MrSnoozles
Link to comment
Share on other sites

@MrSnoozles sorry, I have to correct myself here. I should have written that MariaDB and MySQL are generally interchangeable. There are different features since the very beginning, but they are very specific. It seems like, though, MySQL 8.0 has some bigger differences.

Concerning JSON functionality. For what I understand, the difference is how JSON is stored on each of them. MySQL stores JSON as binary objects, and MariaDB as strings, but the functionality is (again) generally similar.

  • Like 1
Link to comment
Share on other sites

As far as JSON column types, the module lets you choose between JSON, TEXT, MEDIUMTEXT and LONGTEXT (or is it BIGTEXT, I can't remember). The main difference between them is how many kilobytes/megabytes/gigabytes you can hold. Functionally I can't tell any difference between them, and you can easily switch between them in the module settings. But as I understand it, JSON columns have the benefit of being more optimized for MySQL JSON-based queries, even if those queries still work on the text column types. I expect there may be a measurable difference at larger scale that isn't yet apparent at the scale I'm currently working at. The downside with the JSON column type is that you can't have a FULLTEXT index. So you can query individual subfields/properties, but can't perform a text search on all of them at once. As I understand it, JSON column types also have the benefit of being able to support MySQL 8 multivalue indexes. These enable you to pick and choose which individual fields within the JSON you want to index separately, or combine several of them in one index. I plan to support these with CustomFields in a future version. For now, I find MEDIUMTEXT to be a good fit for my project, as I do like to be able to perform text searches on the entire field at once, while also being able to query individual fields within it.

Link to comment
Share on other sites

@ryan Is there a way to make the custom field children iterable? I have a script that loops through a couple of fieldtypes on a page where I don't know the names of the custom field children at runtime.

My solution requires a little digging to access the '_custom_property_name' by drilling down into $customFields->defs()->getInputfields()->children() then looping through the subfield defs and accessing the value using $page->{$customField->name}->{$subfieldDef->_custom_property_name}

I might be missing a better way to do that. I'm coding so much that my brain is going to melt and might have missed the right way to do it.

  • Like 1
Link to comment
Share on other sites

@FireWire You can just iterate $page->your_custom_field as if it were an array. There are a couple of foreach() examples in the blog post, in the section headlined "Outputting custom fields". Though let me know if I've misunderstood what you are looking for. 

  • Like 1
Link to comment
Share on other sites

I had to review my code a bit to see what I meant. Bad job formulating that question... can confirm that my brain is soup. I needed to get the value of addClass.

When I create the custom fields I'm setting a value for 'addClass' and while I can loop through the custom field to get names/values I couldn't access an underlying field object to get to the class string. It ended up being complex but I think that's because of how the data is stored for the child fields.

Here's the loop I put together where the subfield had to be accessed separately to get the value set by 'addClass', using that chain I noted above was just because it was in this context.

<?php

foreach ($customField->defs()->getInputfields()->children() as $subField) {
    $subField->class; // Provides the class that I added when configuring the child field
  
  	// Skip this field if it has X class assigned
  
    $subField->value; // Doesn't provide the value
    $page->{$customField->name}->{$subfieldDef->_custom_property_name}; // Used this to get the value because it was accessible here
}

// Looping this way looped over the 'data' property of the CustomField object
foreach ($customField as $subfield) {
  $subfield; // Not a field object so no value or class property
}

So it was a workaround because I couldn't iterate over the child fields as if they were normal fields.

  • Like 1
Link to comment
Share on other sites

@FireWire Apologies, I still don't completely follow what you are trying to do in the code above, but wanted to comment about a couple of things. 

Quote

$subField->value; // Doesn't provide the value

This is because at this point in your code, you've only dealt with the Field object (or in this case a CustomField object), and no $page has been involved. Since values are stored with pages, all you've got here is a set of blank Inputfields, which probably isn't useful for anything. 

Quote

// Looping this way looped over the 'data' property of the CustomField object
foreach ($customField as $subfield) {
  $subfield; // Not a field object so no value or class property
}

In this case you are iterating that Field object, which I don't think has any value. What you want to iterate is the value from the page. So if your CustomField is named "custom_field": 

foreach($page->custom_field as $property => $value) {
  echo "<li>$property: $value</li>";
}

 

Quote

When I create the custom fields I'm setting a value for 'addClass' and while I can loop through the custom field to get names/values I couldn't access an underlying field object to get to the class string. It ended up being complex but I think that's because of how the data is stored for the child fields.

Are you setting an 'addClass' property to your Inputfield definitions in your /site/templates/custom-fields/field_name.php file? And you want to use the value of that property somehow on the front-end of your site? That property is for adding a class to the Inputfield in the admin, but if you want to have access to it on the front-end of your site, I suppose you could do this:

$defs = $fields->get('custom_field')->defs(); /** @var CustomFieldDefs $defs */
foreach($page->custom_field as $property => $value) {
  $f = $defs->getPropertyInputfield($property);
  echo "<li>addClass for $property is: $f->addClass</li>";
}

But you might also just consider going straight to the source, by including your field definitions php file directly: 

$defs = include('./custom-fields/field_name.php'); /** @var array $defs */
foreach($page->custom_field as $property => $value) {
  $def = $defs[$property];
  if(isset($def['addClass'])) {
    echo "<li>addClass for $property is: $def[addClass]</li>";
  }
}

Note this will only work if you don't have your properties nested within fieldsets. If they are nested in fieldsets, you can still do it, but you'd just need to account for that in the code. You wouldn't need to account for it in the example above this one. 
 

  • Thanks 1
Link to comment
Share on other sites

6 minutes ago, ryan said:

Are you setting an 'addClass' property to your Inputfield definitions in your /site/templates/custom-fields/field_name.php file? And you want to use the value of that property somehow on the front-end of your site? That property is for adding a class to the Inputfield in the admin, but if you want to have access to it on the front-end of your site, I suppose you could do this:

So this is actually something I cooked up to "mark" certain subfields within the custom field and pretty much the entire cause of this approach I took. Your include tip is pretty ingenious, I never would have thought of that. These are good tips.

As for the use case of addClass, it's definitely a hacky method of attaching some additional data to each subfield. I have a Pages::saveReady hook that loops through groups of fields on the page and creates a supplemental index field for the SearchEngine module. It's only necessary for fields that aren't compatible with the module out of the box. I needed a way to indicate which subfields of custom fields shouldn't be added to the search index so in the custom field config I added 'input:nosearchindex'. It's just a workaround to indicate which fields should be excluded at configuration but which wouldn't be known at runtime.

Really appreciate the response, very helpful!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...