Login

For Activists

Making Our Report Sortable and Paged - Drupal 7 Multi-Step Form (FAPI)

In this post we are going to replace the stub code detailed in the my previous (Drupal 7 Multi-Step Form (FAPI) with Table Report). We are going to store our data in a new database table. We'll update our report so that users can sort it and scroll through pages.

We'll add a new file listing for our database table install (and unistall cleanup). I'm using the name "proto" for our example module. In Drupal 7,  you don't need to explicitly install your modules schema. Here is our proto.install listing:

Comments

Table not created

Unpublished

Hi,

I followed your example, but i keep getting:
"Base table or view not found: 1146 Table 'gen_skudb.colordb' doesn't exist"
- gen_skuDB is my data base and colordb is the table that i am trying to create ( you called it folk in your example).

Of course i tried dis/enabling and Un/installing and flushing cache, but still i get that error, and phpMyAdmin does not show that table either.

any ideas where else i should look... i googled without any luck...

Correct function names

Hi,
Please take a look at the articles first code listing "function proto_schema() {". My guess is, your schema method is not called because your function name does not match your module name.

In the article, the module's names is "proto". Therefore, when an administrator enables the module Drupal looks for a function called MODULE_NAME_schema(). Thus the function name "proto_schema()". "proto_schema()" is an implementation of hook_schema. You replace the word "hook" with your module name.

Next, it sounds like you don't have a normal development environment. You should always install Drupal on your local machine (i.e. laptop or desktop). When you develop on your local machine, you have full access to the web server and database logs. I use linux and keep a terminal open which "tails" (displays) the last 40 messages from the web server and database. Many times, those logs have a precise message as to where the application exception is.

When you develop on a remote machine without real time access to the logs, you guarantee that a simple 30 second diagnosis and fix will take hours or days.

I always use a source code debugger. Drupal is written in PHP, thus I use xdebug. I highly recommend you learn how to use a source code debugger. Trying to diagnose a simple problem like "my function is not being called" is child's play with a source code debugger. It is highly unlikely that you will find the exact source code line which is the issue through google. Drupal is completely open source, thus you can set a debugger break point anywhere you want and peruse what Drupal is doing.

To summarize, with xdebug you set a break point on your hook_schema() implementation method. You then log on to your local site as an administrator. You install and enable your module. Verify your hook_schema method is called. If called, check the server logs to see if there is an issue (perhaps you don't have the proper database permissions, the log would show that).

Hope that helps.
Jason

Drupal 7 Field API Simple Example - Digging Deeper

This article is a follow up to "Drupal 7 Field API - A simple example". This article is intended for programmers. We build a proof-of-concept module who's sole purpose is to demonstrate features of the Drupal 7 Field API.

Please note! Folks should always start their learning process by reviewing the official "Examples for Developers" on Drupal.org. I also recommend that programmers peruse Drupal's source code itself to find real-world examples that follow best practices.

Links to the full source code listings are located at the bottom of this article.

two sample date formats
Figure 1

Our new module is called "my date".  We add several new features: the ability to style the date display; better control over the default date values; use of the JQuery date picker widget to select dates.

In the screen shot (Figure 1) we see 2 different content types. I've configured a custom display for each of the 2 content types demonstrated in the Figure 1. "my date" allows you to customize the display through configuration dialogs detailed below.

I broke each of the date instances in to 4 HTML div elements: container, month, day, year.

In addition, each of the date parts (month, day, year) can be toggled on or off (displayed or not displayed). In the Figure 1 examples, I turned the display of the year off.

I also allow an HTML style attribute to be applied to each HTML element. Again, that is the container div, month, date, year.

The date can be aligned (float) either right or left.

You also have the option of choosing a back ground image.

I am not a graphics designer nor a themer.  The idea was to show you how easy it is to set the configuration controls  .

Drupal 7 allows you to create a separate configuration for a teaser view versus a full view, etc.

 

Comments

Very nice writeup ! Thanks

Very nice writeup ! Thanks for sharing :-)

There's an issue in the way you detect whether the widget is displayed in a regular 'entity edit' form or on the 'field configuration' form (for 'default value') :

