Ready to watch this entire course?
Become a member and get unlimited access to the entire skills library of over 4,987 courses, including more Developer and personalized recommendations.Start Your Free Trial Now
- View Offline
In the previous segment, each field instance had a widget associated with it. The widget provides a form API structure that is used when creating and editing a node of that content type. As the form API was used, I can leverage Drupal system of hooks that allows modules to interact with one another. Hook_form_alter alters a form before it's rendered. Return to the IDE and open windfarms.module, and navigate to the end, start with the DocBlock, Implements hook_form_alter(), then the function; like other hooks, start with the module name.
It takes three parameters: the form, passed by reference, the form_state also passed by reference, and a form_id. I want to add validation to the node edit form and I don't know the form_id, therefore I'm going to add some debugging information; dpm($form_id) and I'd like to see the structure of the form, so dpm($form), save, then return to the browser, then clear all caches.
Go to Configuration > Performance, then Clear all caches. Once cleared, the hook_form_altar debugging information will be shown for every form. Return to the node and edit it. The form_id windfarm_node_form is what I'll need to manipulate the form, and now I know the form structure. Click on the form, then scroll down.
The form elements are named the same as the fields which is quite useful. One of the features of the form API is validation mechanism. This is typically a callback function that processes an entire form like windfarms, admin, settings, form, validate. As the form_id is known, I can now contextualize the debugging information to only the windfarm_node_form using a switch statement. As this is for the windfarm content type, note that windfarm is singular, not plural.
Go back to the IDE and place a switch statement, switch on form_id, case 'windfarm_node_form'. We can move the form debugging into the case, so it's not shown on every page. And we can remove the debugging information on the form_id because we already know it. Go back to the browser, reload the page, then inspect the form, looking for validate.
Validate takes an array of function names that are used to validate the form. I can add a custom function to this array. Back to the IDE. Since form is passed by reference, I can directly manipulate it by adding the name of a function that I'm about to write. I'll start with the module name then the form_id followed by validate. This way, it'll be obvious that this function is part of this module and has a very specific purpose. Remove the debugging information from the form, then $form, #validate, and it adds the array 'windfarms_windfarm_node_form_validate'.
Next, I'll add the validation function; start with the DocBlock, Validates Wind Farm content type. As before with the admin settings form, the validation function takes two parameters, the form and the form state passed by reference, function windfarms_windfarm_node_form_validate, which takes the form then the form state passed by reference.
For now I'd like to see what's going to be validated. As this form is generated automatically, this structure is going to be a little different, dpm($form_state). Next, I'll intentionally set an error on the entire form. This will prevent the form from submitting and allow me to see the debugging information, form_set_error with an empty string which sets it to the entire form, and then testing is the message. Save, then return to the browser and submit the form.
This time, you'll get a generic error message, the DEVEL array contains the form state. Look for values, windfarm_unit_count is in there, but look closer, und is shown. This is an artifact of the field definition and automatic form generation. Look deeper into und and you'll see an array with one key followed by value. This is what I'd like to validate. Go back to the IDE and remove the debugging information.
Start with a comment, Validate Unit Count. Remember, it's in values, windfarm_unit_count, instead of using the string und, use the Drupal constant language, none. This is a best practice and makes it clear what's going on. Since there's only one element in the array, due to the cardinality, just take the elements by index 0, then the value. This complication in nesting is the cost trade- off by leveraging Drupal's internal facilities for creating a content type.
Next, validate the value. If the value is not empty and, start parentheses, is not numeric, or the intval of value not equals value, or value is less than equals to zero, set an error on the windfarm_unit_count element, form_set_error 'windfarm_unit_count', then the t function and the explanation 'The number of units on a wind farm must be a positive whole number.' Save and return to the browser.
We'll close the debugging information, put in some bad data for the number of units, and resubmit the form. This time a contextually appropriate message is shown and the Number of Units form element is highlighted. Good! Now I can add the validation for the latitude and longitude. Go back to windfarms.module. I've already created a regular expression for validating signed degrees, but at this point, I need to use it in two places.
I could copy and paste, but that's really not a good idea. If I find myself about to duplicate code with minimal changes, I'll create a function instead. Start by creating a function that starts with an underscore. This visually indicates to other developers that this function is intended to only be used internally by this module. Follow the underscore with the name of the module, underscore then what the function is supposed to do, validate_signed_degrees. Take one parameter, value.
The regular expression can now be copied and pasted without regret. I'm going to copy the signed degree regular expression, and paste it in to the new function, then I'm going to return, preg_match the signed_degree_regex, and the value. I'm going to use a ternary statement as preg_ match returns an integer, so TRUE: FALSE.
Now that the function is defined, include proper documentation. This will determine if a value is in signed degrees format. This takes a string, the value, and then the explanation, The value to be tested, and then this returns Boolean, TRUE if correctly formatted. There is a second set of validations for the geographic coordinate range between negative 180 and 180, function _windfarms_validate _geo_coord_range which takes a value.
We can use the same logic from above. Return, I'm going to paste from above. I'm going to replace latitude with a value, again add the DocBlock, Determine if a geographic coordinate is within the valid range. Takes a string, called value, which is The value to be tested, and it returns a boolean which is TRUE if between -180 and 180.
Before I forget, I'll refactor the admin section to use the new validation functions and remove the unused declarations. So I'll start with the signed degrees. I don't need the regular expression anymore, and we'll use the new function. I will also use the new function for at the latitude and longitude values, windfarms, the range on the lat and on the long.
Now that the admin validation function is cleaned up, I can move on to validating the latitude and longitude provided by the user. Go back to the node_form_validate function, start with the comment, Validate latitude. Similar to the unit count, remember to use LANGUAGE_ NONE, then index zero then the value, $lat = $form_state 'values' 'windfarm_latitude', LANGUAGE_NONE, 0 and 'value'.
I'm going to combine the two arrow checks into one. If either fails, set the error in the form; if not, windfarms geo coordinate range for the latitude, or not windfarm_signed_degrees, lat then form_set_error, and the key 'windfarm_latitude' with a message, using the t function 'Latitude must be valid coordinates in signed degrees'.
The longitude check is same as latitude, only the names have been changed to protect the innocent. I'm going to copy and paste from the latitude because it's very, very similar, validate, longitude; change the key to longitude and the variable. So if not the range of the longitude, or the signed degrees of the longitude, then form_set_error on the longitude, then replace 'Longitude must be valid coordinates in signed degrees.' Now that validation is complete, ensure that all debugging information has been removed, then save and return to the browser.
Attempt to break the form submission with bad values; orange, puppy, and hit Save. You'll now get contextually appropriate error messages. The node form is validating which is a good thing. I still need to add the logic necessary to cleanly remove the new fields, instances and content type upon uninstall.
- Creating your first module
- Interacting with hooks
- Working with permissions and roles
- Controlling access
- Adding a menu item to an admin interface
- Using the Form API (FAPI) to quickly create a form
- Creating custom form validation
- Manually creating a custom content type
- Validating user input
- Importing content using feeds
- Creating a block
- Understanding best practices and coding standards