Index: openacs-4/packages/acs-core-docs/www/subsites.adp =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-core-docs/www/subsites.adp,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-core-docs/www/subsites.adp 7 Aug 2017 23:47:52 -0000 1.2 @@ -0,0 +1,254 @@ + +{/doc/acs-core-docs {ACS Core Documentation}} {Writing OpenACS Application Pages} +Writing OpenACS Application Pages + + +
+

+Writing OpenACS Application Pages

+

By Rafael H. Schloming and Pete Su

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

+Overview

In this document, we'll examine the user interface pages of +the Notes application in more detail, covering two separate aspects +of page development in OpenACS. First, we'll talk about the +code needed to make your pages aware of which application instance +they are running in. Second, we'll talk about using the form +builder to develop form-based user interfaces in OpenACS. While +these seem like unrelated topics, they both come up in the example +page that we are going to look at, so it makes sense to address +them at the same time.

+
+

+Application Instances and Subsites

As you will recall from the packages tutorial, the Request +Processor (RP) and Package Manager (APM) allow site administrators +to define an arbitrary mapping from URLs in the site to objects +representing content. These objects may represent single files, or +entire applications. The APM uses the site map to map application +instances to particular URLs within a site. We call creating such a +mapping mounting the +application instance at a particular URL. The tutorial also showed +how a given URL is translated into a physical file to serve using +the site map. We'll repeat this description here, assuming that +you have mounted an instance of Notes at the URL /notes as we did in the packages-example:

    +
  • AOLserver receives your request for the URL /notes/somepage.

  • This URL is passed to the request processor.

  • The RP looks up the URL in the site map, and sees that the +object mounted at that location is an instance of the notes application.

  • The RP asks the package manager where in the file system the +Notes package lives. In the standard case, this would be +ROOT/packages/notes.

  • The RP translates the URL to serve a page relative to the page +root of the application, which is ROOT/packages/notes/www/. Therefore, the +page that is finally served is ROOT/packages/notes/www/hello.html, which +is what we wanted.

  • +

What is missing from this description is a critical fact for +application developers: In addition to working out what file to +serve, the RP also stores information about which package instance +the file belongs to into the AOLserver connection environment. The +following ad_conn interfaces +can be used to extract this information:

+
[ad_conn +package_url]

If the URL refers to a package instance, this is the URL to the +root of the tree where the package is mounted.

[ad_conn +package_id]

If the URL refers to a package instance, this is the ID of that +package instance.

[ad_conn +package_key]

If the URL refers to a package instance, this is the unique key +name of the package.

[ad_conn +extra_url]

If we found the URL in the site map, this is the tail of the URL +following the part that matched a site map entry.

+

In the Notes example, we are particularly interested in the +package_id field. If you study +the data model and code, you'll see why. As we said before in +the data modeling +tutorial, the Notes application points the context_id of each Note object that it +creates to the package instance that created it. That is, the +context_id corresponds exactly +to the package_id that comes in +from the RP. This is convenient because it allows the administrator +and the owner of the package to easily define access control +policies for all the notes in a particular instance just my setting +permissions on the package instance itself.

The code for adding and editing notes, in notes/www/add-edit.tcl, shows how this +works. At the top of the page, we extract the package_id and use it to do permission +checks:

+
+set package_id [ad_conn package_id]
+
+if {[info exists note_id]} {
+      permission::require_permission -object_id $note_id -privilege write
+
+      set context_bar [ad_context_bar "Edit Note"]
+} else {
+      permission::require_permission -object_id $note_id -privilege create
+
+      set context_bar [ad_context_bar "New Note"]
+}
+
+

This code figures out whether we are editing an existing note or +creating a new one. It then ensures that we have the right +privileges for each action.

Later, when we actually create a note, the SQL that we run +ensures that the context_id is +set the right way:

+
+db_dml new_note {
+  declare
+    id integer;
+  begin
+    id := note.new(
+      owner_id => :user_id,
+      title => :title,
+      body => :body,
+      creation_user => :user_id,
+      creation_ip => :peeraddr,
+      context_id => :package_id
+    );
+  end;
+}
+
+

The rest of this page makes calls to the form builder part of +the template system. This API allows you to write forms-based pages +without generating a lot of duplicated HTML in your pages. It also +encapsulates most of the common logic that we use in dealing with +forms, which we'll discuss next.