$is_settings_form_type = isset($form['type']['#type']) ? FALSE : TRUE;

$form['type']['#type'] is only present for node edit forms. That is, your widget will incorrectly assume it's displayed on the 'field configuration' form when the field is used on users, taxo terms, or any other entity type.

Besides, beware that the $form param received by hook_field_widget_form() might not be the 'full' form, but just a subpart of a larger form. For instance, that's how http://drupal.org/project/field_collection does to provide 'fields within fields' (or 'combo fields'). In this case you wouldn't have access to the form elements that live on the top-level.

I'm actually not sure that there's a safe way to tell the difference. Widgets are supposed to be unaware where they're being included : an edit form, the field config form, ... - could even be displayed as an exposed Views filter (although not currently ;-)

Stored Procedures and Drupal 7

This article is for PHP programmers and those folks who are comfortable working directly with relational databases.

Stored procedures are not a general purpose solution. However, as Drupal is adopted by wider audiences, you may find your self in a situation where a dedicated solution is required (e.g. making calls to stored procedures).

For example, a customer wants Drupal to report the result of a tried and true scientific calculation. The calculation resides in a stored procedure. Drupal may be new to the customer, but the customer's core business processes are well established (i.e. the stored procedure is established).

This article provides a simple proof-of-concept example to get you started. This article does not represent a comprehensive study of Drupal 7 and stored procedures. To summarize, I found this to be an interesting topic.

Using a stored procedure, record selection was 14 times faster (versus a prepared statement via db_select).

Background:

The main advantage of stored procedures is performance.

The main disadvantage is, store procedures are usually vendor specific. Often a stored procedure written for one database (e.g. MySql) can not be run in another database (e.g. Postgres).

A database administrator (DBA) often writes the stored procedure. An application programmer writes code that calls the stored procedure.

The term stored procedure refers to "application logic" stored directly in the database (as opposed to application logic stored in PHP files). Application logic is thus run (and stored) within the database, reduces network traffic.

Case Study Details.

We are going to write 2 simple functions. We'll perform some crude timings and look at the results.

Both methods simply select "titles" from all nodes of a specific content type and publishing state. Not a real world query, but enough to demo performance difference. I am using MySql version 5.5.9 running under Arch Linux 2.6.37. I am using the current Drupal 7.0 release. The database server, web server and Drupal installation all resided on the same host (my laptop).

The first method uses the Drupal API method db_select(), the second method calls a stored procedure. Other than that, both methods are the same. Both methods take the database result, capture a timing value and display everything using theme_table().

Timing results (measured in seconds):

db_select() call stored procedure Difference Magnitude x Faster (stored procedure)
0.0125620365 0.000674963 0.0118870735 18.61
0.0096518993 0.0007021427 0.0089497566 13.75
0.0115540028 0.0007169247 0.0108370781 16.12
0.0083408356 0.0006780624 0.0076627731 12.30
0.0120971203 0.000742197 0.0113549232 16.30
0.0085980892 0.0006630421 0.0079350471 12.97
0.0078208447 0.0006899834 0.0071308613 11.33
0.0103640556 0.0006439686 0.0097200871 16.09
0.0084490776 0.0007648468 0.0076842308 11.05
0.0082781315 0.0006940365 0.007584095 11.93
Total      
0.0977160931 0.0069701672 0.0907459259 14.02

Comments

Mh, mh... - You want to

Mh, mh...

- You want to compare it with a static db_query(). What you are comparing here is Drupal's query builder with a prepared statements. It's quite obvious that db_select() is a lot slower. It's built for creating dynamic queries and should only be used when necessary.

- Your code for calling the prepared statement is imho way too complex than it needs to be. A simple "$result = db_query('Call GetNodeList(:type,:state)', array(':type' => 'article', ':state' => 1));" should be all you need.

- There are some handy helper methods for fetching data which can replace your fetch loop. "$rows = $result->fetchCol()" should be all you need. The 'data' key is only necessary when you have other things you want to apply like classes.

- This is a bad example to start with, because when querying {node} tables, you should *always* use $query->addTag('node_access') for security reasons so that node access is applied correctly.

Proof of Concept

