Search code examples
perluser-interfacemenuconditional-statementssubmenu

Conditionally Excluding Menu Choices From a Menu


I wrote a Perl module that can build simple menus and manage them, but now I need to figure out how to conditionally hide menu choices when I don't want them to be available.

For example how could I make it hide "Choice2" in $menu1 if a specific condition is met?

This question is somewhat of a continuation off of one of my other questions: How can I build a simple menu in Perl?

I've made quite a bit of progress since I've started this, but I seem to have hit a roadblock.

The Menu module looks like this:

# Menu.pm

#!/usr/bin/perl

package Menu;

use strict;
use warnings;

# Menu constructor
sub new {

    # Unpack input arguments
    my $class       = shift;
    my (%args)      = @_;
    my $title       = $args{title};
    my $choices_ref = $args{choices};
    my $noexit      = $args{noexit};

    # Bless the menu object
    my $self = bless {
        title   => $title,
        choices => $choices_ref,
        noexit  => $noexit,
    }, $class;

    return $self;
}

# Print the menu
sub print {

    # Unpack input arguments
    my $self    = shift;
    my $title   =   $self->{title  };
    my @choices = @{$self->{choices}};
    my $noexit  =   $self->{noexit };

    # Print menu
    for (;;) {

        # Clear the screen
        system 'cls';

        # Print menu title
        print "========================================\n";
        print "    $title\n";
        print "========================================\n";

        # Print menu options
        my $index = 0;
        for my $choice(@choices) {
            printf "%2d. %s\n", ++$index, $choice->{text};
        }
        printf "%2d. %s\n", '0', 'Exit' unless $noexit;

        print "\n?: ";

        # Get user input
        chomp (my $input = <STDIN>);

        print "\n";

        # Process input
        if ($input =~ m/\d+/ && $input >= 1 && $input <= $index) {
            return $choices[$input - 1]{code}->();
        } elsif ($input =~ m/\d+/ && !$input && !$noexit) {
            print "Exiting . . .\n";
            exit 0;
        } else {
            print "Invalid input.\n\n";
            system 'pause';
        }
    }
}

1;

Here is an example of how the module is used:

# test.pl

#!/usr/bin/perl

use strict;
use warnings;

use Menu;

my $menu1;
my $menu2;

# define menu1 choices
my @menu1_choices = (
    { text => 'Choice1',
      code => sub { print "I did something!\n"; }},
    { text => 'Choice2',
      code => sub { print "I did something else!\n"; }},
    { text => 'Go to Menu2',
      code => sub { $menu2->print(); }},
);

# define menu2 choices
my @menu2_choices = (
    { text => 'Choice1',
      code => sub { print "I did something in menu 2!\n"; }},
    { text => 'Choice2',
      code => sub { print "I did something else in menu 2!\n"; }},
    { text => 'Go to Menu1',
      code => sub { $menu1->print(); }},
);

# Build menu1
$menu1 = Menu->new(
    title   => 'Menu1',
    choices => \@menu1_choices,
);

# Build menu2
$menu2 = Menu->new(
    title   => 'Menu2',
    choices => \@menu2_choices,
);

# Print menu1
$menu1->print();

Since the menus choices are defined as an array of hashes, I'm not sure how I can conditionally exclude specific options if I don't want them to be shown.

Is there an easy way to do this?


