ZAPBusinessDirectory
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.
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.
Here was my plan:
- 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.
- 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.@.
- 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.
- Set up a pagelist to pull the data out of the Directory-Data group again.
- This displays the directory, described below.
- 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.
- 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:
(:messages:)
- 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:)
- 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".
(:zapget:)
BTW, ZAP isn't enabled on PmWiki (yet) so you don't need [@
just yet. :)
- 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:
(:zapform:) (:zapend:)
(:zapform `Username`Business`Member`Phone`Fax`Email`Website` Photo`BusSummary`Address`myaction`savedata`button`passdata`datapage` :)
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` :)
- 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.@":)
$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.
- 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...
[[ 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:)
- 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. - the second column of the table has the text input box:
- input: create an input form element
- text: type is a text input field
- Business: name the data container "Business"
- size: the length of the text entry box
- the rest of the mess there (
"{Directory-Data.{$Author}$:Business}"
) is the directions to display data in the form, if it's available- it needs to be in quotes in case there's a space in the business's name
- "Directory-Data.AuthorName$:Business" needs to be in curly braces ("
{}
") because it is a page variable to be digested by PmWiki $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.- $:Business is a colon-delimited page variable. It will be looked up from the Directory-Data.Author page.
(: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:)
[[==]]
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.
- 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:) (:ifend:) (:if ! equal {$myaction} "Update" :) (:input hidden myaction {button}:) (:ifend:) (: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?
(:input submit button "Update":) (:zapend:)
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!
The entire ZAP form page
(:messages:) (:pagelist name={$Author} group=Directory-Data fmt=#bizdir:) (:zapdata Directory-Data.@:) (:zapget:) (: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:) \\ Address:\\ (: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:) (:ifend:) (:if ! equal {$myaction} "Update" :) (:input hidden myaction {button}:) (:ifend:) (:input hidden passdata myaction:) (:input submit button "Update":) (:zapend:)
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:) (:Email: info-wikisample@example.com:) (:Website: http://www.eclectictech.net/:) (:Photo: %width=150px%http://www.eclectictech.net/pub/skins/eclectic/etsitelogo.gif:) (: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.:)
PageList
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.
[[#bizdir]] (:if ! equal {<$FullName}:) ---- (:if:) (:table:) (:cellnr width=170px:){{=$BaseName}$:Photo} (:cell:) !!{{=$BaseName}$:Business} !!!{{=$BaseName}$:Member} {{=$BaseName}$:Address}\\ {{=$BaseName}$:Phone}\\ (:if ! equal {{=$BaseName}$:Fax} "":) {{=$BaseName}$:Fax} (fax) (:ifend:) [[ Profiles/{{=$BaseName}$:Username} | email form on profile page]]\\ {{=$BaseName}$:Website}\\ (:if:) (:if equal {$Author} {{=$BaseName}$:Username}:) [[Site/EditMyDirectory | Edit My Directory Entry]]\\ (:if auth admin:) [[{=$BaseName}?action=edit | Edit Directory Data]] (:ifend:) (:tableend:) [==]{{=$BaseName}$:BusSummary} (:if equal {$Author} {{=$BaseName}$:Username}:) [[Site/EditMyDirectory | Edit My Directory Entry]] [[#bizdirend]]
See Also
Contributors
XES December 08, 2006, at 10:00 PM