Index: openacs-4/packages/acs-core-docs/www/tutorial-pages.html =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-core-docs/www/tutorial-pages.html,v diff -u -r1.12 -r1.13 --- openacs-4/packages/acs-core-docs/www/tutorial-pages.html 5 Nov 2003 14:46:52 -0000 1.12 +++ openacs-4/packages/acs-core-docs/www/tutorial-pages.html 11 Nov 2003 10:28:27 -0000 1.13 @@ -1,190 +1,133 @@ -Creating Web Pages

Creating Web Pages

+Creating Web Pages

Creating Web Pages

by Joel Aufrecht
OpenACS docs are written by the named authors, and may be edited by OpenACS documentation staff. -

Build the "Index" page

Each user-visible page in your package has, typically, - three parts. The xql file contains any database queries, the - tcl file holds the procedural logic for the page and does things - like check permissions, invoke the database queries, and modify +

Install some API

As a workaround for missing content-repository functionality, copy a provided file into the directory for tcl files:

+    
cp /var/lib/aolserver/service1/packages/acs-core-docs/www/files/note-procs.tcl /var/lib/aolserver/service1/packages/myfirstpackage/tcl/

To make this file take effect, go to the APM and choose "Reload all files" for "MyFirstPackage".

Build the "Index" page

Each user-visible page in your package has, typically, + three parts. The tcl file + holds the procedural logic for the page, including TCL and + database-independent SQL code, and does things like + check permissions, invoke the database queries, and modify variables, and the adp page - holds html. The default page in any directory is + holds html. The -postgres.xql + and -oracle.xql files contains + database-specific SQL. The default page in any directory is index, so we'll build that - first, starting with the tcl file: -

[service0@yourserver postgresql]$ cd /var/lib/aolserver/service0/myfirstpackages/www
+      first, starting with the tcl file:

