our
$VERSION
=
'0.07'
;
has
root
=> (
is
=>
'ro'
,
isa
=>
'Tree::Navigator::Node'
,
handles
=> [
qw/mount children child descendent/
],
);
has
can_be_killed
=> (
is
=>
'ro'
,
);
sub
BUILD {
my
$self
=
shift
;
my
$mount_point
= {
navigator
=>
$self
};
weaken
$mount_point
->{navigator};
$self
->{root} ||= Tree::Navigator::Node->new(
mount_point
=>
$mount_point
,
path
=>
''
,
);
my
$gvascript_dir
= Alien::GvaScript->path
or
die
"Alien::GvaScript is not installed"
;
$self
->mount(
'_gva'
,
Filesys
=> {
mount_point
=> {
root
=>
$gvascript_dir
}},
{
hidden
=> 1});
}
sub
call {
my
(
$self
,
$env
) =
@_
;
my
$req
= Plack::Request->new(
$env
);
my
$path
=
$req
->path;
$path
=~ s[^/][];
!
$path
and
return
$self
->frameset(
$req
);
$path
=~ s[^_toc/][] and
return
$self
->toc(
$path
,
$req
);
$path
=~ /^_KILL/ and
$self
->can_be_killed
and
$env
->{
'psgix.harakiri.commit'
} = 1
and
return
[200, [
'Content-type'
=>
'text/html'
],
[
"server killed upon user request"
]];
my
$node
=
$self
->descendent(
$path
) or
die
"no such node : $path"
;
return
$node
->response(
$req
);
}
sub
frameset {
my
(
$self
,
$req
) =
@_
;
my
$ini
=
$req
->param(
'open'
) ||
$self
->{initial_page} ||
''
;
my
$ini_toc
=
$ini
?
"_toc/?open=$ini"
:
"_toc/"
;
my
$title
= escape_html(
$self
->{title} ||
'Tree Navigator'
);
my
$body
=
<<__EOHTML__;
<html>
<head><title>$title</title></head>
<frameset cols="25%, 75%">
<frame name="tocFrame" src="$ini_toc">
<frame name="contentFrame" src="$ini">
</frameset>
</html>
__EOHTML__
return
[200, [
'Content-type'
=>
'text/html'
], [
$body
]];
}
sub
toc {
my
(
$self
,
$path
,
$req
) =
@_
;
return
$path
?
$self
->sub_toc(
$path
,
$req
)
:
$self
->main_toc(
$req
);
}
sub
main_toc {
my
(
$self
,
$req
) =
@_
;
my
$base
=
$req
->script_name;
my
$root_nodes
=
$self
->mk_root_nodes;
my
$resp
=
$req
->new_response(200, [
'Content-Type'
=>
'text/html'
]);
my
$kill_serv
= !
$self
->can_be_killed ?
""
:
q{<a href="/_KILL" style="float:right;color:red">Stop debugging</a>}
;
$resp
->body(
<<__EOHTML__);
<html>
<head>
<base target="contentFrame">
<link href="$base/_gva/GvaScript.css" rel="stylesheet" type="text/css">
<script src="$base/_gva/prototype.js"></script>
<script src="$base/_gva/GvaScript.js"></script>
<script>
var treeNavigator;
function open_nodes(first_node, rest) {
var node = \$(first_node);
if (!node || !treeNavigator) return;
// shift to next node in sequence
first_node = rest.shift();
// build a handler for "onAfterLoadContent" (closure on first_node/rest)
var open_or_select_next = function() {
// delete handler that might have been placed by previous call
delete treeNavigator.onAfterLoadContent;
//
if (rest.length > 0) {
open_nodes(first_node, rest)
}
else {
treeNavigator.openEnclosingNodes(\$(first_node));
treeNavigator.select(\$(first_node));
}
};
// if node is closed and currently has no content, we need to register
// a handler, open the node so that it gets its content by Ajax,
// and then execute the handler to open the rest after Ajax returns
if (treeNavigator.isClosed(node)
&& !treeNavigator.content(node)) {
treeNavigator.onAfterLoadContent = open_or_select_next;
treeNavigator.open(node);
}
// otherwise just a direct call
else {
open_or_select_next();
}
}
function setup() {
treeNavigator
= new GvaScript.TreeNavigator('TN_tree', {tabIndex:-1});
}
document.observe('dom:loaded', setup);
function displayContent(event) {
var label = event.controller.label(event.target);
if (label && label.tagName == "A") {
label.focus();
return Event. stopNone;
}
}
</script>
<style>
BODY {margin:0px; font-size: 70%; overflow-x: hidden}
DIV {margin:0px; width: 100%}
.mount_point {color: midnightblue; font-weight: bold;}
</style>
</head>
<body>
$kill_serv
<div id='TN_tree' onPing='displayContent'>
$root_nodes
</div>
</body>
</html>
__EOHTML__
return
$resp
->finalize;
}
sub
sub_toc {
my
(
$self
,
$path
,
$req
) =
@_
;
my
$node
=
$self
->descendent(
$path
) or
die
"no such node : $path"
;
my
$resp
=
$req
->new_response(200);
$resp
->body(
$self
->_TOC_entry(
$node
));
return
$resp
->finalize;
}
my
$TOC_tmpl
=
q{
[% SET full_path = node.full_path;
SET subnodes_and_leaves = node.subnodes_and_leaves;
FOREACH subnode IN subnodes_and_leaves.0;
SET path = "$full_path/$subnode" | url; %]
<div class='TN_node TN_closed' TN:contentURL='[% path %]'>
<a href='../[% path %]' class='TN_label'>[% subnode %]</a>
</div>
[% END; # FOREACH subnode ~%]
[% FOREACH leaf IN subnodes_and_leaves.1;
SET path = "$full_path/$leaf" | url; %]
<div class='TN_leaf'>
<a href='../[% path %]' class='TN_label'>[% leaf %]</a>
</div>
[% END; # FOREACH leaf %]
}
;
sub
_TOC_entry {
my
(
$self
,
$node
) =
@_
;
my
$view
=
$self
->view(
TT2
=> \
$TOC_tmpl
);
my
$request
=
undef
;
my
$response
=
$view
->render(
$node
,
$request
);
my
$toc_html
=
$response
->[2][0];
return
$toc_html
;
}
my
$default_tmpl
=
q{
<head>
<style>
BODY, TD {
font-family: Verdana, Arial, Helvetica;
font-size: 85%;
}
H1, H2, H3, H4, H5, H6 {
display:inline;
margin: 0;
}
.attrs TH { text-align: right; padding-right: 1ex}
.attrs TH, TD { font-size: 80%; }
.highlight { background: lightgreen }
</style>
[% SET base = request.script_name %]
<
link
href=
"[% base %]/_gva/GvaScript.css"
rel=
"stylesheet"
type=
"text/css"
>
<script src=
"[% base %]/_gva/prototype.js"
></script>
<script src=
"[% base %]/_gva/GvaScript.js"
></script>
<script>
var treeNavigator;
function setup() {
treeNavigator
= new GvaScript.TreeNavigator(
'TN_tree'
, {tabIndex:-1});
}
document.observe(
'dom:loaded'
, setup);
function follow_link(event) {
var label = event.controller.label(event.target);
if
(label && label.tagName ==
"A"
) {
label.focus();
return
Event. stopNone;
}
}
</script>
</head>
<body>
<div id=
'TN_tree'
onPing=
'follow_link'
>
<div class=
'TN_node'
>
<h1 class=
"TN_label"
>[% node.full_path %]</h1>
<div class=
"TN_content"
>
[% IF data.attributes.size %]
<div class=
"TN_node"
>
<h2 class=
"TN_label"
>Attributes</h2>
<div class=
"TN_content"
>
<em>[% INCLUDE attrs attrs=data.attributes %]</em>
</div>
</div>
[% END;
[% IF data.children.size %]
<div class=
"TN_node"
>
<h2 class=
"TN_label"
>Children</h2>
<div class=
"TN_content"
>
[% INCLUDE child FOREACH child IN data.children %]
</div>
</div>
[% END;
[% IF data.content_text %]
<div class=
"TN_node"
>
<h2 class=
"TN_label"
>Content</h2>
<div class=
"TN_content"
>
<pre>
[%- data.content_text -%]
</pre>
</div>
</div>
[% END;
</div>
</div>
</div>
</body>
[%~ BLOCK child; %]
<div class=
"[% child.attributes.size ? 'TN_node TN_closed' : 'TN_leaf' %]"
>
<a class=
"TN_label"
href=
"[% node.last_path _ '/' _ child.name | url %]"
>
[%~ child.name ~%]
</a>
[% IF child.attributes.size %]
<div class=
"TN_content"
>
[% INCLUDE attrs attrs=child.attributes %]
</div>
[% END;
</div>
[%~ END;
[%~ BLOCK attrs;
IF attrs.size; %]
<table class=
"attrs"
>
[% FOREACH attr IN attrs; %]
<
tr
><th>[% attr.key %]</th><td>[% attr.value %]</td></
tr
>
[% END;
</table>
[% END;
END;
};
sub
mk_root_nodes {
my
(
$self
) =
@_
;
my
$html
=
""
;
foreach
my
$path
(
$self
->children) {
my
$node
=
$self
->child(
$path
) or
die
"absent root: '$path'"
;
my
$title
= escape_html(
$node
->attributes->{title} ||
''
);
$title
=
" title='$title'"
if
$title
;
my
$node_content
=
$self
->_TOC_entry(
$node
);
my
$node_html
=
"<a href='../$path' class='TN_label mount_point'$title>$path</a>"
;
$node_html
.=
"<div class='TN_content'>$node_content</div>"
if
$node_content
;
$html
.=
"<div class='TN_node'>$node_html</div>"
;
}
$html
or
die
"no mounted nodes"
;
return
$html
;
}
my
%escape_entity
= (
'&'
=>
'amp'
,
'<'
=>
'lt'
,
'>'
=>
'gt'
,
'"'
=>
'quot'
);
my
$entity_regex
=
"(["
.
join
(
""
,
keys
%escape_entity
) .
"])"
;
sub
escape_html {
my
$html
=
shift
;
$html
=~ s/
$entity_regex
/
&$escape_entity
{$1};/g;
return
$html
;
}
sub
view {
my
$self
=
shift
;
my
(
$view_class
,
@args
) =
@_
?
@_
: (
TT2
=> \
$default_tmpl
);
my
$class
= Plack::Util::load_class(
$view_class
,
"Tree::Navigator::View"
);
return
$class
->new(
@args
);
}
__PACKAGE__->meta->make_immutable;
1;