Summary:Adventures in building a business directory

Page is Ready for Comments! Green is XES' responses to Caveman

Is Caveman's responses.

This is my adventure in building a business directory in ZAP.

First, you need to understand ZAP's philosophy: store data; manipulate data; retrieve data. Seems simple, but it's really not. In this case, I took advantage only of ZAP's data saving abilities. At another time, I may use other features of ZAP, but page data retrieval wasn't my goal when I understand PageLists better.

actually I would say the philosophy is along the lines of POST the form and then use form fields to call various functions and commands. Basically ZAP is a mini scripting language for a form. Also, my recommendation below is to use the data retrieval functions also, as it makes things somewhat easier.

Until I understand ZAP's data saving abilities, I am tackling it from the point of view that ZAP makes it easy for me to save things that I can use PmWiki to retrieve. Taking it one step at a time. The storage retrieval options can be entirely separate from the saving. From a tutorial point of view, it's like learning a language one step at a time. You seem to want people to dive in whole-hog, but an example of using ZAP just to augment PmWiki works, too, and is more easily digested. I should have put an example of what the data page looks like in a page edit -- so now that I think of it, I will. That may help explain to a PmWiki power user WHY and HOW ZAP works.

Second, you need only one page with ZAP enabled on it. In the local/ directory, one Group.PageName.php file. On that page (I used Site/EditMyDirectory), I worked out the basis for this recipe. I'll start with my thought processes.

it might not hurt to mention that if you do not have AuthUser enabled, you must reset the ZAP form permissions in the Group Attributes page for the form--probably to nopass.

please rephrase. Too many negatives. If AuthUser is not enabled, why would I need to reset the ZAP form permissions in Group Attributes? What's the difference if AuthUser IS enabled? Also, I would NOT want to reset ZAP's permissions on the GroupAttributes page for the Site group. Why not on the individual page's permissions?

Here was my plan:

  1. Decide my purpose.
    • Create a small business directory, without categories. I don't expect the membership list to go over 50 people anytime soon so I'm listing everyone in a large page *shrugs*. If it's too big, I'll worry about it later.
  2. Figure out how to store the data.
    • Each entry is associated with a member/user of the site, so I would tie each entry directly to the member's username.
    • I considered using the Profiles/Username pages, but decided I would leave that for real user profiles or a dedicated "home page" for each user.
    • I decided instead to use a dedicated data storage group. I chose the descriptive name "Directory-Data" for the group name. Each page would be the username, so I would be CIttermann, and my business directory data would be on Directory-Data/CIttermann.
    • I chose a centralized form, rather than inserting the form in a GroupFooter for my Directory-Data group. Either way works.
      • my recommendation was the latter. I think it might be easier for other users to edit their pages. They just go to it, and the form is ready. But if all the pages are in one long directory, I can see the merit of one update page, with the datapage set to Directory-Data.@.
  3. Use ZAP's form processing ability to create data pages. This saves me the trouble of taking form data and figuring out how to make it into page variables on an entirely separate page. Safely.
    • This is the step I'll go into much more detail with.
  4. Set up a pagelist to pull the data out of the Directory-Data group again.
    • This displays the directory, described below.
  5. Set up a PageList Template (at Site.LocalTemplates) to display the directory information the way I want it to look
    • This formats how the directory looks, more below.
  6. Add in a dynamic hook for the currently logged in user to be able to click a link to the page where they edit their entry.
    • this is part of the Pagelist Template.

Wrestling with ZAP

I don't understand everything in ZAP. But here's things I get, and a line-by-line logic for what I've done:

  • ZAP is an alternative to PmWiki forms, and shares most of the same syntax. It processes form input changes the input, triggers special sub-programs, or directs the data to be saved on a page. When saving data, the default is for ZAP to save the data to the currently viewed page in colon-delimited format, by default using PmWiki's directive version of colon delimited text variables, which are invisible unless the source of a page is viewed.
  • ZAP is set up to send messages to the PmWiki page the form wrapped in ZAP sits on. You need to enable the PmWiki message feature to take advantage of this:
