Skip to content

Commit

Permalink
Item9434: correct/complete zone documentation. No (intentional) funct…
Browse files Browse the repository at this point in the history
…ionality changes, except for new unit tests

git-svn-id: http://svn.foswiki.org/trunk@8443 0b4bb1d4-4e5a-0410-9cc4-b2b747904278
  • Loading branch information
CrawfordCurrie authored and CrawfordCurrie committed Aug 9, 2010
1 parent bc47375 commit 3fa4563
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 73 deletions.
98 changes: 90 additions & 8 deletions UnitTestContrib/test/unit/ZoneTests.pm
Expand Up @@ -3,25 +3,36 @@ package ZoneTests;
use strict;
use warnings;

use FoswikiFnTestCase;
our @ISA = qw( FoswikiFnTestCase );
use FoswikiTestCase;
our @ISA = qw( FoswikiTestCase );

sub new {
my $self = shift()->SUPER::new(@_);
return $self;
my $class = shift;
my $this = $class->SUPER::new(@_);
$this->{test_web} = 'Temporary' . $class . 'TestWeb';
$this->{test_topic} = 'TestTopic' . $class;
return $this;
}

sub set_up {
my ($this) = @_;

$this->SUPER::set_up();

# Disable JQueryPlugin, which adds noise to body zone
# Disable plugins whiich add noise
$Foswiki::cfg{Plugins}{JQueryPlugin}{Enabled} = 0;
$this->{session}->finish();
$this->{session} = Foswiki::new('Foswiki');
$Foswiki::cfg{Plugins}{TablePlugin}{Enabled} = 0;

return;
my $query = new Unit::Request("");
$query->path_info("/$this->{test_web}/$this->{test_topic}");

$this->{session} = new Foswiki( undef, $query );
$this->{request} = $query;
$this->{response} = new Unit::Response();

$this->{test_topicObject} = Foswiki::Meta->new(
$this->{session}, $this->{test_web},
$this->{test_topic}, "BLEEGLE\n");
}

sub test_1 {
Expand Down Expand Up @@ -407,6 +418,77 @@ HERE
return;
}

sub test_explicit_RENDERZONE_no_optimization {
my $this = shift;
$this->_setOptimizePageLayout(0);

my $tml = <<'HERE';
<head>
%RENDERZONE{"head"}%
<!--end of rendered head-->
%ADDTOZONE{"head" id="head1" text="head_1"}%
%ADDTOZONE{"body" id="body1" text="body_1" requires="head1"}%
</head>
<body>
%RENDERZONE{"body"}%
<!--body-->
</body>
HERE

my $expect = <<'HERE';
<head>
head_1 <!-- head1 -->
body_1 <!-- body1 -->
<!--end of rendered head-->
<!--A2Z:head1-->
<!--A2Z:body1-->
</head>
<body>
<!--body-->
</body>
HERE
chomp($expect);
$tml = $this->{test_topicObject}->expandMacros( $tml );
my $result = $this->{session}->_renderZones( $tml );
$this->assert_str_equals($expect, $result);
}

sub test_explicit_RENDERZONE_with_optimization {
my $this = shift;
$this->_setOptimizePageLayout(1);

my $tml = <<'HERE';
<head>
%RENDERZONE{"head"}%
<!--end of rendered head-->
%ADDTOZONE{"head" id="head1" text="head_1"}%
%ADDTOZONE{"body" id="body1" text="body_1" requires="head1"}%
</head>
<body>
%RENDERZONE{"body"}%
<!--body-->
</body>
HERE

my $expect = <<'HERE';
<head>
head_1 <!-- head1 -->
<!--end of rendered head-->
<!--A2Z:head1-->
<!--A2Z:body1-->
</head>
<body>
body_1 <!-- body1 required id(s) that were missing from body zone: head1 -->
<!--body-->
</body>
HERE
chomp($expect);
$tml = $this->{test_topicObject}->expandMacros( $tml );
my $result = $this->{session}->_renderZones( $tml );
$this->assert_str_equals($expect, $result);
}

sub test_legacy_tag_param_compatibility {
my $this = shift;
$this->_setOptimizePageLayout(1);
Expand Down
52 changes: 38 additions & 14 deletions core/data/System/VarADDTOZONE.txt
Expand Up @@ -10,7 +10,7 @@
}%
</verbatim>

_Zones_ are specific places in the output HTML that are marked with occurances of the [[VarRENDERZONE][RENDERZONE]] macro. Zones are used to collect various content together, such as Javascript and CSS markup that must be included in the output HTML in a specific order, and in a specific place.
_Zones_ are specific places in the output HTML that are marked by calls to the [[VarRENDERZONE][RENDERZONE]] macro. Zones are used to collect various content together, such as Javascript and CSS, that must be included in the output HTML in a specific order, and in a specific place.