Hi,
See my comment below titled Network Traffic. In the real world a legacy scientific calculation that's been in place to 15 years is not going to be written with the Drupal node table? Perhaps the author used the node table because most Drupal installations have one. The article is not about how to select nodes??? It's as stated, a proof-of-concept?

I tried your example and it was slower than the stored procedure. Have you tried your db_query mechanism with a stored procedure that contains out parameters? Are you certain that db_query binds the parameters like a PDOStatement? A stored procedure is pre-compiled executable in the database. The bind process is there for a reason.

Devel Generate

Try running Devel Generate (http://drupal.org/project/devel) to create a whole boatload of nodes and see what performance is like with different sized sites.

I suspect the difference will be much smaller for large data sets.

Network Traffic

Often a business method is a mix of application language code and calls to the database. It's not uncommon to have your web server and database server running on different hosts. Thus a stored procedure that performs all of the business logic in the database, by definition, reduces network traffic. A reduction is network traffic is a performance boost. More records in a typical complex proc would produce a much wider performance gap.

The stored procedure used in this example doesn't represent a typical "proc". As alluded in the article, stored procedures are normally complex processes that have many steps. I'm guessing the example used in the article was used just as a succinct example. Something that folks could easily paste in to the command line.

I've worked in places where stored procedures are created by a team of DBA's. You don't have a choice, they require you to use their centralized procs.

Modify and Delete Record Operations - Drupal 7 Multi-Step Form (FAPI)

This post is part of a series. We discuss how to build a custom module using Drupal 7's API.

In our previous posts "Drupal 7 Multi-Step Form (FAPI) with Table Report" and  "Making Our Report Sortable and Paged - Drupal 7 Multi-Step Form (FAPI)", we created a multi-step form that allows users to add new records. We display a synchronized (dependent) table report which is both sortable and page-able.

In this post, we add  "Modify" and "Delete" record operations. The following screen-shots display our new form and report.

new form layout

In order to accommodate the new record operations, we add a field set. The field set notifies the user which record operation is currently selected ("Add" is selected by default). The field set acts as a container for the record operation form. Our module dynamically changes the field set and form elements when users select a record operation (E.G. Add, Modify or Delete).

The following screen-shots display the new "Modify" and "Delete" form elements.

 new delete

The "Modify" operation works the same way as the "Add" operation. Both operations are divided into 2 steps (AKA multi-step).

Our module now supports three record operations. Thus our module requires additional source code instructions. It's a good practice to keep your internal design (source code structure) modular. Keep the size of individual methods as small as possible. Well organized source code allows the developer to identify logic flow. Well organized code supports changeability. a measurement of how easy is your code to change.

In that spirit, we divide the form definition (hook_form implementation) into helper methods.

Drupal 7 Adding Custom Content Type with Custom Fields (Field API)

In this post we are going to use the custom field "news_date" described in the previous post "Drupal 7 Field API - A simple example".

The module in this post automatically installs a content type with two instances of our "news_date" field (begin and end). 

First a little background:

Drupal 7 introduces the  concept of "entities"  A "node" is a specialization of an "entity". Content types are a further specialization of "node".

An entity can be defined as "fieldable" (which node is).  Thus, "content types" are also "fieldable" (i.e. we can add fields).

Drupal 7 Field API - A simple example

The following example uses Drupal 7 beta 3 release. A simple introduction and demonstration of the new Field API (CCK rolled in the Core Drupal API).

When you install Drupal 7 beta 3,  the system is pre-configured with several base field types (e.g. Decimal, Boolean, Integer, Image. etc). An adminstrator add one of the forementioned field types to a content type (e.g. Story or Page) and thus create a "custom" content type. An admistrator simply uses the adminstrative user interface (web forms) to create a custom content type. So far, no programming required.

For our example, we decided to add a new field type, using the new Drupal 7 Field API. We decided to call our field "News Date". It's a simple field that is stored in the database as an integer (number of seconds), The field is displayed in M-d-Y format (e.g. Nov-17-2010). When you create or edit the "News Date" field, the system displays the standard Drupal 3 drop down combo boxes (month, date, year).

To create our module. we start by creating a new folder (news_date) with 3 files: news_date.info, news_date.install and news_date.module. 

Comments

Few remarks

Unpublished

Hi,

Nice script and basis to build on. A few remarks:

I'd rather use the current date instead of a default date and want to be able to retrieve a previous saved date from the db, so I changed news_date_field_widget_form() from:

$default_date_int = $instance['default_value'][0][$field_type][$field_type];
$default_date_array = _intToDateArray($default_date_int);
$news_data_value = isset($date_array) ? $date_array : _intToDateArray(time());

to

$default_date_int = isset($items[$delta][$field_type]) && !is_array($items[$delta][$field_type]) ? $items[$delta][$field_type] : time();
$news_data_value = _intToDateArray($default_date_int);

But also, now I load previous set dates, the did not get selected in the HTML select element, because _intToDateArray() returned month and day numbers with leading zeros. So I changed this function from:

$month = date('m',$timestamp);
$day = date('d',$timestamp);

to

$month = date('n',$timestamp);
$day = date('j',$timestamp);

Nice way to learn how to make custom fields! :-)