+
+

+Using Forms

The forms API is pretty simple: You use calls in the +template::form namespace in +your Tcl script to create form elements. The final template page +then picks this stuff up and lays the form out for the user. The +form is set up to route submit buttons and whatnot back to the same +Tcl script that set up the form, so your Tcl script will also +contain the logic needed to process these requests.

So, given this outline, here is a breakdown of how the forms +code works in the add-edit.tcl +page. First, we create a form object called new_note:

+
+template::form create new_note
+
+

All the forms related code in this page will refer back to this +object. In addition, the adp +part of this page does nothing but display the form object:

+
+<master>
+
+\@context_bar\@
+
+<hr>
+
+<center>
+<formtemplate id="new_note"></formtemplate>
+</center>
+
+

The next thing that the Tcl page does is populate the form with +form elements. This code comes first:

+
+if {[template::form is_request new_note] && [info exists note_id]} {
+
+  template::element create new_note note_id \
+      -widget hidden \
+      -datatype number \
+      -value $note_id
+
+  db_1row note_select {
+    select title, body
+    from notes
+    where note_id = :note_id
+  }
+}
+
+

The if_request call returns +true if we are asking the page to render the form for the first +time. That is, we are rendering the form to ask the user for input. +The tcl part of a form page can +be called in 3 different states: the initial request, the initial +submission, and the validated submission. These states reflect the +typical logic of a forms based page in OpenACS:

    +
  • First render the input form.

  • Next, control passes to a validation page that checks and +confirms the inputs.

  • Finally, control passes to the page that performs the update in +the database.

  • +

The rest of the if condition +figures out if we are creating a new note or editing an existing +note. If note_id is passed to +us from the calling page, we assume that we are editing an existing +note. In this case, we do a database query to grab the data for the +note so we can populate the form with it.

The next two calls create form elements where the user can +insert or edit the title and body of the Note. The interface to +template::element is pretty +straightforward.

Finally, the code at the bottom of the page performs the actual +database updates when the form is submitted and validated:

+
+if {[template::form is_valid new_note]} {
+  set user_id [ad_conn user_id]
+  set peeraddr [ad_conn peeraddr]
+
+  if {[info exists note_id]} {
+    db_dml note_update {
+      update notes
+      set title = :title,
+          body = :body
+      where note_id = :note_id
+    }
+  } else {
+    db_dml new_note {
+      declare
+        id integer;
+      begin
+        id := note.new(
+          owner_id => :user_id,
+          title => :title,
+          body => :body,
+          creation_user => :user_id,
+          creation_ip => :peeraddr,
+          context_id => :package_id
+        );
+      end;
+    }
+  }
+
+  ad_returnredirect "."
+}
+
+

In this simple example, we don't do any custom validation. +The nice thing about using this API is that the forms library +handles all of the HTML rendering, input validation and database +transaction logic on your behalf. This means that you can write +pages without duplicating all of that code in every set of pages +that uses forms.

+
+

+How it All Fits

To watch all of this work, use the installer to update the Notes +package with the new code that you grabbed out of CVS or the +package repository, mount an instance of Notes somewhere in your +server and then try out the user interface pages. It should become +clear that in a real site, you would be able to, say, create a +custom instance of Notes for every registered user, mount that +instance at the user's home page, and set up the permissions so +that the instance is only visible to that user. The end result is a +site where users can come and write notes to themselves.

This is a good example of the leverage available in the OpenACS +5.9.0 system. The code that we have written for Notes is not at all +more complex than a similar application without access control or +site map awareness. By adding a small amount of code, we have taken +a small, simple, and special purpose application to something that +has the potential to be a very useful, general-purpose tool, +complete with multi-user features, access control, and centralized +administration.

+
+

+Summary

In OpenACS 5.9.0, application pages and scripts can be aware of +the package instance, or subsite in which they are executing. This +is a powerful general purpose mechanism that can be used to +structure web services in very flexible ways.

We saw how to use this mechanism in the Notes application and +how it makes it possible to easily turn Notes into an application +that appears to provide each user in a system with their own +private notes database.

We also saw how to use the templating system's forms API in +a simple way, to create forms based pages with minimal duplication +of code.

($‌Id: subsites.xml,v 1.9.2.1 2016/06/23 +08:32:46 gustafn Exp $)
+
+
+ + \ No newline at end of file