summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRandy Fay2009-10-03 14:41:09 (GMT)
committer Randy Fay2009-10-03 14:41:09 (GMT)
commitc4950818be1bbbc8bf1a301c2c017f26a79725c4 (patch)
tree9488257e4f8327f1d522eb96c894300a04a07027
parent7d7d4ad3deb655c2205105f3bec99b896b112a0a (diff)
Forgot scaffolding_example
-rw-r--r--scaffolding_example/images/edit-delete.pngbin0 -> 783 bytes
-rw-r--r--scaffolding_example/images/text-editor.pngbin0 -> 560 bytes
-rw-r--r--scaffolding_example/scaffolding_example.admin.inc363
-rw-r--r--scaffolding_example/scaffolding_example.info4
-rw-r--r--scaffolding_example/scaffolding_example.install106
-rw-r--r--scaffolding_example/scaffolding_example.module200
-rw-r--r--scaffolding_example/scaffolding_example.pages.inc42
7 files changed, 715 insertions, 0 deletions
diff --git a/scaffolding_example/images/edit-delete.png b/scaffolding_example/images/edit-delete.png
new file mode 100644
index 0000000..184f762
--- /dev/null
+++ b/scaffolding_example/images/edit-delete.png
Binary files differ
diff --git a/scaffolding_example/images/text-editor.png b/scaffolding_example/images/text-editor.png
new file mode 100644
index 0000000..9c25055
--- /dev/null
+++ b/scaffolding_example/images/text-editor.png
Binary files differ
diff --git a/scaffolding_example/scaffolding_example.admin.inc b/scaffolding_example/scaffolding_example.admin.inc
new file mode 100644
index 0000000..e43965d
--- /dev/null
+++ b/scaffolding_example/scaffolding_example.admin.inc
@@ -0,0 +1,363 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Administrative pages for the module.
+ *
+ * Contains form building functions, submit handlers, and theme functions for
+ * the module's overview form, add and edit forms, and the delete confirmation
+ * form.
+ */
+
+/**
+ * Build an overview form with drag and drop re-ordering of records.
+ *
+ * Loads all records and builds an overview form with weight elements for each
+ * record, then adds Drupal's tabledrag.js file. Because ALL records will be
+ * displayed on this page, this style of overview is best suited to small sets
+ * of data.
+ *
+ * If your module will work with large numbers of records, or if you don't need
+ * the drag-and-drop re-ordering feature, the paged version of the overview
+ * is likly be a better solution.
+ *
+ * @ingroup forms
+ * @see _scaffolding_example_overview_record_field()
+ * @see scaffolding_example_overview_form_submit()
+ * @see theme_scaffolding_example_overview_form()
+ * @see scaffolding_example_overview_pager()
+ */
+function scaffolding_example_overview_form(&$form_state) {
+ $records = scaffolding_example_record_load_all();
+
+ $form['records']['#tree'] = TRUE;
+ foreach ($records as $record_id => $record) {
+ $form['records'][$record_id] = _scaffolding_example_overview_record_field($record);
+ }
+
+ $form['buttons']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save changes'),
+ '#disabled' => empty($records),
+ );
+
+ return $form;
+}
+
+/**
+ * Builds the fields for a single record on the drag-and-drop overview form.
+ *
+ * This internal function should not be called outside the module, unless you're
+ * feeling particularly cheeky.
+ *
+ * @ingroup forms
+ * @see scaffolding_example_overview_form()
+ */
+function _scaffolding_example_overview_record_field($record) {
+ $form['record_id'] = array(
+ '#type' => 'hidden',
+ '#value' => $record['record_id'],
+ );
+
+ $form['title'] = array(
+ '#type' => 'markup',
+ '#value' => check_plain($record['title']),
+ );
+
+ $form['weight'] = array(
+ '#type' => 'weight',
+ '#default_value' => $record['weight'],
+ );
+
+ $form['operations'] = array(
+ '#type' => 'markup',
+ '#value' => _scaffolding_example_record_links($record),
+ );
+
+ return $form;
+}
+
+/**
+ * Build the edit and delete links for a single record.
+ *
+ * @see scaffolding_example_overview_form()
+ * @see scaffolding_example_overview_pager()
+ */
+function _scaffolding_example_record_links($record) {
+ $path = drupal_get_path('module', 'scaffolding_example') . '/images/';
+ $links['edit'] = array(
+ 'title' => theme('image', $path . 'text-editor.png', t('Edit')),
+ 'href' => 'admin/build/scaffolding_example/' . $record['record_id'] . '/edit',
+ 'html' => TRUE,
+ 'query' => drupal_get_destination(),
+ );
+ $links['delete'] = array(
+ 'title' => theme('image', $path . 'edit-delete.png', t('Delete')),
+ 'href' => 'admin/build/scaffolding_example/' . $record['record_id'] . '/delete',
+ 'html' => TRUE,
+ 'query' => drupal_get_destination(),
+ );
+ return theme('links', $links);
+}
+
+/**
+ * General submit handler for the drag-and-drop overview form.
+ *
+ * Updates the weights of all records on the form.
+ *
+ * @ingroup formapi
+ * @see scaffolding_example_overview_form()
+ */
+function scaffolding_example_overview_form_submit($form, &$form_state) {
+ $records = $form_state['values']['records'];
+ foreach ($records as $record) {
+ scaffolding_example_record_save($record);
+ }
+}
+
+/**
+ * Theme the drag-and-drop overview form.
+ *
+ * Arranges records in a table, and adds the css and js for draggable sorting.
+ *
+ * @ingroup themeable
+ * @ingroup forms
+ * @see scaffolding_example_overview_form()
+ */
+function theme_scaffolding_example_overview_form($form) {
+ // Each record has a 'weight' that can be used to arrange it in relation to
+ // other records. Drupal's tabledrag.js library allows users to control these
+ // weights by dragging and dropping the records in a list -- we just need to
+ // add identifying CSS classes to key elements in the table.
+
+ $rows = array();
+ foreach (element_children($form['records']) as $key) {
+ $row = array();
+
+ // Render the hidden 'record id' field and the title of the record into the
+ // same column of the row.
+ $row[] = drupal_render($form['records'][$key]['record_id']) . drupal_render($form['records'][$key]['title']);
+
+ // Add an identifying CSS class to our weight field, as it's the one
+ // the tabledrag.js will be controlling. This can be anything we want it to
+ // be, we'll just tell the tabledrag.js library what it should look for.
+ $form['records'][$key]['weight']['#attributes']['class'] = 'scaffolding-example-weight';
+ $row[] = drupal_render($form['records'][$key]['weight']);
+
+ // Render the edit and delete links into their own column.
+ $row[] = drupal_render($form['records'][$key]['operations']);
+
+ // Add the new row to our collection of rows, and give it the 'draggable'
+ // class, indicating that it should be... well, draggable.
+ $rows[] = array(
+ 'data' => $row,
+ 'class' => 'draggable',
+ );
+ }
+
+ // If there were no records found, note the fact so users don't get confused
+ // by a completely empty table.
+ if (count($rows) == 0) {
+ $rows[] = array(t('No records have been added.'), '<span class="scaffolding-example-weight"></span>', '');
+ }
+
+ // Render a list of header titles, and our array of rows, into a table. Even
+ // we've already rendered all of our records, we always call drupal_render()
+ // on the form itself after we're done, so hidden security fields and other
+ // elements (like buttons) will appear properly at the bottom of the form.
+ $header = array(t('Title'), t('Weight'), t('Operations'));
+ $output = theme('table', $header, $rows, array('id' => 'scaffolding-example-overview'));
+ $output .= drupal_render($form);
+
+ // Now that we've built our output, tell Drupal to add the tabledrag.js library.
+ // We'll pass in the ID of the table, the behavior we want it to use, and the
+ // class that appears on each 'weight' form element it should be controlling.
+ drupal_add_tabledrag('scaffolding-example-overview', 'order', 'self', 'scaffolding-example-weight');
+
+ return $output;
+}
+
+/**
+ * Builds a sortable, paged overview of all records.
+ *
+ * This version of the overview page doesn't allow administrators to re-order
+ * records, but it breaks up long lists into groups of 20, with a convenient
+ * pager at the bottom of the list of records. This style of overview page is
+ * very useful when you know you'll be managing dozens (or hundreds) of records.
+ *
+ * Because this version of the overview page uses Drupal's helper functions to
+ * build a paged table with clickable column-headers, it can't use the module's
+ * build-in scaffolding_example_record_load_all() function. Instead, it will
+ * build its own SQL in a way that works with the Pager and Sortable Table APIs.
+ *
+ * @see scaffolding_example_overview_form()
+ */
+function scaffolding_example_overview_pager() {
+ $sql = "SELECT * FROM {scaffolding_record}";
+ $header = array(
+ array('data' => t('Title'), 'field' => 'title', 'sort' => 'asc'),
+ array('data' => t('Weight'), 'field' => 'weight'),
+ t('Operations'),
+ );
+ $sql .= tablesort_sql($header);
+
+ $limit = 10;
+ $result = pager_query($sql, $limit);
+
+ while ($record = db_fetch_array($result)) {
+ $rows[] = check_plain($record['title']);
+ $rows[] = check_plain($record['weight']);
+ $rows[] = _scaffolding_example_record_links($record);
+ }
+ if (!isset($rows)) {
+ $rows[] = array(array('data' => t('No records have been added.'), 'colspan' => 3));
+ }
+
+ $output = theme('table', $header, $rows);
+ $output .= theme('pager', NULL, $limit);
+
+ return $output;
+}
+
+/**
+ * Build the record editing form.
+ *
+ * If a record is passed in, an edit form with both Save and Delete buttons will
+ * be built. Otherwise, a blank 'add new record' form, without the Delete button,
+ * will be built.
+ *
+ * @ingroup forms
+ * @see scaffolding_example_form_submit()
+ * @see scaffolding_example_form_delete()
+ */
+function scaffolding_example_form(&$form_state, $record = array()) {
+ // Set the default values for a new item. By using += rather than =, we
+ // only overwrite array keys that have not yet been set. It's safe to use
+ // on both an empty array, and an incoming array with full or partial data.
+ $record += array(
+ 'title' => '',
+ 'content' => '',
+ 'weight' => 0,
+ );
+
+ // If we're editing an existing record, we'll add a value field to the form
+ // containing the record's unique ID.
+ if (!empty($record['record_id'])) {
+ $form['record_id'] = array(
+ '#type' => 'value',
+ '#value' => $record['record_id'],
+ );
+ }
+
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Title'),
+ '#required' => TRUE,
+ '#default_value' => $record['title'],
+ );
+
+ $form['weight'] = array(
+ '#type' => 'weight',
+ '#title' => t('Weight'),
+ '#default_value' => $record['weight'],
+ );
+
+ $form['content'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Content'),
+ '#required' => TRUE,
+ '#default_value' => $record['content'],
+ );
+
+ $form['buttons']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Submit'),
+ );
+
+ // Only show the delete button if we already have an ID. Set the delete
+ // button's submit handler to a custom function that should only fire if
+ // this button is clicked. In all other cases, the form will fall back to
+ // the default $form_id_submit() function.
+ if (!empty($record['record_id'])) {
+ $form['buttons']['delete'] = array(
+ '#type' => 'submit',
+ '#value' => t('Delete'),
+ '#submit' => array('scaffolding_example_form_delete'),
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * General submit handler for Scaffolding's add/edit form.
+ *
+ * Simply passes incoming form values on to the module's CRUD save function,
+ * then redirects to the overview form.
+ *
+ * @ingroup formapi
+ * @see scaffolding_example_form()
+ */
+function scaffolding_example_form_submit($form, &$form_state) {
+ $record = $form_state['values'];
+ scaffolding_example_record_save($record);
+ $form_state['redirect'] = 'admin/build/scaffolding_example';
+}
+
+/**
+ * Delete button submit handler for Scaffolding's add/edit form.
+ *
+ * Redirects to the 'delete record' confirmation page without performing any
+ * operations.
+ *
+ * @ingroup formapi
+ * @see scaffolding_example_form()
+ */
+function scaffolding_example_form_delete($form, &$form_state) {
+ $form_state['redirect'] = 'admin/build/scaffolding_example/' . $form_state['values']['record_id'] . '/delete';
+}
+
+/**
+ * Build the delete confirmation form.
+ *
+ * A simple wrapper around Drupal's core confirm_form() function. Adds a value
+ * field to store the ID of the record being deleted.
+ *
+ * @ingroup forms
+ * @see scaffolding_example_delete_confirm_submit()
+ * @see confirm_form()
+ */
+function scaffolding_example_delete_confirm(&$form_state, $record) {
+ $form['record_id'] = array(
+ '#type' => 'value',
+ '#value' => $record['record_id'],
+ );
+
+ return confirm_form($form,
+ t('Are you sure you want to delete %title?', array('%title' => $record['title'])),
+ isset($_GET['destination']) ? $_GET['destination'] : 'admin/build/scaffolding_example',
+ t('This action cannot be undone.'),
+ t('Delete'),
+ t('Cancel')
+ );
+}
+
+/**
+ * General submit handler for the delete confirmation form.
+ *
+ * Core's confirm_form() function adds the 'confirm' value element we check
+ * against to ensure the form was properly submitted. If it's there, delete
+ * the record and redirect to the overview form.
+ *
+ * @ingroup formapi
+ * @see scaffolding_example_form()
+ */
+
+function scaffolding_example_delete_confirm_submit($form, &$form_state) {
+ if ($form_state['values']['confirm']) {
+ scaffolding_example_record_delete($form_state['values']['record_id']);
+ drupal_set_message(t('Your record was deleted.'));
+ }
+ $form_state['redirect'] = 'admin/build/scaffolding_example';
+}
diff --git a/scaffolding_example/scaffolding_example.info b/scaffolding_example/scaffolding_example.info
new file mode 100644
index 0000000..4dfd13b
--- /dev/null
+++ b/scaffolding_example/scaffolding_example.info
@@ -0,0 +1,4 @@
+; $Id$
+name = Scaffolding
+description = Provides example scaffolding for a module that maintains its own database table, and exposes an editing interface for the module's data.
+core = 6.x
diff --git a/scaffolding_example/scaffolding_example.install b/scaffolding_example/scaffolding_example.install
new file mode 100644
index 0000000..2b0ac4d
--- /dev/null
+++ b/scaffolding_example/scaffolding_example.install
@@ -0,0 +1,106 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Scaffolding example module's install and uninstall code.
+ */
+
+/**
+ * Implementation of hook_install().
+ *
+ * This hook is called the first time the module is installed. Unless it is
+ * explicitly uninstalled, disabling and re-enabling will not trigger this hook
+ * a second time.
+ */
+function scaffolding_example_install() {
+ drupal_install_schema('scaffolding_example');
+}
+
+/**
+ * Implementation of hook_schema().
+ *
+ * This hook should return a SchemaAPI array with a full definition of the most
+ * up-to-date version of the module's database tables.
+ */
+function scaffolding_example_schema() {
+ $schema['scaffolding_record'] = array(
+ 'description' => t('Stores custom links to be added to nodes.'),
+ 'fields' => array(
+ 'record_id' => array(
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'description' => t('Unique identifier for the {scaffolding_record}.'),
+ ),
+ 'title' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => t("The visible title of the {scaffolding_record}.")
+ ),
+ 'content' => array(
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'description' => t('A description of the term.'),
+ ),
+ 'weight' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ 'description' => t('The weight of this {scaffolding_record}.'),
+ ),
+ ),
+ 'primary key' => array('record_id'),
+ );
+ return $schema;
+}
+
+/**
+ * Implementation of hook_update_N().
+ *
+ * This function is responsible for updating the module's database tables when
+ * a new version requires changes. (For example, if version 1.1 of the module
+ * added a new field to the database).
+ *
+ * The numbers of your module's update functions should follow the pattern
+ * hook_update_XYZZ, where X is the version of Drupal your module is compatible
+ * with, Y is the major version of your module, and ZZ is the number of the
+ * update. For example, the first update for version 1.x of this module would
+ * be numbered 6100, while the first update for the 2.x version of the module
+ * would be numbered 6200. For more details on update numbering conventions,
+ * see http://drupal.org/node/114774#update-n.
+ *
+ * hook_update_N() functions only run when upgrading an already-installed module
+ * to a new version, NOT when initially installing the module.
+ */
+function scaffolding_example_update_6100() {
+ $new_column = array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ 'description' => t('The weight of this {scaffolding_record}.'),
+ );
+
+ $ret = array();
+ db_add_field($ret, 'scaffolding_record', 'weight', $new_column);
+ return $ret;
+}
+
+/**
+ * Implementation of hook_uninstall().
+ *
+ * This hook is called when the already-disabled module is explicitly uninstalled
+ * by the administrator -- simple disabling the module will trigger hook_disable().
+ * It should delete any database tables added by the module, remove any variables
+ * that are unique to the module, and clear out any cached data.
+ */
+function scaffolding_example_uninstall() {
+ drupal_uninstall_schema('scaffolding_example');
+ cache_clear_all('scaffolding_example:*', 'cache', TRUE);
+ variable_del('scaffolding_example_setting');
+}
diff --git a/scaffolding_example/scaffolding_example.module b/scaffolding_example/scaffolding_example.module
new file mode 100644
index 0000000..b0cff42
--- /dev/null
+++ b/scaffolding_example/scaffolding_example.module
@@ -0,0 +1,200 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Provides example scaffolding for a basic Drupal module with CRUD.
+ *
+ * This module implements all the permissions, menu callbacks, form handling,
+ * installation tasks, and CRUD functions needed by most Drupal modules. You'll
+ * need to customize it with your own table definitions and tweak the user
+ * interface, obviously, but this skeleton should save quite a bit of time.
+ */
+
+/**
+ * Implementation of hook_menu().
+ *
+ * Defines four pages: an overview page that lists all records in the module's
+ * database table, add and edit pages for each record, and a confirmation page
+ * for deleting records.
+ */
+function scaffolding_example_menu() {
+ $items = array();
+
+ $items['admin/build/scaffolding_example'] = array(
+ 'title' => 'Scaffolding',
+ 'description' => 'Manage records',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('scaffolding_example_overview_form'),
+ 'access arguments' => array('administer scaffolding data'),
+ 'file' => 'scaffolding_example.admin.inc',
+ );
+
+ $items['admin/build/scaffolding_example/sorted'] = array(
+ 'title' => 'Sorted list',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+
+ $items['admin/build/scaffolding_example/pager'] = array(
+ 'title' => 'Paged list',
+ 'description' => 'Manage records',
+ 'page callback' => 'scaffolding_example_overview_pager',
+ 'access arguments' => array('administer scaffolding data'),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => -5,
+ 'file' => 'scaffolding_example.admin.inc',
+ );
+
+ $items['admin/build/scaffolding_example/add'] = array(
+ 'title' => 'Add record',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('scaffolding_example_form'),
+ 'access arguments' => array('administer scaffolding data'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'scaffolding_example.admin.inc',
+ );
+
+ $items['admin/build/scaffolding_example/%scaffolding_example_record/edit'] = array(
+ 'title' => 'Edit record',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('scaffolding_example_form', 3),
+ 'access arguments' => array('administer scaffolding data'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'scaffolding_example.admin.inc',
+ );
+
+ $items['admin/build/scaffolding_example/%scaffolding_example_record/delete'] = array(
+ 'title' => 'Delete record',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('scaffolding_example_delete_confirm', 3),
+ 'access arguments' => array('administer scaffolding data'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'scaffolding_example.admin.inc',
+ );
+
+ $items['scaffolding_example'] = array(
+ 'title' => 'Scaffolding records',
+ 'description' => 'Lists records saved by the Scaffolding Example module.',
+ 'page callback' => 'scaffolding_example_page',
+ 'access arguments' => array('view content'),
+ 'file' => 'scaffolding_example.pages.inc',
+ );
+ return $items;
+}
+
+/**
+ * Implementation of hook_perm().
+ *
+ * Defines access permissions that may be assigned to roles and used to restrict
+ * access.
+ */
+function scaffolding_example_perm() {
+ return array('administer scaffolding data');
+}
+
+/**
+ * Implementation of hook_theme().
+ *
+ * Returns information about every themable function defined by the module.
+ */
+function scaffolding_example_theme() {
+ $items = array();
+ $items['scaffolding_example_overview_form'] = array(
+ 'arguments' => array('form' => array()),
+ 'file' => 'scaffolding_example.admin.inc',
+ );
+ $items['scaffolding_example_record'] = array(
+ 'arguments' => array('record' => array()),
+ 'file' => 'scaffolding_example.pages.inc',
+ );
+ return $items;
+}
+
+/**
+ * Loader function for individual records.
+ *
+ * Because we use '%scaffolding_example_record' as a wildcard in our hook_menu()
+ * handler, this function will also be called automatically when we go to edit
+ * or delete a record. Thanks, Menu API!.
+ *
+ * @param $record_id
+ * An int containing the ID of a record.
+ * @return
+ * A single record in array format, or FALSE if none matched the incoming ID.
+ */
+function scaffolding_example_record_load($record_id) {
+ $sql = "SELECT * FROM {scaffolding_record} WHERE record_id = %d";
+ $result = db_query($sql, $record_id);
+ if ($record = db_fetch_array($result)) {
+ return $record;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+/**
+ * Public loader function for the full collection of records.
+ *
+ * In situations where the module's data rarely changes, or is being used
+ * frequently (for example, loaded and processed on every page load), this
+ * is a prime candidate for caching. See The Beginner's Guide to Caching at
+ * http://www.lullabot.com/articles/a_beginners_guide_to_caching_data for more
+ * details.
+ *
+ * This function assumes that results should be sorted by 'weight' -- if your
+ * module doesn't store a weight column on its records, or if you need to sort
+ * on some other property, this function's SQL should be updated as well.
+ *
+ * @return
+ * An array of all records, keyed by id.
+ */
+function scaffolding_example_record_load_all() {
+ $sql = 'SELECT * FROM {scaffolding_record} ORDER BY weight ASC';
+ $result = db_query($sql);
+
+ $records = array();
+ while ($record = db_fetch_array($result)) {
+ $records[$record['record_id']] = $record;
+ }
+ return $records;
+}
+
+/**
+ * Inserts a new record, or updates an existing one.
+ *
+ * Automatically inserts or updates, based on whether the record's unique ID has
+ * been set yet. Because drupal_write_record() updates the record itself (adding
+ * the unique ID after the database has been updated), we return the record
+ * after saving it.
+ *
+ * This allows any calling function to check the id of the returned record and
+ * act on the ID as needed (redirecting to a 'view' page after inserting, etc).
+ *
+ * @param $record
+ * A record to be saved. If $record['record_id'] is set, the record will be updated.
+ * Otherwise, a new record will be inserted into the database.
+ * @return
+ * The saved record, with its ID set.
+ */
+function scaffolding_example_record_save($record) {
+ if (isset($record['record_id'])) {
+ drupal_write_record('scaffolding_record', $record, 'record_id');
+ }
+ else {
+ drupal_write_record('scaffolding_record', $record);
+ }
+ return $record;
+}
+
+/**
+ * Deletes a record, given its unique ID.
+ *
+ * @param $record_id
+ * An int containing the ID of a record.
+ */
+function scaffolding_example_record_delete($record_id) {
+ $sql = 'DELETE FROM {scaffolding_record} WHERE record_id = %d';
+ db_query($sql, $record_id);
+}
diff --git a/scaffolding_example/scaffolding_example.pages.inc b/scaffolding_example/scaffolding_example.pages.inc
new file mode 100644
index 0000000..78ca7f7
--- /dev/null
+++ b/scaffolding_example/scaffolding_example.pages.inc
@@ -0,0 +1,42 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Builds the module's user-facing pages.
+ *
+ * Contains a simple page callback and themable function for rendering a single
+ * record.
+ */
+
+/**
+ * Build a simple listing page for records.
+ */
+function scaffolding_example_page() {
+ $output = '';
+
+ if ($records = scaffolding_example_record_load_all()) {
+ foreach ($records as $record) {
+ $output .= theme('scaffolding_example_record', $record);
+ }
+ }
+ else {
+ drupal_set_message(t('No records have been added.'));
+ }
+
+ return $output;
+}
+
+/**
+ * Theme a single record.
+ *
+ * @ingroup themeable
+ */
+function theme_scaffolding_example_record($record) {
+ $output = '<div class="scaffolding-example-record" id="scaffolding-example-record-' . check_plain($record['record_id']) . '">';
+ $output .= '<h3 class="title">' . check_plain($record['title']) . '</h3>';
+ $output .= '<p>' . check_markup($record['content']) . '</p>';
+ $output .= '</div>';
+
+ return $output;
+}