Cheers!

Laurens Meurs, Rotterdam

Nice tutorial

Nice tutorial, but how do you associate it to a node / entity? Could you add a tutorial on how to add this new field to something please. I assume you use the field_attach.api but I cant find any examples that go through the whole create field -> attach field to something. Cheers love the site :-)

My bad I see the follow-up blog has the answer :-)

Unpublished

I see the answer is here in this post.
http://public-action.org/content/drupal-7-field-api-drupal-7-adding-cust...
However how would you add it to say

user/%/edit page?

Change the entity type to user

If you takes the code in http://public-action.org/content/drupal-7-field-api-drupal-7-adding-cust... and change the entity type to user, wouldn't that do it?

In Drupal 7 both Nodes and User objects are specializations of Entity. Thus replacing an entity type of "node" with an entity type of "user" should suffice.

Drupal 7 Multi-Step Form (FAPI) with Table Report

In this post we are going to show how easy it is using Drupal 7's Form API (aka FAPI) to create a multi-step form with a simple html table report.

We'll also demonstrate the development technique of stubbing. In our case, we haven't decided how we want to store our data (for our final design). Therefore, we will use a simple Collection class and Drupal cache mechanism to perform a temporary yet simple stub.

The stub allows a developer to concentrate on one task (e.g. form/user interface), while another developer designs the full implementation (i.e. decides/codes a Drupal Field API instance, custom database tables, etc).

In our example we going to have users enter their name and a favorite color. The first step in our multi-step form is to ask the user to enter their name:

Comments

How do I clear the entries from the database

Hi,
Nice example. I tried it. Was able to enter and then get my system to display multiple entries in the report. Do I have to clear the cache to delete the records?

Yes - Clear the cache to delete the entries

Hi,

For brevity sake I didn't implement an explicit "delete entries" method. You are exactly correct, you clear the cache (i.e. log in as an admin, go to configuration, then performance, the clear cache).

Again, we aren't recommending storing you normal data in cache. We just wanted to show you how you might stub it on a development machine (installation). Note@ We never develop on remote hosts. we always string test our code using a debugger before deploying anything to a QA environment (let alone a remote production installation).

Hope that helps.

Jason

 

 

Dependent Lists and Ajax Form Submission

In this post we going to demo how simple it is to create dynamic forms with Drupal 7 Form API (aka FAPI).

Our form will have 2 select lists (aka drop down combo boxes). Our first list lets a user select a US State. Once a state has been selected, we dynamically render a "city" select list. The city select list only contain cities for the selected state.

We will include a submit button, however the form will be submitted asynchronously (aka via AJAX). 

Upon submission, we will asynchronously render a short message. The message tells the user which city they just submitted.

Create a Simple Drupal Module - Use the Form API -- An Introduction

In this diary, we'll construct a simple custom drupal module.

Feel free to post questions in the comment area. Beginners are welcome. In this diary,  we won't assume "you are just supposed to know that". 

The goals of this post is to detail:

Creating a Drupal Hello World Module

We just posted our new online tool which generates a full custom Drupal module. This post talks about that tool and how modules fit in to the Drupal ecosystem.

