diff --git a/include/class.dept.php b/include/class.dept.php
index d06bef503bf0447b92ec74c61d251a7f4f4182a5..6bb8625a27ff7dd2fd7ef8f8ae3611f480b23fd9 100644
--- a/include/class.dept.php
+++ b/include/class.dept.php
@@ -548,7 +548,7 @@ implements TemplateVariable {
         if ($vars['pid'] && !($p = static::lookup($vars['pid'])))
             $errors['pid'] = __('Department selection is required');
 
-        // Format access update as [array(dept_id, alerts?)]
+        // Format access update as [array(dept_id, role_id, alerts?)]
         $access = array();
         if (isset($vars['members'])) {
             foreach (@$vars['members'] as $staff_id) {
@@ -576,8 +576,8 @@ implements TemplateVariable {
         $this->flags = isset($vars['assign_members_only']) ? self::FLAG_ASSIGN_MEMBERS_ONLY : 0;
         $this->path = $this->getFullPath();
 
-        if ($rv = $this->save())
-            return $rv;
+        if ($this->save())
+            return $this->extended->saveAll();
 
         if (isset($this->id))
             $errors['err']=sprintf(__('Unable to update %s.'), __('this department'))
@@ -611,8 +611,6 @@ implements TemplateVariable {
               $da->role_id = $role_id;
           }
           $da->setAlerts($alerts);
-          if (!$errors)
-              $da->save();
       }
       if (!$errors && $dropped) {
           $this->extended
diff --git a/include/class.orm.php b/include/class.orm.php
index 8e1a35537ca6ab7803dcce3496bef1bf05113a20..56ee74bdba907098b8f7ca07a87e44c5a5d5ef08 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -1602,6 +1602,58 @@ class InstrumentedList extends ModelInstanceManager {
         }
     }
 
+    /**
+     * Sort the instrumented list in place. This would be useful to change the
+     * sorting order of the items in the list without fetching the list from
+     * the database again.
+     *
+     * Parameters:
+     * $key - (callable|int) A callable function to produce the sort keys
+     *      or one of the SORT_ constants used by the array_multisort
+     *      function
+     * $reverse - (bool) true if the list should be sorted descending
+     *
+     * Returns:
+     * This instrumented list for chaining and inlining.
+     */
+    function sort($key=false, $reverse=false) {
+        // Fetch all records into the cache
+        $this->asArray();
+        if (is_callable($key)) {
+            array_multisort(
+                array_map($key, $this->cache),
+                $reverse ? SORT_DESC : SORT_ASC,
+                $this->cache);
+        }
+        elseif ($key) {
+            array_multisort($this->cache,
+                $reverse ? SORT_DESC : SORT_ASC, $key);
+        }
+        elseif ($reverse) {
+            rsort($this->cache);
+        }
+        else
+            sort($this->cache);
+        return $this;
+    }
+
+    /**
+     * Reverse the list item in place. Returns this object for chaining
+     */
+    function reverse() {
+        $this->asArray();
+        array_reverse($this->cache);
+        return $this;
+    }
+
+    // Save all changes made to any list items
+    function saveAll() {
+        foreach ($this as $I)
+            if (!$I->save())
+                return false;
+        return true;
+    }
+
     // QuerySet delegates
     function count() {
         return $this->objects()->count();
diff --git a/include/class.staff.php b/include/class.staff.php
index d830515b84eb937fb87e24783e0b6924c1e964ee..bf3e59a3f711ec743d197ce489f52bea0e8d18f7 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -609,7 +609,8 @@ implements AuthenticatedUser, EmailContact, TemplateVariable {
             }
         }
 
-        if($errors) return false;
+        if ($errors)
+            return false;
 
         $this->firstname = $vars['firstname'];
         $this->lastname = $vars['lastname'];
diff --git a/include/class.team.php b/include/class.team.php
index 1165ecc8a104c06e09ba2badd92006c452d8a6ce..fe80bd4f997f12222b1b76b81b717186f9b0e54a 100644
--- a/include/class.team.php
+++ b/include/class.team.php
@@ -110,7 +110,7 @@ implements TemplateVariable {
         $base = $this->ht;
         $base['isenabled'] = $this->isEnabled();
         $base['noalerts'] = !$this->alertsEnabled();
-        unset($base['staffmembers']);
+        unset($base['members']);
         return $base;
     }
 
@@ -166,15 +166,20 @@ implements TemplateVariable {
         $this->name = Format::striptags($vars['name']);
         $this->notes = Format::sanitize($vars['notes']);
 
-        if ($this->save()) {
-            // Remove checked members
-            if ($vars['remove'] && is_array($vars['remove'])) {
-                TeamMember::objects()
-                    ->filter(array('staff_id__in' => $vars['remove']))
-                    ->delete();
+        // Format access update as [array(staff_id, alerts?)]
+        $access = array();
+        if (isset($vars['members'])) {
+            foreach (@$vars['members'] as $staff_id) {
+                $access[] = array($staff_id, @$vars['member_alerts'][$staff_id]);
             }
-            return true;
         }
+        $this->updateMembers($access, $errors);
+
+        if ($errors)
+            return false;
+
+        if ($this->save())
+            return $this->members->saveAll();
 
         if (isset($this->team_id)) {
             $errors['err']=sprintf(__('Unable to update %s.'), __('this team'))
@@ -187,6 +192,31 @@ implements TemplateVariable {
         return false;
     }
 
+    function updateMembers($access, &$errors) {
+      reset($access);
+      $dropped = array();
+      foreach ($this->members as $member)
+          $dropped[$member->staff_id] = 1;
+      while (list(, list($staff_id, $alerts)) = each($access)) {
+          unset($dropped[$staff_id]);
+          if (!$staff_id || !Staff::lookup($staff_id))
+              $errors['members'][$staff_id] = __('No such agent');
+          $member = $this->members->findFirst(array('staff_id' => $staff_id));
+          if (!isset($member)) {
+              $member = TeamMember::create(array('staff_id' => $staff_id));
+              $this->members->add($member);
+          }
+          $member->setAlerts($alerts);
+      }
+      if (!$errors && $dropped) {
+          $this->members
+              ->filter(array('staff_id__in' => array_keys($dropped)))
+              ->delete();
+          $this->members->reset();
+      }
+      return !$errors;
+    }
+
     function save($refetch=false) {
         if ($this->dirty)
             $this->updated = SqlFunction::NOW();
@@ -205,11 +235,12 @@ implements TemplateVariable {
             return false;
 
         # Remove members of this team
-        $this->staffmembers->delete();
+        $this->members->delete();
 
         # Reset ticket ownership for tickets owned by this team
-        db_query('UPDATE '.TICKET_TABLE.' SET team_id=0 WHERE team_id='
-            .db_input($id));
+        Ticket::objects()
+            ->filter(array('team_id' => $id))
+            ->update(array('team_id' => 0));
 
         return true;
     }
@@ -289,6 +320,7 @@ class TeamMember extends VerySimpleModel {
     static $meta = array(
         'table' => TEAM_MEMBER_TABLE,
         'pk' => array('team_id', 'staff_id'),
+        'select_related' => array('staff'),
         'joins' => array(
             'team' => array(
                 'constraint' => array('team_id' => 'Team.team_id'),
diff --git a/include/staff/department.inc.php b/include/staff/department.inc.php
index e3be963f66c13d92a2cae69031887744c5a1cbde..c5f474255a47f260155a158278ddc0e2c5e4f2c5 100644
--- a/include/staff/department.inc.php
+++ b/include/staff/department.inc.php
@@ -305,7 +305,7 @@ foreach ($dept->getMembers() as $member) {
             <option value="0">&mdash; <?php echo __('Select Agent');?> &mdash;</option>
             <?php
             foreach ($agents as $id=>$name) {
-              echo sprintf('<option value="%d">%s</option>',$id,$name);
+              echo sprintf('<option value="%d">%s</option>',$id,Format::htmlchars($name));
             }
             ?>
             <option value="0" data-quick-add>&mdash; <?php echo __('Add New');?> &mdash;</option>
@@ -332,8 +332,10 @@ foreach ($dept->getMembers() as $member) {
             <option value="0" data-quick-add>&mdash; <?php echo __('Add New');?> &mdash;</option>
           </select>
           <span style="display:inline-block;width:60px"> </span>
-          <input type="checkbox" data-name="member_alerts" value="1" />
-          <?php echo __('Alerts'); ?>
+          <label>
+            <input type="checkbox" data-name="member_alerts" value="1" />
+            <?php echo __('Alerts'); ?>
+          </label>
           <a href="#" class="pull-right drop-membership" title="<?php echo __('Delete');
             ?>"><i class="icon-trash"></i></a>
         </td>
diff --git a/include/staff/staff.inc.php b/include/staff/staff.inc.php
index 9bc201df682559cf191bc51b6ec11ddf63600b81..96cb584877af45512c66174258821ec98bee806c 100644
--- a/include/staff/staff.inc.php
+++ b/include/staff/staff.inc.php
@@ -253,8 +253,10 @@ if (count($bks) > 1) {
               <option value="0" data-quick-add>&mdash; <?php echo __('Add New');?> &mdash;</option>
             </select>
             <span style="display:inline-block;width:20px"> </span>
-            <input type="checkbox" data-name="dept_access_alerts" value="1" />
-            <?php echo __('Alerts'); ?>
+            <label>
+              <input type="checkbox" data-name="dept_access_alerts" value="1" />
+              <?php echo __('Alerts'); ?>
+            </label>
             <a href="#" class="pull-right drop-access" title="<?php echo __('Delete');
               ?>"><i class="icon-trash"></i></a>
           </td>
@@ -279,7 +281,7 @@ foreach ($staff->dept_access as $dept_access) {
               <option value="0">&mdash; <?php echo __('Select Department');?> &mdash;</option>
               <?php
               foreach ($depts as $id=>$name) {
-                echo sprintf('<option value="%d">%s</option>',$id,$name);
+                echo sprintf('<option value="%d">%s</option>',$id,Format::htmlchars($name));
               }
               ?>
               <option value="0" data-quick-add>&mdash; <?php echo __('Add New');?> &mdash;</option>
@@ -371,7 +373,7 @@ foreach ($staff->teams as $TM) {
               <option value="0">&mdash; <?php echo __('Select Team');?> &mdash;</option>
               <?php
               foreach ($teams as $id=>$name) {
-                echo sprintf('<option value="%d">%s</option>', $id, $name);
+                echo sprintf('<option value="%d">%s</option>',$id,Format::htmlchars($name));
               }
               ?>
               <option value="0" data-quick-add>&mdash; <?php echo __('Add New');?> &mdash;</option>
@@ -388,8 +390,10 @@ foreach ($staff->teams as $TM) {
             <input type="hidden" data-name="teams[]" value="" />
           </td>
           <td>
-            <input type="checkbox" data-name="team_alerts" value="1" />
-            <?php echo __('Alerts'); ?>
+            <label>
+              <input type="checkbox" data-name="team_alerts" value="1" />
+              <?php echo __('Alerts'); ?>
+            </label>
             <a href="#" class="pull-right drop-membership" title="<?php echo __('Delete');
               ?>"><i class="icon-trash"></i></a>
           </td>
diff --git a/include/staff/team.inc.php b/include/staff/team.inc.php
index 93719667251ffef9ff2ac99ea041907eb62a1add..c055f8743711d05bee1bbf6e842d199ee2ee9659 100644
--- a/include/staff/team.inc.php
+++ b/include/staff/team.inc.php
@@ -6,8 +6,6 @@ if ($team && $_REQUEST['a']!='add') {
     $title=__('Update Team');
     $action='update';
     $submit_text=__('Save Changes');
-    $info=$team->getInfo();
-    $info['id']=$team->getId();
     $trans['name'] = $team->getTranslateTag('name');
     $members = $team->getMembers();
     $qs += array('id' => $team->getId());
@@ -15,18 +13,21 @@ if ($team && $_REQUEST['a']!='add') {
     $title=__('Add New Team');
     $action='create';
     $submit_text=__('Create Team');
-    $info['isenabled']=1;
-    $info['noalerts']=0;
+    if (!$team) {
+        $team = Team::create(array(
+            'flags' => Team::FLAG_ENABLED,
+        ));
+    }
     $qs += array('a' => $_REQUEST['a']);
 }
 
-$info = Format::htmlchars(($errors && $_POST) ? $_POST : $info);
+$info = $team->getInfo();
 ?>
 <form action="teams.php?<?php echo Http::build_query($qs); ?>" method="post" id="save">
  <?php csrf_token(); ?>
  <input type="hidden" name="do" value="<?php echo $action; ?>">
  <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>">
- <input type="hidden" name="id" value="<?php echo $info['id']; ?>">
+ <input type="hidden" name="id" value="<?php echo $team->getId(); ?>">
  <h2><?php echo __('Team');?>&nbsp;
     <i class="help-tip icon-question-sign" href="#teams"></i>
     </h2>
@@ -34,13 +35,10 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info);
 <ul class="clean tabs">
     <li class="active"><a href="#team">
         <i class="icon-file"></i> <?php echo __('Team'); ?></a></li>
-    <?php
-    if ($members) { ?>
     <li><a href="#members">
         <i class="icon-group"></i> <?php echo __('Members'); ?></a></li>
-    <?php
-    } ?>
 </ul>
+
 <div id="team" class="tab_content">
  <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
     <thead>
@@ -57,7 +55,7 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info);
                 <?php echo __('Name');?>:
             </td>
             <td>
-                <input type="text" size="30" name="name" value="<?php echo $info['name']; ?>"
+                <input type="text" size="30" name="name" value="<?php echo Format::htmlchars($team->name); ?>"
                     autofocus data-translate-tag="<?php echo $trans['name']; ?>"/>
                 &nbsp;<span class="error">*&nbsp;<?php echo $errors['name']; ?></span>
             </td>
@@ -68,9 +66,9 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info);
             </td>
             <td>
                 <span>
-                <input type="radio" name="isenabled" value="1" <?php echo $info['isenabled']?'checked="checked"':''; ?>><strong><?php echo __('Active');?></strong>
+                <input type="radio" name="isenabled" value="1" <?php echo $team->isEnabled()?'checked="checked"':''; ?>><strong><?php echo __('Active');?></strong>
                 &nbsp;
-                <input type="radio" name="isenabled" value="0" <?php echo !$info['isenabled']?'checked="checked"':''; ?>><?php echo __('Disabled');?>
+                <input type="radio" name="isenabled" value="0" <?php echo !$team->isEnabled()?'checked="checked"':''; ?>><?php echo __('Disabled');?>
                 &nbsp;<span class="error">*&nbsp;</span>
                 <i class="help-tip icon-question-sign" href="#status"></i>
                 </span>
@@ -87,7 +85,7 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info);
                     <?php
                     if ($members) {
                         foreach($members as $k=>$staff){
-                            $selected=($info['lead_id'] && $staff->getId()==$info['lead_id'])?'selected="selected"':'';
+                            $selected=($team->lead_id && $staff->getId()==$team->lead_id)?'selected="selected"':'';
                             echo sprintf('<option value="%d" %s>%s</option>',$staff->getId(),$selected,$staff->getName());
                         }
                     }
@@ -103,7 +101,7 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info);
                 <?php echo __('Assignment Alert');?>:
             </td>
             <td>
-                <input type="checkbox" name="noalerts" value="1" <?php echo $info['noalerts']?'checked="checked"':''; ?> >
+                <input type="checkbox" name="noalerts" value="1" <?php echo !$team->alertsEnabled()?'checked="checked"':''; ?> >
                 <?php echo __('<strong>Disable</strong> for this Team'); ?>
                 <i class="help-tip icon-question-sign" href="#assignment_alert"></i>
             </td>
@@ -116,44 +114,119 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info);
         <tr>
             <td colspan=2>
                 <textarea class="richtext no-bar" name="notes" cols="21"
-                    rows="8" style="width: 80%;"><?php echo $info['notes']; ?></textarea>
+                    rows="8" style="width: 80%;"><?php echo Format::htmlchars($team->notes); ?></textarea>
             </td>
         </tr>
     </tbody>
 </table>
 </div>
+
 <?php
-if ($members) { ?>
+$agents = Staff::getStaffMembers();
+foreach ($members as $m)
+    unset($agents[$m->staff_id]);
+?>
+
 <div id="members" class="tab_content" style="display:none">
-   <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
-    <thead>
-        <tr>
-            <th>
-                <em><?php echo __('Agents who are members of this team'); ?><i
-                    class="help-tip icon-question-sign" href="#members"></i></em>
-            </th>
+   <table class="two-column table" width="100%">
+    <tbody>
+        <tr class="header">
+            <td colspan="2">
+                <?php echo __('Team Members'); ?>
+                <div><small>
+                <?php echo __('Agents who are members of this team'); ?>
+                <i class="help-tip icon-question-sign" href="#members"></i>
+                </small></div>
+            </td>
         </tr>
-    </thead>
+      <tr id="add_member">
+        <td colspan="2">
+          <i class="icon-plus-sign"></i>
+          <select id="add_access" data-quick-add="staff">
+            <option value="0">&mdash; <?php echo __('Select Agent');?> &mdash;</option>
+            <?php
+            foreach ($agents as $id=>$name) {
+              echo sprintf('<option value="%d">%s</option>',$id,Format::htmlchars($name));
+            }
+            ?>
+            <option value="0" data-quick-add>&mdash; <?php echo __('Add New');?> &mdash;</option>
+          </select>
+          <button type="button" class="action-button">
+            <?php echo __('Add'); ?>
+          </button>
+        </td>
+      </tr>
+    </tbody>
     <tbody>
-    <?php
-        foreach($members as $k=>$staff) {
-            echo sprintf('<tr><td colspan=2><span style="width:350px;padding-left:5px; display:block;" class="pull-left">
-                        <b><a href="staff.php?id=%d">%s</a></span></b>
-                        &nbsp;<input type="checkbox" name="remove[]" value="%d"><i>'.__('Remove').'</i></td></tr>',
-                        $staff->getId() ,
-                        $staff->getName(),
-                        $staff->getId());
-
-        }
-     ?>
+      <tr id="member_template" class="hidden">
+        <td>
+          <input type="hidden" data-name="members[]" value="" />
+        </td>
+        <td>
+          <label>
+            <input type="checkbox" data-name="member_alerts" value="1" />
+            <?php echo __('Alerts'); ?>
+          </label>
+          <a href="#" class="pull-right drop-membership" title="<?php echo __('Delete');
+            ?>"><i class="icon-trash"></i></a>
+        </td>
+      </tr>
     </tbody>
    </table>
 </div>
-<?php
-} ?>
+
 <p style="text-align:center">
     <input type="submit" name="submit" value="<?php echo $submit_text; ?>">
     <input type="reset"  name="reset"  value="<?php echo __('Reset');?>">
     <input type="button" name="cancel" value="<?php echo __('Cancel');?>" onclick='window.location.href="?"'>
 </p>
 </form>
+
+<script type="text/javascript">
+var addMember = function(staffid, name, alerts, error) {
+  var copy = $('#member_template').clone();
+
+  copy.find('[data-name=members\\[\\]]')
+    .attr('name', 'members[]')
+    .val(staffid);
+  copy.find('[data-name^=member_alerts]')
+    .attr('name', 'member_alerts['+staffid+']')
+    .prop('checked', alerts);
+  copy.find('td:first').append(document.createTextNode(name));
+  copy.attr('id', '').show().insertBefore($('#add_member'));
+  copy.removeClass('hidden')
+  if (error)
+      $('<div class="error">').text(error).appendTo(copy.find('td:last'));
+};
+
+$('#add_member').find('button').on('click', function() {
+  var selected = $('#add_access').find(':selected');
+  addMember(selected.val(), selected.text(), true);
+  selected.remove();
+  return false;
+});
+
+$(document).on('click', 'a.drop-membership', function() {
+  var tr = $(this).closest('tr');
+  $('#add_access').append(
+    $('<option>')
+    .attr('value', tr.find('input[name^=members][type=hidden]').val())
+    .text(tr.find('td:first').text())
+  );
+  tr.fadeOut(function() { $(this).remove(); });
+  return false;
+});
+
+<?php
+if ($team) {
+    foreach ($team->members->sort(function($a) { return $a->staff->getName(); }) as $member) {
+        echo sprintf('addMember(%d, %s, %d, %s);',
+            $member->staff_id,
+            JsonDataEncoder::encode((string) $member->staff->getName()),
+            $member->isAlertsEnabled(),
+            JsonDataEncoder::encode($errors['members'][$member->staff_id])
+        );
+    }
+}
+?>
+</script>
diff --git a/scp/teams.php b/scp/teams.php
index 28e72adbf8e850a6c59a3f85b846514b2222f2d2..791181487cd471b8687b39b4a704a4bdd2e60630 100644
--- a/scp/teams.php
+++ b/scp/teams.php
@@ -33,8 +33,8 @@ if($_POST){
             }
             break;
         case 'create':
-            $_team = Team::create();
-            if (($_team->update($_POST, $errors))){
+            $team = Team::create();
+            if (($team->update($_POST, $errors))){
                 $msg=sprintf(__('Successfully added %s'),Format::htmlchars($_POST['team']));
                 $_REQUEST['a']=null;
             }elseif(!$errors['err']){