summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FS/FS/Record.pm18
-rw-r--r--FS/FS/access_user.pm14
-rw-r--r--FS/FS/m2m_Common.pm92
-rw-r--r--FS/FS/option_Common.pm105
-rw-r--r--httemplate/elements/header.html68
-rw-r--r--httemplate/elements/menu.html15
-rw-r--r--httemplate/elements/xmenu.css11
-rw-r--r--httemplate/elements/xmenu.top.css211
-rw-r--r--httemplate/elements/xmenu.top.js671
-rw-r--r--httemplate/images/arrow.down.pngbin170 -> 155 bytes
-rw-r--r--httemplate/images/menu-left-example.pngbin0 -> 24709 bytes
-rw-r--r--httemplate/images/menu-top-example.pngbin0 -> 22816 bytes
-rw-r--r--httemplate/pref/pref-process.html41
-rw-r--r--httemplate/pref/pref.html28
14 files changed, 1173 insertions, 101 deletions
diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm
index 34b556eea..6b7e8d5fa 100644
--- a/FS/FS/Record.pm
+++ b/FS/FS/Record.pm
@@ -26,7 +26,7 @@ use Tie::IxHash;
#export dbdef for now... everything else expects to find it here
@EXPORT_OK = qw(dbh fields hfields qsearch qsearchs dbdef jsearch);
-$DEBUG = 0;
+$DEBUG = 3;
$me = '[FS::Record]';
$nowarn_identical = 0;
@@ -563,6 +563,17 @@ sub dbdef_table {
dbdef->table($table);
}
+=item primary_key
+
+Returns the primary key for the table.
+
+=cut
+
+sub primary_key {
+ my $self = shift;
+ my $pkey = $self->dbdef_table->primary_key;
+}
+
=item get, getfield COLUMN
Returns the value of the column/field/key COLUMN.
@@ -688,6 +699,8 @@ sub insert {
my $self = shift;
my $saved = {};
+ warn "$self -> insert" if $DEBUG;
+
my $error = $self->check;
return $error if $error;
@@ -784,8 +797,7 @@ sub insert {
dbh->rollback if $FS::UID::AutoCommit;
return dbh->errstr;
};
- #$i_sth->execute($oid) or do {
- $i_sth->execute() or do {
+ $i_sth->execute() or do { #$i_sth->execute($oid)
dbh->rollback if $FS::UID::AutoCommit;
return $i_sth->errstr;
};
diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm
index f45f17d60..9be9166f0 100644
--- a/FS/FS/access_user.pm
+++ b/FS/FS/access_user.pm
@@ -6,10 +6,12 @@ use FS::UID;
use FS::Conf;
use FS::Record qw( qsearch qsearchs dbh );
use FS::m2m_Common;
+use FS::option_Common;
use FS::access_usergroup;
use FS::agent;
-@ISA = qw( FS::m2m_Common FS::Record );
+@ISA = qw( FS::m2m_Common FS::option_Common FS::Record );
+#@ISA = qw( FS::m2m_Common FS::option_Common );
#kludge htpasswd for now (i hope this bootstraps okay)
FS::UID->install_callback( sub {
@@ -74,6 +76,10 @@ points to. You can ask the object for a copy with the I<hash> method.
sub table { 'access_user'; }
+sub _option_table { 'access_user_pref'; }
+sub _option_namecol { 'prefname'; }
+sub _option_valuecol { 'prefvalue'; }
+
=item insert
Adds this record to the database. If there is an error, returns the error,
@@ -177,7 +183,11 @@ returns the error, otherwise returns false.
=cut
sub replace {
- my($new, $old) = ( shift, shift );
+ my $new = shift;
+
+ my $old = ( ref($_[0]) eq ref($new) )
+ ? shift
+ : $new->replace_old;
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
diff --git a/FS/FS/m2m_Common.pm b/FS/FS/m2m_Common.pm
index fd8700a2d..5dc2a8ec8 100644
--- a/FS/FS/m2m_Common.pm
+++ b/FS/FS/m2m_Common.pm
@@ -3,25 +3,26 @@ package FS::m2m_Common;
use strict;
use vars qw( @ISA $DEBUG );
use FS::Schema qw( dbdef );
-use FS::Record qw( qsearch qsearchs ); #dbh );
+use FS::Record qw( qsearch qsearchs dbh );
-@ISA = qw( FS::Record );
+#hmm. well. we seem to be used as a mixin.
+#@ISA = qw( FS::Record );
$DEBUG = 0;
=head1 NAME
-FS::m2m_Common - Base class for classes in a many-to-many relationship
+FS::m2m_Common - Mixin class for classes in a many-to-many relationship
=head1 SYNOPSIS
use FS::m2m_Common;
-@ISA = qw( FS::m2m_Common );
+@ISA = qw( FS::m2m_Common FS::Record );
=head1 DESCRIPTION
-FS::m2m_Common is intended as a base class for classes which have a
+FS::m2m_Common is intended as a mixin class for classes which have a
many-to-many relationship with another table (via a linking table).
Note: It is currently assumed that the link table contains two fields
@@ -31,7 +32,17 @@ named the same as the primary keys of ths base and target tables.
=over 4
-=item process_m2m
+=item process_m2m OPTION => VALUE, ...
+
+Available options:
+
+link_table (required) -
+
+target_table (required) -
+
+params (required) - hashref; keys are primary key values in target_table (values are boolean). For convenience, keys may optionally be prefixed with the name
+of the primary key, as in agentnum54 instead of 54, or passed as an arrayref
+of values.
=cut
@@ -39,41 +50,64 @@ sub process_m2m {
my( $self, %opt ) = @_;
my $self_pkey = $self->dbdef_table->primary_key;
+ my %hash = ( $self_pkey => $self->$self_pkey() );
my $link_table = $self->_load_table($opt{'link_table'});
my $target_table = $self->_load_table($opt{'target_table'});
my $target_pkey = dbdef->table($target_table)->primary_key;
- foreach my $target_obj ( qsearch($target_table, {} ) ) {
+ if ( ref($opt{'params'}) eq 'ARRAY' ) {
+ $opt{'params'} = { map { $_=>1 } @{$opt{'params'}} };
+ }
- my $targetnum = $target_obj->$target_pkey();
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ foreach my $del_obj (
+ grep {
+ my $targetnum = $_->$target_pkey();
+ ( ! $opt{'params'}->{$targetnum}
+ && ! $opt{'params'}->{"$target_pkey$targetnum"}
+ );
+ }
+ qsearch( $link_table, \%hash )
+ ) {
+ my $error = $del_obj->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
- my $link_obj = qsearchs( $link_table, {
- $self_pkey => $self->$self_pkey(),
- $target_pkey => $targetnum,
+ foreach my $add_targetnum (
+ grep { ! qsearchs( $link_table, { %hash, $target_pkey => $_ } ) }
+ map { /^($target_pkey)?(\d+)$/; $2; }
+ grep { /^($target_pkey)?(\d+)$/ }
+ grep { $opt{'params'}->{$_} }
+ keys %{ $opt{'params'} }
+ ) {
+
+ my $add_obj = "FS::$link_table"->new( {
+ %hash,
+ $target_pkey => $add_targetnum,
});
-
- if ( $link_obj && ! $opt{'params'}->{"$target_pkey$targetnum"} ) {
-
- my $d_link_obj = $link_obj; #need to save $link_obj for below.
- my $error = $d_link_obj->delete;
- die $error if $error;
-
- } elsif ( $opt{'params'}->{"$target_pkey$targetnum"} && ! $link_obj ) {
-
- #ok to clobber it now (but bad form nonetheless?)
- #$link_obj = new "FS::$link_table" ( {
- $link_obj = "FS::$link_table"->new( {
- $self_pkey => $self->$self_pkey(),
- $target_pkey => $targetnum,
- });
- my $error = $link_obj->insert;
- die $error if $error;
+ my $error = $add_obj->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
}
-
}
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
'';
}
diff --git a/FS/FS/option_Common.pm b/FS/FS/option_Common.pm
index ad3c26958..0efacbd8a 100644
--- a/FS/FS/option_Common.pm
+++ b/FS/FS/option_Common.pm
@@ -6,7 +6,7 @@ use FS::Record qw( qsearch qsearchs dbh );
@ISA = qw( FS::Record );
-$DEBUG = 0;
+$DEBUG = 3;
=head1 NAME
@@ -18,6 +18,11 @@ use FS::option_Common;
@ISA = qw( FS::option_Common );
+#optional for non-standard names
+sub _option_table { 'table_name'; } #defaults to ${table}_option
+sub _option_namecol { 'column_name'; } #defaults to optionname
+sub _option_valuecol { 'column_name'; } #defaults to optionvalue
+
=head1 DESCRIPTION
FS::option_Common is intended as a base class for classes which have a
@@ -66,14 +71,17 @@ sub insert {
return $error;
}
- my $pkey = $self->pkey;
+ my $pkey = $self->primary_key;
my $option_table = $self->option_table;
+ my $namecol = $self->_option_namecol;
+ my $valuecol = $self->_option_valuecol;
+
foreach my $optionname ( keys %{$options} ) {
my $href = {
- $pkey => $self->get($pkey),
- 'optionname' => $optionname,
- 'optionvalue' => $options->{$optionname},
+ $pkey => $self->get($pkey),
+ $namecol => $optionname,
+ $valuecol => $options->{$optionname},
};
#my $option_record = eval "new FS::$option_table \$href";
@@ -123,7 +131,7 @@ sub delete {
return $error;
}
- my $pkey = $self->pkey;
+ my $pkey = $self->primary_key;
#my $option_table = $self->option_table;
foreach my $obj ( $self->option_objects ) {
@@ -140,7 +148,7 @@ sub delete {
}
-=item replace OLD_RECORD [ HASHREF | OPTION => VALUE ... ]
+=item replace [ OLD_RECORD ] [ HASHREF | OPTION => VALUE ... ]
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
@@ -152,12 +160,16 @@ created or modified (see L<FS::part_export_option>).
sub replace {
my $self = shift;
- my $old = shift;
+
+ my $old = ( ref($_[0]) eq ref($self) )
+ ? shift
+ : $self->replace_old;
+
my $options =
( ref($_[0]) eq 'HASH' )
? shift
: { @_ };
- warn "FS::option_Common::insert called on $self with options ".
+ warn "FS::option_Common::replace called on $self with options ".
join(', ', map "$_ => ". $options->{$_}, keys %$options)
if $DEBUG;
@@ -178,30 +190,44 @@ sub replace {
return $error;
}
- my $pkey = $self->pkey;
+ my $pkey = $self->primary_key;
my $option_table = $self->option_table;
+ my $namecol = $self->_option_namecol;
+ my $valuecol = $self->_option_valuecol;
+
foreach my $optionname ( keys %{$options} ) {
- my $old = qsearchs( $option_table, {
- $pkey => $self->get($pkey),
- 'optionname' => $optionname,
+
+ warn "FS::option_Common::replace: inserting or replacing option: $optionname"
+ if $DEBUG > 1;
+
+ my $oldopt = qsearchs( $option_table, {
+ $pkey => $self->get($pkey),
+ $namecol => $optionname,
} );
my $href = {
- $pkey => $self->get($pkey),
- 'optionname' => $optionname,
- 'optionvalue' => $options->{$optionname},
+ $pkey => $self->get($pkey),
+ $namecol => $optionname,
+ $valuecol => $options->{$optionname},
};
- #my $new = eval "new FS::$option_table \$href";
+ #my $newopt = eval "new FS::$option_table \$href";
#if ( $@ ) {
# $dbh->rollback if $oldAutoCommit;
# return $@;
#}
- my $new = "FS::$option_table"->new($href);
+ my $newopt = "FS::$option_table"->new($href);
+
+ my $opt_pkey = $newopt->primary_key;
- $new->optionnum($old->optionnum) if $old;
- my $error = $old ? $new->replace($old) : $new->insert;
+ $newopt->$opt_pkey($oldopt->$opt_pkey) if $oldopt;
+ warn $oldopt;
+ warn "FS::option_Common::replace: ".
+ ( $oldopt ? "$newopt -> replace($oldopt)" : "$newopt -> insert" )
+ if $DEBUG > 2;
+ my $error = $oldopt ? $newopt->replace($oldopt) : $newopt->insert;
+ warn $error;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
@@ -210,7 +236,7 @@ sub replace {
#remove extraneous old options
foreach my $opt (
- grep { !exists $options->{$_->optionname} } $old->option_objects
+ grep { !exists $options->{$_->$namecol()} } $old->option_objects
) {
my $error = $opt->delete;
if ( $error ) {
@@ -233,7 +259,7 @@ Returns all options as FS::I<tablename>_option objects.
sub option_objects {
my $self = shift;
- my $pkey = $self->pkey;
+ my $pkey = $self->primary_key;
my $option_table = $self->option_table;
qsearch($option_table, { $pkey => $self->get($pkey) } );
}
@@ -246,7 +272,9 @@ Returns a list of option names and values suitable for assigning to a hash.
sub options {
my $self = shift;
- map { $_->optionname => $_->optionvalue } $self->option_objects;
+ my $namecol = $self->_option_namecol;
+ my $valuecol = $self->_option_valuecol;
+ map { $_->$namecol() => $_->$valuecol() } $self->option_objects;
}
=item option OPTIONNAME
@@ -257,30 +285,35 @@ Returns the option value for the given name, or the empty string.
sub option {
my $self = shift;
- my $pkey = $self->pkey;
+ my $pkey = $self->primary_key;
my $option_table = $self->option_table;
- my $obj =
- qsearchs($option_table, {
- $pkey => $self->get($pkey),
- optionname => shift,
- } );
- $obj ? $obj->optionvalue : '';
+ my $namecol = $self->_option_namecol;
+ my $valuecol = $self->_option_valuecol;
+ my $hashref = {
+ $pkey => $self->get($pkey),
+ $namecol => shift,
+ };
+ warn "$self -> option: searching for ".
+ join(' / ', map { "$_ => ". $hashref->{$_} } keys %$hashref )
+ if $DEBUG;
+ my $obj = qsearchs($option_table, $hashref);
+ $obj ? $obj->$valuecol() : '';
}
-sub pkey {
- my $self = shift;
- my $pkey = $self->dbdef_table->primary_key;
-}
-
sub option_table {
my $self = shift;
- my $option_table = $self->table . '_option';
+ my $option_table = $self->_option_table;
eval "use FS::$option_table";
die $@ if $@;
$option_table;
}
+#defaults
+sub _option_table { shift->table .'_option'; }
+sub _option_namecol { 'optionname'; }
+sub _option_valuecol { 'optionvalue'; }
+
=back
=head1 BUGS
diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html
index 0e69b19ca..1f5674885 100644
--- a/httemplate/elements/header.html
+++ b/httemplate/elements/header.html
@@ -1,11 +1,3 @@
-%
-% my($title, $menubar) = ( shift, shift );
-% my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc.
-% my $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section
-% my $conf = new FS::Conf;
-%
-%
-
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML>
<HEAD>
@@ -16,7 +8,10 @@
<META HTTP-Equiv="Pragma" Content="no-cache">
<META HTTP-Equiv="Expires" Content="0">
- <% include('menu.html', 'freeside_baseurl' => $fsurl ) %>
+ <% include('menu.html', 'freeside_baseurl' => $fsurl,
+ 'position' => $menu_position,
+ )
+ %>
<SCRIPT TYPE="text/javascript">
function clearhint_search_cust (what) {
@@ -43,7 +38,7 @@
<% $head %>
</HEAD>
- <BODY BACKGROUND="<%$fsurl%>images/background-cheat.png" <% $etc %> STYLE="margin-top:0; margin-bottom:0; margin-left:0; margin-right:0">
+ <BODY <% $menu_position eq 'left' ? qq( BACKGROUND="${fsurl}images/background-cheat.png" ) : ' BGCOLOR="#e8e8e8" ' %> <% $etc %> STYLE="margin-top:0; margin-bottom:0; margin-left:0; margin-right:0">
<table width="100%" CELLPADDING=0 CELLSPACING=0 STYLE="padding-left:0; padding-right:4">
<tr>
<td rowspan=2 BGCOLOR="#ffffff"><IMG BORDER=0 ALT="freeside" SRC="<%$fsurl%>images/small-logo.png"></td>
@@ -121,6 +116,30 @@ input.fsblackbuttonselected {
<TD COLSPAN=5 WIDTH="100%" STYLE="padding:0"><IMG BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gradient.png" HEIGHT="13" WIDTH="100%"></TD>
</TR>
+% if ( $menu_position eq 'top' ) {
+
+ <TR>
+
+ <TD COLSPAN="5" WIDTH="100%" STYLE="padding:0">
+ <SCRIPT TYPE="text/javascript">
+ document.write(myBar);
+ </SCRIPT>
+ </TD>
+
+ </TR>
+
+ <TR>
+ <TD COLSPAN="5" WIDTH="100%" HEIGHT="2px" STYLE="padding:0" BGCOLOR="#000000">
+ </TD>
+ </TR>
+
+ <TR>
+ <TD COLSPAN="5" WIDTH="100%" HEIGHT="4px" STYLE="padding:0" BGCOLOR="#000000">
+ </TD>
+ </TR>
+
+% }
+
<TR>
<TD COLSPAN=1 BGCOLOR="#000000" ALIGN="right">
@@ -172,13 +191,26 @@ input.fsblackbuttonselected {
</TR>
</TABLE>
+
<TABLE WIDTH="100%" HEIGHT="100%" CELLSPACING=0 CELLPADDING=4>
+
<TR>
+
+% if ( $menu_position eq 'left' ) {
+
<TD BGCOLOR="#000000" STYLE="padding:0" WIDTH="154"></TD>
<TD STYLE="padding:0" WIDTH="13"><IMG BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-corner.png"></TD>
- <TD STYLE="padding:0"><IMG BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-top.png" HEIGHT="13" WIDTH="100%"></TD>
+
+% }
+
+ <TD STYLE="padding:0" WIDTH="100%"><IMG BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-top.png" HEIGHT="13" WIDTH="100%"></TD>
+
</TR>
+
<TR HEIGHT="100%">
+
+% if ( $menu_position eq 'left' ) {
+
<TD BGCOLOR="#000000" ALIGN="left" HEIGHT="100%" WIDTH="154" VALIGN="top" ALIGN="right">
<SCRIPT TYPE="text/javascript">
document.write(myBar);
@@ -188,6 +220,9 @@ input.fsblackbuttonselected {
</TD>
<TD STYLE="padding:0" HEIGHT="100%" WIDTH=13 VALIGN="top"><IMG WIDTH="13" HEIGHT="100%" BORDER=0 ALT="" SRC="<%$fsurl%>images/black-gray-side.png"></TD>
+
+% }
+
<TD BGCOLOR="#e8e8e8" HEIGHT="100%" VALIGN="top"> <!-- WIDTH="100%"> -->
<FONT SIZE=6>
@@ -196,3 +231,14 @@ input.fsblackbuttonselected {
<BR><BR>
<% $menubar !~ /^\s*$/ ? "$menubar<BR><BR>" : '' %>
+<%init>
+
+my($title, $menubar) = ( shift, shift );
+my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc.
+my $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section
+my $conf = new FS::Conf;
+
+my $menu_position = $FS::CurrentUser::CurrentUser->option('menu_position')
+ || 'left';
+
+</%init>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index 25dd619f6..94bb0e0ba 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -1,6 +1,17 @@
<script type="text/javascript" src="<%$fsurl%>elements/cssexpr.js"></script>
-<script type="text/javascript" src="<%$fsurl%>elements/xmenu.js"></script>
-<link href="<%$fsurl%>elements/xmenu.css" type="text/css" rel="stylesheet">
+
+% if ( $opt{'position'} eq 'top' ) {
+
+ <script type="text/javascript" src="<%$fsurl%>elements/xmenu.top.js"></script>
+ <link href="<%$fsurl%>elements/xmenu.top.css" type="text/css" rel="stylesheet">
+
+% } else { # elsif ( $opt{'position'} eq 'left' ) {
+
+ <script type="text/javascript" src="<%$fsurl%>elements/xmenu.js"></script>
+ <link href="<%$fsurl%>elements/xmenu.css" type="text/css" rel="stylesheet">
+
+% }
+
<link href="<%$fsurl%>elements/freeside.css" type="text/css" rel="stylesheet">
<SCRIPT TYPE="text/javascript">
diff --git a/httemplate/elements/xmenu.css b/httemplate/elements/xmenu.css
index 46b221bda..97c7da8bb 100644
--- a/httemplate/elements/xmenu.css
+++ b/httemplate/elements/xmenu.css
@@ -95,6 +95,9 @@
}
.webfx-menu-bar {
+ /* i want a vertical bar */
+ display: block;
+
/* background: rgb(120,172,255);/*rgb(255,128,0);*/
/* background: #a097ed; */
background: #000000;
@@ -167,10 +170,10 @@
*/
ie-dummy: expression(this.hideFocus=true);
- border-left: 1px solid rgb(0,66,174);
- border-right: 1px solid rgb(234,242,255);
- border-top: 1px solid rgb(0,66,174);
- border-bottom: 1px solid rgb(234,242,255);
+/* border-left: 1px solid rgb(0,66,174); */
+/* border-right: 1px solid rgb(234,242,255); */
+/* border-top: 1px solid rgb(0,66,174); */
+/* border-bottom: 1px solid rgb(234,242,255); */
}
.webfx-menu-title {
diff --git a/httemplate/elements/xmenu.top.css b/httemplate/elements/xmenu.top.css
new file mode 100644
index 000000000..75917031b
--- /dev/null
+++ b/httemplate/elements/xmenu.top.css
@@ -0,0 +1,211 @@
+
+.webfx-menu, .webfx-menu * {
+ /*
+ Set the box sizing to content box
+ in the future when IE6 supports box-sizing
+ there will be an issue to fix the sizes
+
+ There is probably an issue with IE5 mac now
+ because IE5 uses content-box but the script
+ assumes all versions of IE uses border-box.
+
+ At the time of this writing mozilla did not support
+ box-sizing for absolute positioned element.
+
+ Opera only supports content-box
+ */
+ box-sizing: content-box;
+ -moz-box-sizing: content-box;
+}
+
+.webfx-menu {
+ position: absolute;
+ z-index: 100;
+ visibility: hidden;
+ border: 1px solid black;
+ padding: 1px;
+ background: white;
+ filter: progid:DXImageTransform.Microsoft.Shadow(color="#777777", Direction=135, Strength=4)
+ alpha(Opacity=95);
+ -moz-opacity: 0.95;
+ /* a drop shadow would be nice in moz/others too... */
+}
+
+.webfx-menu-empty {
+ display: block;
+ border: 1px solid white;
+ padding: 2px 5px 2px 5px;
+ font-size: 11px;
+ /* font-family: Tahoma, Verdan, Helvetica, Sans-Serif; */
+ color: black;
+}
+
+.webfx-menu a {
+ display: block;
+ /* width: expression(constExpression(ieBox ? "100%": "auto")); /* should be ignored by mz and op */
+ width: expression(constExpression(ie ? "98%": "auto")); /* should be ignored by mz and op */
+ overflow: visible;
+ /* padding: 2px 0px 2px 5px; */
+ padding: 1px 0px 1px 5px;
+ font-size: 14px;
+/* font-family: Verdana, Arial, Helvetica, sans-serif; */
+ font-weight: bold;
+ text-decoration: none;
+ vertical-align: center;
+ color: black;
+ border: 1px solid white;
+}
+
+.webfx-menu a:visited {
+ color: black;
+ border: 1px solid white;
+}
+
+.webfx-menu a:hover {
+ color: black;
+ border: 1px solid #7e0079;
+}
+
+.webfx-menu a:hover {
+ color: black;
+ /* background: #faf7fa; #f5ebf4; #efdfef; white; #BC79B8; */
+ /* background: #ffe6fe; */
+ /* background: #ffc2fe; */
+ background: #fff2fe;
+ border: 1px solid #7e0079; /*rgb(120,172,255);#ff8800;*/
+}
+
+.webfx-menu a .arrow {
+ float: right;
+ border: 0;
+ width: 3px;
+ margin-right: 3px;
+ margin-top: 4px;
+}
+
+/* separtor */
+.webfx-menu div {
+ height: 0;
+ height: expression(constExpression(ieBox ? "2px" : "0"));
+ border-top: 1px solid #7e0079; /* rgb(120,172,255); */
+ border-bottom: 1px solid rgb(234,242,255);
+ overflow: hidden;
+ margin: 2px 0px 2px 0px;
+ font-size: 0mm;
+}
+
+.webfx-menu-bar {
+ /* background: rgb(120,172,255);/*rgb(255,128,0);*/
+ /* background: #a097ed; */
+ background: #000000;
+ /* border: 1px solid #7E0079; */
+ /* border: 1px solid #000000; */
+ /* border: none */
+ color: white;
+
+ padding: 2px;
+
+ /* IE5.0 has the wierdest box model for inline elements */
+ padding: expression(constExpression(ie50 ? "0px" : "2px"));
+}
+
+.webfx-menu-bar a,
+.webfx-menu-bar a:visited {
+ /* i want a vertical bar */
+ /* display: block; */
+
+ /* border: 1px solid black; /*rgb(0,0,0);/*rgb(255,128,0);*/
+ /* border: 1px solid black; /* #ffffff; */
+ /* border-bottom: 1px solid black; */
+ /* border-bottom: 1px solid white; */
+ /* border-bottom: 1px solid rgb(0,66,174);
+ /* border-bottom: 1px solid black;
+ border-bottom: 1px solid black;
+ border-bottom: 1px solid black; */
+
+ padding: 1px 5px 1px 5px;
+
+ /* color: black; */
+ color: white;
+ text-decoration: none;
+
+ /* IE5.0 Does not paint borders and padding on inline elements without a height/width */
+ height: expression(constExpression(ie50 ? "17px" : "auto"));
+
+ background-color:#333333;
+ border:1px solid;
+ border-top-color:#cccccc;
+ border-left-color:#cccccc;
+ border-right-color:#aaaaaa;
+ border-bottom-color:#aaaaaa;
+
+ margin-right: 4px
+
+}
+
+.webfx-menu-bar a:link {
+ color: white;
+}
+
+.webfx-menu-bar a:hover {
+ /* color: black; */
+ color: white;
+ /* background: rgb(120,172,255); */
+ /* background: #BC79B8; */
+ background: #7e0079;
+ /* border-left: 1px solid rgb(234,242,255);
+ border-right: 1px solid rgb(0,66,174);
+ border-top: 1px solid rgb(234,242,255);
+ border-bottom: 1px solid rgb(0,66,174); */
+
+ border:1px solid;
+ border-top-color:#cccccc;
+ border-left-color:#cccccc;
+ border-right-color:#aaaaaa;
+ border-bottom-color:#aaaaaa;
+
+}
+
+.webfx-menu-bar a .arrow {
+ /* float: right; */
+ border: 0;
+/* vertical-align: top; */
+/* width: 3px; */
+/* margin-right: 3px; */
+ margin-bottom: 2px;
+
+}
+
+.webfx-menu-bar a:active, .webfx-menu-bar a:focus {
+ -moz-outline: none;
+ outline: none;
+ /*
+ ie does not support outline but ie55 can hide the outline using
+ a proprietary property on HTMLElement. Did I say that IE sucks at CSS?
+ */
+ ie-dummy: expression(this.hideFocus=true);
+
+/* border-left: 1px solid rgb(0,66,174); */
+/* border-right: 1px solid rgb(234,242,255); */
+/* border-top: 1px solid rgb(0,66,174); */
+/* border-bottom: 1px solid rgb(234,242,255); */
+}
+
+.webfx-menu-title {
+ color: black;
+ /* background: #faf7fa; #f5ebf4; #efdfef; white; #BC79B8; */
+ background: #7e0079;
+/* border: 1px solid #7e0079; /*rgb(120,172,255);#ff8800;*/
+ /* padding: 3px 1px 3px 6px; */
+ padding: 3px 1px 3px 5px;
+ display: block;
+ font-size: 16px;
+/* font-family: Verdana, Arial, Helvetica, sans-serif; */
+ font-weight: bold;
+ text-decoration: none;
+ color: white;
+/* border: 1px solid white; */
+ border-bottom: 1px solid white;
+ width: expression(constExpression(ie ? "98%": "auto")); /* should be ignored by mz and op */
+}
+
diff --git a/httemplate/elements/xmenu.top.js b/httemplate/elements/xmenu.top.js
new file mode 100644
index 000000000..8d81035a2
--- /dev/null
+++ b/httemplate/elements/xmenu.top.js
@@ -0,0 +1,671 @@
+//<script>
+/*
+ * This script was created by Erik Arvidsson (erik@eae.net)
+ * for WebFX (http://webfx.eae.net)
+ * Copyright 2001
+ *
+ * For usage see license at http://webfx.eae.net/license.html
+ *
+ * Created: 2001-01-12
+ * Updates: 2001-11-20 Added hover mode support and removed Opera focus hacks
+ * 2001-12-20 Added auto positioning and some properties to support this
+ * 2002-08-13 toString used ' for attributes. Changed to " to allow in args
+ */
+
+// check browsers
+var ua = navigator.userAgent;
+var opera = /opera [56789]|opera\/[56789]/i.test(ua);
+var ie = !opera && /MSIE/.test(ua);
+var ie50 = ie && /MSIE 5\.[01234]/.test(ua);
+var ie6 = ie && /MSIE [6789]/.test(ua);
+var ieBox = ie && (document.compatMode == null || document.compatMode != "CSS1Compat");
+var moz = !opera && /gecko/i.test(ua);
+var nn6 = !opera && /netscape.*6\./i.test(ua);
+var khtml = /KHTML/i.test(ua);
+
+// define the default values
+
+webfxMenuDefaultWidth = 154;
+
+webfxMenuDefaultBorderLeft = 1;
+webfxMenuDefaultBorderRight = 1;
+webfxMenuDefaultBorderTop = 1;
+webfxMenuDefaultBorderBottom = 1;
+
+webfxMenuDefaultPaddingLeft = 1;
+webfxMenuDefaultPaddingRight = 1;
+webfxMenuDefaultPaddingTop = 1;
+webfxMenuDefaultPaddingBottom = 1;
+
+webfxMenuDefaultShadowLeft = 0;
+webfxMenuDefaultShadowRight = ie && !ie50 && /win32/i.test(navigator.platform) ? 4 :0;
+webfxMenuDefaultShadowTop = 0;
+webfxMenuDefaultShadowBottom = ie && !ie50 && /win32/i.test(navigator.platform) ? 4 : 0;
+
+
+webfxMenuItemDefaultHeight = 18;
+webfxMenuItemDefaultText = "Untitled";
+webfxMenuItemDefaultHref = "javascript:void(0)";
+
+webfxMenuSeparatorDefaultHeight = 6;
+
+webfxMenuDefaultEmptyText = "Empty";
+
+webfxMenuDefaultUseAutoPosition = nn6 ? false : true;
+
+
+
+// other global constants
+
+webfxMenuImagePath = "";
+
+webfxMenuUseHover = opera ? true : false;
+webfxMenuHideTime = 500;
+webfxMenuShowTime = 200;
+
+
+
+var webFXMenuHandler = {
+ idCounter : 0,
+ idPrefix : "webfx-menu-object-",
+ all : {},
+ getId : function () { return this.idPrefix + this.idCounter++; },
+ overMenuItem : function (oItem) {
+ if (this.showTimeout != null)
+ window.clearTimeout(this.showTimeout);
+ if (this.hideTimeout != null)
+ window.clearTimeout(this.hideTimeout);
+ var jsItem = this.all[oItem.id];
+ if (webfxMenuShowTime <= 0)
+ this._over(jsItem);
+ else if ( jsItem )
+ //this.showTimeout = window.setTimeout(function () { webFXMenuHandler._over(jsItem) ; }, webfxMenuShowTime);
+ // I hate IE5.0 because the piece of shit crashes when using setTimeout with a function object
+ this.showTimeout = window.setTimeout("webFXMenuHandler._over(webFXMenuHandler.all['" + jsItem.id + "'])", webfxMenuShowTime);
+ },
+ outMenuItem : function (oItem) {
+ if (this.showTimeout != null)
+ window.clearTimeout(this.showTimeout);
+ if (this.hideTimeout != null)
+ window.clearTimeout(this.hideTimeout);
+ var jsItem = this.all[oItem.id];
+ if (webfxMenuHideTime <= 0)
+ this._out(jsItem);
+ else if ( jsItem )
+ //this.hideTimeout = window.setTimeout(function () { webFXMenuHandler._out(jsItem) ; }, webfxMenuHideTime);
+ this.hideTimeout = window.setTimeout("webFXMenuHandler._out(webFXMenuHandler.all['" + jsItem.id + "'])", webfxMenuHideTime);
+ },
+ blurMenu : function (oMenuItem) {
+ window.setTimeout("webFXMenuHandler.all[\"" + oMenuItem.id + "\"].subMenu.hide();", webfxMenuHideTime);
+ },
+ _over : function (jsItem) {
+ if (jsItem.subMenu) {
+ jsItem.parentMenu.hideAllSubs();
+ jsItem.subMenu.show();
+ }
+ else
+ jsItem.parentMenu.hideAllSubs();
+ },
+ _out : function (jsItem) {
+ // find top most menu
+ var root = jsItem;
+ var m;
+ if (root instanceof WebFXMenuButton)
+ m = root.subMenu;
+ else {
+ m = jsItem.parentMenu;
+ while (m.parentMenu != null && !(m.parentMenu instanceof WebFXMenuBar))
+ m = m.parentMenu;
+ }
+ if (m != null)
+ m.hide();
+ },
+ hideMenu : function (menu) {
+ if (this.showTimeout != null)
+ window.clearTimeout(this.showTimeout);
+ if (this.hideTimeout != null)
+ window.clearTimeout(this.hideTimeout);
+
+ this.hideTimeout = window.setTimeout("webFXMenuHandler.all['" + menu.id + "'].hide()", webfxMenuHideTime);
+ },
+ showMenu : function (menu, src, dir) {
+ if (this.showTimeout != null)
+ window.clearTimeout(this.showTimeout);
+ if (this.hideTimeout != null)
+ window.clearTimeout(this.hideTimeout);
+
+ if (arguments.length < 3)
+ dir = "vertical";
+
+ menu.show(src, dir);
+ }
+};
+
+function WebFXMenu() {
+ this._menuItems = [];
+ this._subMenus = [];
+ this.id = webFXMenuHandler.getId();
+ this.top = 0;
+ this.left = 0;
+ this.shown = false;
+ this.parentMenu = null;
+ webFXMenuHandler.all[this.id] = this;
+}
+
+WebFXMenu.prototype.width = webfxMenuDefaultWidth;
+WebFXMenu.prototype.emptyText = webfxMenuDefaultEmptyText;
+WebFXMenu.prototype.useAutoPosition = webfxMenuDefaultUseAutoPosition;
+
+WebFXMenu.prototype.borderLeft = webfxMenuDefaultBorderLeft;
+WebFXMenu.prototype.borderRight = webfxMenuDefaultBorderRight;
+WebFXMenu.prototype.borderTop = webfxMenuDefaultBorderTop;
+WebFXMenu.prototype.borderBottom = webfxMenuDefaultBorderBottom;
+
+WebFXMenu.prototype.paddingLeft = webfxMenuDefaultPaddingLeft;
+WebFXMenu.prototype.paddingRight = webfxMenuDefaultPaddingRight;
+WebFXMenu.prototype.paddingTop = webfxMenuDefaultPaddingTop;
+WebFXMenu.prototype.paddingBottom = webfxMenuDefaultPaddingBottom;
+
+WebFXMenu.prototype.shadowLeft = webfxMenuDefaultShadowLeft;
+WebFXMenu.prototype.shadowRight = webfxMenuDefaultShadowRight;
+WebFXMenu.prototype.shadowTop = webfxMenuDefaultShadowTop;
+WebFXMenu.prototype.shadowBottom = webfxMenuDefaultShadowBottom;
+
+
+
+WebFXMenu.prototype.add = function (menuItem) {
+ this._menuItems[this._menuItems.length] = menuItem;
+ if (menuItem.subMenu) {
+ this._subMenus[this._subMenus.length] = menuItem.subMenu;
+ menuItem.subMenu.parentMenu = this;
+ }
+
+ menuItem.parentMenu = this;
+};
+
+WebFXMenu.prototype.show = function (relObj, sDir) {
+ if (this.useAutoPosition)
+ this.position(relObj, sDir);
+
+ var divElement = document.getElementById(this.id);
+ if ( divElement ) {
+
+ divElement.style.left = opera ? this.left : this.left + "px";
+ divElement.style.top = opera ? this.top : this.top + "px";
+ divElement.style.visibility = "visible";
+
+ if ( ie ) {
+ var shimElement = document.getElementById(this.id + "Shim");
+ if ( shimElement ) {
+ shimElement.style.width = divElement.offsetWidth;
+ shimElement.style.height = divElement.offsetHeight;
+ shimElement.style.top = divElement.style.top;
+ shimElement.style.left = divElement.style.left;
+ /*shimElement.style.zIndex = divElement.style.zIndex - 1; */
+ shimElement.style.display = "block";
+ shimElement.style.filter='progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)';
+ }
+ }
+
+ }
+
+ this.shown = true;
+
+ if (this.parentMenu)
+ this.parentMenu.show();
+};
+
+WebFXMenu.prototype.hide = function () {
+ this.hideAllSubs();
+ var divElement = document.getElementById(this.id);
+ if ( divElement ) {
+ divElement.style.visibility = "hidden";
+ if ( ie ) {
+ var shimElement = document.getElementById(this.id + "Shim");
+ if ( shimElement ) {
+ shimElement.style.display = "none";
+ }
+ }
+ }
+
+ this.shown = false;
+};
+
+WebFXMenu.prototype.hideAllSubs = function () {
+ for (var i = 0; i < this._subMenus.length; i++) {
+ if (this._subMenus[i].shown)
+ this._subMenus[i].hide();
+ }
+};
+
+WebFXMenu.prototype.toString = function () {
+ var top = this.top + this.borderTop + this.paddingTop;
+ var str = "<div id='" + this.id + "' class='webfx-menu' style='" +
+ "width:" + (!ieBox ?
+ this.width - this.borderLeft - this.paddingLeft - this.borderRight - this.paddingRight :
+ this.width) + "px;" +
+ (this.useAutoPosition ?
+ "left:" + this.left + "px;" + "top:" + this.top + "px;" :
+ "") +
+ (ie50 ? "filter: none;" : "") +
+ "'>";
+
+ if (this._menuItems.length == 0) {
+ str += "<span class='webfx-menu-empty'>" + this.emptyText + "</span>";
+ }
+ else {
+ str += '<span class="webfx-menu-title" onmouseover="webFXMenuHandler.overMenuItem(this)"' +
+ (webfxMenuUseHover ? " onmouseout='webFXMenuHandler.outMenuItem(this)'" : "") +
+ '>' + this.emptyText + '</span>';
+ // str += '<div id="' + this.id + '-title">' + this.emptyText + '</div>';
+ // loop through all menuItems
+ for (var i = 0; i < this._menuItems.length; i++) {
+ var mi = this._menuItems[i];
+ str += mi;
+ if (!this.useAutoPosition) {
+ if (mi.subMenu && !mi.subMenu.useAutoPosition)
+ mi.subMenu.top = top - mi.subMenu.borderTop - mi.subMenu.paddingTop;
+ top += mi.height;
+ }
+ }
+
+ }
+
+ str += "</div>";
+
+ if ( ie ) {
+ str += "<iframe id='" + this.id + "Shim' src='javascript:false;' scrolling='no' frameBorder='0' style='position:absolute; top:0px; left: 0px; display:none;'></iframe>";
+ }
+
+ for (var i = 0; i < this._subMenus.length; i++) {
+ this._subMenus[i].left = this.left + this.width - this._subMenus[i].borderLeft;
+ str += this._subMenus[i];
+ }
+
+ return str;
+};
+// WebFXMenu.prototype.position defined later
+
+function WebFXMenuItem(sText, sHref, sToolTip, oSubMenu) {
+ this.text = sText || webfxMenuItemDefaultText;
+ this.href = (sHref == null || sHref == "") ? webfxMenuItemDefaultHref : sHref;
+ this.subMenu = oSubMenu;
+ if (oSubMenu)
+ oSubMenu.parentMenuItem = this;
+ this.toolTip = sToolTip;
+ this.id = webFXMenuHandler.getId();
+ webFXMenuHandler.all[this.id] = this;
+};
+WebFXMenuItem.prototype.height = webfxMenuItemDefaultHeight;
+WebFXMenuItem.prototype.toString = function () {
+ return "<a" +
+ " id='" + this.id + "'" +
+ " href=\"" + this.href + "\"" +
+ (this.toolTip ? " title=\"" + this.toolTip + "\"" : "") +
+ " onmouseover='webFXMenuHandler.overMenuItem(this)'" +
+ (webfxMenuUseHover ? " onmouseout='webFXMenuHandler.outMenuItem(this)'" : "") +
+ (this.subMenu ? " unselectable='on' tabindex='-1'" : "") +
+ ">" +
+ (this.subMenu ? "<img class='arrow' src=\"" + webfxMenuImagePath + "arrow.right.black.png\">" : "") +
+ this.text +
+ "</a>";
+};
+
+
+function WebFXMenuSeparator() {
+ this.id = webFXMenuHandler.getId();
+ webFXMenuHandler.all[this.id] = this;
+};
+WebFXMenuSeparator.prototype.height = webfxMenuSeparatorDefaultHeight;
+WebFXMenuSeparator.prototype.toString = function () {
+ return "<div" +
+ " id='" + this.id + "'" +
+ (webfxMenuUseHover ?
+ " onmouseover='webFXMenuHandler.overMenuItem(this)'" +
+ " onmouseout='webFXMenuHandler.outMenuItem(this)'"
+ :
+ "") +
+ "></div>"
+};
+
+function WebFXMenuBar() {
+ this._parentConstructor = WebFXMenu;
+ this._parentConstructor();
+}
+WebFXMenuBar.prototype = new WebFXMenu;
+WebFXMenuBar.prototype.toString = function () {
+ var str = "<div id='" + this.id + "' class='webfx-menu-bar'>";
+
+ // loop through all menuButtons
+ for (var i = 0; i < this._menuItems.length; i++)
+ str += this._menuItems[i];
+
+ str += "</div>";
+
+ for (var i = 0; i < this._subMenus.length; i++)
+ str += this._subMenus[i];
+
+ return str;
+};
+
+function WebFXMenuButton(sText, sHref, sToolTip, oSubMenu) {
+ this._parentConstructor = WebFXMenuItem;
+ this._parentConstructor(sText, sHref, sToolTip, oSubMenu);
+}
+WebFXMenuButton.prototype = new WebFXMenuItem;
+WebFXMenuButton.prototype.toString = function () {
+ return "<a" +
+ " id='" + this.id + "'" +
+ " href='" + this.href + "'" +
+ (this.toolTip ? " title='" + this.toolTip + "'" : "") +
+ (webfxMenuUseHover ?
+ (" onmouseover='webFXMenuHandler.overMenuItem(this)'" +
+ " onmouseout='webFXMenuHandler.outMenuItem(this)'") :
+ (
+ " onfocus='webFXMenuHandler.overMenuItem(this)'" +
+ (this.subMenu ?
+ " onblur='webFXMenuHandler.blurMenu(this)'" :
+ ""
+ )
+ )) +
+ ">" +
+ this.text +
+ (this.subMenu ? "<img class='arrow' src='" + webfxMenuImagePath + "arrow.down.png'>" : "") +
+ "</a>";
+};
+
+
+
+
+
+/* Position functions */
+
+
+function getInnerLeft(el, debug) {
+
+ if (el == null) return 0;
+
+ if (ieBox && el == document.body || !ieBox && el == document.documentElement) return 0;
+
+ //if ( debug )
+ // alert ( 'getInnerLeft: ' + getLeft(el) + ' - ' + getBorderLeft(el) );
+
+ return parseInt( getLeft(el) + parseInt(getBorderLeft(el)) );
+
+}
+
+
+
+function getLeft(el, debug) {
+
+ if (el == null) return 0;
+
+ //if ( debug )
+ // alert ( el + ': ' + el.offsetLeft + ' - ' + getInnerLeft(el.offsetParent) );
+
+ return parseInt( el.offsetLeft + parseInt(getInnerLeft(el.offsetParent)) );
+
+}
+
+
+
+function getInnerTop(el) {
+
+ if (el == null) return 0;
+
+ if (ieBox && el == document.body || !ieBox && el == document.documentElement) return 0;
+
+ return parseInt( getTop(el) + parseInt(getBorderTop(el)) );
+
+}
+
+
+
+function getTop(el) {
+
+ if (el == null) return 0;
+
+ return parseInt( el.offsetTop + parseInt(getInnerTop(el.offsetParent)) );
+
+}
+
+
+
+function getBorderLeft(el) {
+
+ return ie ?
+
+ el.clientLeft :
+
+ ( khtml
+ ? parseInt(document.defaultView.getComputedStyle(el, null).getPropertyValue("border-left-width"))
+ : parseInt(window.getComputedStyle(el, null).getPropertyValue("border-left-width"))
+ );
+
+}
+
+
+
+function getBorderTop(el) {
+
+ return ie ?
+
+ el.clientTop :
+
+ ( khtml
+ ? parseInt(document.defaultView.getComputedStyle(el, null).getPropertyValue("border-left-width"))
+ : parseInt(window.getComputedStyle(el, null).getPropertyValue("border-top-width"))
+ );
+
+}
+
+
+
+function opera_getLeft(el) {
+
+ if (el == null) return 0;
+
+ return el.offsetLeft + opera_getLeft(el.offsetParent);
+
+}
+
+
+
+function opera_getTop(el) {
+
+ if (el == null) return 0;
+
+ return el.offsetTop + opera_getTop(el.offsetParent);
+
+}
+
+
+
+function getOuterRect(el, debug) {
+
+ return {
+
+ left: (opera ? opera_getLeft(el) : getLeft(el, debug)),
+
+ top: (opera ? opera_getTop(el) : getTop(el)),
+
+ width: el.offsetWidth,
+
+ height: el.offsetHeight
+
+ };
+
+}
+
+
+
+// mozilla bug! scrollbars not included in innerWidth/height
+
+function getDocumentRect(el) {
+
+ return {
+
+ left: 0,
+
+ top: 0,
+
+ width: (ie ?
+
+ (ieBox ? document.body.clientWidth : document.documentElement.clientWidth) :
+
+ window.innerWidth
+
+ ),
+
+ height: (ie ?
+
+ (ieBox ? document.body.clientHeight : document.documentElement.clientHeight) :
+
+ window.innerHeight
+
+ )
+
+ };
+
+}
+
+
+
+function getScrollPos(el) {
+
+ return {
+
+ left: (ie ?
+
+ (ieBox ? document.body.scrollLeft : document.documentElement.scrollLeft) :
+
+ window.pageXOffset
+
+ ),
+
+ top: (ie ?
+
+ (ieBox ? document.body.scrollTop : document.documentElement.scrollTop) :
+
+ window.pageYOffset
+
+ )
+
+ };
+
+}
+
+
+/* end position functions */
+
+WebFXMenu.prototype.position = function (relEl, sDir) {
+ var dir = sDir;
+ // find parent item rectangle, piRect
+ var piRect;
+ if (!relEl) {
+ var pi = this.parentMenuItem;
+ if (!this.parentMenuItem)
+ return;
+
+ relEl = document.getElementById(pi.id);
+ if (dir == null)
+ dir = pi instanceof WebFXMenuButton ? "vertical" : "horizontal";
+ //alert('created RelEl from parent: ' + pi.id);
+ piRect = getOuterRect(relEl, 1);
+ }
+ else if (relEl.left != null && relEl.top != null && relEl.width != null && relEl.height != null) { // got a rect
+ //alert('passed a Rect as RelEl: ' + typeof(relEl));
+
+ piRect = relEl;
+ }
+ else {
+ //alert('passed an element as RelEl: ' + typeof(relEl));
+ piRect = getOuterRect(relEl);
+ }
+
+ var menuEl = document.getElementById(this.id);
+ var menuRect = getOuterRect(menuEl);
+ var docRect = getDocumentRect();
+ var scrollPos = getScrollPos();
+ var pMenu = this.parentMenu;
+
+ if (dir == "vertical") {
+ if (piRect.left + menuRect.width - scrollPos.left <= docRect.width) {
+ //alert('piRect.left: ' + piRect.left);
+ this.left = piRect.left;
+// if ( ! ie )
+// this.left = this.left + 138;
+ } else if (docRect.width >= menuRect.width) {
+ //konq (not safari though) winds up here by accident and positions the menus all weird
+ //alert('docRect.width + scrollPos.left - menuRect.width');
+
+ this.left = docRect.width + scrollPos.left - menuRect.width;
+ } else {
+ //alert('scrollPos.left: ' + scrollPos.left);
+ this.left = scrollPos.left;
+ }
+
+ if (piRect.top + piRect.height + menuRect.height <= docRect.height + scrollPos.top)
+
+ this.top = piRect.top + piRect.height;
+
+ else if (piRect.top - menuRect.height >= scrollPos.top)
+
+ this.top = piRect.top - menuRect.height;
+
+ else if (docRect.height >= menuRect.height)
+
+ this.top = docRect.height + scrollPos.top - menuRect.height;
+
+ else
+
+ this.top = scrollPos.top;
+ }
+ else {
+ if (piRect.top + menuRect.height - this.borderTop - this.paddingTop <= docRect.height + scrollPos.top)
+
+ this.top = piRect.top - this.borderTop - this.paddingTop;
+
+ else if (piRect.top + piRect.height - menuRect.height + this.borderTop + this.paddingTop >= 0)
+
+ this.top = piRect.top + piRect.height - menuRect.height + this.borderBottom + this.paddingBottom + this.shadowBottom;
+
+ else if (docRect.height >= menuRect.height)
+
+ this.top = docRect.height + scrollPos.top - menuRect.height;
+
+ else
+
+ this.top = scrollPos.top;
+
+
+
+ var pMenuPaddingLeft = pMenu ? pMenu.paddingLeft : 0;
+
+ var pMenuBorderLeft = pMenu ? pMenu.borderLeft : 0;
+
+ var pMenuPaddingRight = pMenu ? pMenu.paddingRight : 0;
+
+ var pMenuBorderRight = pMenu ? pMenu.borderRight : 0;
+
+
+
+ if (piRect.left + piRect.width + menuRect.width + pMenuPaddingRight +
+
+ pMenuBorderRight - this.borderLeft + this.shadowRight <= docRect.width + scrollPos.left)
+
+ this.left = piRect.left + piRect.width + pMenuPaddingRight + pMenuBorderRight - this.borderLeft;
+
+ else if (piRect.left - menuRect.width - pMenuPaddingLeft - pMenuBorderLeft + this.borderRight + this.shadowRight >= 0)
+
+ this.left = piRect.left - menuRect.width - pMenuPaddingLeft - pMenuBorderLeft + this.borderRight + this.shadowRight;
+
+ else if (docRect.width >= menuRect.width)
+
+ this.left = docRect.width + scrollPos.left - menuRect.width;
+
+ else
+
+ this.left = scrollPos.left;
+ }
+};
diff --git a/httemplate/images/arrow.down.png b/httemplate/images/arrow.down.png
index 675d84bde..34cb0286a 100644
--- a/httemplate/images/arrow.down.png
+++ b/httemplate/images/arrow.down.png
Binary files differ
diff --git a/httemplate/images/menu-left-example.png b/httemplate/images/menu-left-example.png
new file mode 100644
index 000000000..375725cb9
--- /dev/null
+++ b/httemplate/images/menu-left-example.png
Binary files differ
diff --git a/httemplate/images/menu-top-example.png b/httemplate/images/menu-top-example.png
new file mode 100644
index 000000000..bd9bea883
--- /dev/null
+++ b/httemplate/images/menu-top-example.png
Binary files differ
diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html
index a342a51ec..221edc682 100644
--- a/httemplate/pref/pref-process.html
+++ b/httemplate/pref/pref-process.html
@@ -1,26 +1,41 @@
% my $error = '';
%
-% my $access_user = qsearchs( 'access_user', {
-% 'username' => getotaker,
-% '_password' => $cgi->param('_password'),
-% } );
+% my $access_user;
+% if ( grep { $cgi->param($_) !~ /^\s*$/ }
+% qw(_password new_password new_password2)
+% ) {
%
-% $error = 'Current password incorrect; password not changed'
-% unless $access_user;
+% my $access_user = qsearchs( 'access_user', {
+% 'username' => getotaker,
+% '_password' => $cgi->param('_password'),
+% } );
%
-% $error ||= "New passwords don't match"
-% unless $cgi->param('new_password') eq $cgi->param('new_password2');
+% $error = 'Current password incorrect; password not changed'
+% unless $access_user;
%
-% $error ||= "No new password entered"
-% unless length($cgi->param('new_password'));
+% $error ||= "New passwords don't match"
+% unless $cgi->param('new_password') eq $cgi->param('new_password2');
%
-% $access_user->_password($cgi->param('new_password')) unless $error;
-% $error ||= $access_user->replace;
+% $error ||= "No new password entered"
+% unless length($cgi->param('new_password'));
+%
+% $access_user->_password($cgi->param('new_password')) unless $error;
+%
+% } else {
+%
+% $access_user = $FS::CurrentUser::CurrentUser;
+%
+% }
+%
+% $error ||= $access_user->replace( { map { $_ => scalar($cgi->param($_)) }
+% qw( menu_position ) #XXX autogen
+% }
+% );
%
% if ( $error ) {
% $cgi->param('error', $error);
% print $cgi->redirect(popurl(1). "pref.html?". $cgi->query_string );
% } else {
-<% include('/elements/header.html', 'Password changed') %>
+<% include('/elements/header.html', 'Preferences updated') %>
<% include('/elements/footer.html') %>
% }
diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html
index 259523941..2dca3b84d 100644
--- a/httemplate/pref/pref.html
+++ b/httemplate/pref/pref.html
@@ -4,6 +4,8 @@
<% include('/elements/error.html') %>
+
+Change password (leave blank for no change)
<% ntable("#cccccc",2) %>
<TR>
@@ -22,7 +24,31 @@
</TR>
</TABLE>
+<BR>
+
+Interface
+<% ntable("#cccccc",2) %>
+
+<TR>
+ <TD>Menu location: </TD>
+ <TD>
+ <INPUT TYPE="radio" NAME="menu_position" VALUE="left" onClick="document.images['menu_example'].src='../images/menu-left-example.png';" <% $menu_position eq 'left' ? ' CHECKED' : ''%>> Left<BR>
+ <INPUT TYPE="radio" NAME="menu_position" VALUE="top"onClick="document.images['menu_example'].src='../images/menu-top-example.png';" <% $menu_position eq 'top' ? ' CHECKED' : ''%>> Top <BR>
+ </TD>
+ <TD><IMG NAME="menu_example" SRC="../images/menu-<% $menu_position %>-example.png"></TD>
+</TR>
+
+</TABLE>
+<BR>
-<INPUT TYPE="submit" VALUE="Change password">
+<INPUT TYPE="submit" VALUE="Update preferences">
<% include('/elements/footer.html') %>
+<%init>
+
+# XSS via your own preferences? seems unlikely, but nice try anyway...
+( $FS::CurrentUser::CurrentUser->option('menu_position') || 'left' )
+ =~ /^(\w+)$/ or die "illegal menu_position";
+my $menu_position = $1;
+
+</%init>