A little background first. Occasionally Jason and I will answer questions on the Drupal.org Module Development and Code Questions forum. Some time last year, someone asked for a "Hello World" module.  A "Hello World" application is a barebones implementation of any development environment. If you read any books that cover programming languages they inevitably start with a "Hello World" example. So on the Drupal.org forum, I posted a simple "Hello World" module for Drupal 7. That post generated an unusual amount of interest. Therefore we decided, rather than just repost the "Hello World" example, we would create a tool to allow folks to generate their own.

So our online tool allows you to name the module and customize the package name. The module itself, is still only going to do 2 things:

  • Creates a menu item link (so folks can click and invoke the hello function).
  • Creates a custom function which simply displays "Hello World"

However, for a developer, the tool

  • Creates the proper directory structure and name.
  • Creates the proper file listings.
  • Creates a custom "info" file that allows you to install your custom module in your Drupal installation.
  • The source code includes an implementation of hook_menu().

So the developer gets a starter it. You can add addition functions, menu items etc. The proper naming conventions are started for you (by our tool).

Comments

Nice and Easy

I just generated a Hello World module with your tool and installed it on a Drupal 6 development site (running on my laptop). Worked like a charm. Any thoughts on enhancing this tool to provide some of the standard node hooks?

It is under consideration

Hi, First thank you so much for using our tool and providing feedback.

Sure we will consider enhancing the tool. We encourage folks like your self to provide feedback here so that we can get an indication of what folks most want to see added. Therefore a question. Are your a software developer or more of a themer (graphic designer)? Are you looking for any specific hooks?

How about the node hooks used in the "Examples for Developers"

Actually I am a software developer, not a "themer".

I would take a look hooks used in the node examples published at "Examples for Developers" http://drupal.org/project/examples. I think a lot of developers would find it useful to get a boilerplate of the common nook hook (over-rides).

Again thank you.

Creating A Simple Drupal Module -- Files and Directories

This this diary we are demonstrating how to create a simple Drupal module ( see introduction for more details) .

Our modules name is "My Color".

We will create one directory and three files:

Where Do I Install Custom Modules

Create a new subdirectory under [base drupal location]/sites/all/modules . Install optional and custom modules in the new subdirectory.

Don't use the "modules" directory that is created with your base Drupal installation. When it comes time to upgrade Drupal itself (e.g. 6.14 to 6.15), you need to be able clearly identify optional and custom modules. Therefore, create a new subdirectory under [base drupal location]/sites/all/modules.

Comments

Great Tip

Hi,
Thanks for the simple, yet very useful tip. On Drupal 6 it's really not obvious that's where you are supposed to install optional modules.

My Profile - Proof of Concept Module

This is another installment of our series on beginning module development for Drupal

We'll build a "proof of concept" module called "My Profile" which demonstrates the following:

My Profile - Object Class Pattern

A simple object pattern for our custom data.

The following listing demonstrates 2 classes:

My Profile - Installation

In this installment we'll focus on the install and un-install routines for our My Profile module. The first article (Part 1) is located here.. Part 1 discusses the data structure, database table and class model (to store and retrieve our custom data).

The following code should be stored in a file listing called "my_profile.install"

. Our module's name is "My Profile".

My Profile - Persisting User Profile Data

In part 1 of this series we showed you how to custom the Drupal account registration form. We now need to make sure that the new data is stored in the database (after a user completes the user registration form).

We implement hook_user()

My Profile - Maintenance Table

This post is part of a series on our proof-of-concept module "My Profile". We are going to detail a maintenance table, presumably for a system administrator. The maintenance table allows an administrator to add, modify and delete records.

Before we get in to the code, let's refresh folks on what "My Profile" does and how it fits in to the rest of our Drupal installation.

My Profile adds a simple text field to each Drupal (website) user account. That text field is called "Alias".

A My Profile Alias is optional for user accounts.

My Profile - Maintenance Forms and Handlers

In our previous post we constructed a maintenance table. That allows our administrators to either add a new alias, modify a current alias, or delete a current alias.

In this post we will detail the edit and delete current alias form and routines.

We will implement hook_form() to display a form that either modifies or deletes a current user alias.