You don't need to do anything else to get the messages: ZAP is already set up to send the messages to PmWiki.
  • I decided I wanted the directory entry for the current PmWiki Author to show at the top of the page, if found:
    (:pagelist name={$Author} group=Directory-Data fmt=#bizdir:)
The page name is the name of the current author (or logged in user), from the group "Directory-Data" and it uses the pagelist template named "bizdir" (details later).
  • zapdata? pulls all the variables from the User's Directory-Data page and creates page variables from them. If I had known this before, it would have saved me a good bit of trouble with PmWiki's text vars: I could have used "{$:Business}" instead of "{Directory-Data.{$Author}$:Business}" to pull variables into the form. Hopefully the documentation on this feature will improve.
    (:zapdata Directory-Data.@:)
  • This is used to pass the $myaction GET variable back to the form as a page variable. Group.Name?myaction=Update creates the page variable {$myaction} on the next page display will be "Update".

BTW, ZAP isn't enabled on PmWiki (yet) so you don't need [@ just yet. :)

Yes -- it keeps "code" consistent throughout the page. It's monospaced, indented, etc. It's obviously "code".
  • The next visual item I want on the page is the form for ZAP data entry, so it's time to start the form. To run the form information through ZAP, you need to tell PmWiki to trigger ZAP. Later you will need to turn it off again -- so you might as well put both directives in so that you can sandwich the form in between:
All forms that contain ZAP commands need to be between these two zap directives
However, since I want to use ZAP to send data to a different page, there are changes to this command that are vital. For security reasons, some directives require extra information in the (:zapform:) directive. NO LINE BREAK in the line!!:
(:zapform `Username`Business`Member`Phone`Fax`Email`Website`
Photo`BusSummary`Address`myaction`savedata`button`passdata`datapage` :)
This is a security/permissions line?. When a form is submitted, ZAP checks this line to make sure that the items being entered have permission to be submitted to PmWiki. "Username", "Business", "Member" to "Address" are form fields or data I want saved to the Directory-Data pages (you will see these in the input form lines below). "myaction" is the variable used for form submit actions, "savedata?" gives the form permission to save to PmWiki pages (which variables are saved are specified in the directive), "button" is an arbitrary field on the form, "ifbutton?" is an "if" conditional that tests the field "button", "passdata?" allows the ZAP form to use variables in the page URL to pass information between form submission clicks, and "datapage?" gives the ZAP application permission to save data to another page in the wiki.

You might recall the lock pattern was designed to prevent forged headers, and I think it works well. However with a pattern like yours, you are only controlling what fields can be set, and not any of their values. I would probably want to include the values for at least some of these fields: ie all hidden field, and especially the datapage. Else anyone with access to the page could get the session variable set, retrieve the key, enter whatever values the want and submit the form. ie overwrite someone else's page. I mean, unlikely but doable. That's why at least some values are important. The plus is, when you put the values in the lockpattern, you don't need to put them in the form. They are automatically added (I think). To put values into the lock pattern, try this:

(:zapform `Username={$Author}`Business`Member`Phone`Fax`Email`Website`
Photo`BusSummary`Address`myaction=whatever`savedata=field1,field2,etc`button`passdata=field`datapage=Group.Page` :)
Can you please correct this directive to be specific to my form? there is no "field1,field2,etc". The datapage is not Group.Page. myaction is not "whatever". Take the time to make this right, otherwise you and I are going to go back and forth a bunch of times. I went through the trouble of posting my exact form on the site. All the values are available. If I try to follow your example I'm bound to make mistakes. Like "passdata=field" -- WHAT is field? Am I supposed to leave it literally "field" or change it to something else? If something else, then what?
Also, why all this redundancy. If I'm setting all my savedata values here, why am I bothering with a savedata directive later? There is no good reason I can think of that I'm doing this twice. Why not have the zapform directive automatically set all these directives, and make them unnecessary in the rest of the form?
  • Now that security for the form is taken care of, we get to the portions of the form that control the form process. This next line uses the datapage? directive, with the specific Group I want to retrieve my data from, and uses the "ZAPfixpage"? instructions for the '@' shortcut (@ = current $Author). So this is "save data to the Directory-Data group with the pagename of the current author."
    (:input hidden datapage "Directory-Data.@":)
If you process a form through ZAP without telling it which page to save information to, ZAP will save the data on the same page as the form. In this case, it would be a disaster because every user's directory entry would overwrite others'. There are other ways to do this, but I decided I wanted a single central page from which every user could take care of their own directory entry. The "@" portion of the command is pulling in the $Author variable of the page, a shortcut called ZAPFixPage?. This means I'm pointing the form to the place I want the user's data stored.
"ZAPfixpage"? directives must be used in (:zapdata:) and (:zapget:) directives. They are processed BEFORE page variables, in order to set page variables. Other ZAP directives can use normal page variables or the "ZAPfixpage"? shortcuts.
  • I wanted to save a hidden variable called "Username" to be the same as the current $Author variable. I use this in the PageList format in a conditional statement later when I want the current $Author to see a link to modify their own profile. There are other ways to create that link.
    (:input hidden Username "{$Author}":)

Not sure this is necessary in your case, as you will always have the {$Name} variable = to the same thing... Ah, but if you are not putting the form in a groupfooter, this might be a great idea. You would have {$=Name} available in the pagelist template, but not in a conditional on the page, and you might need that. But then again, if you know the page name, you know this value already, somehow...

The alternative is to test whether the current page name is equal to "{Directory-Data.{$Author}". I actually may be using the $Username field to create a drop-down list of usernames for the admin to choose from. I wanted to store this on the page instead of having to parse the pagenames for the username in other circumstances. It helps if I want to link the Business Directory list to the Profiles, for example -- I use it to link to the profiles for the emails in the PageList Template - [[ Profiles/{{=$BaseName}$:Username} | email form on profile page]]
  • Next is a conditional statement, so that the form is only shown if the "Update" button is clicked. I mentioned above that $myaction is a variable sent with the form button click, and this checks whether $myaction is equal to "Update" -- if so, it displays the form. This is the only place where my form requires the (:zapget:) directive (above) that changes form variables into page variables.
    (:if equal {$myaction} "Update" :)
  • The next two lines are a horizontal rule, and setting up a table. Then the table form begins.
    ||border=0 cellspacing=0 width=300
    ||Business: ||(:input text Business "{Directory-Data.{$Author}$:Business}" size=35:)

If you have (:zapdata Directory-Data.@:) on the page (anywhere) you can change this input field to:

(:input text Business "{$Business}" size=35:)
I see that now, but it wasn't documented, so I stuck with what I understand; PmWiki page text vars.
I will explain the Business line, which is intended for the user's business name. This form input line is straight out of PmWiki's forms abilities, and documented there.
  1. the double pipe || defines the table boundaries in PmWiki -- inside the first column is the word "Business:" -- this just prints straight to the screen the second double pipe starts the next column in the same row.
  2. the second column of the table has the text input box:
    1. input: create an input form element
    2. text: type is a text input field
    3. Business: name the data container "Business"
    4. size: the length of the text entry box
    5. the rest of the mess there ( "{Directory-Data.{$Author}$:Business}") is the directions to display data in the form, if it's available
      1. it needs to be in quotes in case there's a space in the business's name
      2. "Directory-Data.AuthorName$:Business" needs to be in curly braces ("{}") because it is a page variable to be digested by PmWiki
      3. $Author also needs to be in curly braces -- INSIDE the outer braces -- because it needs to resolve the author's name before PmWiki can REALLY build the page name to look at.
      4. $:Business is a colon-delimited page variable. It will be looked up from the Directory-Data.Author page.
All the other (:input text ... :) directives follow similar reasoning.
  • There are two "textareas" on the form. these are the larger multiline text input fields. Use of ZAP allows these. I don't believe they are a part of the PmWiki core forms (not as of PmWiki 2.2beta16 anyway).
    (:textarea name=Address cols=28 rows=7:){Directory-Data.{$Author}$:Address}(:textareaend:)

I don't know if you tested this, but I get html in my textarea if I do it like this. That's why I wrap it in the Keep directive. It also adds some other nice features, like getting rid of the need for \\ and preserving spaces as  . But mostly to avoid problems with html in the text areas. I'm surprized you didn't have any problems... Hmmm. I think with or without keep, directives and the like are automatically disabled when saved. Might mention it. Anyway, heres how I would write this line:

(:textarea name=Address cols=28 rows=7:)(:keep {$Address}:)(:textareaend:)
I thought it was working fine for me, but the problem happens when I edit things in the text area, and the problem is saved TO the data page output. Fixing this required me to add a [[==]] at the beginning of the output line on the pagelist template (see below) because sometimes there's a leading space in the output from the page text var in the pagelist template, which causes bad formatting for my purposes in PmWiki, so I put nonsense at the beginning of the line so PmWiki wouldn't read that space as meaning that the input is code. Oh, and for some reason, when I use the PmWiki text var, I can't use the italic markup in the pagelist template anymore -- it comes through with two single quotes instead of being read to italicize the text :/ Maybe I can find another way to get it to ignore the leading space.
Because the textarea can be filled with multiline data, it requires an explicit beginning and ending. As you can see, I pre-filled in the user's Address information so that the person can edit it.
  • The next line tells ZAP which fields to save as data on the datapage (Directory-Data.Username). Without it, none of the data being played with in the form will be saved anywhere.
    (:input hidden savedata "Username,Business,Member,Phone,Fax,Email,Website,Photo,Address,BusSummary":)
  • The remainder of the form I could use some clarification on. I took a snippet from ZAP's rolodex example, and still have some redundant or unused directives in my form.
    (:input hidden myaction View:)
    (:if ! equal {$myaction} "Update" :)
    (:input hidden myaction {button}:)
    (:input hidden passdata myaction:)

OK, kind of twisted logic. Probably a hodgepodge of several tries and somehow ended up with something that worked. :) Anyway, here's the idea: First, myaction is a flag to tell what parts of the form to display. It's a GET value passed back to the page via the passdata action (you'll see it in the browser address bar). When the page loads it's initially set to View in the form. Then if the GET value myaction is not equal to Update then it is changed to Update in the form. (No clue why I didn't just write "Update" instead of button. Probably had more than one button at some point. the {button} thing is an example of field replacement. Means: substitute in value of "button" field into this field. VERY powerful and useful feature in ZAP--but no reason for it here).

Ok, so in short, when $myaction is Update, it shows the update form and says next time set $myaction to View. If $myaction is View or "", then the Update Form does not show up but the form passed myaction=Update to the page when the button is clicked (ie: change to update form). Does that help?

No, not really. Here's what I want: I want the page to have the update form on it, it doesn't need to be hidden from view to start with, and when the Update button is clicked, it's submitted. I don't need people to click Update twice. How do I fix this? There were so many directives left, and it worked, so I was happy enough ;)
(:input submit button "Update":)
With some guidance, I could probably eliminate a number of these lines. I only need a single "Update" button that opens up the edit form, or saves the new data. Actually, I could probably eliminate the hiding of the form entirely.

You might take a look at the Show/Hide snippet (and the Rolodex, again) for simpler examples to see how this kind of thing works. Also the comments on the Snippets and Doc's section uses this kind of system, and the Tagging page for the snippets. Combining passdata with zapget can be very powerful!

Not sure I'll get the time to do all that hunting through snippets right now. The snippets don't explain themselves, so until I understand more, they're not helpful as building blocks, only as direct copy/paste.

The entire ZAP form page


(:pagelist name={$Author} group=Directory-Data fmt=#bizdir:)

(:zapdata Directory-Data.@:)

(:zapform `Username`Business`Member`Phone`Fax`Email`Website`Photo`BusSummary` %red%LINEBREAK%%
Address`myaction`savedata`button`passdata`datapage` :)

(:input hidden datapage "Directory-Data.@":)
(:input hidden Username "{$Author}":)

(:if equal {$myaction} "Update" :)
||border=0 cellspacing=0 width=300
||Business: ||(:input text Business "{Directory-Data.{$Author}$:Business}" size=35:)
||Member: ||(:input text Member "{Directory-Data.{$Author}$:Member}" size=35:)
||Phone: ||(:input text Phone "{Directory-Data.{$Author}$:Phone}" size=35:)
||Fax: ||(:input text Fax "{Directory-Data.{$Author}$:Fax}" size=35:)
||Email: ||(:input text Email "{Directory-Data.{$Author}$:Email}" size=35:)
||Website: ||(:input text Website "{Directory-Data.{$Author}$:Website}" size=35:)
||Photo: ||(:input text Photo "{Directory-Data.{$Author}$:Photo}" size=35:)
(:textarea name=Address cols=28 rows=7:){Directory-Data.{$Author}$:Address}(:textareaend:)\\
Business Summary:\\
(:textarea name=BusSummary cols=28 rows=7:){Directory-Data.{$Author}$:BusSummary}(:textareaend:)\\
(:input hidden savedata "Username,Business,Member,Phone,Fax,Email,Website,Photo,Address,BusSummary":)
(:input hidden myaction View:)

(:if ! equal {$myaction} "Update" :)
(:input hidden myaction {button}:)

(:input hidden passdata myaction:)
(:input submit button "Update":)

The saved data page (edit/source view)

(email address changed to protect my inbox, line breaks added to multi-line entry for readability) This page is invisible in browse mode:


(:comment data:)

(:Username: CIttermann:)

(:Business: Eclectic Tech, LLC:)

(:Member: Criss Ittermann:)

(:Phone: 845-820-0262:)

(:Fax: 801-289-3923:)



(:Photo: %width=150px%

(:Address: Middletown, NY:)

(:BusSummary: Eclectic Tech provides web design and web development/programming services,
 specializing in web application installation and customization, website odd-jobs and getting
 people out of bad web hosting situations.[[<<]]15% discount offered on services and packages
 for organic, holistic, childcare and educational non-profit businesses.:)


To list only the author's own directory entry:

(:pagelist name={$Author} group=Directory-Data fmt=#bizdir:)

To list all directory entries:

(:pagelist group=Directory-Data list=normal fmt=#bizdir:)

PageList Template

You can't use ZAP's data retrieval for pagelist templates, so you must use PmWiki's built-in page text vars to retrieve ZAP data for pagelists. (:zapdata:) & (:zapget:) won't save you any typing here.

I created a custom pagelist template. Custom templates need to be stored on the wiki page LocalTemplates.

  • This template starts off with a conditional ("if") to determine if it needs to draw a horizontal rule. If it's the first item in the list, it does NOT draw the line.
  • I have the photo/logo float to the left, under it (also on the left) are the Address, Phone & Fax.
  • On the right is the business name, name of the member, etc.
  • Instead of listing member's email addresses, there is a link to the member's profile page. I will add a "secured" email form to Profiles/GroupFooter, and tie in the members' email address to the form submissions. More on that when I figure it out :)
  • The business summary goes across the whole entry.
  • If the viewer is a member, their entry will invite them to update it.

(:if ! equal {<$FullName}:)

(:cellnr width=170px:){{=$BaseName}$:Photo} 


(:if ! equal {{=$BaseName}$:Fax} "":)
{{=$BaseName}$:Fax} (fax)

[[ Profiles/{{=$BaseName}$:Username} | email form on profile page]]\\
(:if equal {$Author} {{=$BaseName}$:Username}:)
[[Site/EditMyDirectory | Edit My Directory Entry]]\\
(:if auth admin:)
[[{=$BaseName}?action=edit | Edit Directory Data]]


(:if equal {$Author} {{=$BaseName}$:Username}:)
[[Site/EditMyDirectory | Edit My Directory Entry]]

NOTE: The other time you have limitations with (:zapdata:) & (:zapget:) is if you attempt to pull data in from more than one data page to a page. If all the data fields available are unique, great. But if not you will have some overwriting going on. In that case, page text vars may be safer, since each page is explicitly addressed.

See Also


XES December 08, 2006, at 10:00 PM