[service0@yourserver postgresql]$ cd /var/lib/aolserver/service0/myfirstpackages/www
 [service0@yourserver www emacs index.tcl

Paste this into the file.

ad_page_contract {
     This is the main page for the package.  It displays all of the Notes and provides links to edit them and to create new Notes.
 
-    @author rhs@mit.edu
-    @creation-date 2000-10-23
+    @author Your Name (you@yourserver.test)
     @cvs-id $Id$
-    @param orderby indicates when the user clicks on a column to order by that \column
-    @return table_html preformatting html table constructed by querying the sam\plenotes table
-
-} {
-    {orderby:optional {title}}
-} -properties {
-    table_html
 }
-# define the columns in the table
-set table_def   {
-    {title "Note"}
-    {body "Contents"}
-    {edit "" {} {<td><a href="note-edit?note_id=$note_id">Edit</a></td>}}
-}
 
-# construct an html table from the samplenotes database table
-set table_html [ad_table -Torderby $orderby notes_query { *SQL* } $table_def]

There are several things to -note about the file:

  • The page begins with an - ad_page_contract function. - This is where we declare the input and output variables and - their types and restrictions. It's also where we document the - page, including descriptions of the parameters and return. - (More information about TCL - pages and page contracts)

  • We have one input variable, - orderby, which is optional - and defaults to title.

  • We have one output variable, table_html

  • We populate the table_html variable with a function call, ad_table, which does most of the work of generating an html table from a database recordset. We pass it several parameters:

    -Torderby $orderby

    If the user has selected a column for sorting, this passes that information to the function.

    notes_query

    This is the name of the SQL query that we'll put in the xql file.

    { *SQL* }

    This is a dummy placeholder. It's possible to put sql directly in the tcl file, but this is deprecated because it's harder to make portable.

    $table_def

    Here we pass in the variable we just constructed; it contains a list of column names and display titles.

Put the database query into a separate file. If the - database query is exactly the same for Oracle and PostgreSQL, it - can go into a file with the same name as the tcl file but an xql - extension, e.g., index.xql. If - it is database-specific, it goes in - index-oracle.xql or - index-postgresql.xql. The - format is the same in each case, an XML structure that contains - the SQL query. Create the file now.

[service0@yourserver www]$ emacs index.xql

Note that the - name parameter of the - fullquery tag exactly matches - the SQL query name specified in the - ad_table call. Also, the SQL query ends with a tcl function call that generates a SQL ORDER BY clause using several TCL variables.

<?xml version="1.0"?>
-<queryset>
-  <fullquery name="notes_query">
-    <querytext>
-    select note_id,
-           title,
-           body
-      from samplenote
-    [ad_order_by_from_sort_spec $orderby $table_def]
-    </querytext>
-  </fullquery>
-</queryset>

Create the user-visible page.

[service0@yourserver www]$ emacs index.adp

The first line indicates that this page should be rendered within the the master template, which defaults to /web/service0/www/default-master. The second line passes a title variable to the master template. The third line inserts the contents of the variable table_html. The last line is a link to a page we haven't created yet.

<master>
-<property name="title">Sample Notes</property>
-@table_html@
-<p><a href="note-edit">Add a note</a></p>

Making the APM load your files

Before we can test these files, we have to notify the - package manager that they exist. (More precisely, the tcl and - adp will work fine as-is, but the xql file will not be - recognized until we tell the APM about it.).

  • Go to http://yourserver.test:8000/acs-admin/apm

  • Click on the samplenote link

  • Click Manage file information

  • - Check the values in the file type column for your files. A - question mark means the APM doesn't recognize the file - type and probably means you have mistyped a filename. -

  • - At the bottom of the file list page - click on the - watch all files link. - Unlike adp and tcl pages, xql pages get cached. (And new - xql files don't get loaded when they're watched or the - server is restarted.) Watching an xql file causes the APM - to load the contents of the XQL into memory so that it can - be used, and to reload it whenever the file is changed. - The watch will last until the server is restarted. -

Now that the APM is aware of your files, check to make sure that the self-documenting code is working.

  • Browse to http://yourserver.test:8000/api-doc/

  • Click Notes 0.1d

  • Click Content Pages

  • Click index.tcl and examine the results.

Test the index page

Go to http://yourserver.test:8000/note/. You should see this:

-Sample Notes
-Your Workspace : Main Site : Sample Note 
+set page_title [ad_conn instance_name]
+set context [list]
 
-No data found.
+template::list::create \
+    -name notes \
+    -multirow notes \
+    -actions { "Add a Note" note-edit} \
+    -elements {
+	edit {
+	    link_url_col edit_url
+	    display_template {
+		<img src="/resources/acs-subsite/Edit16.gif" width="16" height="16" border="0">
+	    }
+	    sub_class narrow
+	}
+	title {
+	    label "Title"
+	}
+	delete {
+	    link_url_col delete_url 
+	    display_template {
+		<img src="/resources/acs-subsite/Delete16.gif" width="16" height="16" border="0">
+	    }
+	    sub_class narrow
+	}
+    }
 
-Add a note.
+db_multirow \
+    -extend {
+	edit_url
+	delete_url
+    } notes notes_select {
+	select ci.item_id,
+	       n.title
+        from   cr_items ci,
+               mfp_notesx n
+        where  n.revision_id = ci.live_revision
+    } {
+	set edit_url [export_vars -base "note-edit" {item_id}]
+	set delete_url [export_vars -base "note-delete" {item_id}]
+    }
+

Now index.adp:

+<master>
+  <property name="title">@page_title;noquote@</property>
+  <property name="context">@context;noquote@</property>
+<listtemplate name="notes"></listtemplate>
+    

Now we create the add/edit page. If note_id is passed in, + it edits that note. Otherwise, it presents a form for adding + notes. Edit + note-edit.tcl:

ad_page_contract {
+    This is the main page for the package.  It displays all of the Notes and provides links to edit them and to create new Notes.
 
-foo@yourserver.test
-

Since our table is empty, it's a pretty boring page. So next we'll make it possible to add records.

If you get any other output, such as an error message, skip to Section�, “Debugging and Automated Testing”.

Add the add/edit page

We'll create a single page to handle both adding and - editing records. In this recursive approach, the same tcl - function can present a blank HTML form, present the same form - pre-loaded with an existing record, and handle the resulting - submission of either updated or new records. This recursive - approach reduces the total amount of code and files. First, - create the tcl:

[service0@yourserver www]$ emacs note-edit.tcl

Paste and save and edit:

ad_page_contract {
-        Simple add/edit form for samplenote.
+    @author Your Name (you@yourserver.test)
+    @cvs-id $Id$
+ 
+    @param item_id If present, assume we are editing that note.  Otherwise, we are creating a new note.
 } {
-    note_id:integer,optional
+    item_id:integer,optional
 }
-set user_id [ad_maybe_redirect_for_registration]
-set title "Add a note"
 
-if {[exists_and_not_null note_id]} {
-    set title "Edit a note"
-}
-
 ad_form -name note -form {
-    note_id:key
-    {title:text
-        {label "Title"}
-    }
-    {body:text(textarea)
-        {label "Body"}
-    }
-} -select_query_name note_query -new_data {
-db_1row do_insert { *SQL* }
+    {item_id:key}
+    {title:text {label Title}}
+} -new_request {
+    permission::require_permission -object_id [ad_conn package_id] -privilege create
+    set page_title "Add a Note"
+    set context [list $page_title]
+} -edit_request {
+    permission::require_write_permission -object_id $item_id
+    mfp::note::get \
+	-item_id $item_id \
+	-array note_array 
+
+    set title $note_array(title)
+
+    set page_title "Edit a Note"
+    set context [list $page_title]
+} -new_data {
+    mfp::note::add \
+	-title $title
 } -edit_data {
-db_dml do_update { *SQL* }
+    mfp::note::edit \
+	-item_id $item_id \
+	-title $title
 } -after_submit {
-ad_returnredirect "index"
-}

We use ad_form - to automate most of the work here. Ad_form is a wrapper for the - template - functions for creating HTML forms. These functions should - always be used for HTML forms; this promotes consistency and, - since all template functions use the same stylesheet system, makes it easy to change - the appearance of forms.

The page takes a single, optional input parameter, - note_id. If it's present, ad_form will assume that we're editing - an existing record, look up that record, and pre-populate the - form. We'll also check and change the page title if necessary. We check user_id with ad_maybe_redirect_for_registration, - which will redirect to the login page (with an automatic return - path to bring them back after login or registration) if the - visitor isn't logged in. Then we call ad_form, specifying the - primary key of the table, the fields we want to edit, and - functions for insert and update.

Next, we create the database functions.

[service0@yourserver www]$ emacs note-edit.xql
<?xml version="1.0"?>
-<queryset>
-  <fullquery name="do_insert">
-    <querytext>
-        select samplenote__new(null,:title, :body,null,:user_id,null,null)
-    </querytext>
-  </fullquery>
-  <fullquery name="do_update">
-    <querytext>
-       update samplenote
-          set title = :title,
-              body = :body
-        where note_id = :note_id
-    </querytext>
-  </fullquery>
-  <fullquery name="note_query">
-    <querytext>
-      select title,
-             body
-        from samplenote
-       where note_id = :note_id
-    </querytext>
-  </fullquery>
-</queryset>

Create the user-visible page:

[service0@yourserver www]$ emacs note-edit.adp
<master>
-<property name="title">@title@</property>
-<property name="context">{@title@}</property>
-<formtemplate id="note"></formtemplate>
-

The property tags are passed to the master template, which - uses their values to set the page title and context bar - (breadcrumb trail). We use the same variable, - title, for both variables but - wrap it in curly brackets for context so that the spaces aren't - interpreted separators. The formtemplate tag outputs the form - html with the matching name.

Go to the APM as before and reload. Then test all this by going to the package home page and adding and editing a few records.

Adding files to cvs

Put your new work into source control.

[service0@yourserver www]$ cvs add *.adp *.tcl *.xql
-cvs add: cannot add special file `CVS'; skipping
-cvs add: doc/CVS already exists
-cvs add: scheduling file `index.adp' for addition
-cvs add: scheduling file `index.tcl' for addition
-cvs add: scheduling file `index.xql' for addition
-cvs add: scheduling file `note-edit.adp' for addition
-cvs add: scheduling file `note-edit.tcl' for addition
-cvs add: scheduling file `note-edit.xql' for addition
-cvs add: use 'cvs commit' to add these files permanently
-[service0@yourserver www]$  cvs commit -m "new work"
-/cvsroot/service0/packages/samplenote/www/note-edit.xql~,v  <--  note-edit.xql
-(many lines omitted)
-initial revision: 1.1
-done
-[service0@yourserver www]$
View comments on this page at openacs.org
+ ad_returnredirect "." + ad_script_abort +}

And note-edit.adp:

<master>
+  <property name="title">@page_title;noquote@</property>
+  <property name="context">@context;noquote@</property>
+  <property name="focus">note.title</property>
+  
+<formtemplate id="note"></formtemplate>

And the delete page. Since it has no UI, there is only a + tcl page, and no adp page. Edit +note-delete.tcl:

ad_page_contract {
+    This deletes a note
+
+    @author Your Name (you@yourserver.test)
+    @cvs-id $Id$
+ 
+    @param item_id The item_id of the note to delete
+} {
+    item_id:integer
+}
+
+permission::require_write_permission -object_id $item_id
+
+mfp::note::delete -item_id $item_id
+
+ad_returnredirect "."
View comments on this page at openacs.org