11.1 ⁂Writing a Class File for a Form
The above may be suitable for a short form to be filled in by hand, but it may be that you want to produce a more complex form to be filled in by LaTeX users. In this case, it may be more appropriate to write a class file that provides commands to fill in the form data. This section describes how to do this and is developed from an article I wrote on the LaTeX Community Forum [92]. The next section will look at interactive form elements.
§7.3 Displaying a Date briefly introduced package writing. There
are similar commands for classes, and some of the package commands,
such as \RequirePackage
, may also be used in class files. As
with packages, the class first identifies the TeX format using
and then identifies the class using
This has the same syntax as \ProvidesPackage
described in
§7.3 Displaying a Date. The class code should be saved in a file
called ⟨name⟩.cls and placed somewhere on TeX's path.
Many classes load a parent class, which saves defining many common elements, such as the sectioning commands or list environments. The parent class is loaded using:
where ⟨name⟩ is the name of the parent class. The optional
arguments are analogous to the optional arguments of
\RequirePackage
. Before you load a class, you can specify
which options to pass to it using:
where ⟨option-list⟩ is a comma-separated list of options to pass to the class specified by ⟨class-name⟩. An option is defined using:
where ⟨option⟩ is the option name and ⟨code⟩ is the code to perform for that option. The starred version of this command only has one argument:
This indicates the code to perform for an unknown option. The option name can be referenced within ⟨code⟩ using
Once all the options have been declared, they then need to be processed using:
Here's the code for a trivial class called simple-form:
\NeedsTeXFormat{LaTeX2e} \ProvidesClass{simple-form}[2014/10/11] \DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}} \ProcessOptions \LoadClass{article} % class code \endinput
This code needs to be saved in a file called
simple-form.cls. At the moment this class doesn't provide
anything in addition to the article class, but the new code
will be added in the area between the \LoadClass
line and the
\endinput
line.
In addition to \Square
, which produces an empty square ☐,
the wasysym package also defines
which produces a box with a cross in it ☒, and
which produces a box with a tick in it ☑. These symbols will be useful for this new class, so the class code needs to load the wasysym package using
\RequirePackage{wasysym}
Information for the form can be gathered using the same type of
mechanism as \author
, \title
and \date
. These
work by having an internal command that stores the information and
a user command that sets the internal command. For example, if the
form requires a person's name, the internal command could be called,
say, \@name
which is initially empty
\newcommand*{\@name}{}
and the user command could be called, say, \name
which
redefines the internal command:
Then a command analogous to \maketitle
is required to typeset
the form. For example, this command could be called
\makeform
and it would use the internal commands to fill in
the required areas. A trivial example would be:
(\@date
is the internal command used by \date
and is
initially defined as \today
.)
This definition of \makeform
has a problem when
\name
isn't used. If \@name
is empty it won't take
up any space. A better solution is to put \@name
inside
a horizontal box with a fixed width:
This will leave a blank space if the name hasn't been set. If you
prefer a lined space you could make the initial definition of
\@name
use \hrulefill
\newcommand*{\@name}{\hrulefill}
Now a lined space will appear if \name
hasn't been used,
but the line won't be present if \name
has been used. If
you still want a line to appear even if \name
has been
used, then you could replace
with
If you have more than one blank area to fill in, then it's best to define a command to do this. For example:
This has the syntax
so the trivial definition of \makeform
can now look
something like:
Variations of \form@fillin
could include
which uses a dotted line instead or
which centres the text within the ruled area or
which right-aligns the text within the ruled area.
Check boxes require a different interface, but there are various
methods you can use. For example, for a gender check box you might want
a command called, say, \male
that ticks the “male” box
and a command called, say, \female
that ticks the
“female” box. Alternatively you might prefer a command called,
say, \gender
that takes an argument which can either be
male or female. In both cases, internal commands
are defined for each option that default to the unchecked case:
The user commands redefine these internal commands. In the first case:
\newcommand*{\male}{% \renewcommand*{\gender@male}{\XBox}% } \newcommand*{\female}{% \renewcommand*{\gender@female}{\XBox}% }
In the second case:
\newcommand*{\gender}[1]{% \ifcsdef{gender@#1}% {\csdef{gender@#1}{\XBox}} {% unknown option produces an error \ClassError{simple-form}{Unknown gender `#1'} {Options: `male', `female'}% }% }
This uses the etoolbox commands \ifcsdef
and \csdef
described in §2.1.1 Macro Definitions, and also uses
to display an error message. The first argument is the class name (simple-form in this case) and the second argument is the error message. The third argument provides a help message if the user types “h” in TeX's interactive mode.
What if I later decide to use \CheckedBox
instead of
\XBox
? Alternatively, I might decide to use a radio button
style. To help with code maintenance it's better to define commands
for the checked and unchecked status and use those commands for
the form data. For example:
\newcommand*{\form@unchecked}{\Square} \newcommand*{\form@checked}{\XBox} \newcommand*{\gender@male}{\form@unchecked} \newcommand*{\gender@female}{\form@unchecked} \newcommand*{\male}{% \renewcommand*{\gender@male}{\form@checked}% } \newcommand*{\female}{% \renewcommand*{\gender@female}{\form@checked}% }
Or
\newcommand*{\gender}[1]{% \ifcsdef{gender@#1}% {\csdef{gender@#1}{\form@checked}} {% \ClassError{simple-form}{Unknown gender `#1'} {Options: `male', `female'}% }% }
Now there are only one or two lines to change if I want to use different
symbols. For example, to use \CheckedBox
instead of
\XBox
just requires one edit:
\newcommand*{\form@checked}{\CheckedBox}
If you can't find a symbol that suits you, it's possible to combine
symbols using a command such as \rlap
. For example, to make
round radio style buttons, you could use the ifsym
package [45] with the geometry
option and
combine \BigCircle
with \FilledSmallCircle
.
\newcommand*{\form@unchecked}{\BigCircle} \newcommand*{\form@checked}{\rlap{\FilledSmallCircle}\BigCircle}
These produce the symbols and .
geometry
option. For example, both define \Square
.
If you want both packages, load ifsym without the
geometry
option and use \textifsymbol
to access the
symbols. For example:
\newcommand*{\form@checked}{% \rlap{\textifsymbol[ifgeo]{117}}\textifsymbol[ifgeo]{37}} \newcommand*{\form@unchecked}{\textifsymbol[ifgeo]{37}}
Alternatively, if you want fancier buttons you can use picture
drawing code. The following example creates on and off buttons using
tikz with the shadings
and
shadows
libraries:
\RequirePackage[x11names]{xcolor} \RequirePackage{tikz} \usetikzlibrary{shadings} \usetikzlibrary{shadows} \newcommand*{\form@unchecked}{% \resizebox{!}{2ex}% {% \begin{tikzpicture} \path[fill=LightYellow4,circular glow] (0,0) circle(.5cm); \path[fill=LightYellow1,circular glow={fill=LightYellow3}] (0,0) circle(.35cm); \end{tikzpicture}% }% } \newcommand*{\form@checked}{% \resizebox{!}{2ex}% {% \begin{tikzpicture} \path[shade,inner color=LightYellow2,outer color=LightYellow4, circular glow] (0,0) circle(.5cm); \end{tikzpicture}% }% }
This produces and .
Similarly, it's a good idea to provide a command to layout the check box or fill-in area and its accompanying text. For example:
This has the syntax:
For example:
This means that if, say, you want to change all your check boxes so
that the text is to the left of the check box symbol, then all you
need to do is change the definition of
\form@layout@checkbox
. Similarly for the fill-in text
fields:
This has the syntax
For example:
Here's a simple form class with two fill-in areas (for the name and
date) and two check boxes (for the gender). The article class
is loaded with the options a4paper
and 12pt
as this example is simulating a form with specific paper size and
font requirements.
The contents of the file simple-form.cls are as follows:
\NeedsTeXFormat{LaTeX2e} \ProvidesClass{simple-form}[2014/10/11] \DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}} \ProcessOptions \LoadClass[a4paper,12pt]{article} \RequirePackage{etoolbox} \RequirePackage{wasysym} \newcommand*{\form@fillin}[2]{% \makebox[#1][l]{\rlap{#2}\hrulefill}% } \newcommand*{\form@checked}{\XBox} \newcommand*{\form@unchecked}{\Square} \newcommand*{\form@layout@checkbox}[2]{#1 #2} \newcommand*{\form@layout@fillin}[3]{#3: \form@fillin{#1}{#2}} \newcommand*\@name{} \newcommand*{\name}[1]{\renewcommand*{\@name}{#1}} \newcommand*{\gender@male}{\form@unchecked} \newcommand*{\gender@female}{\form@unchecked} \newcommand*{\gender}[1]{% \ifcsdef{gender@#1}% {\csdef{gender@#1}{\form@checked}} {% \ClassError{simple-form}{Unknown gender `#1'}% {Options: `male', `female'}% }% } \newcommand{\makeform}{% \form@layout@fillin{8em}{\@name}{Name}\qquad \form@layout@fillin{12em}{\@date}{Date} \par \bigskip \par \form@layout@checkbox{\gender@male}{Male}\qquad \form@layout@checkbox\gender@female{Female} } \endinput
An example document:
\documentclass{simple-form} \name{Mabel Canary} \gender{female} \begin{document} \makeform \end{document}
The result is shown in Figure 11.1. You can download or view this example.
The above example doesn't test if \gender
has already been used, so
it's possible for a user to do:
which would cause both boxes to be checked. If you want to prevent this from happening you could either produce an error message if the command is used more than once or make each subsequent use of the command reset the boxes before setting the new choice.
Here's a possible way of implementing the first case. It uses the
\let
assignment described in §2.1.1 Macro Definitions.
\newcommand*{\@gendererror}[1]{% \ClassError{simple-form} {\string\gender\space may only be used once} {}% } \newcommand*{\gender}[1]{% \let\gender\@gendererror \ifcsdef{gender@#1}% {\csdef{gender@#1}{\form@checked}} {% \ClassError{simple-form}{Unknown gender `#1'}% {Options: `male', `female'}% }% }
This works as follows: the first time \gender
is used, it
redefines itself to have the same definition as \@gendererror
, so the next time
\gender
is used, it's now equivalent to
\@gendererror
, which ignores its argument and produces an
error message. (\string
is a TeX primitive that converts the
following control sequence into a list of characters, which provides
an easy way of printing the control sequence in the transcript file
or console.)
Here's a possible way of implementing the second case that defines a reset command:
\newcommand*{\@resetgender}{% \renewcommand*{\gender@male}{\form@unchecked}% \renewcommand*{\gender@female}{\form@unchecked}% } \newcommand*{\gender}[1]{% \@resetgender \ifcsdef{gender@#1}% {\csdef{gender@#1}{\form@checked}} {% \ClassError{simple-form}{Unknown gender `#1'}% {Options: `male', `female'}% }% }
The \male
/\female
version is simpler:
\newcommand*{\male}{% \renewcommand*{\gender@female}{\form@unchecked} \renewcommand*{\gender@male}{\form@checked} } \newcommand*{\female}{% \renewcommand*{\gender@male}{\form@unchecked} \renewcommand*{\gender@female}{\form@checked} }
However the other method is neater for a large set of check boxes. If you do have many choices, you may find it easier to use a list-based approach. For example, suppose I want to produce the following:
☐ | Mind-Controlling Cookies | ☐ | Telepathic Cakes |
☐ | Exploding Chocolates | ☐ | Ray Gun |
A convenient user command might be called, say, \project
where the argument may be one of: cookies, cakes,
chocolates or raygun. The internal commands are
called \project@
⟨label⟩ where ⟨label⟩ is the
argument of \project
. These commands can be reset using:
and set using
These can be wrapped up in two commands that each take the label as the argument:
\newcommand*{\reset@project}[1]{% \csdef{project@#1}{\form@unchecked}% } \newcommand*{\set@project}[1]{% \ifcsdef{project@#1} {\csdef{project@#1}{\form@checked}} {% \ClassError{simple-form}{Unknown project `#1'}{}% }% }
It's also useful to provide a command to use the internal
\project@
⟨label⟩ command:
This will produce an unchecked box if the label hasn't been defined, which means that the internal commands don't need to be initialised if the user wants a blank form to fill in by hand.
Here's a comma-separated list where each element contains two
groups. The first is the label that will be used in the argument of
\project
and the second is the text to appear next to the
check box in the form:
\newcommand*{\@projectlist}{% {cookies}{Mind-Controlling Cookies},% {cakes}{Telepathic Cakes},% {chocolates}{Exploding Chocolates},% {raygun}{Ray Gun}% }
Various list-iteration commands were discussed in
§2.7.2 Iterating Over a Comma-Separated List, but in the examples from that section all of the
lists had an element that could be used as a single argument to
a command such as \do
. However in this list each element
needs to be treated as two arguments. For example, the command to
reset the check boxes should iterate through this list but only grab
the first group (the label) of each element.
Consider first:
This won't work because it's equivalent to doing
and so on. I could try using\expandafter
described in
§2.7.2 Iterating Over a Comma-Separated List:
\@for\this@element:=\@projectlist\do{% ✘\expandafter\reset@project\this@element }
This is an improvement as this is now equivalent to doing
and so on. Now\reset@project
picks up the label correctly,
but the text after the label is left dangling and needs to be
discarded. There are various ways to deal with this. The simplest
solution is just to make \reset@project
take two arguments
and ignore the second argument:
A more generic approach is to leave \reset@project
with
just one argument as before and use the LaTeX kernel command
which does ⟨first⟩ and discards ⟨second⟩. This requires
\expandafter
to expand \this@element
before applying
\@firstoftwo
:
\@for\this@element:=\@projectlist\do{% \reset@project{\expandafter\@firstoftwo\this@element}% }
A similar method can be used to display the check boxes and their associated text within the form. There is an analogous LaTeX kernel command that grabs the second argument and discards the first:
Here's a simple example that just displays the check boxes with their associated text without any tabulation:
\@for\this@element:=\@projectlist\do{% \use@project{\expandafter\@firstoftwo\this@element}% check box \space \expandafter\@secondoftwo\this@element \qquad }
Or using the layout command \form@layout@checkbox
defined
earlier:
\@for\this@element:=\@projectlist\do{% \form@layout@checkbox {\use@project{\expandafter\@firstoftwo\this@element}}% check box {\expandafter\@secondoftwo\this@element}% text \qquad }
This can be converted into a tabular
environment but we
need a way to track which column we're in. One way to do this is to
define a register (recall §2.1.3 Arithmetic).
% initialise \newcount\form@columncount \form@columncount=1\relax \def\form@precolumn{}% % layout check boxes and text: \begin{tabular}{ll} \@for\this@element:=\@projectlist\do{% \global\let\this@element\this@element \form@precolumn \form@layout@checkbox {\use@project{\expandafter\@firstoftwo\this@element}}% {\expandafter\@secondoftwo\this@element}% \global\advance\form@columncount by 1\relax \ifnum\form@columncount>2\relax \global\form@columncount=1\relax \gdef\form@precolumn{\\}% \else \gdef\form@precolumn{&}% \fi }% \end{tabular}%
(\global
is required because of the local scoping effect of
tabular cells.) This uses a similar method to those discussed in
§2.7.5 Iteration Tips and Tricks.
If you are likely to have more than one group of check boxes, then
it makes more sense to create generic commands. First, we need generic
versions of the above \reset@project
, \set@project
and \use@project
where the first argument is the element
label (such as cakes) and the second argument is the block label
(such as project):
\newcommand*{\reset@element}[2]{% \csdef{#2@#1}{\form@unchecked}% } \newcommand*{\set@element}[2]{% \ifcsdef{#2@#1}% {\csdef{#2@#1}{\form@checked}}% {% \ClassError{simple-form}{Unknown #2 `#1'}{}% }% } \newcommand*{\use@element}[2]{% \ifcsdef{#2@#1}{\csuse{#2@#1}}{\form@unchecked}% }
So now instead of
I need to use
and so on. It's also convenient to provide a command that can
iterate over the {
⟨label⟩}{
⟨text⟩}
list (such as
\@projectlist
) for the block:
\newcommand*{\for@block}[3]{% \ifcsdef{@#2list}% {% \expandafter\@for\expandafter #1\expandafter:\expandafter=\csname @#2list\endcsname\do{#3}% }% {% \ClassError{simple-form}{Unknown block `#2'}{}% }% }
(The \expandafter
s are required because the list control
sequence provided by \csname
@#2list\endcsname
needs to be expanded to
the actual control sequence \@⟨block-label⟩list
, for
example \@projectlist
,
before \@for
tries to iterate over it.) This has the syntax:
where ⟨cs⟩ is assigned to the {
⟨label⟩}{
⟨text⟩}
element
for the current iteration.
All elements within a block can be reset using
\reset@block
, which is defined as:
\newcommand*{\reset@block}[1]{% \for@block{\this@element}{#1}% {% \reset@element{\expandafter\@firstoftwo\this@element}{#1}% }% }
This means that \project
can now be defined as
The generic two-column tabulated block of elements used
by \makeform
can be defined as follows:
\newcount\form@columncount \newcommand*{\form@block}[1]{% \def\form@precolumn{}% \form@columncount=1\relax \begin{tabular}{ll} \for@block{\this@element}{#1}% {% \global\let\this@element\this@element \form@precolumn \form@layout@checkbox {\use@element{\expandafter\@firstoftwo\this@element}{#1}}% {\expandafter\@secondoftwo\this@element}% \global\advance\form@columncount by 1\relax \ifnum\form@columncount>2\relax \global\form@columncount=1\relax \gdef\form@precolumn{\\}% \else \gdef\form@precolumn{&}% \fi }% \end{tabular}% }
This custom command has the syntax:
So for the project example, this would just require
This is hard-coded for two columns, but it would be more flexible to allow an arbitrary number of columns. For example if the command had the syntax
then the project block could be generated using
In this case, the hard-coded conditional in \form@block
can now have the total column count replaced with #2:
However the column specifier argument for the tabular
environment is a little more complicated as it now requires
#2 lots of l (or whatever alignment
specifier you want).
Recall TeX's \loop
command from §2.7.4 General Iteration with TeX's \loop
and
the hook management commands from §2.1.2 Hook Management. These can
be used to generate the argument for the tabular
environment:
% initialise \def\form@columnargs{}% \form@columncount=0\relax % iterate #2 times \loop \appto\form@columnargs{l}% \advance\form@columncount by 1\relax \ifnum\form@columncount<#2 \repeat
This will store the column specifiers in \form@columnargs
which can now be used in the tabular
environment
argument:
Therefore the new two-argument version of \form@block
can be defined as:
\newcount\form@columncount \newcommand*{\form@block}[2]{% \def\form@columnargs{}% \form@columncount=0\relax \loop \appto\form@columnargs{l}% \advance\form@columncount by 1\relax \ifnum\form@columncount<#2 \repeat \def\form@precolumn{}% \form@columncount=1\relax \begin{tabular}{\form@columnargs} \for@block\this@element{#1}% {% \global\let\this@element\this@element \form@precolumn \form@layout@checkbox {\use@element{\expandafter\@firstoftwo\this@element}{#1}}% {\expandafter\@secondoftwo\this@element}% \global\advance\form@columncount by 1\relax \ifnum\form@columncount>#2\relax \global\form@columncount=1\relax \gdef\form@precolumn{\\}% \else \gdef\form@precolumn{&}% \fi }% \end{tabular}% }
The form check box elements are now much simpler to define:
\newcommand*{\@genderlist}{{male}{Male},{female}{Female}} \newcommand*{\gender}[1]{% \reset@block{gender}% \set@element{#1}{gender}% } \newcommand*{\@projectlist}{% {cookies}{Mind-Controlling Cookies},% {cakes}{Telepathic Cakes},% {chocolates}{Exploding Chocolates},% {raygun}{Ray Gun}% } \newcommand*{\project}[1]{% \reset@block{project}% \set@element{#1}{project}% }
If multiple selections are permitted, then the \reset@block
command needs to be moved outside the user command definition to
initialise all the elements. For example, if multiple projects may
be selected:
Adapt the class file simple-form.cls from Example 56 so that the form shown in Figure 11.2 can be created with the following document:
\documentclass{simple-form} \name{Mabel Canary} \date{2014-10-13} \gender{female} \project{cakes} \icecream{vanilla} \icecream{fudge} \icecream{other} \begin{document} \makeform \end{document}
For the More Adventurous
Add a fill-in area for the “Other” ice-cream option so that instead of the user writing:
they can use a new command:
which both checks the “Other” box and fills in the area, as shown in Figure 11.3. You can download or view a solution to this exercise.
If you have a large text area that needs to be filled in, you may prefer to use an environment to collect the information. For example, instead of creating a command to specify, say, a project description:
which can be defined using
\newcommand{\@projectdescription}{} \newcommand{\projectdescription}[1]{% \renewcommand{\@projectdescription}{#1}% }
you may prefer to have the user interface:
This is more complicated to define, as you can't simply gather the
contents of an environment when you use \newenvironment
. There
are a number of ways to achieve this.
The collect package [77] provides the
collectinmacro
environment:
This defines the command ⟨macro⟩ to be ⟨before⟩⟨body⟩⟨after⟩. For example,
This is equivalent to:
\newcommand{\mycommand}{Before. Some text here. After.}
If you want to define an environment that uses this method, you
can't use the environment form \begin
{collectinmacro}
and
\end
{collectinmacro}
, but must instead use the
commands \collectinmacro
and \endcollectinmacro
.
Example:
\newcommand{\@projectdescription}{} \newenvironment{ProjectDescription}% {\collectinmacro{@projectdescription}{}{}}% {\endcollectinmacro}
Another possibility is to use the amsmath package's
command. This gathers the contents of the current environment
⟨body⟩ and applies ⟨cs⟩{
⟨body⟩}
.
Example:
\newcommand{\@projectdescription}{} \newcommand{\projectdescription}[1]{% \gdef\@projectdescription{#1}% } \newenvironment{ProjectDescription}% {\collect@body\projectdescription}% {}
This now means that the user can do either
or
Note that I had to use \gdef
instead of \renewcommand
otherwise the change will be scoped by the encasing environment.
\collect@body
command uses short internal commands
to gather the environment contents, which means that the environment
can't contain paragraph breaks. If you want to allow paragraph
breaks, you can use an analogous command provided by the
environ package [75]:
Note that the unstarred version of \newcommand
allows
a paragraph break to be present within #1 so
\projectdescription
can be used by \Collect@Body
in
the following:
\newcommand{\@projectdescription}{} \newcommand{\projectdescription}[1]{% \gdef\@projectdescription{#1}% } \newenvironment{ProjectDescription}% {\Collect@Body\projectdescription}% {}
This example uses the \Collect@Body
command from the
environ package to allow the user to enter multi-paragraph
data in a form. First the class file, sample-form.cls:
\NeedsTeXFormat{LaTeX2e} \ProvidesClass{sample-form}[2014/11/03] \DeclareOption*{\PassOptionsToClass{\CurrentOption}{article}} \ProcessOptions \LoadClass[a4paper,12pt]{article} \RequirePackage{environ} \newcommand*{\form@fillin}[2]{% \makebox[#1][l]{\rlap{#2}\hrulefill}% } \newcommand*{\form@layout@fillin}[3]{#3: \form@fillin{#1}{#2}} \newcommand*\@name{} \newcommand*{\name}[1]{% \renewcommand*{\@name}{#1}% } \newcommand*\@projectdescription{} \newcommand{\projectdescription}[1]{% \long\gdef\@projectdescription{#1}% } \newenvironment{ProjectDescription}{\Collect@Body\projectdescription}{} \newcommand{\makeform}{% \section{Applicant Details} \form@layout@fillin{8em}{\@name}{Name} \section{Project Description} \@projectdescription } \endinput
Here's an example document:
\documentclass{sample-form} \name{Mabel Canary} \begin{ProjectDescription} This project will be very interesting. This is another paragraph. \end{ProjectDescription} \begin{document} \makeform \end{document}
The result is shown in Figure 11.4. You can download or view this example document.
This book is also available as A4 PDF or 12.8cm x 9.6cm PDF or paperback (ISBN 978-1-909440-07-4).