X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Flib%2FRT%2FInterface%2FWeb%2FMenu.pm;h=03ce8ac5ac85d78381358bf6e183ccd2eedd671c;hp=3b6ce888e2edf5818a14413fd626d2358245ff78;hb=919e930aa9279b3c5cd12b593889cd6de79d67bf;hpb=0fb307c305e4bc2c9c27dc25a3308beae3a4d33c diff --git a/rt/lib/RT/Interface/Web/Menu.pm b/rt/lib/RT/Interface/Web/Menu.pm index 3b6ce888e..03ce8ac5a 100644 --- a/rt/lib/RT/Interface/Web/Menu.pm +++ b/rt/lib/RT/Interface/Web/Menu.pm @@ -2,7 +2,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -48,22 +48,325 @@ package RT::Interface::Web::Menu; +use strict; +use warnings; + + +use base qw/Class::Accessor::Fast/; +use URI; +use Scalar::Util qw(weaken); + +__PACKAGE__->mk_accessors(qw( + key title description raw_html escape_title sort_order target class +)); + +=head1 NAME + +RT::Interface::Web::Menu - Handle the API for menu navigation + +=head1 METHODS + +=head2 new PARAMHASH + +Creates a new L object. Possible keys in the +I are L, L, L, L, +L, L, L, L, L and +L. See the subroutines with the respective name below for +each option's use. + +=cut sub new { - my $class = shift; - my $self = bless {}, $class; - $self->{'root_node'} = RT::Interface::Web::Menu::Item->new(); + my $package = shift; + my $args = ref($_[0]) eq 'HASH' ? shift @_ : {@_}; + + my $parent = delete $args->{'parent'}; + $args->{sort_order} ||= 0; + + # Class::Accessor only wants a hashref; + my $self = $package->SUPER::new( $args ); + + # make sure our reference is weak + $self->parent($parent) if defined $parent; + return $self; } -sub as_hash_of_hashes { +=head2 title [STRING] + +Sets or returns the string that the menu item will be displayed as. + +=head2 escape_title [BOOLEAN] + +Sets or returns whether or not to HTML escape the title before output. + +=head2 parent [MENU] + +Gets or sets the parent L of this item; this defaults +to null. This ensures that the reference is weakened. + +=head2 raw_html [STRING] + +Sets the content of this menu item to a raw blob of HTML. When building the +menu, rather than constructing a link, we will return this raw content. No +escaping is done. + +=cut + +sub parent { + my $self = shift; + if (@_) { + $self->{parent} = shift; + weaken $self->{parent}; + } + + return $self->{parent}; +} + + +=head2 sort_order [NUMBER] + +Gets or sets the sort order of the item, as it will be displayed under +the parent. This defaults to adding onto the end. + +=head2 target [STRING] + +Get or set the frame or pseudo-target for this link. something like L<_blank> + +=head2 class [STRING] + +Gets or sets the CSS class the menu item should have in addition to the default +classes. This is only used if L isn't specified. + +=head2 path + +Gets or sets the URL that the menu's link goes to. If the link +provided is not absolute (does not start with a "/"), then is is +treated as relative to it's parent's path, and made absolute. + +=cut + +sub path { + my $self = shift; + if (@_) { + if (defined($self->{path} = shift)) { + my $base = ($self->parent and $self->parent->path) ? $self->parent->path : ""; + $base .= "/" unless $base =~ m{/$}; + my $uri = URI->new_abs($self->{path}, $base); + $self->{path} = $uri->as_string; + } + } + return $self->{path}; +} + +=head2 active [BOOLEAN] + +Gets or sets if the menu item is marked as active. Setting this +cascades to all of the parents of the menu item. + +This is currently B. + +=cut + +sub active { + my $self = shift; + if (@_) { + $self->{active} = shift; + $self->parent->active($self->{active}) if defined $self->parent; + } + return $self->{active}; +} + +=head2 child KEY [, PARAMHASH] + +If only a I is provided, returns the child with that I. + +Otherwise, creates or overwrites the child with that key, passing the +I to L. Additionally, the paramhash's +L defaults to the I, and the L defaults to the +pre-existing child's sort order (if a C is being over-written) or +the end of the list, if it is a new C. + +If the paramhash contains a key called C, that will be used instead +of creating a new RT::Interface::Web::Menu. + + +=cut + +sub child { + my $self = shift; + my $key = shift; + my $proto = ref $self || $self; + + if ( my %args = @_ ) { + + # Clear children ordering cache + delete $self->{children_list}; + + my $child; + if ( $child = $args{menu} ) { + $child->parent($self); + } else { + $child = $proto->new( + { parent => $self, + key => $key, + title => $key, + escape_title=> 1, + %args + } + ); + } + $self->{children}{$key} = $child; + + $child->sort_order( $args{sort_order} || (scalar values %{ $self->{children} }) ) + unless ($child->sort_order()); + + # URL is relative to parents, and cached, so set it up now + $child->path( $child->{path} ); + + # Figure out the URL + my $path = $child->path; + + # Activate it + if ( defined $path and length $path ) { + my $base_path = $HTML::Mason::Commands::r->path_info; + my $query = $HTML::Mason::Commands::m->cgi_object->query_string; + $base_path =~ s!/+!/!g; + $base_path .= "?$query" if defined $query and length $query; + + $base_path =~ s/index\.html$//; + $base_path =~ s/\/+$//; + $path =~ s/index\.html$//; + $path =~ s/\/+$//; + + if ( $path eq $base_path ) { + $self->{children}{$key}->active(1); + } + } + } + + return $self->{children}{$key}; +} + +=head2 active_child + +Returns the first active child node, or C is there is none. + +=cut + +sub active_child { + my $self = shift; + foreach my $kid ($self->children) { + return $kid if $kid->active; + } + return undef; +} + + +=head2 delete KEY + +Removes the child with the provided I. + +=cut + +sub delete { + my $self = shift; + my $key = shift; + delete $self->{children_list}; + delete $self->{children}{$key}; +} + + +=head2 has_children + +Returns true if there are any children on this menu +=cut + +sub has_children { + my $self = shift; + if (@{ $self->children}) { + return 1 + } else { + return 0; + } +} + + +=head2 children + +Returns the children of this menu item in sorted order; as an array in +array context, or as an array reference in scalar context. + +=cut + +sub children { + my $self = shift; + my @kids; + if ($self->{children_list}) { + @kids = @{$self->{children_list}}; + } else { + @kids = values %{$self->{children} || {}}; + @kids = sort {$a->{sort_order} <=> $b->{sort_order}} @kids; + $self->{children_list} = \@kids; + } + return wantarray ? @kids : \@kids; } -sub root { +=head2 add_after + +Called on a child, inserts a new menu item after it and shifts any other +menu items at this level to the right. + +L by default would insert at the end of the list of children, unless you +did manual sort_order calculations. + +Takes all the regular arguments to L. + +=cut + +sub add_after { shift->_insert_sibling("after", @_) } + +=head2 add_before + +Called on a child, inserts a new menu item at the child's location and shifts +the child and the other menu items at this level to the right. + +L by default would insert at the end of the list of children, unless you +did manual sort_order calculations. + +Takes all the regular arguments to L. + +=cut + +sub add_before { shift->_insert_sibling("before", @_) } + +sub _insert_sibling { my $self = shift; - return $self->{'root_node'}; + my $where = shift; + my $parent = $self->parent; + my $sort_order; + for my $contemporary ($parent->children) { + if ( $contemporary->key eq $self->key ) { + if ($where eq "before") { + # Bump the current child and the following + $sort_order = $contemporary->sort_order; + } + elsif ($where eq "after") { + # Leave the current child along, bump the rest + $sort_order = $contemporary->sort_order + 1; + next; + } + else { + # never set $sort_order, act no differently than ->child() + } + } + if ( $sort_order ) { + $contemporary->sort_order( $contemporary->sort_order + 1 ); + } + } + $parent->child( @_, sort_order => $sort_order ); } 1;