concrete5’s attribute system is one the shining gems of its flexibility. Although out of the box C5 comes stock with a host of usable attributes, sometimes, you have a need for something a little more complex. Many users are terrified to dig into creating their own attributes, but it’s not really all that hard!
Let’s walk through a sample need, and how to fill that need using what’s already there within the system. Starting from an existing attribute is a great way to learn and experiment.
Defining the Goal
I have a client that wants users to enter their phone number, but they also want to make sure that the data entered is consistent from user to user. In other words, they want this field validated and normalized.
Out of the box, C5 doesn’t offer a validated text field that can accommodate this. So we want to create something that takes a basic text input, and validates it to a format.
Determining Data Structure
The standard text box attribute simply takes a value and posts it to a data field. So we can use this as a starting point. But we need to decide if we want to programmatically designate a format, or create an input format to use as a comparison variable. This would mean that when creating your attribute, it could be a phone number format, credit card, address.....etc. Doing this would require extending our attribute to have an additional set of data to be saved in the attribute type form. So for now, we are going to officially designate this as a “phone number” attribute that simply validates to a standard pre-set phone number format. We will re-visit creating more complex attribute data structures in a later tutorial.
Getting Started
Now that we know that we are basically making the text attribute with simply some validation for phone numbers added, lets copy the text attribute from the core to get started.
Core attributes can be found in your Root->concrete->models->attribute->types
We are going to copy the text attribute folder to our Root->models->attribute->types folder. You will need to create these folders before you try to copy, as they do not exist in a standard install.
At this point, even though you haven’t re-named anything yet, this is still a usable attribute. At this stage, this would be referred to as a “root level override” for the text attribute type. Anything you may change would override the core text attribute type.
What WE want, however, is something like the text attribute, and to keep the text attribute like it is.
So lets do some renaming.
First, we are going to rename the text folder in our new root level copy to “phone”. Inside of that, you will notice only one file... the controller.
Before we move on, you will want to open up the controller file, and rename the class on line 6 to “PhoneAttributeTypeController”.
Extending our Attribute
If you noticed in the controller, the standard text attribute simply “extends” the default attribute type.
I really like this design because there are many attributes that simply only need one data field, and extending the default attribute is a real timesaver in that it prevents you from have scads of duplicitous code.
You’ll notice the controller file defines the form method and outputs a text box.
There’s really no need to alter this as from an “input” perspective, all we really need is a text box.
But what we DO want, is validation. There may be several ways to do this, but one way I like to approach the matter, is within the save function, to send the proposed value to a validate function that simply checks the value, and if it fails, calls the validation error helper and loads in an error message. Aside of this technique, you would be left with doing a root level override for the validation form helper. If you have several custom attributes that require custom validation such as this, you may want to go ahead and do that...but for this application, we will KISS. (keep it stupid simple)
So having said that, we will add two methods to our Phone controller. A standard saveForm method, and a validateForm method.
The saveForm method
The saveForm method is a required method. The reason we don’t see this method in the copied text attribute controller, is because this example controller is “extending” the default attribute type. If we hop on over to the default attribute type controller, we will see the saveForm method there. And because this controller is “extending” that...the save method defaults to the parent controller, or the controller that is being “extended”. In our case, we want to perform some checking before we save it off. So lets add in our saveForm method like so:
public function saveForm($data) {
$this->saveValue($data['value']);
}
The validateForm method
Now we need to add in our validation.
To do this, we are going to pass the value, check it to php ereg() string format, and if it fails, create an error message, and pass a value of true to the save function where we can check if there is in fact an error, and either proceed the save, or put the breaks on.
To generate the error message, we want to load the validation/error helper, then add errors to it.
You may want to learn more about the ereg() function native to PhP if you are not familiar with it, or at least google some examples of ereg and string comparisons in Php.
Here is our validation method:
public function validateForm($value) {
///////////////////////////////////////////////////////////////
//check our $value to a string comparison
// “!” is equal to “not”
///////////////////////////////////////////////////////////////
if(!ereg("^[0-9]{3}-[0-9]{3}-[0-9]{4}$", $value)){
///////////////////////////////////////////////////////////////
// if our comparison indeed fails,
// load up the validation helper, and add in
// our error message.
// then tell our save function that yes,
// sadly, it’s true...we DO have an error!
///////////////////////////////////////////////////////////////
$error = Loader::helper('validation/error');
$error->add( t('Your phone number must be in the correct format: xxx-xxx-xxxx.'));
$error->output();
return true;
}
}
Now lets go back to our save function, and add in our new validation check using our new method.
public function saveForm($data) {
///////////////////////////////////////////////////////////////
//hop on over to our validation method
// and check it out...then return null or true
///////////////////////////////////////////////////////////////
$e = $this->validateForm($data ['value']);
///////////////////////////////////////////////////////////////
//if there is no error, save it.
///////////////////////////////////////////////////////////////
if(!$e){
$this->saveValue($data['value'] );
}
}
Let’s Test it out!
Now lets head to our C5 dashboard and go to Pages & Themes->Attributes and click on “manage attribute types”.
In our Attribute Type Association form, scroll down to the bottom and you’ll see our new Phone attribute is ready to install.
Go ahead and install it, and then find it in the list above, and check the checkbox in the “User” column and save/update.
Now head back to Pages & Themes->Attributes and add a new “Phone” attribute.
ALL DONE! Now you can go to a user and test drive your new validated phone field!
Next article, we will do a similar attribute, but add in some heavy form elements, validation, and working with attribute type arrays and tables.