You may create as many zones in addition to the standard [[#HeadAndBody][ =head= and =body= ]] zones as you like. Interesting use cases in wiki applications:
* Create a =sidebar= zone to add widgets,
Expand Down Expand Up @@ -38,25 +38,45 @@ You may create as many zones in addition to the standard [[#HeadAndBody][ =head=
#HeadAndBody
---+++ How to use the =head= and =body= zones

Notionally the =head= and =body= zones correspond to the end of the HTML =&lt;HEAD&gt;= tag and the end of the =&lt;BODY&gt;= tag respectively. Normally you should add CSS (and other HTML =&lt;HEAD&gt;= content, such as =&lt;META&gt;=) to the =head= zone, and Javascript to the =body= zone. There is a setting in =configure=, ={OptimizePageLayout}=, that controls what happens next.
When an HTML page is loaded by a browser, most browsers automatically request
Javascript from a URL as soon as it is encountered in the page, and then wait
for a response from the server before continuing. When a page loads a lot of
Javascript (as is often the case when using UI libraries such
as jQuery) you can get a "blank screen" effect in the browser while this
URL is downloaded. To avoid this effect, you can usually move Javascript
loading to the end of the HTML page, so that the "visuals" of the page are
loaded first, giving the user something to admire while the Javascript loads.

Foswiki makes this move easy to manage by providing the =head= and =body=
zones. These are _automatic_ zones - they do not require a
corresponding =RENDERZONE=.

Notionally the =head= and =body= zones correspond to a point just before the HTML =&lt;/HEAD&gt;= tag and a point just before the =&lt;/BODY&gt;= tag respectively. Normally you should add CSS (and other HTML =&lt;HEAD&gt;= content, such as =&lt;META&gt;=) to the =head= zone, and Javascript to the =body= zone. There is a setting in =configure=, ={OptimizePageLayout}=, that controls what happens next.

First, dependencies between the individual =ADDTOZONE= statements are resolved *within each zone*. Then, if ={OptimizePageLayout}= is enabled, the =head= content is added to the =&lt;HEAD&gt;,= and the =body= content is added to the =&lt;BODY&gt;=. However, if ={OptimizePageLayout}= is disabled (the default), both the =head= and =body= zones will be added to the HTML =&lt;HEAD&gt;=.

For this reason, consideration needs to be given to the way =head= and =body= zones are behaving with your Foswiki configuration, dictated by the ={OptimizePageLayout}= setting
For this reason, consideration needs to be given to the way =head= and =body= zones behave with your Foswiki configuration, as dictated by the ={OptimizePageLayout}= setting.

---++++ Working with ={OptimizePageLayout}= disabled (default)
* Be aware that in this mode, =body= and =head= are separate zones
when adding to them, but merged as one when you call
[[VarRENDERZONE][RENDERZONE]]. This allows an ADDTOZONE to the
=head= to successfully require an =id= that has been added to the
=body=.
* %H% =%<nop>RENDERZONE{"head"}%= will expand *both*
=head= and =body= zone content. =%<nop>RENDERZONE{"body"}%= will
be empty.
* Only add HTML to the body zone _that is also legal in the =&lt;HEAD&gt;=._
If your pages rely on Javascript running to "finish" the layout of the page,
then you may want to disable ={OptimizePageLayout}= to prevent flickering
during loading.

In this mode, both =head= and =body= zone content will be expanded in the
=&lt;HEAD&gt;=. Nothing will be inserted in the =&lt;BODY&gt;=.

In this mode, the =body= and =head= are separate zones
when adding to them, but merged as one when you call
[[VarRENDERZONE][RENDERZONE]]. This allows an ADDTOZONE to the
=head= to successfully require an =id= that has been added to the =body=.

Only add content to the =body= zone _that is also legal in the =&lt;HEAD&gt;=.

---++++ Working with ={OptimizePageLayout}= enabled
* Ensure that no =head= content (and no inline Javascript) depends on =body= content, or vice-versa. Any such dependency will be _ignored_, however, the HTML comment decoration which normally appears after each id's text in the rendered HTML will contain a small informative text to aid debugging<br/> *Example* <br/>
If your pages exhibit the "blank screen" effect during load due to requests
for large Javascript files, you can enable ={OptimizePageLayout}=.

You must ensure that no =head= content (and no inline Javascript) depends on =body= content, or vice-versa. Any such dependency will be _ignored_. However, the HTML comment decoration which normally appears after each id's text in the rendered HTML will contain a small informative text to aid debugging<br/> *Example* <br/>
<verbatim class="tml">%ADDTOZONE{
"head"
text="
Expand All @@ -71,7 +91,11 @@ For this reason, consideration needs to be given to the way =head= and =body= zo
alert('test');
</script>
<!-- MY::TEST required id(s) that were missing from head zone: some-id-that-exists-in-body --></verbatim>
By contrast, as explained earlier - when ={OptimizePageLayout}= is disabled (default) - Foswiki is able resolve such dependencies successfully.
By contrast, as explained earlier, when ={OptimizePageLayout}= is disabled (default), Foswiki is able resolve such dependencies successfully.

Note that if you *do* have an explicit call to =%<nop>RENDERZONE{"head"}%= in
your templates then the content expanded at that point will be the same content
as would be inserted before the =&lt;/HEAD&gt;=.

#ExampleAddingJS
---+++ Example: Adding Javascript to a page
Expand Down
29 changes: 15 additions & 14 deletions core/data/System/VarRENDERZONE.txt
Expand Up @@ -10,11 +10,6 @@ See [[VarADDTOZONE][ADDTOZONE]] for an explanation of _zones_.

Parameters:
* ="zone"= required, name of the zone.
* %H% If ={OptimizePageLayout}= is *not* enabled in
[[%SCRIPTURL{"configure"}%/configure][configure]] (the default),
=%<nop>RENDERZONE{"head"}%= will expand both =head= and =body=
zone content. =%<nop>RENDERZONE{"body"}%= will be empty.
* =header="..."= optional, prefix format string
* =format="..."= optional, format string for each item added to the zone, default:
<verbatim class="tml">$item <!--<literal> $id $missing</literal>--></verbatim>Tokens:
* =$id= - =id= of the [[VarADDTOZONE][ADDTOZONE]] call within the =zone= currently being rendered.
Expand All @@ -25,17 +20,23 @@ Parameters:
* =missingtoken="..."= optional, this will be the string assigned to the =$missing= format token for use in the =format= parameter. Default:
<verbatim class="tml">required id(s) that were missing from $zone zone: $missingids</verbatim>
* =chomp="on"= remove leading and trailing whitespace from formatted items, can be useful for pretty-printing and compression.
* =footer="..."= optional, suffix format string
* =header="..."= optional, prepended to the output
* =footer="..."= optional, appended to the output
* =separator="..."= optional, put between each item of a zone

Supports the [[FormatTokens][standard format tokens]] in the =format= and =separator= (but *not* in the =header= or =footer=).

=header= and =footer= are output irrespective of whether there is any content
in the zone or not.

=head= and =body= are _built-in_ zones - there is no corresponding =RENDERZONE=
required in the templates, they will be rendered automatically.

%H% Zones are flushed when rendered; they may only be rendered once
__Notes:__
* =header= and =footer= are output irrespective of whether there is any
content in the zone or not.
* Zones are cleared after being rendered; they are only ever rendered once.
* =head= and =body= are _automatic zones_. They don't require a
corresponding =RENDERZONE= anywhere in the templates - they are
automatically inserted before the =&lt;/head&gt;= and =&lt;/body&gt;=
of the output HTML page.
* If ={OptimizePageLayout}= is *disabled* in
[[%SCRIPTURL{"configure"}%/configure][configure]] (the default),
then the content of both the =head= *and* the =body= zones will
be inserted where the =head= zone is expanded, and nothing will be
inserted where the =&lt;/body&gt;= zone is expanded.

See also [[VarADDTOZONE][ADDTOZONE]] for more information on zones. %JQREQUIRE{"chili"}%
87 changes: 57 additions & 30 deletions core/lib/Foswiki.pm
Expand Up @@ -65,7 +65,14 @@ our $RELEASE;
our $TRUE = 1;
our $FALSE = 0;
our $engine;
our $TranslationToken = "\3";

# Note: the following marker is used in text to mark RENDERZONE
# macros that have been hoisted from the source text of a page. It is
# carefully chosen so that it is (1) not normally present in written
# text (2) does not combine with other characters to form valid
# wide-byte characters and (3) does not conflict with other markers used
# by Foswiki/Render.pm
our $RENDERZONE_MARKER = "\3";

# Used by takeOut/putBack blocks
our $BLOCKID = 0;
Expand Down Expand Up @@ -741,28 +748,7 @@ JS

if ( $contentType ne 'text/plain' ) {

# render zones
$text =~
s/${TranslationToken}RENDERZONE{(.*?)}${TranslationToken}/_renderZoneById($this, $1)/ge;

# get the head zone ones again and insert it at </head>
my $headZone = _renderZone( $this, 'head', { chomp => "on" } ) || '';
$text =~ s!(</head>)!$headZone\n$1!i if $headZone;

# get the body zone ones again and insert it at </body>
my $bodyZone = _renderZone( $this, 'body', { chomp => "on" } ) || '';

# in compatibility mode all body material is still appended to the head
if ($bodyZone) {
unless ( $Foswiki::cfg{OptimizePageLayout} ) {
$text =~ s!(</head>)!$bodyZone\n$1!i;
}
else {
$text =~ s!(</body>)!$bodyZone\n$1!i;
}
}

chomp($text);
$text = $this->_renderZones( $text );
}

# SMELL: can't compute; faking content-type for backwards compatibility;
Expand Down Expand Up @@ -3239,17 +3225,17 @@ sub expandMacros {
---++ ObjectMethod addToZone($zone, $id, $data, $requires)
Add =$data= identified as =$id= to =$zone=, which will later be expanded (with
renderZone() - implements =%<nop>RENDERZONE%=). =$ids= are unique within the zone that
they are added - dependencies between =$ids= in different zones will not be
resolved, except for the special case of =head= and =body= zones when
={OptimizePageLayout}= is not set.
renderZone() - implements =%<nop>RENDERZONE%=). =$ids= are unique within
the zone that they are added - dependencies between =$ids= in different zones
will not be resolved, except for the special case of =head= and =body= zones
when ={OptimizePageLayout}= is disabled.
In this case, they are treated as separate zones when adding to them, but as
one merged zone when rendering, i.e. a call to render either =head= or =body=
zones will actually render both zones in this one call. Both zones are undef'd
afterward to avoid double rendering of content from either zone, e.g. to support
afterward to avoid double rendering of content from either zone, to support
proper behaviour when =head= and =body= are rendered with separate calls even
when ={OptimizePageLayout}= is not set. See ZoneTests/Item9317.
when ={OptimizePageLayout}= is not set. See ZoneTests/explicit_RENDERZONE*.
This behaviour allows an addToZone('head') call to require an id that has been
added to =body= only.
Expand Down Expand Up @@ -3335,9 +3321,11 @@ sub _getMissingRequiredZoneIDs {
return join( ', ', @{ $zoneID->{missingrequires} } );
}

# This private function is used in ZoneTests
sub _renderZone {
my ( $this, $zone, $params, $topicObject ) = @_;

# Check the zone is defined and has not already been rendered
return '' unless $zone && $this->{_zones}{$zone};

$params->{header} ||= '';
Expand Down Expand Up @@ -3390,7 +3378,9 @@ sub _renderZone {
$this->_visitZoneID( $zoneID, \%visited, \@total );
}

# kill a zone once it has been rendered
# kill a zone once it has been rendered, to prevent it being
# added twice (e.g. by duplicate %RENDERZONEs or by automatic
# zone expansion in the head or body)
undef $this->{_zones}{$zone};
}

Expand Down Expand Up @@ -3484,6 +3474,43 @@ sub _visitZoneID {
return;
}

# This private function is used in ZoneTests
sub _renderZones {
my ( $this, $text ) = @_;

# Render zones that were pulled out by Foswiki/Macros/RENDERZONE.pm
# NOTE: once a zone has been rendered it is cleared, so cannot
# be rendered again.

$text =~ s/${RENDERZONE_MARKER}RENDERZONE{(.*?)}${RENDERZONE_MARKER}/
_renderZoneById($this, $1)/geo;

# get the head zone and insert it at the end of the </head>
# *if it has not already been rendered*
my $headZone = _renderZone( $this, 'head', { chomp => "on" } );
$text =~ s!(</head>)!$headZone\n$1!i if $headZone;

# get the body zone and insert it at the end of the </body>
# *if it has not already been rendered*
my $bodyZone = _renderZone( $this, 'body', { chomp => "on" } );

if ($bodyZone) {
# Unless optimize mode is enabled, or the body zone has been
# explicitly expanded by %RENDERZONE{"body"}%, the body zone
# is appended to the head.
unless ( $Foswiki::cfg{OptimizePageLayout} ) {
$text =~ s!(</head>)!$bodyZone\n$1!i;
}
else {
$text =~ s!(</body>)!$bodyZone\n$1!i;
}
}

chomp($text);

return $text;
}

=begin TML
---++ StaticMethod readFile( $filename ) -> $text
Expand Down
4 changes: 2 additions & 2 deletions core/lib/Foswiki/Macros/RENDERZONE.pm
Expand Up @@ -23,9 +23,9 @@ sub RENDERZONE {
};

return
$Foswiki::TranslationToken
$Foswiki::RENDERZONE_MARKER
. "RENDERZONE{$id}"
. $Foswiki::TranslationToken;
. $Foswiki::RENDERZONE_MARKER;
}

1;
Expand Down

0 comments on commit 3fa4563

Please sign in to comment.