Notes from a Newbie 15: Edit, Delete and View Members

Notes from a Newbie document the creation and deployment of yardbirdfanclub.org with Perl Catalyst on shared hosting. They are intended for a Perl Catalyst Newbie who would like to study the creation and deployment of a simple Perl Catalyst application.

Login

Before going any further you should probably go to yardbirdfanclub.org, login and test-drive the application's "Members" features. Doing so will help you more easily understand what we will be doing.

Currently your Yardbird application provides no way for members to edit their membership information, cancel their membership, or to view other member's information. The first step we will take to add these features will be to display a "Members" link in the main menu at the top of the page when users login.

Replace the "Members" item in your header.tt template with this:

[% IF c.user_exists %] 
<li><a href="[% c.uri_for_action('/member/index') %]">Members</a></li>
[% END %]

After doing so you should have this:

Yardbird/root/src/header.tt:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>[% title %]</title>
  <link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/main.css') %]" />
</head>
<body>
  <div id="wrapper">
    <div id="main_menu">
      <ul>
        <div id="main_menu_left">
          <li><a href="[% c.uri_for_action('/index') %]">Home</a></li>
          <li><a href="[% c.uri_for_action('blog/index') %]">Blog</a></li>
          <li>Events</li>
          <li>Links</li>
          <li>News</li>
          <li>Photos</li>
          <li>Video</li>
          [% IF c.user_exists %] 
            <li><a href="[% c.uri_for_action('/member/index') %]">Members</a></li>
          [% END %] 
        </div>
        <div id="main_menu_right">
          [% IF c.user_exists %] 
            <li>[% c.user.name %] | <a href="[% c.uri_for_action('/logout/index') %]">Logout</a></li>
          [% ELSE %]
            <li><a href="[% c.uri_for_action('/login/index') %]">Login</a> | <a href="[% c.uri_for_action('/member/about') %]">Not a member?</a></li> 
          [% END %]
        </div>  
      </ul>
    </div> <!-- main_menu -->

Next we will edit our controller action that we are linking to:

Yardbird/lib/Yardbird/Controller/Member.pm:

sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  # Detach if user is not logged-in.
  $c->detach('/unauthorized_action') if !$c->user_exists;

  # Sort users by name and put in stash for template sidebar menu.
  $c->stash(users => [sort_users_by_name($self, $c)]);

  $c->detach($c->view("TT"));
}

Recall that we display Member's names in a rather complicated manner, using a Result class method to return a username, firstname, or firstname and lastname, depending on what info members provided for themselves. Without this complexity it would be much easier to use the "order_by" attribute and search in our ResultSet class (as we've done previously and will soon do again) rather than writing our own sort routine.

See:

  • DBIx::Class::ResultSet - ATTRIBUTES - order_by

Since we're using the complicated process described above, we need to create a routine to sort members by name:

Yardbird/lib/Yardbird/Controller/Member.pm:

sub sort_users_by_name {
  my ( $self, $c ) = @_;

  my @users;
  for my $user ($c->model('DB::User')->all) {
    push @users, { id => $user->id, name => $user->name };
  }

  my @sorted_users = sort { "\L$a->{name}" cmp "\L$b->{name}" } @users;

  my @rows;
  for my $user (@sorted_users) {
    push @rows, $c->model('DB::User')->specific($user->{id})->first;
  }

  return @rows;
}

We add the new search method we are using in our sort routine above to our ResultSet class:

Yardbird/lib/Yardbird/Schema/ResultSet/User.pm:

sub specific {
  my ($self, $uID) = @_;

  return $self->search({id => $uID});
}

Finally, we will create the template for our Member.pm controller index action. As we proceed, we will add links to edit, delete and view member info, but for now let's just show the sorted member's names in a sidebar menu:

Yardbird/root/src/member/index.tt:

[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
<div id="header">
  <div id="header_title">[% c.user.name %]</div>
  <div id="header_subtitle">The YARDBIRD Fan Club</div>
</div>

<div id="content_wrapper">
  <div id="sidebar_wrapper">
    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>Members</strong></p>
        [% FOREACH user IN users %]
          <p class="sidebar_item_title">[% user.name %]</p>
        [% END %]
      </div>
    </div>
  </div>

  <div id="content_with_sidebar">
  <h3>Members</h3>
  <p>You may update your personal membership information and view other member's information.</p>
  </div>
</div>

[% INCLUDE footer.tt %]

Run the application:

Yardbird]$ script/yardbird_server.pl -d -r

You should now get a "Members" link in the main menu at the top of the page when you login:

yfc15a1.png

The "Members" link should now be active and give you a sidebar menu with member names sorted:

yfc15a2.png

Edit Members

We will start by creating a new edit action for our Member.pm controller:

Yardbird/lib/Yardbird/Controller/Member.pm:

sub edit :Local :Args(1) {
  my ( $self, $c, $uID ) = @_;

  # Detach if user is not logged-in.
  $c->detach('/unauthorized_action') if !$c->user_exists;

  # Detach if user entered a malicious uri and is trying to edit another user's info.
  $c->detach('/unauthorized_action') if $c->user->id != $uID;

  # Sort users by username and put in stash for template sidebar menu.
  $c->stash(users => [$c->model('DB::User')->all_username]);

  # Prepare to use the form.
  $c->stash(template => 'member/edit.tt', form => $self->form );

  # Validate and update user row in user table.
  return unless $self->form->process(
    item_id => $uID, 
    params => $c->req->params,
    schema => $c->model('DB')->schema,
  );

  # Redirect to Members page.
  $c->res->redirect($c->uri_for_action('/member/index'));
}

If you compare our new edit method to our create method in the Member.pm controller, you won't find anything we haven't already done. Yes, I'm not consistent with my Perl identifiers, i.e. my parameters $user_id and $uID are not consistent - but please forgive me for this. I would fix it if this weren't a series of tutorials, but I don't want to confuse anybody by changing what we've already done.

