You are here
Drupal 7 - A Simple Content Type: Text fields and areas - Take 2
This post builds upon a previous post: Drupal 7 - A Simple Content Type: Text fields and areas
Up until now we have been using the Field API to create tables to store our data and also to build and populate the forms and node views for each of the examples. Well what if you don't want to use the Field API? How would you implement the same functionality of the previous post? Let's check that out.
The first place we want to start working is the module.install file and the first thing you'll probably notice is that we no longer implement the hook_install() function but instead have the hook_schema() function:
/**
* Implements hook_schema().
*/
function simple_content_type_schema() {
// table schema to track example data
$schema['simple_content_type_data'] = array(
'description' => 'Stores information for our example content type.',
'fields' => array(
'nid' => array(
'description' => 'The {node}.nid of the node being created.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
),
'uid' => array(
'description' => 'The {user}.uid of the user this node belongs to.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
),
'simple_content_type_textfield' => array(
'description' => 'The value entered into the text field.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'simple_content_type_textarea' => array(
'description' => 'The value for our textarea without formats.',
'type' => 'text',
'size' => 'big',
'not null' => TRUE,
),
'simple_content_type_text' => array(
'description' => 'The value for our textarea with formats.',
'type' => 'text',
'size' => 'big',
'not null' => TRUE,
),
'simple_content_type_format' => array(
'description' => 'The format for our textarea with formats.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
),
'index' => array('nid'),
'primary key' => array('nid'),
'unique keys' => array(
'nid' => array('nid'),
),
);
return $schema;
}
The schema hook allows us to create our own tables in the database. Woot! I love the Field API but I dislike the way it clutters up my database with a table for each field I create. Now I can keep all my datas in one place should I choose too. I have defined six fields (columns) in my table:
- nid - This is the {node}.nid for this node as determined by the node module.
- uid - This is the {users}.uid of the user that created the node.
- simple_content_type_textfield - The value for the data we are storing in the textfield.
- simple_content_type_textarea - The value for the data we are storing in the textarea without format filters.
- simple_content_type_text - The value for the data we are storing in the textarea with format filters.
- simple_content_type_format - The format filter that should be applied to simple_content_type_text.
I want the 'nid' field to be a unique key as each node should only have one entry. Once our module is installed the table should look something like this:
The other function in our .install file is the new uninstall function:
/**
* Implements hook_uninstall().
*/
function simple_content_type_uninstall() {
// gather all the content created while the module was enabled
$result = db_select('node', 'n')
->fields('n', array('nid'))
->condition("n.type", 'simple_content_type')
->execute();
$nids = array();
foreach ($result as $row) {
$nids[] = $row->nid;
}
// delete all the nodes at once
node_delete_multiple($nids);
// delete our content type
node_type_delete('simple_content_type');
// purge all field information
field_purge_batch(1000);
// get rid of any tables we created
$sql = 'DROP TABLE IF EXISTS simple_content_type_data';
$result = db_query($sql);
}
Not much is different about what's going on here except the last part of the function where we drop the table we created in the schema hook implementation. We can now turn our attention to the module.module file and define our node type with hook_node_info():
/**
* Implements hook_node_info().
*/
function simple_content_type_node_info() {
// define our content type info
$items['simple_content_type'] = array(
'name' => t('Simple Content Type'),
'type' => 'simple_content_type',
'base' => 'simple_content_type',
'description' => t('A simple demonstration content type using different text field types.'),
'has_title' => '1',
'title_label' => t('Simple Title'),
'has_body' => FALSE,
'help' => t('Please fill in all fields.'),
);
return $items;
}
Not much is different here between this version and the previous one, the main difference being the value of 'base' being 'simple_content_type' instead of 'node_content'. At the time of this writing (current release is Drupal 7.4), having the base set in this manner will cause the comments module to throw some error messages when you delete a content type (I have a solution). But I need the base set in this manner because I want to define my own function to create the form that will be used to add and edit nodes of our content type:
/**
* Implement hook_form() with the standard default form.
*/
function simple_content_type_form($node, &$form_state) {
// get the default values for our form fields
if (!isset($node->nid)) {
$txt_field = '';
$txt_area = '';
$txt_area_with_format = '';
$txt_format = 'filtered_html';
} else {
// query the db for info about our node
$data = db_select('simple_content_type_data', 'd')
->fields('d', array(
'simple_content_type_textfield',
'simple_content_type_textarea',
'simple_content_type_text',
'simple_content_type_format'))
->condition("d.nid", $node->nid)
->execute()
->fetchAssoc();
$txt_field = $data['simple_content_type_textfield'];
$txt_area = $data['simple_content_type_textarea'];
$txt_area_with_format = $data['simple_content_type_text'];
$txt_format = $data['simple_content_type_format'];
}
// build the form
$form = array();
$form['simple_content_type_textfield'] = array(
'#title' => t('Email Address'),
'#type' => 'textfield',
'#description' => t('Enter an email address.'),
'#default_value' => $txt_field,
'#weight' => -9,
'#required' => TRUE,
);
$form['simple_content_type_textarea'] = array(
'#title' => t('Enter a plain text message.'),
'#type' => 'textarea',
'#description' => t('This text area will be sanitized and not run through any filters prior to display.'),
'#default_value' => $txt_area,
'#weight' => -8,
'#rows' => 5,
'#required' => TRUE,
);
$form['simple_content_type_text'] = array(
'#title' => t('Enter some a formatted text message.'),
'#type' => 'text_format',
'#description' => t('This text area will be sanitized and run through the designated filter prior to display.'),
'#default_value' => $txt_area_with_format,
'#format' => $txt_format,
'#weight' => -7,
'#rows' => 10,
'#required' => TRUE,
);
return $form;
}
The first thing we do is put together some variables holding the default values for the fields we are going to present to the user. If the $node->nid is not set then we are creating a new node and the default values will be set accordingly. Otherwise we need to query the database and pull the appropriate data from our module's table. Next we build the form using the Form API. This is pretty straightforward if you use the API reference chart which has a handy grid and anchors for each property and field type. We need to validate our form when it is submitted. Drupal's built in form functionality means that when you set a form field as being required Drupal will make sure it has a value before it allows the form to be submitted. But what if you want to make sure that the value that the user entered for their email address was a valid email address? Implement hook_node_validate():
/**
* Implements hook_node_validate().
*/
function simple_content_type_node_validate($node, $form, &$form_state) {
// if we're not validating one of our own get out of here
if ($node->type != "simple_content_type") {
return;
}
// Check to see if email address is formatted correctly
if (isset($node->simple_content_type_textfield) && !valid_email_address($node->simple_content_type_textfield)) {
form_set_error('simple_content_type_textfield', t('Please enter a valid email address.'), NULL);
}
}
This function is called when any node form is submitted so we want to kick out of the code if the node type doesn't match our own. Then, if the textfield containing the email address has a value we pass that value through Drupal's handy, dandy valid_email_address function to determine if the value the user entered passes mustard. If the value is valid we let the user pass, otherwise we set a form error to let the user know what to fix. Once the form has been through the validation wringer we need to handle the data being submitted. Depending on what action is being taken (adding vs editing) a different function is going to be used. When creating a new node of our content type we use hook_node_insert():
/**
* Implement hook_node_insert()
*/
function simple_content_type_node_insert($node) {
// if we're not inserting one of our own get out of here
if ($node->type != "simple_content_type") {
return;
}
global $user;
$nid = $node->nid;
$uid = $user->uid;
$insert = db_insert('simple_content_type_data')
->fields(array(
'nid' => $nid,
'uid' => $uid,
'simple_content_type_textfield' => $node->simple_content_type_textfield,
'simple_content_type_textarea' => check_plain($node->simple_content_type_textarea),
'simple_content_type_text' => check_markup($node->simple_content_type_text['value'], $node->simple_content_type_text['format']),
'simple_content_type_format' => $node->simple_content_type_text['format'],))
->execute();
}
When editing we use hook_node_update():
/**
* Implement hook_node_update()
*/
function simple_content_type_node_update($node) {
// if we're not updating one of our own get out of here
if ($node->type != "simple_content_type") {
return;
}
$update = db_update('simple_content_type_data')
->fields(array(
'simple_content_type_textfield' => $node->simple_content_type_textfield,
'simple_content_type_textarea' => check_plain($node->simple_content_type_textarea),
'simple_content_type_text' => check_markup($node->simple_content_type_text['value'], $node->simple_content_type_text['format']),
'simple_content_type_format' => $node->simple_content_type_text['format'],
))
->condition('nid', $node->nid, '=')
->execute();
}
Once again we need to check to make sure we are operating on a node of the proper type then we need to gather the data needed to insert or update the database. The node will have a nid by now so we can grab that, then we get the current user's id by pulling it from the global user object. From here the last step is only different in the function we use to access the database and which values we pass along. We need to sanitize the text for both text areas to make sure no malicious data gets sent to the database. That is done with check_plain and check_markup depending on what type of text area is being checked. The last thing we need to define is hook_node_view(), which is used to show the user the node when it is displayed:
/**
* Implements hook_node_view().
*/
function simple_content_type_node_view($node, $view_mode, $langcode) {
if ($node->type != 'simple_content_type') {
return;
}
// get the data we want to display
$data = db_select('simple_content_type_data', 'd')
->fields('d', array(
'uid',
'simple_content_type_textfield',
'simple_content_type_textarea',
'simple_content_type_text'))
->condition("d.nid", $node->nid)
->execute()
->fetchAssoc();
// fetch the user's name using {simple_content_type_data}.uid
$user_name = db_select('users', 'u')
->fields('u', array('name'))
->condition("u.uid", $data['uid'])
->execute()
->fetchField();
$txt_field = $data['simple_content_type_textfield'];
$txt_area = $data['simple_content_type_textarea'];
$txt_area_with_format = $data['simple_content_type_text'];
// add what we want to the node content area
$node->content['intro'] = array(
'#markup' => '<p>This node was created by user ' . $user_name . '. Thanks for your contribution.</p>',
'#weight' => -10,
);
$node->content['email'] = array(
'#markup' => '<p><strong>Example Email:</strong> ' . $txt_field . '</p>',
'#weight' => -9,
);
$node->content['plain_text'] = array(
'#markup' => '<p><strong>The plain text:</strong> ' . $txt_area . '</p>',
'#weight' => -8,
);
$node->content['formatted_text'] = array(
'#markup' => $txt_area_with_format,
'#weight' => -7,
);
}
Just as when we are presenting the node edit form we need to get the data we need to present to the user. We need the textfield value, the value for the textarea without formatting, and the value for the textarea with formatting (We don't need the filter type because the data was formatted prior to being placed in the database). Here we also use the uid value to get the name of the user who created the node to use in our display. Finally we add the data to the node's content area and call it done. The node_view hook also includes a parameter that allows us to present the node content in different ways depending on how the content is being presented (full vs teaser) but we are not going to use that here for simplicity's sake. After installing our updated module we create a new node of our content type and enter some data:
Feel free to play with the data you submit to test the validation functionality before moving on. Once you submit valid data you can take a look at the new node. You should see something like this:
We present the data in a slightly different manner than it is by default when using the Field API to create our content type but you can see how many steps that API takes care of for you if you decide to use it. I think both methods of content type creation have their advantages and disadvantages and use both of them in my Drupal development.
Download the source for this example.
- 0 comments




Post new comment