top bar option!
authorivan <ivan>
Fri, 26 Jan 2007 08:04:37 +0000 (08:04 +0000)
committerivan <ivan>
Fri, 26 Jan 2007 08:04:37 +0000 (08:04 +0000)
14 files changed:
FS/FS/Record.pm
FS/FS/access_user.pm
FS/FS/m2m_Common.pm
FS/FS/option_Common.pm
httemplate/elements/header.html
httemplate/elements/menu.html
httemplate/elements/xmenu.css
httemplate/elements/xmenu.top.css [new file with mode: 0644]
httemplate/elements/xmenu.top.js [new file with mode: 0644]
httemplate/images/arrow.down.png
httemplate/images/menu-left-example.png [new file with mode: 0644]
httemplate/images/menu-top-example.png [new file with mode: 0644]
httemplate/pref/pref-process.html
httemplate/pref/pref.html

index 34b556e..6b7e8d5 100644 (file)
@@ -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;
       };
index f45f17d..9be9166 100644 (file)
@@ -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';
index fd8700a..5dc2a8e 100644 (file)
@@ -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;
   '';
 }
 
index ad3c269..0efacbd 100644 (file)
@@ -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
index 0e69b19..1f56748 100644 (file)
@@ -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>
index 25dd619..94bb0e0 100644 (file)
@@ -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">
index 46b221b..97c7da8 100644 (file)
@@ -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;
        */
        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 (file)
index 0000000..7591703
--- /dev/null
@@ -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 (file)
index 0000000..8d81035
--- /dev/null
@@ -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;
+       }
+};
index 675d84b..34cb028 100644 (file)
Binary files a/httemplate/images/arrow.down.png and b/httemplate/images/arrow.down.png differ
diff --git a/httemplate/images/menu-left-example.png b/httemplate/images/menu-left-example.png
new file mode 100644 (file)
index 0000000..375725c
Binary files /dev/null and b/httemplate/images/menu-left-example.png differ
diff --git a/httemplate/images/menu-top-example.png b/httemplate/images/menu-top-example.png
new file mode 100644 (file)
index 0000000..bd9bea8
Binary files /dev/null and b/httemplate/images/menu-top-example.png differ
index a342a51..221edc6 100644 (file)
@@ -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') %>
 % }
index 2595239..2dca3b8 100644 (file)
@@ -4,6 +4,8 @@
 
 <% include('/elements/error.html') %>
 
+
+Change password (leave blank for no change)
 <% ntable("#cccccc",2) %>
 
 <TR>
 </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>