Solution

  • Thanks lovedatsnow for the idea to make the items their own object! I basically took your answer and revamped it to make the interface for creating a menu look much cleaner than it did before.

    Here is my new code:

    # test.pl
    
    #!/usr/bin/perl
    
    # Always use these
    use strict;
    use warnings;
    
    # Other use statements
    use Menu;
    
    # Create a menu object
    my $menu = Menu->new();
    
    # Add a menu item
    $menu->add(
        'Test'  => sub { print "This is a test\n";  system 'pause'; },
        'Test2' => sub { print "This is a test2\n"; system 'pause'; },
        'Test3' => sub { print "This is a test3\n"; system 'pause'; },
    );
    
    # Disable a menu item
    $menu->disable('Test2');
    $menu->print();
    
    # Enable a menu item
    $menu->enable('Test2');
    $menu->print();
    

    I created a menu class with a few useful functions that allow you to act directly on the menu items. This allows you to enable/disable them easily.

    # Menu.pm
    
    #!/usr/bin/perl
    
    package Menu;
    
    # Always use these
    use strict;
    use warnings;
    
    # Other use statements
    use Carp;
    use Menu::Item;
    
    # Menu constructor
    sub new {
    
        # Unpack input arguments
        my ($class, $title) = @_;
    
        # Define a default title
        if (!defined $title) {
            $title = 'MENU';
        }
    
        # Bless the Menu object
        my $self = bless {
            _title => $title,
            _items => [],
        }, $class;
    
        return $self;
    }
    
    # Title accessor method
    sub title {
        my ($self, $title) = @_;
        $self->{_title} = $title if defined $title;
        return $self->{_title};
    }
    
    # Items accessor method
    sub items {
        my ($self, $items) = @_;
        $self->{_items} = $items if defined $items;
        return $self->{_items};
    }
    
    # Add item(s) to the menu
    sub add {
    
        # Unpack input arguments
        my ($self, @add) = @_;
        croak 'add() requires name-action pairs' unless @add % 2 == 0;
    
        # Add new items
        while (@add) {
            my ($name, $action) = splice @add, 0, 2;
    
            # If the item already exists, remove it
            for my $index(0 .. $#{$self->{_items}}) {
                if ($name eq $self->{_items}->[$index]->name()) {
                    splice @{$self->{_items}}, $index, 1;
                }
            }
    
            # Add the item to the end of the menu
            my $item = Menu::Item->new($name, $action);
            push @{$self->{_items}}, $item;
        }
    
        return 0;
    }
    
    # Remove item(s) from the menu
    sub remove {
    
        # Unpack input arguments
        my ($self, @remove) = @_;
    
        # Remove items
        for my $name(@remove) {
    
            # If the item exists, remove it
            for my $index(0 .. $#{$self->{_items}}) {
                if ($name eq $self->{_items}->[$index]->name()) {
                    splice @{$self->{_items}}, $index, 1;
                }
            }
        }
    
        return 0;
    }
    
    # Disable item(s)
    sub disable {
    
        # Unpack input arguments
        my ($self, @disable) = @_;
    
        # Disable items
        for my $name(@disable) {
    
            # If the item exists, disable it
            for my $index(0 .. $#{$self->{_items}}) {
                if ($name eq $self->{_items}->[$index]->name()) {
                    $self->{_items}->[$index]->active(0);
                }
            }
        }
    
        return 0;
    }
    
    # Enable item(s)
    sub enable {
    
        # Unpack input arguments
        my ($self, @enable) = @_;
    
        # Disable items
        for my $name(@enable) {
    
            # If the item exists, enable it
            for my $index(0 .. $#{$self->{_items}}) {
                if ($name eq $self->{_items}->[$index]->name()) {
                    $self->{_items}->[$index]->active(1);
                }
            }
        }
    }
    
    # Print the menu
    sub print {
    
        # Unpack input arguments
        my ($self) = @_;
    
        # Print the menu
        for (;;) {
            system 'cls';
    
            # Print the title
            print "========================================\n";
            print "    $self->{_title}\n";
            print "========================================\n";
    
            # Print menu items
            for my $index(0 .. $#{$self->{_items}}) {
                my $name   = $self->{_items}->[$index]->name();
                my $active = $self->{_items}->[$index]->active();
                if ($active) {
                    printf "%2d. %s\n", $index + 1, $name;
                } else {
                    print "\n";
                }
            }
            printf "%2d. %s\n", 0, 'Exit';
    
            # Get user input
            print "\n?: ";
            chomp (my $input = <STDIN>);
    
            # Process user input
            if ($input =~ m/\d+/ && $input > 0 && $input <= scalar @{$self->{_items}}) {
                my $action = $self->{_items}->[$input - 1]->action();
                my $active = $self->{_items}->[$input - 1]->active();
                if ($active) {
                    print "\n";
                    return $action->();
                }
            } elsif ($input =~ m/\d+/ && $input == 0) {
                return 0;
            }
    
            # Deal with invalid input
            print "\nInvalid input.\n\n";
            system 'pause';
        }
    }
    
    1;
    

    Lastly I created the Menu::Item class that allows you to create individual items and stores the necessary information for each item. (Note that this is stored in a folder named "Menu" in order for the reference to work properly).

    # Item.pm
    
    #!/usr/bin/perl
    
    package Menu::Item;
    
    # Always use these
    use strict;
    use warnings;
    
    # Menu::Item constructor
    sub new {
    
        # Unpack input arguments
        my ($class, $name, $action) = @_;
    
        # Bless the Menu::Item object
        my $self = bless {
            _name   => $name,
            _action => $action,
            _active => 1,
        }, $class;
    
        return $self;
    }
    
    # Name accessor method
    sub name {
        my ($self, $name) = @_;
        $self->{_name} = $name if defined $name;
        return $self->{_name};
    }
    
    # Action accessor method
    sub action {
        my ($self, $action) = @_;
        $self->{_action} = $action if defined $action;
        return $self->{_action};
    }
    
    # Active accessor method
    sub active {
        my ($self, $active) = @_;
        $self->{_active} = $active if defined $active;
        return $self->{_active};
    }
    
    1;
    

    This creates a very elegant interface for building and using menus!

    No more ugly arrays of hashes. :)