My Profile - Dynamic Ajax Form Processing

In this post we are going to discuss the final method for administering our user alias records. That method is the add new alias method.

Let's review the business requirements briefly.

A user alias is not mandatory. However, a single user account can never have more than one alias. So we have 0:1 (zero to one) relationship between alias and user accounts.

So for our add dialog, that means we need to display only user accounts that do not have a related alias.

My Profile - Populating Forms from the Database

In the prior post we showed you how to build a parent form which performs an asynchronous (AJAX) call to dynamically repaint a child form.

In this post we'll detail the child form.

First we need to create a path (url) which maps to our form. Drupal calls it's request dispatching mechanism "Menu Callbacks". We implemented hook_menu() already, we'll add a new callback definition:

My Profile - Add Configuration Controls

In this post we are going to add a configuration page for the system administrator.

Our "My Profile" project has grown. So far the system is set up so that logically and administrator performs all the work. The design assumption so far has been that only a user granted special permissions can edit or delete "my profile" records. A new user is allowed to enter their "my profile alias" when they create an account. However, we haven't facilitated a user editing their alias at some later date.

My Profile - Modiying the standard My Account/User Profile

In this post we'll modify the standard "My Account" user/profile menu and edit forms. We'll code the routines that allow users to edit their own "my profile alias".

In out previous post we discussed the fact that an administrator can turn this feature on or off. In other words, our code needs to check to see what the current system settings are before we allow a user to edit their alias.

First lets add our menu item. Note, we are appending the standard path "user". The following item is added to our hook_menu() impementation:

Module Development in Drupal 7 - Presentation Outline

What Are Modules
  • Customize, change and extend Drupal business functions.
    • You don't change the distributed Drupal source code. You add computer instructions in a module.

Community Modules versus Custom Modules

  • Community modules are generalized solutions.
  • Use Custom Modules for very specialized, narrowly scoped solutions. Modules are not required to be downloaded from Drupal.org.

Drupal 7 Runtime Requirments

  • PDO  -  Drupal 7 requires PDO enabled in your PHP environment.
    • PDO provides transaction support
    • PDO supports prepared statements
      • Prepared statements are faster (database engine caches the exection plan).
      • Prepared satements are safer, much less vulnerable to sql injection attacks.

Module Requirements

  • ".install" files now support implicit database table installation (and table drop). All you need to do is include schema expression.
  • ".info" files now allow you to declare file listings that are dynamically loaded. You don't need to use a PHP "include" expression for Drupal to find methods and objects provided in the dynamic load files. Dynamic loading allows Drupal to reduce your module's memory footprint (usage).

 Drupal Form API

  • Redirect moved from the $form object to the $form_state object.
  • Drupal 7 adds a set of "ajax" attributes that peform some partial page repaint.
    • drupal_set_message() and form_set_error() invoke full page paint. Therefore, we moved our validation and message notification in to our ajax response.
    • ajax response can be either a:

 Drupal Theme

  • Arguments to theme_table() are now required
    • Current version requires a minimum of 2 rows (a bug).

Drupal's Database Abstraction Layer

  • db_insert() is new to Drupal 7.
    • Closest relative in Drupal 6 is the drupal_write_record() method.
    • Both methods return the database autogen (serial) field values.
    • Db_Insert is a more elegant solution (easier to read, has an implicit return value -- drupal_write_record stores response in an object passed by reference).
  • db_query() in Drupal 7 returns a prepared statement
    • Drupal 6 db_query() returned a database query result object.
  • Method Chaining -- Since PDO is an Object Oriented Library it allows for method chaining. One exression can invoke many methods. Note! You may want to alter how much chaining you are using to control granularity of your exception handling.

 

Drupal 7 Dynamic Class Loading

Drupal 7 introduces dynamic class loading. When you package your modules, you need to supply Druapl with a ".info" file. Inside the ".info" file you now declare an array of file listings. Drupal's new Module Registry inventories the file listings and dynamically loads method and objects as needed.

In our example below, we have have a file listing called "my_color_class.inc". The file listing includes several application objects. Before Drupal 7 we would need to use an "include..." statement in any other file listing before we could use those objects.

my_color.info