[ZDP] ZBook: changing contexts in Zope - part 3 - Draft 1

Rik Hoekstra hoekstra@fsw.LeidenUniv.nl
Wed, 01 Dec 1999 17:49:13 +0100


Dear all,

This is the third part of my changing contexts chapter for ZBook. As
promised, it is more complex than the previous parts - that is, a _lot_ more
complex. Once again I stole most of it from the mail lists.

I will be very glad with _any_ feedback. Please at least mail me whether
this is understandable _at_ _all_

Rik

==============
part 3 When acquisition gets complicated
===============

3.4 Complications in acquisition

It should be clear that acquisition adds some incredible flexibility to
your site. There are already many excellent examples of it in the Zope
community. Many of them are too complicated to explain here (if only if
I do not completely understand them myself), but they are worthwile to
study. Remember that Zope is Open Source and most of the Zope Products
are also, and reading the sources is often a great way of learning more -
it does require knowledge of the Python language [links to Chameleon
Folder and SiteAccess and who knows what]

Example: multilingual site

There has been expressed interest in internationalizing Zope. Now this
is an issue with many sides, and also concerned with Zope internals and
browser issues, but part of it can be handled by site design. Only the
Zope site design part will concern us here, as it is an example per
excellence of a changing context. In many non-US sites it is very common
to have a multilingal site, and that is what the example is about.

Multilingual sites

The problem:

The goal is: the same DTML document should be able to create pages in
multiple languages and with the same application logic for each
language. That is, without copying. This means we aim at a site with a
single tree, not two trees for two languages.
Some things to take into account: a site may have content in several
languages, but not all pages may be or should be available in all
languages. Therefore, it should also have content in a default language.

This seems like an ideal problem for Zope's acquisition. The different
solutions were (once again) taken from an actual discussion on one of
the Zope related lists. There were several solutions proposed to the
problem. To be sure, none of them was completely 'right' in the sense of
solving the whole problem. But one approach was definitely better than
the other (sadly the solution that did not work was mine). The solutions
proposed show different approaches, both of them trying to use
acquisition as an important tool. The basic analysis both solutions
agreed upon was that The minimum difference between a site in Dutch and
the same site in English is the textual contents. I'm hoping layout
issues will at least with European languages be relatively minor, so the
layout can stay the same.

First I present the solutions, then we go on to an analysis.

Solution 1

ROOT
    contents_en
    contents_nl
    index_html
  English
  Dutch
  Foo
  Bar
  ....


English and Dutch are folders and each contains a property called
'language', set to "English" and "Dutch" respectively. They are empty
folders.

Foo is a folder with an index_html containing layout info, and whenever
it tries to get some textual contents, it'll dtml-var it in from the folder
dependent on language.

<dtml-if "language=='Dutch'">
  <dtml-var contents_nl>
<dtml-else>
  <dtml-var contents_en>
</dtml-if>

You call the Dutch page with:

Dutch/foo

You call the English page with:

English/foo

You'd call the 'default' language with:

foo (precise implementation for default is left as an exercise for the
reader)

The same for bar and any other subfolders.


Solution 2

ROOT
  en
   ---
   ---
   ---
  de
   ---
   ---
   ---
  sitehome
   ---
   ---
   ---
  ch
   ---
   ---
   ---
  nl
   ---
   ---
   ---

If your en, de, ch, nl all contain a standard_html_header method and other
usable methods. And if you make one language the main language of the site
and place this in the folder I called 'ROOT'. Acquisition makes it possible
to
call urls like http://root/en/sitehome/page.html or
http://root/nl/sitehome/page.html
or http://whatever/dutch|english|antarctic/site/content
The DTML-source of the page.html looks like this:

<dtml-var standard_html_header>
all sorts of fancy html layout
<dtml-var tekst1>
more layout
<dtml-var tekst2>
even more layout
<dtml-var standard_html_footer>

It will give you your language specific pages, unless there aren't any, in
which case you get the text in the standard language.

Exercise: does this solution work? Why not?


Analysis and some remarks:

(see the tree above)
Calling the page in sibling folders works as proposed in the solution:
It _is_ possible to place a contents in the directories at the same level as
foo and then call them as
http://whatever/dutch|english|antarctic/site/content

[also refer to http://www.zope.org/pipermail/zope/1999-June/005141.html]

The default part does _not_ work as described.

If sitehome is on the same level as the language specific folders, in
a URL like http://ROOT/en/sitehome/page (in which page refers to text
methods in en) acquisition only returns the page with the method from en, if
there is no method with the same name anywhere else in the hierarchy: not
above and not below. If there is, _that_ method will be returned.
Anyway, if you want to return language specific content, unless there is
none available, in which case you would return default content, you
should construct something like:

<dtml-var standard_html_header>
<all sorts of fancy html layout>
<dtml-if tekst1>
  <dtml-var tekst1>
<dtml-else>
  <dtml-var default-text1>
</dtml-if>

<more layout>

<dtml-if tekst2>
  <dtml-var tekst2>
<dtml-else>
  <dtml-var default-text2>
</dtml-if>
bla bla
<dtml-var standard_html_footer>

Note 1: This means that the sitehome folder would have to have a different
naming convention than the other directories. This is due to a feature
in acquisition.

Note 2:
Now in either solution you would have to be careful with hyperlinks. They
should not be absolute because in that case you run the risk of 'jumping
out of the environment'. [This last point could of course be solved by
taking the variable from cookies or even http header variables, but
these are much more inflexible in changing context.]

[As a solution I propose to either use relative url's, the Zope BASE/URL
tag in some way if you have to refer to other parts. I do not have time
to sort that out right now]