Notice in our new edit method we are putting a list of users in the stash. I do this so we may display a sidebar menu of usernames, for members to see the ones that have already been used.

You need to add the new search method we use above to our ResultSet Class:

Yardbird/lib/Yardbird/Schema/ResultSet/User.pm:

sub all_username {
  my ($self, $uID) = @_;

  return $self->search({}, {order_by => ['username']});
}

Notice how much easier it is to sort by a table column rather than a Result Class Row method?

We need to create a template for our new edit action, which is almost identical to the template we created to create new members:

Yardbird/root/src/member/edit.tt:

[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
<div id="header">
  <div id="header_title">[% c.user.name %]</div>
  <div id="header_subtitle">The YARDBIRD Fan Club</div>
</div>

<div id="content_wrapper">
  <div id="sidebar_wrapper">
    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>Member Usernames</strong></p>
        [% FOREACH user IN users %]
          <p class="sidebar_item_title">[% user.username %]</p>
        [% END %]
      </div>
    </div>
  </div>

  <div id="content_with_sidebar">
    <h3>Edit Your Membership Information</h3>

    <form id="user_form" name="[% form.name %]" action="[% c.uri_for_action('/member/edit', c.user.id) %]" method="post"> 
        [% f = form.field('username') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        [% f = form.field('password') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="password" name="[% f.name %]" id="[% f.name %]" value=""> 
      <br>
        [% f = form.field('password_confirm') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="password" name="[% f.name %]" id="[% f.name %]" value=""> 
      <br>
        [% f = form.field('email') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="email" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
        [% f = form.field('email_visible') %]
        <input type="checkbox" name="[% f.name %]" id="[% f.name %]" value="1"> 
        <label for="[% f.name %]">Visible</label>
      <br>
        [% f = form.field('firstname') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        [% f = form.field('lastname') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        [% f = form.field('location') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        About me:
        <div id='user_form_textarea'>
        [% form.field('about_me').render %]
        </div>
      <input id="user_form_button" class="button" name="submit" type="submit" value="Submit" />
    </form> 

    <div class="page_divider_with_sidebar"></div>
    <p>Username and Password are required.</p>
    <ul>
      <li>Username must be unique among member usernames.</li>
      <li>They may contain upper and lowercase letters, digits, and the underscore character.</li>
    </ul>
    <p>A valid, unique email address is required.</p>
    <ul>
      <li>If "Email visible" is checked, it will be visible only to members via the Members page.</li>
      <li>If "Email visible" is not checked, it will be visible only to the YARDBIRD Fan Club Webmaster.</li>
    </ul>
    <p>Firstname and Lastname are optional.</p>
    <ul>
      <li>If either are provided, they will be used in page content and visible to both members and non-members alike.</li>
      <li>If neither are provided, your Username will be used in-lieu of them.</li>
    </ul>
    <p>Location is optional.</p>
    <ul>
      <li>If provided, it will be visible only to members via the Members page.</li>
      <li>For example, the YARDBIRD Fan Club Webmaster's location is: Dover-Foxcroft, Maine, USA</li>
    </ul>
    <p>"About me" is optional.</p>
    <ul>
      <li>If provided, it will be visible only to members via the Members page.</li>
      <li>HTML Tags may be used for styling.</li>
    </ul>
  </div>
</div>

[% INCLUDE footer.tt %]

I added a new div tag to this template because I decided the page could be improved with a horizontal line dividing the form from the info under it, therefore you need to add a new style to your stylesheet:

Yardbird/root/static/css/main.css:

.page_divider_with_sidebar {
  border-bottom: 2px solid #87ceeb;
  margin-top: 1.3em;
  margin-bottom: 1.3em;
}

Finally, let's create a new sidebar menu in our index.tt template with a link to edit member info:

<div class="sidebar_item">
  <div class="sidebar_item_content">
    <p><strong>[% c.user.name %]</strong></p>
    <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/edit', c.user.id) %]">Edit Membership Info</a></p>
  </div>
</div>

This is what you should have after adding the above code:

Yardbird/root/src/member/index.tt:

[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
<div id="header">
  <div id="header_title">[% c.user.name %]</div>
  <div id="header_subtitle">The YARDBIRD Fan Club</div>
</div>

<div id="content_wrapper">
  <div id="sidebar_wrapper">
    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>[% c.user.name %]</strong></p>
        <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/edit', c.user.id) %]">Edit Membership Info</a></p>
      </div>
    </div>

    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>Members</strong></p>
        [% FOREACH user IN users %]
          <p class="sidebar_item_title">[% user.name %]</p>
        [% END %]
      </div>
    </div>
  </div>

  <div id="content_with_sidebar">
  <h3>Members</h3>
  <p>You may update your personal membership information and view other member's information.</p>
  </div>
</div>

[% INCLUDE footer.tt %]

Run the application and you should get a sidebar menu with a link to edit member info:

yfc15b1.png

You should also be able to edit member info:

yfc15b2.png

Create New Member: Improved

If you would try to create a new member you would find some differences between the page to create and the one we just implemented to edit member info. When we create members, we don't have the sidebar menu of usernames, or the horizontal line under the form that we added to our edit page. Let's add these features, and automatically login the new user after their membership is created.

We will start with our controller, this is what we now have:

Yardbird/lib/Yardbird/Controller/Member.pm:

sub create :Local {
  my ( $self, $c, $user_id ) = @_;

  $c->stash(template => 'member/create.tt', form => $self->form );

  # Validate and insert data into database.
  return unless $self->form->process(
    item_id => $user_id,
    params => $c->req->parameters,
    schema => $c->model('DB')->schema
  );

  # Form validated and data inserted into database,
  # now auto-generate new member's first blog entry.

  # Get new member info.
  my $row = $c->model('DB::User')->most_recent->first; 
  my $userid = $row->id; 
  my $name = $row->name; 

  # Create new member's first blog entry.
  $row = $c->model('DB::Blog')->new_result({}); 
  $row->userid($userid);
  $row->title("Welcome ".$name."!");
  $row->content(
"<h3>You Are Now A Member Of The YARDBIRD Fan Club!</h3>
<p>You may now:</p>
<ul>
<li>Post blog entries and comments via the Blog pages.</li>
<li>Optionally provide your contact and other information to other users via the Members pages.</li>
<li>View other member's contact information via the Members pages.</li>
</ul>
<h3>About This Blog Entry</h3>
<p>This blog entry was automatically created when you became a member of the YARDBIRD Fan Club, you may edit or delete it as you wish.</p>
<p>It is suggested that you edit this blog entry to see how HTML Tags may be used to style blog entries and comments.</p>
<p>Happy blogging!</p>"
);
  $row->created(DateTime->now);
  $row->insert; 

  # Return to homepage.
  $c->res->redirect($c->uri_for_action('/index'));
}

We will first put a list of users sorted by username in the stash as we do when we edit member info:

# Sort users by username and put in stash for template sidebar menu.
$c->stash(users => [$c->model('DB::User')->all_username]);

Then we will login the new user after auto-generating their first blog entry, and redirect to their blog entries page:

# Automatically login new user.
$c->authenticate({ username => $self->form->value->{username}, password => $self->form->value->{password} });

# Redirect to new user's blog entries page.
$c->res->redirect($c->uri_for_action('/blog/entries/index', $row->id));

This is what you should have after adding the above changes:

Yardbird/lib/Yardbird/Controller/Member.pm:

sub create :Local {
  my ( $self, $c, $user_id ) = @_;

  # Sort users by username and put in stash for template sidebar menu.
  $c->stash(users => [$c->model('DB::User')->all_username]);

  $c->stash(template => 'member/create.tt', form => $self->form );

  # Validate and insert data into database.
  return unless $self->form->process(
    item_id => $user_id,
    params => $c->req->parameters,
    schema => $c->model('DB')->schema
  );

  # Form validated and data inserted into database,
  # now auto-generate new member's first blog entry.

  # Get new member info.
  my $row = $c->model('DB::User')->most_recent->first; 
  my $userid = $row->id; 
  my $name = $row->name; 

  # Create new member's first blog entry.
  $row = $c->model('DB::Blog')->new_result({}); 
  $row->userid($userid);
  $row->title("Welcome ".$name."!");
  $row->content(
"<h3>You Are Now A Member Of The YARDBIRD Fan Club!</h3>
<p>You may now:</p>
<ul>
<li>Post blog entries and comments via the Blog pages.</li>
<li>Optionally provide your contact and other information to other users via the Members pages.</li>
<li>View other member's contact information via the Members pages.</li>
</ul>
<h3>About This Blog Entry</h3>
<p>This blog entry was automatically created when you became a member of the YARDBIRD Fan Club, you may edit or delete it as you wish.</p>
<p>It is suggested that you edit this blog entry to see how HTML Tags may be used to style blog entries and comments.</p>
<p>Happy blogging!</p>"
);
  $row->created(DateTime->now);
  $row->insert; 

  # Automatically login new user.
  $c->authenticate({ username => $self->form->value->{username}, password => $self->form->value->{password} });

  # Redirect to new user's blog entries page.
  $c->res->redirect($c->uri_for_action('/blog/entries/index', $row->id));
}

We need to add the sidebar menu and horizontal bar to the corresponding template. This is what you should have after doing so:

Yardbird/root/src/member/create.tt:

[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Join'; %]
<div id="header">
  <div id="header_title">The YARDBIRD Fan Club</div>
  <div id="header_subtitle">Celebrating the NFL Rookie Sensation!</div>
</div>

<div id="content_wrapper">
  <div id="sidebar_wrapper">
    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>Member Usernames</strong></p>
        [% FOREACH user IN users %]
          <p class="sidebar_item_title">[% user.username %]</p>
        [% END %]
      </div>
    </div>
  </div>

  <div id="content_with_sidebar">
    <h3>Join The YARDBIRD Fan Club</h3>

    <form id="user_form" name="[% form.name %]" action="[% c.uri_for_action('/member/create') %]" method="post"> 
        [% f = form.field('username') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        [% f = form.field('password') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="password" name="[% f.name %]" id="[% f.name %]" value=""> 
      <br>
        [% f = form.field('password_confirm') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="password" name="[% f.name %]" id="[% f.name %]" value=""> 
      <br>
        [% f = form.field('email') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="email" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
        [% f = form.field('email_visible') %]
        <input type="checkbox" name="[% f.name %]" id="[% f.name %]" value="1"> 
        <label for="[% f.name %]">Visible</label>
      <br>
        [% f = form.field('firstname') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        [% f = form.field('lastname') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        [% f = form.field('location') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        About me:
        <div id='user_form_textarea'>
        [% form.field('about_me').render %]
        </div>
      <input id="user_form_button" class="button" name="submit" type="submit" value="Submit"/>
    </form> 

    <div class="page_divider_with_sidebar"></div>
    <p>Username and Password are required.</p>
    <ul>
      <li>Username must be unique among member usernames.</li>
      <li>They may contain upper and lowercase letters, digits, and the underscore character.</li>
    </ul>
    <p>A valid, unique email address is required.</p>
    <ul>
      <li>If "Email visible" is checked, it will be visible only to members via the Members page.</li>
      <li>If "Email visible" is not checked, it will be visible only to the YARDBIRD Fan Club Webmaster.</li>
    </ul>
    <p>Firstname and Lastname are optional.</p>
    <ul>
      <li>If either are provided, they will be used in page content and visible to both members and non-members alike.</li>
      <li>If neither are provided, your Username will be used in-lieu of them.</li>
    </ul>
    <p>Location is optional.</p>
    <ul>
      <li>If provided, it will be visible only to members via the Members page.</li>
      <li>For example, the YARDBIRD Fan Club Webmaster's location is: Dover-Foxcroft, Maine, USA</li>
    </ul>
    <p>"About me" is optional.</p>
    <ul>
      <li>If provided, it will be visible only to members via the Members page.</li>
      <li>HTML Tags may be used for styling.</li>
    </ul>
  </div>
</div>

[% INCLUDE footer.tt %]

Run the application and the page to create new members should be consistent with the one to edit members, and the new user should be automatically logged-in and redirected to view their first blog entry after creating their membership:

yfc15c1.png

yfc15c2.png

yfc15c3.png

Delete Members

We need to create a controller action:

Yardbird/lib/Yardbird/Controller/Member.pm:

sub delete :Local :Args(1) {
  my ( $self, $c, $uID ) = @_;

  # Detach if user is not logged-in.
  $c->detach('/unauthorized_action') if !$c->user_exists;

  # Detach if user entered a malicious uri and is trying to delete another user's membership.
  $c->detach('/unauthorized_action') if $c->user->id != $uID;

  my $submit = $c->req->params;
  if ($submit->{submit} eq 'Yes') {
    $c->model('DB::User')->specific($uID)->delete; 
    $c->res->redirect($c->uri_for_action('/logout/index'));
  }
  elsif ($submit->{submit} eq 'No') {
    $c->res->redirect($c->uri_for_action('member/index'));
  }

  $c->detach($c->view("TT"));
}

Since both the blog and blog_comment tables have foreign keys that reference the user table and cascade delete, all blog entries and comments belonging to a deleted user are automatically deleted when that user is deleted. Since the blog_comment table also has a foreign key that references the blog table and cascade deletes, all blog comments made by others for a deleted blog entry are also deleted when that blog entry is deleted.

So when a user is deleted, that causes all blog entries and comments belonging to the deleted user to be automatically deleted, and when one of those blog entries has comments somebody else has made for it - those comments are deleted too!

Dung Beetle -> Dung Beetle dung -> Dung Beetle Dung Beetle -> Dung Beetle Dung Beetle dung -> Dung Beetle Dung Beetle Dung Beetle -> ...

Create a corresponding template:

Yardbird/root/src/member/delete.tt:

[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
<div id="header">
  <div id="header_title">[% c.user.name %]</div>
  <div id="header_subtitle">The YARDBIRD Fan Club</div>
</div>

<div id="content_wrapper">
  <div id="content_without_sidebar">
  <h3>Cancel Membership</h3>

  <form method="post" action="[% c.uri_for_action('/member/delete', c.user.id) %]">
    <table>

      <tr>
        <td>Canceling your membership permanently deletes:
          <ul>
          <li>All your membership information</li>
          <li>All your blog entries</li>
          <li>All your comments on yours and other's blog entries</li>
          <li>All comments on your blog entries</li>
          </ul>
        </td>
      <tr>
        <td>Do you really want to cancel your membership?</td>
      </tr>
      <tr>
        <td><p><input type="submit" name="submit" value="Yes" /> <input type="submit" name="submit" value="No" /></p></td>
      </tr>

    </table>
  </form> 

  </div>
</div>

[% INCLUDE footer.tt %]

Add a link to our sidebar menu:

<p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/delete', c.user.id) %]">Cancel Membership</a></p>

After adding the link you should have:

Yardbird/root/src/member/index.tt:

[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
<div id="header">
  <div id="header_title">[% c.user.name %]</div>
  <div id="header_subtitle">The YARDBIRD Fan Club</div>
</div>

<div id="content_wrapper">
  <div id="sidebar_wrapper">
    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>[% c.user.name %]</strong></p>
        <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/edit', c.user.id) %]">Edit Membership Info</a></p>
        <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/delete', c.user.id) %]">Cancel Membership</a></p>
      </div>
    </div>

    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>Members</strong></p>
        [% FOREACH user IN users %]
          <p class="sidebar_item_title">[% user.name %]</p>
        [% END %]
      </div>
    </div>
  </div>

  <div id="content_with_sidebar">
  <h3>Members</h3>
  <p>You may update your personal membership information and view other member's information.</p>
  </div>
</div>

[% INCLUDE footer.tt %]

Run it and you should be able to delete memberships:

yfc15d1.png

yfc15d2.png

Don't forget to verify that blog entries and comments belonging to the deleted user, as well as comments (by others) for the deleted user's blog entries are all deleted too.

View Members

Let's start with the controller:

Yardbird/lib/Yardbird/Controller/Member.pm:

sub show :Local :Args(1) {
  my ( $self, $c, $uID ) = @_;

  # Detach if user is not logged-in.
  $c->detach('/unauthorized_action') if !$c->user_exists;

  # Sort users by name and put in stash for template sidebar menu.
  $c->stash(users => [sort_users_by_name($self, $c)]);

  $c->stash(row => $c->model('DB::User')->specific($uID)->first);

  $c->detach($c->view("TT"));
}

Create the corresponding template:

Yardbird/root/src/member/show.tt:

[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
<div id="header">
  <div id="header_title">[% c.user.name %]</div>
  <div id="header_subtitle">The YARDBIRD Fan Club</div>
</div>

<div id="content_wrapper">
  <div id="sidebar_wrapper">
    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>[% c.user.name %]</strong></p>
        <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/edit', c.user.id) %]">Edit Membership Info</a></p>
        <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/delete', c.user.id) %]">Cancel Membership</a></p>
      </div>
    </div>

    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>Members</strong></p>
        [% FOREACH user IN users %]
          <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/show', user.id) %]">[% user.name %]</a></p>
        [% END %]
      </div>
    </div>
  </div>

  <div id="content_with_sidebar">
    <p>
      <span class="label10">Username:</span>[% row.username %]<br>
      [% IF row.has_name %]
        <span class="label10">Name:</span>[% row.name %]<br>
      [% END %]
      [% IF row.email_visible %]
        <span class="label10">Email:</span>[% row.email %]<br>
      [% END %]
      [% IF row.location %]
        <span class="label10">Location:</span>[% row.location %]<br>
      [% END %]
      [% IF row.about_me %]
        <p>[% row.about_me %]</p>
      [% END %]
    </p>
  </div>
</div>

[% INCLUDE footer.tt %]

Notice that our new template needs a new style:

Yardbird/root/static/css/main.css:

.label10 {
  float: left;
  width: 5.5em;
}

It also needs a new Row method in our Result class:

Yardbird/lib/Yardbird/Schema/Result/User.pm:

sub has_name {
  my ($self) = @_;

  if ($self->firstname) {
    return 1;
  }
  elsif ($self->lastname) {
    return 1;
  }
  else {
    return 0;
  }
}

Finally, we can activate a link in our index.tt template sidebar menu to view member's info. Currently we have this:

Yardbird/root/src/member/index.tt:

<div class="sidebar_item">
  <div class="sidebar_item_content">
    <p><strong>Members</strong></p>
    [% FOREACH user IN users %]
      <p class="sidebar_item_title">[% user.name %]</p>
    [% END %]
  </div>
</div>

We need to change it to this:

Yardbird/root/src/member/index.tt:

<div class="sidebar_item">
  <div class="sidebar_item_content">
    <p><strong>Members</strong></p>
    [% FOREACH user IN users %]
      <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/show', user.id) %]">[% user.name %]</a></p>
    [% END %]
  </div>
</div>

Run the application and you should be able to view member info:

yfc15e1.png

yfc15e2.png

Summary

Yardbird/lib/Yardbird/Controller/Member.pm

package Yardbird::Controller::Member;
use Moose;
use namespace::autoclean;
use Yardbird::Form::User; 

BEGIN { extends 'Catalyst::Controller'; }

has 'form' => (
  isa => 'Yardbird::Form::User',
  is => 'rw',
  lazy => 1,
  default => sub { Yardbird::Form::User->new }
);

sub index :Path :Args(0) {
  my ( $self, $c ) = @_;

  # Detach if user is not logged-in.
  $c->detach('/unauthorized_action') if !$c->user_exists;

  # Sort users by name and put in stash for template sidebar menu.
  $c->stash(users => [sort_users_by_name($self, $c)]);

  $c->detach($c->view("TT"));
}

sub about :Local :Args(0) {
  my ( $self, $c ) = @_;

  $c->detach($c->view("TT"));
}

sub create :Local {
  my ( $self, $c, $user_id ) = @_;

  # Sort users by username and put in stash for template sidebar menu.
  $c->stash(users => [$c->model('DB::User')->all_username]);

  $c->stash(template => 'member/create.tt', form => $self->form );

  # Validate and insert data into database.
  return unless $self->form->process(
    item_id => $user_id,
    params => $c->req->parameters,
    schema => $c->model('DB')->schema
  );

  # Form validated and data inserted into database,
  # now auto-generate new member's first blog entry.

  # Get new member info.
  my $row = $c->model('DB::User')->most_recent->first; 
  my $userid = $row->id; 
  my $name = $row->name; 

  # Create new member's first blog entry.
  $row = $c->model('DB::Blog')->new_result({}); 
  $row->userid($userid);
  $row->title("Welcome ".$name."!");
  $row->content(
"<h3>You Are Now A Member Of The YARDBIRD Fan Club!</h3>
<p>You may now:</p>
<ul>
<li>Post blog entries and comments via the Blog pages.</li>
<li>Optionally provide your contact and other information to other users via the Members pages.</li>
<li>View other member's contact information via the Members pages.</li>
</ul>
<h3>About This Blog Entry</h3>
<p>This blog entry was automatically created when you became a member of the YARDBIRD Fan Club, you may edit or delete it as you wish.</p>
<p>It is suggested that you edit this blog entry to see how HTML Tags may be used to style blog entries and comments.</p>
<p>Happy blogging!</p>"
);
  $row->created(DateTime->now);
  $row->insert; 

  # Automatically login new user.
  $c->authenticate({ username => $self->form->value->{username}, password => $self->form->value->{password} });

  # Redirect to new user's blog entries page.
  $c->res->redirect($c->uri_for_action('/blog/entries/index', $row->id));
}

sub edit :Local :Args(1) {
  my ( $self, $c, $uID ) = @_;

  # Detach if user is not logged-in.
  $c->detach('/unauthorized_action') if !$c->user_exists;

  # Detach if user entered a malicious uri and is trying to edit another user's info.
  $c->detach('/unauthorized_action') if $c->user->id != $uID;

  # Sort users by username and put in stash for template sidebar menu.
  $c->stash(users => [$c->model('DB::User')->all_username]);

  # Prepare to use the form.
  $c->stash(template => 'member/edit.tt', form => $self->form );

  # Validate and update user row in user table.
  return unless $self->form->process(
    item_id => $uID, 
    params => $c->req->params,
    schema => $c->model('DB')->schema,
  );

  # Redirect to Members page.
  $c->res->redirect($c->uri_for_action('/member/index'));
}

sub delete :Local :Args(1) {
  my ( $self, $c, $uID ) = @_;

  # Detach if user is not logged-in.
  $c->detach('/unauthorized_action') if !$c->user_exists;

  # Detach if user entered a malicious uri and is trying to delete another user's membership.
  $c->detach('/unauthorized_action') if $c->user->id != $uID;

  my $submit = $c->req->params;
  if ($submit->{submit} eq 'Yes') {
    $c->model('DB::User')->specific($uID)->delete; 
    $c->res->redirect($c->uri_for_action('/logout/index'));
  }
  elsif ($submit->{submit} eq 'No') {
    $c->res->redirect($c->uri_for_action('member/index'));
  }

  $c->detach($c->view("TT"));
}

sub show :Local :Args(1) {
  my ( $self, $c, $uID ) = @_;

  # Detach if user is not logged-in.
  $c->detach('/unauthorized_action') if !$c->user_exists;

  # Sort users by name and put in stash for template sidebar menu.
  $c->stash(users => [sort_users_by_name($self, $c)]);

  $c->stash(row => $c->model('DB::User')->specific($uID)->first);

  $c->detach($c->view("TT"));
}

sub sort_users_by_name {
  my ( $self, $c ) = @_;

  my @users;
  for my $user ($c->model('DB::User')->all) {
    push @users, { id => $user->id, name => $user->name };
  }

  my @sorted_users = sort { "\L$a->{name}" cmp "\L$b->{name}" } @users;

  my @rows;
  for my $user (@sorted_users) {
    push @rows, $c->model('DB::User')->specific($user->{id})->first;
  }

  return @rows;
}

__PACKAGE__->meta->make_immutable;

1;

Yardbird/root/src/member/index.tt

[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
<div id="header">
  <div id="header_title">[% c.user.name %]</div>
  <div id="header_subtitle">The YARDBIRD Fan Club</div>
</div>

<div id="content_wrapper">
  <div id="sidebar_wrapper">
    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>[% c.user.name %]</strong></p>
        <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/edit', c.user.id) %]">Edit Membership Info</a></p>
        <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/delete', c.user.id) %]">Cancel Membership</a></p>
      </div>
    </div>

    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>Members</strong></p>
        [% FOREACH user IN users %]
          <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/show', user.id) %]">[% user.name %]</a></p>
        [% END %]
      </div>
    </div>
  </div>

  <div id="content_with_sidebar">
  <h3>Members</h3>
  <p>You may update your personal membership information and view other member's information.</p>
  </div>
</div>

[% INCLUDE footer.tt %]

Yardbird/root/src/member/edit.tt

[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
<div id="header">
  <div id="header_title">[% c.user.name %]</div>
  <div id="header_subtitle">The YARDBIRD Fan Club</div>
</div>

<div id="content_wrapper">
  <div id="sidebar_wrapper">
    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>Member Usernames</strong></p>
        [% FOREACH user IN users %]
          <p class="sidebar_item_title">[% user.username %]</p>
        [% END %]
      </div>
    </div>
  </div>

  <div id="content_with_sidebar">
    <h3>Edit Your Membership Information</h3>

    <form id="user_form" name="[% form.name %]" action="[% c.uri_for_action('/member/edit', c.user.id) %]" method="post"> 
        [% f = form.field('username') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        [% f = form.field('password') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="password" name="[% f.name %]" id="[% f.name %]" value=""> 
      <br>
        [% f = form.field('password_confirm') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="password" name="[% f.name %]" id="[% f.name %]" value=""> 
      <br>
        [% f = form.field('email') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="email" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
        [% f = form.field('email_visible') %]
        <input type="checkbox" name="[% f.name %]" id="[% f.name %]" value="1"> 
        <label for="[% f.name %]">Visible</label>
      <br>
        [% f = form.field('firstname') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        [% f = form.field('lastname') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        [% f = form.field('location') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        About me:
        <div id='user_form_textarea'>
        [% form.field('about_me').render %]
        </div>
      <input id="user_form_button" class="button" name="submit" type="submit" value="Submit" />
    </form> 

    <div class="page_divider_with_sidebar"></div>
    <p>Username and Password are required.</p>
    <ul>
      <li>Username must be unique among member usernames.</li>
      <li>They may contain upper and lowercase letters, digits, and the underscore character.</li>
    </ul>
    <p>A valid, unique email address is required.</p>
    <ul>
      <li>If "Email visible" is checked, it will be visible only to members via the Members page.</li>
      <li>If "Email visible" is not checked, it will be visible only to the YARDBIRD Fan Club Webmaster.</li>
    </ul>
    <p>Firstname and Lastname are optional.</p>
    <ul>
      <li>If either are provided, they will be used in page content and visible to both members and non-members alike.</li>
      <li>If neither are provided, your Username will be used in-lieu of them.</li>
    </ul>
    <p>Location is optional.</p>
    <ul>
      <li>If provided, it will be visible only to members via the Members page.</li>
      <li>For example, the YARDBIRD Fan Club Webmaster's location is: Dover-Foxcroft, Maine, USA</li>
    </ul>
    <p>"About me" is optional.</p>
    <ul>
      <li>If provided, it will be visible only to members via the Members page.</li>
      <li>HTML Tags may be used for styling.</li>
    </ul>
  </div>
</div>

[% INCLUDE footer.tt %]

Yardbird/root/src/member/create.tt

[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: Join'; %]
<div id="header">
  <div id="header_title">The YARDBIRD Fan Club</div>
  <div id="header_subtitle">Celebrating the NFL Rookie Sensation!</div>
</div>

<div id="content_wrapper">
  <div id="sidebar_wrapper">
    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>Member Usernames</strong></p>
        [% FOREACH user IN users %]
          <p class="sidebar_item_title">[% user.username %]</p>
        [% END %]
      </div>
    </div>
  </div>

  <div id="content_with_sidebar">
    <h3>Join The YARDBIRD Fan Club</h3>

    <form id="user_form" name="[% form.name %]" action="[% c.uri_for_action('/member/create') %]" method="post"> 
        [% f = form.field('username') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        [% f = form.field('password') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="password" name="[% f.name %]" id="[% f.name %]" value=""> 
      <br>
        [% f = form.field('password_confirm') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="password" name="[% f.name %]" id="[% f.name %]" value=""> 
      <br>
        [% f = form.field('email') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="email" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
        [% f = form.field('email_visible') %]
        <input type="checkbox" name="[% f.name %]" id="[% f.name %]" value="1"> 
        <label for="[% f.name %]">Visible</label>
      <br>
        [% f = form.field('firstname') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        [% f = form.field('lastname') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        [% f = form.field('location') %]
        <label class="label" for="[% f.name %]">[% f.label %]:</label>
        <input type="text" name="[% f.name %]" id="[% f.name %]" value="[% f.fif %]"> 
      <br>
        About me:
        <div id='user_form_textarea'>
        [% form.field('about_me').render %]
        </div>
      <input id="user_form_button" class="button" name="submit" type="submit" value="Submit"/>
    </form> 

    <div class="page_divider_with_sidebar"></div>
    <p>Username and Password are required.</p>
    <ul>
      <li>Username must be unique among member usernames.</li>
      <li>They may contain upper and lowercase letters, digits, and the underscore character.</li>
    </ul>
    <p>A valid, unique email address is required.</p>
    <ul>
      <li>If "Email visible" is checked, it will be visible only to members via the Members page.</li>
      <li>If "Email visible" is not checked, it will be visible only to the YARDBIRD Fan Club Webmaster.</li>
    </ul>
    <p>Firstname and Lastname are optional.</p>
    <ul>
      <li>If either are provided, they will be used in page content and visible to both members and non-members alike.</li>
      <li>If neither are provided, your Username will be used in-lieu of them.</li>
    </ul>
    <p>Location is optional.</p>
    <ul>
      <li>If provided, it will be visible only to members via the Members page.</li>
      <li>For example, the YARDBIRD Fan Club Webmaster's location is: Dover-Foxcroft, Maine, USA</li>
    </ul>
    <p>"About me" is optional.</p>
    <ul>
      <li>If provided, it will be visible only to members via the Members page.</li>
      <li>HTML Tags may be used for styling.</li>
    </ul>
  </div>
</div>

[% INCLUDE footer.tt %]

Yardbird/root/src/member/delete.tt

[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
<div id="header">
  <div id="header_title">[% c.user.name %]</div>
  <div id="header_subtitle">The YARDBIRD Fan Club</div>
</div>

<div id="content_wrapper">
  <div id="content_without_sidebar">
  <h3>Cancel Membership</h3>

  <form method="post" action="[% c.uri_for_action('/member/delete', c.user.id) %]">
    <table>

      <tr>
        <td>Canceling your membership permanently deletes:
          <ul>
          <li>All your membership information</li>
          <li>All your blog entries</li>
          <li>All your comments on yours and other's blog entries</li>
          <li>All comments on your blog entries</li>
          </ul>
        </td>
      <tr>
        <td>Do you really want to cancel your membership?</td>
      </tr>
      <tr>
        <td><p><input type="submit" name="submit" value="Yes" /> <input type="submit" name="submit" value="No" /></p></td>
      </tr>

    </table>
  </form> 

  </div>
</div>

[% INCLUDE footer.tt %]

Yardbird/root/src/member/show.tt

[% INCLUDE header.tt title = 'The YARDBIRD Fan Club: '_ c.user.name %]
<div id="header">
  <div id="header_title">[% c.user.name %]</div>
  <div id="header_subtitle">The YARDBIRD Fan Club</div>
</div>

<div id="content_wrapper">
  <div id="sidebar_wrapper">
    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>[% c.user.name %]</strong></p>
        <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/edit', c.user.id) %]">Edit Membership Info</a></p>
        <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/delete', c.user.id) %]">Cancel Membership</a></p>
      </div>
    </div>

    <div class="sidebar_item">
      <div class="sidebar_item_content">
        <p><strong>Members</strong></p>
        [% FOREACH user IN users %]
          <p class="sidebar_item_title"><a href="[% c.uri_for_action('/member/show', user.id) %]">[% user.name %]</a></p>
        [% END %]
      </div>
    </div>
  </div>

  <div id="content_with_sidebar">
    <p>
      <span class="label10">Username:</span>[% row.username %]<br>
      [% IF row.has_name %]
        <span class="label10">Name:</span>[% row.name %]<br>
      [% END %]
      [% IF row.email_visible %]
        <span class="label10">Email:</span>[% row.email %]<br>
      [% END %]
      [% IF row.location %]
        <span class="label10">Location:</span>[% row.location %]<br>
      [% END %]
      [% IF row.about_me %]
        <p>[% row.about_me %]</p>
      [% END %]
    </p>
  </div>
</div>

[% INCLUDE footer.tt %]

Yardbird/root/src/header.tt

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>[% title %]</title>
  <link rel="stylesheet" type="text/css" media="screen" href="[% c.uri_for('/static/css/main.css') %]" />
</head>
<body>
  <div id="wrapper">
    <div id="main_menu">
      <ul>
        <div id="main_menu_left">
          <li><a href="[% c.uri_for_action('/index') %]">Home</a></li>
          <li><a href="[% c.uri_for_action('blog/index') %]">Blog</a></li>
          <li>Events</li>
          <li>Links</li>
          <li>News</li>
          <li>Photos</li>
          <li>Video</li>
          [% IF c.user_exists %] 
            <li><a href="[% c.uri_for_action('/member/index') %]">Members</a></li>
          [% END %] 
        </div>
        <div id="main_menu_right">
          [% IF c.user_exists %] 
            <li>[% c.user.name %] | <a href="[% c.uri_for_action('/logout/index') %]">Logout</a></li>
          [% ELSE %]
            <li><a href="[% c.uri_for_action('/login/index') %]">Login</a> | <a href="[% c.uri_for_action('/member/about') %]">Not a member?</a></li> 
          [% END %]
        </div>  
      </ul>
    </div> <!-- main_menu -->

Yardbird/root/static/css/main.css

#wrapper {
  width: 960px;
  margin-top: 0px;
  margin-right: auto;
  margin-left: auto;
}

#header {
  height: 100px;
  margin-left: 20px;
  background: #004060;
  background-image: url(../images/bird/bird.gif);
  background-position: left bottom;
  background-repeat: no-repeat; 
}

#header_title {
  margin-left: 70px;
  padding-top: 22px;
  color: #87ceeb;
  font-size: 2.2em;
  font-weight: bold;
}

#header_subtitle {
  margin-left: 70px;
  margin-top: 2px;
  color: #87ceeb;
  font-size: 0.9em;
}

#footer {
  padding-top: 0.4em;
  padding-bottom: 1.5em;
  background: #004060;
  color: #FFFFFF;
  font-size: 0.9em;
}

body {
  font-family: arial;
  margin-top: 0px;
  background: #004060;
}

#main_menu {
  border-left: 1px solid #FFFFFF;
  border-right: 1px solid #FFFFFF;
  border-bottom: 1px solid #FFFFFF;
  border-bottom-left-radius: 0.3em; 
  border-bottom-right-radius: 0.3em; 
  background: #003050;
  font-size: 0.9em;

  color: #FFFFFF;
}

#main_menu ul {
  padding-top: 0.275em;
  padding-bottom: 0.18em;
  margin: auto; /* center horizontally */
  list-style: none; 

  /* Menu background/border has no vertical height without
  this, I don't know why it works. */
  overflow: hidden;
}

#main_menu a {
  color: #87ceeb;
  text-decoration: none; 
}

#main_menu a:hover {
  color: #FFFFFF;
  text-decoration: underline; 
}

#main_menu_left li {
  padding-right: 5em;
  /*padding-left: 0em;*/
  margin-left: -1.5em;
  float: left;
  /*width: 1em;*/
}

#main_menu_right li {
  /*padding-right: 0em;*/
  /*padding-left: 40px;*/
  margin-right: 1em;
  float: right;
  /*width: 12.5em;*/
}

#content_wrapper {
  border: 1px solid #000000;
  border-radius: 0.3em; 
  background: #FFFFFF;
  /*background: #A4D3EE;*/

  /* Expand content area when sidebar is vertically longer than content. */
  overflow: auto; 
}

/* formhandler generates html with id selectors named 'content' for it's
 * 'content' fields in the blog and blogcomments forms. For this reason I
 * renamed the 'content' id style 'content_with_sidebar'. */

#content_with_sidebar {
  margin-right: 225px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#content_without_sidebar {
  margin-right: 10px;
  margin-left: 10px;
  margin-top: 0px;
  margin-bottom: 20px;
}

#sidebar_wrapper {
  float: right;
}

.sidebar_item {
  width: 200px;
  margin-right: 10px;
  margin-top: 13px;
  margin-bottom: 10px;
  border: 1px solid #000000;
  border-radius: 0.3em; 
  /*background: #FFFFFF;*/
}

.sidebar_item_content {
  padding-left: 12px;
  padding-right: 12px;
}

#user_form_button {
  margin-left: 140px;
}

#user_form .label {
  float: left;
  width: 140px;
} 

#user_form_textarea {
  margin-top: -1.15em;
  margin-left: 140px;
}

.content_title {
  font-size: 1.4em;
  font-weight: bold;
  margin-top: 0.3em;
}

.content_subtitle {
  font-size: 0.8em;
  margin-top: -1.6em;
}

.content_title a {
  color: #004080;
  text-decoration: none; 
}

.content_title a:hover {
  color: #004080;
  text-decoration: underline; 
}

.sidebar_item_title {
  font-size: 0.9em;
  font-weight: bold;
}

.sidebar_item_title a {
  color: #004080;
  text-decoration: none; 
}

.sidebar_item_title a:hover {
  color: #004080;
  text-decoration: underline; 
}

.sidebar_item_subtitle {
  font-size: 0.65em;
  margin-top: -1.5em;
}

.sidebar_item_sub_subtitle {
  font-size: 0.65em;
  margin-top: -1.0em;
}

.bottom_of_content {
  border-bottom: 2px solid #87ceeb;
  margin-top: 1.5em;
  margin-bottom: 1.3em;
}

.top_of_comments {
  margin-top: 1.3em;
  border-bottom: 2px solid #87ceeb;
  margin-bottom: -0.5em;
}

.between_comments {
  margin-top: -0.6em;
  border-bottom: 2px solid #87ceeb;
  margin-bottom: -0.5em;
}

.bottom_of_comments {
  border-bottom: 2px solid #87ceeb;
  margin-top: -0.6em;
  margin-bottom: 1.3em;
}

.comment_content {
  margin-bottom: 1em;
}

.comment_subtitle {
  font-size: 0.7em;
  color: #004080; 
  margin-top: 1.2em;
}

.page_divider_with_sidebar {
  border-bottom: 2px solid #87ceeb;
  margin-top: 1.3em;
  margin-bottom: 1.3em;
}

.label10 {
  float: left;
  width: 5.5em;
}

Yardbird/lib/Yardbird/Schema/Result/User.pm

# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:s1r1mjXJqZzRdLk0PGpC9A


# You can replace this text with custom code or comments, and it will be preserved on regeneration

# Have the 'password' column use a SHA-1 hash and 20-byte salt
# with RFC 2307 encoding; Generate the 'check_password" method
__PACKAGE__->add_columns(
    'password' => {
        passphrase       => 'rfc2307',
        passphrase_class => 'SaltedDigest',
        passphrase_args  => {
            algorithm   => 'SHA-1',
            salt_random => 20.
        },
        passphrase_check_method => 'check_password',
    },
); 

sub name {
  my ($self) = @_;
  my $bestname;

  if ($self->firstname) {
    $bestname = $self->firstname;
    if ($self->lastname) {
      $bestname .= ' ' . $self->lastname;
    }
  }
  elsif ($self->lastname) {
    $bestname = $self->lastname;
  }
  else {
    $bestname = $self->username;
  }
  return $bestname;
}

sub has_name {
  my ($self) = @_;

  if ($self->firstname) {
    return 1;
  }
  elsif ($self->lastname) {
    return 1;
  }
  else {
    return 0;
  }
}

__PACKAGE__->meta->make_immutable;
1;

Yardbird/lib/Yardbird/Schema/ResultSet/User.pm

package Yardbird::Schema::ResultSet::User;
use strict;
use warnings;
use base 'DBIx::Class::ResultSet';

sub most_recent {
  my ($self) = @_;

  return $self->search({}, { order_by => {-desc => ['id']} });
} 

sub specific {
  my ($self, $uID) = @_;

  return $self->search({id => $uID});
} 

sub all_username {
  my ($self, $uID) = @_;

  return $self->search({}, {order_by => ['username']});
} 

1;

Leave a comment

About j0e

user-pic I have experience and am skilled at maintaining old school Perl 5 and am seeking opportunities to use modern Perl.