This is complicated matter, so we'll present another example, once again
taken from 'real life', that is, the Zope lists. This time it will
provide a quite detailed explanation of how it works, so it inevitable
involves some Python, as that's what Zope is (mostly) written in.



Example: A flexible homepage

The problem:
In many cases it is desirable to make a flexible homepage, with a
frames/no-frames/flash version etcetera.

Once again Zope's flexibility will make this much easier than with a
filesytem-based site. Once again, a flexible homepage will mean that you
have content stored once, and the environment in sibling folders.

Take the following proposal [/ means ROOT]:

/
   index_html    (checks a cookie, gives
                  a frameset or a redirect
                  to /noframes/home (frameset
                  loads /home))
   home          (content)
   standard_html_header (together define my page
   standard_html_footer  layout)

   frames/     <- Framesets
     standard_html_header (define a frameset that
     standard_html_footer  loads the same URL minus
     index_html            the /frames prefix)
     nav                   (navigational frame)

   noframes/   <- No framesets
     standard_html_header (define a layout with
     standard_html_footer  navigation in a table)
     index_html


All objects are either Folders or DTML Methods. So far so good, this works
like charm. With acquisition, the home document can be viewed with and
without a frameset.

But then I make another Folder, lets say Personal, and define a index_html
in that Folder:

   Personal/
     index_html

Calling http://examplesite.com/Personal/ works fine, but when I call
http://examplesite/frames/Personal/ the same layout appears as the
previous URL. De standard_html_header and -_footer from the root are being
used, not the ones in the frames/ Folder! The same goes for noframes/.

[For draft readers: I do not have time for it now, but all this is going
to be in an example Zope site, to be provided with this chapter]


Analysis:

Jim Fulton, creator of much of Zope' core, and certainly of acquisition
explained at the mail list why it works as it works (this involves some
python):

When you acquire an object, the acquired object gets the context of
the aquirer *and* the context it was acquired from, with the source
context taking precedence over the destination context.  In the
example above, 'Personal' acquires from the top-level folder first,
and then from the 'frames' folder.

Lets walk through why this is so.  Suppose in Python, we have
a variable 'app' that is the top-level folder.

The expression:

  app.Personal

Gets 'Personal' in the context of 'app'.

  app.frames

Gets 'frames' in the context if 'app'.  So far so good.

Now consider:

  app.frames.Personal

'Personal' is acquired from 'app'.  We have to get 'app.Personal',
and then use it in the context of 'app.frames'.  This results in:

  (Personal of app) of (frames of app)

When searching for an attribute, we always search the
innermost contexts first.  In this example, we search
'Personal of app' before we search 'frames of app'.


The solution:

There may be several workarounds and solutions to the problem. Let's
look at one

[Note: Often a solution to some of these problems may be is simply
changing the order of objects also changes the order of acquisition.
In this particular case that did not work, as there was an index_html
method in /frames. And the right index_html had to be acquired.]


/frames
/framedcontent
/noframes

Were the three URL modifiers to be implemented.

A. Framed

/frames in front of the URL results in a frameset with navigation in one
frame and the same URL in the other, but with /framedcontent in front of
it instead of /frames. In framedcontent we present the content without
further navigating. With javascript we check whether we are inside a frame.

For example:

http://examplesite.com/framed/content

results in:

<frameset cols="150, 1*" border=0 frameborder=0 framespacing=0>
  <frame name=nav src="/frames/nav" scrolling=no noresize framesborder=0>
  <frame name=con src="/framedcontent/content" frameborder=0>

etcetera

and the javascript in /framedcontent/content looks like:

<script language=javascript><!--
  if (window.name != 'con')


    top.document.location = 'http://mj.antraciet.nl/frames/home';

//--></script>

B.Not framed

/noframes presents the same as /framedcontent, but now we put navigation
within the same document (using a table)

For example: http://examplesite.com/noframes/content

results in:

<table width=100% border=0>
<tr valign=top>
  <td width=150>
    navigation
    <a href="/home">Home</a><BR>
    <a href="/destination1/">destination1</a><BR>
    <a href="/destination2/">destination2</a><BR>
  </td>
  <td>Al sorts of interesting content
  </td>
</tr>
</table>


The tricks for this are in standard_html_header and standard_html_footer:

s_h_header:
<dtml-if st_html_header>
   <dtml-var st_html_header>
<dtml-else>
 <dtml-if "REQUEST.cookies.has_key('st_frames') and st_frames == 'no'">
   <dtml-call "RESPONSE.redirect(_.string.replace(URL, BASE0,
               BASE0 + '/noframes'))">
 <dtml-else>
   <dtml-call "RESPONSE.redirect(_.string.replace(URL, BASE0, BASE0 +
               '/framedcontent'))">
 </dtml-if>
</dtml-if>

In (other) words:
If we find st_html_header in the acquisition path, use it. Otherwise
redirect to the frames or noframes version, depending on a cookie.

s_h_footer:
<dtml-if st_html_footer><dtml-var st_html_footer></dtml-if>

Only show a st_html_footer if it is in the acquisition path.


st_html_header en st_footer are only defined in /frames, /framedcontent
and in /noframes. So someone who uses the url
http://examplesite.com/content
will be redirected to the frames or noframes version, in this case
frames/content or noframes/comtent. And frames/content is a frameset of
which one frame shows the URL /framedcontent/home

Resuming:
Trying to override standard_html_header and standard_html_footer is not a
good idea.
If you use acquisition on sibling folders as described in the examples
above, you will
have to define you own conditional options. In the case of the last example
standard_html_header and standard_html_footer made use of these optional
replacements.