diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css
index 5b98eaee93d60bc9f6732c12cd2e3e39d24ca10d..73a76e4aa4e4089bda6264ea9d4fd07ab65448cc 100644
--- a/assets/default/css/theme.css
+++ b/assets/default/css/theme.css
@@ -794,6 +794,7 @@ label.required, span.required {
   border: 1px solid #aaa;
   border-left: none;
   border-bottom: none;
+  table-layout: fixed;
 }
 #ticketTable caption {
   padding: 5px;
@@ -811,12 +812,13 @@ label.required, span.required {
   border: 1px solid #aaa;
   border-right: none;
   border-top: none;
+  padding: 0 5px;
 }
 #ticketTable th a {
   color: #000;
 }
 #ticketTable td {
-  padding: 2px;
+  padding: 3px 5px;
   border: 1px solid #aaa;
   border-right: none;
   border-top: none;
@@ -1037,3 +1039,11 @@ img.sign-in-image {
     margin: 0 1%;
     vertical-align: top;
 }
+.truncate {
+    display: inline-block;
+    width: auto;
+    max-width: 100%;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
diff --git a/include/ajax.forms.php b/include/ajax.forms.php
index fcdc4fbc57b734cb89c955df36cb41dd238fe156..d9e61dbd5312a9d5df4d5ca70e92fd25bc2b6025 100644
--- a/include/ajax.forms.php
+++ b/include/ajax.forms.php
@@ -98,29 +98,228 @@ class DynamicFormsAjaxAPI extends AjaxController {
         $ent->delete();
     }
 
-    function getListItemProperties($list_id, $item_id) {
+
+    function _getListItemEditForm($source=null, $item=false) {
+        return new Form(array(
+            'value' => new TextboxField(array(
+                'required' => true,
+                'label' => __('Value'),
+                'configuration' => array(
+                    'translatable' => $item ? $item->getTranslateTag('value') : false,
+                    'size' => 60,
+                    'length' => 0,
+                ),
+            )),
+            'extra' => new TextboxField(array(
+                'label' => __('Abbreviation'),
+                'configuration' => array(
+                    'size' => 60,
+                    'length' => 0,
+                ),
+            )),
+        ), $source);
+    }
+
+    function getListItem($list_id, $item_id) {
 
         $list = DynamicList::lookup($list_id);
         if (!$list || !($item = $list->getItem( (int) $item_id)))
             Http::response(404, 'No such list item');
 
+        $action = "#list/{$list->getId()}/item/{$item->getId()}/update";
+        $item_form = $this->_getListItemEditForm($item->ht, $item);
+
         include(STAFFINC_DIR . 'templates/list-item-properties.tmpl.php');
     }
 
-    function saveListItemProperties($list_id, $item_id) {
+    function getListItems($list_id) {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Login required');
+
+        if (!($list = DynamicList::lookup($list_id)))
+            Http::response(404, 'No such list');
+
+        $pjax_container = '#items';
+        include(STAFFINC_DIR . 'templates/list-items.tmpl.php');
+    }
+
+    function saveListItem($list_id, $item_id) {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Login required');
 
         $list = DynamicList::lookup($list_id);
         if (!$list || !($item = $list->getItem( (int) $item_id)))
             Http::response(404, 'No such list item');
 
-        if (!$item->setConfiguration()) {
+        $item_form = $this->_getListItemEditForm($_POST, $item);
+
+        if ($valid = $item_form->isValid()) {
+            // Update basic information
+            $basic = $item_form->getClean();
+            $item->extra = $basic['extra'];
+            $item->value = $basic['value'];
+
+            if ($_item = DynamicListItem::lookup(array('value'=>$item->value)))
+                if ($_item && $_item->id != $item->id)
+                    $item_form->getField('value')->addError(
+                        __('Value already in use'));
+        }
+
+        // Context
+        $action = "#list/{$list->getId()}/item/{$item->getId()}/update";
+        $icon = ($list->get('sort_mode') == 'SortCol')
+            ? '<i class="icon-sort"></i>&nbsp;' : '';
+
+        if (!$valid || !$item->setConfiguration()) {
             include STAFFINC_DIR . 'templates/list-item-properties.tmpl.php';
             return;
         }
-        else
+        else {
             $item->save();
+        }
+
+        // Send the whole row back
+        $prop_fields = array();
+        foreach ($list->getConfigurationForm()->getFields() as $f) {
+            if (in_array($f->get('type'), array('text', 'datetime', 'phone')))
+                $prop_fields[] = $f;
+            if (strpos($f->get('type'), 'list-') === 0)
+                $prop_fields[] = $f;
+
+            // 4 property columns max
+            if (count($prop_fields) == 4)
+                break;
+        }
+        ob_start();
+        $item->_config = null;
+        include STAFFINC_DIR . 'templates/list-item-row.tmpl.php';
+        $html = ob_get_clean();
+        Http::response(201, $this->encode(array(
+            'id' => $item->getId(),
+            'row' => $html,
+            'success' => true,
+        )));
+    }
+
+    function addListItem($list_id) {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Login required');
+        elseif (!($list = DynamicList::lookup($list_id)))
+            Http::response(404, 'No such list');
+
+        $action = "#list/{$list->getId()}/item/add";
+        $item_form = $this->_getListItemEditForm($_POST ?: null);
+
+        if ($_POST && ($valid = $item_form->isValid())) {
+            $data = $item_form->getClean();
+            if ($_item = DynamicListItem::lookup(array('value'=>$data['value'])))
+                if ($_item && $_item->id)
+                    $item_form->getField('value')->addError(
+                        __('Value already in use'));
+            $data['list_id'] = $list_id;
+            $item = DynamicListItem::create($data);
+            if ($item->save() && $item->setConfiguration())
+                Http::response(201, $this->encode(array('success' => true)));
+        }
 
-        Http::response(201, 'Successfully updated record');
+        include(STAFFINC_DIR . 'templates/list-item-properties.tmpl.php');
+    }
+
+    function importListItems($list_id) {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Login required');
+        elseif (!($list = DynamicList::lookup($list_id)))
+            Http::response(404, 'No such list');
+
+        $info = array(
+            'title' => sprintf('%s &mdash; %s',
+                $list->getName(), __('Import Items')),
+            'action' => "#list/{$list_id}/import",
+            'upload_url' => "lists.php?id={$list_id}&amp;do=import-users",
+        );
+
+        if ($_POST) {
+            $status = $list->importFromPost($_POST['pasted']);
+            if (is_string($status))
+                $info['error'] = $status;
+            else
+                Http::response(201, $this->encode(array('success' => true, 'count' => $status)));
+        }
+
+        include(STAFFINC_DIR . 'templates/list-import.tmpl.php');
+    }
+
+    function disableItems($list_id) {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Login required');
+        elseif (!($list = DynamicList::lookup($list_id)))
+            Http::response(404, 'No such list');
+        elseif (!$_POST['ids'])
+            Http::response(422, 'Send `ids` parameter');
+
+        foreach ($_POST['ids'] as $id) {
+            if ($item = $list->getItem( (int) $id)) {
+                $item->disable();
+                $item->save();
+            }
+            else {
+                Http::response(404, 'No such list item');
+            }
+        }
+        Http::response(200, $this->encode(array('success' => true)));
+    }
+
+    function undisableItems($list_id) {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Login required');
+        elseif (!($list = DynamicList::lookup($list_id)))
+            Http::response(404, 'No such list');
+        elseif (!$_POST['ids'])
+            Http::response(422, 'Send `ids` parameter');
+
+        foreach ($_POST['ids'] as $id) {
+            if ($item = $list->getItem( (int) $id)) {
+                $item->enable();
+                $item->save();
+            }
+            else {
+                Http::response(404, 'No such list item');
+            }
+        }
+        Http::response(200, $this->encode(array('success' => true)));
+    }
+
+    function deleteItems($list_id) {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Login required');
+        elseif (!($list = DynamicList::lookup($list_id)))
+            Http::response(404, 'No such list');
+        elseif (!$_POST['ids'])
+            Http::response(422, 'Send `ids` parameter');
+
+        foreach ($_POST['ids'] as $id) {
+            if ($item = $list->getItem( (int) $id)) {
+                $item->delete();
+            }
+            else {
+                Http::response(404, 'No such list item');
+            }
+        }
+        #Http::response(200, $this->encode(array('success' => true)));
     }
 
     function upload($id) {
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index 015c2a42ed461518d9760c876c005ef4190ae469..d51ddc7af107193a6a76ef24e3e9c836bed5e600 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -601,7 +601,7 @@ class DynamicFormField extends VerySimpleModel {
     }
 
     function  isChangeable() {
-        return $this->hasFlag(self::FLAG_MASK_CHANGE);
+        return !$this->hasFlag(self::FLAG_MASK_CHANGE);
     }
 
     function  isEditable() {
@@ -1276,7 +1276,8 @@ class SelectionField extends FormField {
             }
         }
 
-        return $selection;
+        // Don't return an empty array
+        return $selection ?: null;
     }
 
     function to_database($value) {
diff --git a/include/class.list.php b/include/class.list.php
index be6baec233279867eb46081bf852ff2db180c8a0..5f4ce5aeac6ebb60037dcc1f80807d3ac6a9d336 100644
--- a/include/class.list.php
+++ b/include/class.list.php
@@ -188,14 +188,14 @@ class DynamicList extends VerySimpleModel implements CustomList {
     }
 
     function getName() {
-        return $this->get('name');
+        return $this->getLocal('name');
     }
 
     function getPluralName() {
-        if ($name = $this->get('name_plural'))
+        if ($name = $this->getLocal('name_plural'))
             return $name;
         else
-            return $this->get('name') . 's';
+            return $this->getName . 's';
     }
 
     function getItemCount() {
@@ -447,6 +447,151 @@ class DynamicList extends VerySimpleModel implements CustomList {
         return $selections;
     }
 
+    function importCsv($stream, $defaults=array()) {
+        //Read the header (if any)
+        $headers = array('value' => __('Value'), 'abbrev' => __('Abbreviation'));
+        $form = $this->getConfigurationForm();
+        $named_fields = $fields = array(
+            'value' => new TextboxField(array(
+                'label' => __('Value'),
+                'name' => 'value',
+                'configuration' => array(
+                    'length' => 0,
+                ),
+            )),
+            'abbrev' => new TextboxField(array(
+                'name' => 'abbrev',
+                'label' => __('Abbreviation'),
+                'configuration' => array(
+                    'length' => 0,
+                ),
+            )),
+        );
+        $all_fields = $form->getFields();
+        $has_header = false;
+        foreach ($all_fields as $f)
+            if ($f->get('name'))
+                $named_fields[] = $f;
+
+        if (!($data = fgetcsv($stream, 1000, ",")))
+            return __('Whoops. Perhaps you meant to send some CSV records');
+
+        foreach ($data as $D) {
+            if (strcasecmp($D, 'value') === 0)
+                $has_header = true;
+        }
+        if ($has_header) {
+            foreach ($data as $h) {
+                $found = false;
+                foreach ($all_fields as $f) {
+                    if (in_array(mb_strtolower($h), array(
+                            mb_strtolower($f->get('name')), mb_strtolower($f->get('label'))))) {
+                        $found = true;
+                        if (!$f->get('name'))
+                            return sprintf(__(
+                                '%s: Field must have `variable` set to be imported'), $h);
+                        $headers[$f->get('name')] = $f->get('label');
+                        break;
+                    }
+                }
+                if (!$found) {
+                    $has_header = false;
+                    if (count($data) == count($named_fields)) {
+                        // Number of fields in the user form matches the number
+                        // of fields in the data. Assume things line up
+                        $headers = array();
+                        foreach ($named_fields as $f)
+                            $headers[$f->get('name')] = $f->get('label');
+                        break;
+                    }
+                    else {
+                        return sprintf(__('%s: Unable to map header to a property'), $h);
+                    }
+                }
+            }
+        }
+
+        // 'name' and 'email' MUST be in the headers
+        if (!isset($headers['value']))
+            return __('CSV file must include `value` column');
+
+        if (!$has_header)
+            fseek($stream, 0);
+
+        $items = array();
+        $keys = array('value', 'abbrev');
+        foreach ($headers as $h => $label) {
+            if (!($f = $form->getField($h)))
+                continue;
+
+            $name = $keys[] = $f->get('name');
+            $fields[$name] = $f->getImpl();
+        }
+
+        // Add default fields (org_id, etc).
+        foreach ($defaults as $key => $val) {
+            // Don't apply defaults which are also being imported
+            if (isset($header[$key]))
+                unset($defaults[$key]);
+            $keys[] = $key;
+        }
+
+        while (($data = fgetcsv($stream, 1000, ",")) !== false) {
+            if (count($data) == 1 && $data[0] == null)
+                // Skip empty rows
+                continue;
+            elseif (count($data) != count($headers))
+                return sprintf(__('Bad data. Expected: %s'), implode(', ', $headers));
+            // Validate according to field configuration
+            $i = 0;
+            foreach ($headers as $h => $label) {
+                $f = $fields[$h];
+                $T = $f->parse($data[$i]);
+                if ($f->validateEntry($T) && $f->errors())
+                    return sprintf(__(
+                        /* 1 will be a field label, and 2 will be error messages */
+                        '%1$s: Invalid data: %2$s'),
+                        $label, implode(', ', $f->errors()));
+                // Convert to database format
+                $data[$i] = $f->to_database($T);
+                $i++;
+            }
+            // Add default fields
+            foreach ($defaults as $key => $val)
+                $data[] = $val;
+
+            $items[] = $data;
+        }
+
+        $errors = array();
+        foreach ($items as $u) {
+            $vars = array_combine($keys, $u);
+            $item = $this->addItem($vars);
+            if (!$item || !$item->setConfiguration($errors, $vars))
+                return sprintf(__('Unable to import item: %s'),
+                    print_r($vars, true));
+        }
+
+        return count($items);
+    }
+
+    function importFromPost($stuff, $extra=array()) {
+        if (is_array($stuff) && !$stuff['error']) {
+            // Properly detect Macintosh style line endings
+            ini_set('auto_detect_line_endings', true);
+            $stream = fopen($stuff['tmp_name'], 'r');
+        }
+        elseif ($stuff) {
+            $stream = fopen('php://temp', 'w+');
+            fwrite($stream, $stuff);
+            rewind($stream);
+        }
+        else {
+            return __('Unable to parse submitted items');
+        }
+
+        return self::importCsv($stream, $extra);
+    }
 }
 FormField::addFieldTypes(/* @trans */ 'Custom Lists', array('DynamicList', 'getSelections'));
 
@@ -551,9 +696,9 @@ class DynamicListItem extends VerySimpleModel implements CustomListItem {
         return $this->_config;
     }
 
-    function setConfiguration(&$errors=array()) {
+    function setConfiguration(&$errors=array(), $source=false) {
         $config = array();
-        foreach ($this->getConfigurationForm($_POST)->getFields() as $field) {
+        foreach ($this->getConfigurationForm($source ?: $_POST)->getFields() as $field) {
             $config[$field->get('id')] = $field->to_php($field->getClean());
             $errors = array_merge($errors, $field->errors());
         }
diff --git a/include/class.pagenate.php b/include/class.pagenate.php
index 2d6e8b8505233b5535432372d4848c145b6e4749..7f4be6a37ec1ff5b0269cccc67b64d3883a7a672 100644
--- a/include/class.pagenate.php
+++ b/include/class.pagenate.php
@@ -18,6 +18,7 @@ class PageNate {
 
     var $start;
     var $limit;
+    var $slack = 0;
     var $total;
     var $page;
     var $pages;
@@ -54,13 +55,16 @@ class PageNate {
     }
 
     function getStart() {
-        return $this->start;
+        return max($this->start - $this->slack, 0);
     }
 
     function getLimit() {
         return $this->limit;
     }
 
+    function setSlack($count) {
+        $this->slack = $count;
+    }
 
     function getNumPages(){
         return $this->pages;
@@ -72,27 +76,28 @@ class PageNate {
 
     function showing() {
         $html = '';
-        $from= $this->start+1;
-        if ($this->start + $this->limit < $this->total) {
-            $to= $this->start + $this->limit;
+        $start = $this->getStart() + 1;
+        $end = min($start + $this->limit + $this->slack - 1, $this->total);
+        if ($end < $this->total) {
+            $to= $end;
         } else {
             $to= $this->total;
         }
         $html="&nbsp;".__('Showing')."&nbsp;&nbsp;";
         if ($this->total > 0) {
             $html .= sprintf(__('%1$d - %2$d of %3$d' /* Used in pagination output */),
-               $from, $to, $this->total);
+               $start, $end, $this->total);
         }else{
             $html .= " 0 ";
         }
         return $html;
     }
 
-    function getPageLinks() {
+    function getPageLinks($hash=false, $pjax=false) {
         $html                 = '';
         $file                =$this->url;
         $displayed_span     = 5;
-        $total_pages         = ceil( $this->total / $this->limit );
+        $total_pages         = ceil( ($this->total - $this->slack) / $this->limit );
         $this_page             = ceil( ($this->start+1) / $this->limit );
 
         $last=$this_page-1;
@@ -116,15 +121,24 @@ class PageNate {
 
         for ($i=$start_loop; $i <= $stop_loop; $i++) {
             $page = ($i - 1) * $this->limit;
+            $href = "{$file}&amp;p={$i}";
+            if ($hash)
+                $href .= '#'.$hash;
             if ($i == $this_page) {
                 $html .= "\n<b>[$i]</b>";
+            }
+            elseif ($pjax) {
+                $html .= " <a href=\"{$href}\" data-pjax-container=\"{$pjax}\"><b>$i</b></a>";
             } else {
-                $html .= "\n<a href=\"$file&p=$i\" ><b>$i</b></a>";
+                $html .= "\n<a href=\"{$href}\" ><b>$i</b></a>";
             }
         }
         if($stop_loop<$total_pages){
             $nextspan=($stop_loop+$displayed_span>$total_pages)?$total_pages-$displayed_span:$stop_loop+$displayed_span;
-            $html .= "\n<a href=\"$file&p=$nextspan\" ><strong>&raquo;</strong></a>";
+            $href = "{$file}&amp;p={$nextspan}";
+            if ($hash)
+                $href .= '#'.$hash;
+            $html .= "\n<a href=\"{$href}\" ><strong>&raquo;</strong></a>";
         }
 
 
@@ -133,7 +147,9 @@ class PageNate {
     }
 
     function paginate(QuerySet $qs) {
-        return $qs->limit($this->getLimit())->offset($this->getStart());
+        $start = $this->getStart();
+        $end = min($start + $this->getLimit() + $this->slack + ($start > 0 ? $this->slack : 0), $this->total);
+        return $qs->limit($end-$start)->offset($start);
     }
 
 }
diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php
index 76412f7e3b49e8921a432cc4f65369fcb46d1007..58d056a1786b624bb3ea0e63e5c10ae62cc24d80 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -141,9 +141,9 @@ $tickets->values(
             $dept = $T['dept__ispublic']
                 ? Dept::getLocalById($T['dept_id'], 'name', $T['dept__name'])
                 : $defaultDept;
-            $subject = Format::truncate($subject_field->display(
+            $subject = $subject_field->display(
                 $subject_field->to_php($T['cdata__subject']) ?: $T['cdata__subject']
-            ), 40);
+            );
             $status = TicketStatus::getLocalById($T['status_id'], 'value', $T['status__name']);
             if (false) // XXX: Reimplement attachment count support
                 $subject.='  &nbsp;&nbsp;<span class="Icon file"></span>';
@@ -162,9 +162,9 @@ $tickets->values(
                 <td>&nbsp;<?php echo Format::date($T['created']); ?></td>
                 <td>&nbsp;<?php echo $status; ?></td>
                 <td>
-                    <a href="tickets.php?id=<?php echo $T['ticket_id']; ?>"><?php echo $subject; ?></a>
+                    <a class="truncate" href="tickets.php?id=<?php echo $T['ticket_id']; ?>"><?php echo $subject; ?></a>
                 </td>
-                <td>&nbsp;<?php echo Format::truncate($dept,30); ?></td>
+                <td>&nbsp;<span class="truncate"><?php echo $dept; ?></span></td>
             </tr>
         <?php
         }
diff --git a/include/staff/dynamic-list.inc.php b/include/staff/dynamic-list.inc.php
index 9fe06217332e4afc6e2d2b8a2bccf717a7faafba..6a09009906e1d54c33a3e3a187e4a9bb35e87a7b 100644
--- a/include/staff/dynamic-list.inc.php
+++ b/include/staff/dynamic-list.inc.php
@@ -121,6 +121,7 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info)
             <th nowrap></th>
             <th nowrap><?php echo __('Label'); ?></th>
             <th nowrap><?php echo __('Type'); ?></th>
+            <th nowrap><?php echo __('Visibility'); ?></th>
             <th nowrap><?php echo __('Variable'); ?></th>
             <th nowrap><?php echo __('Delete'); ?></th>
         </tr>
@@ -160,6 +161,8 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info)
                     href="#form/field-config/<?php
                         echo $f->get('id'); ?>"><i
                         class="icon-cog"></i> <?php echo __('Config'); ?></a> <?php } ?></td>
+            <td>
+                <?php echo $f->getVisibilityDescription(); ?></td>
             <td>
                 <input type="text" size="20" name="name-<?php echo $id; ?>"
                     value="<?php echo Format::htmlchars($f->get('name'));
@@ -200,6 +203,7 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info)
                 </optgroup>
                 <?php } ?>
             </select></td>
+            <td></td>
             <td><input type="text" size="20" name="name-new-<?php echo $i; ?>"
                 value="<?php echo $info["name-new-$i"]; ?>"/>
                 <font class="error"><?php
@@ -212,125 +216,9 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info)
 </table>
 </div>
 <div id="items" class="hidden tab_content">
-    <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
-    <thead>
-    <?php if ($list) {
-        $page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1;
-        $count = $list->getNumItems();
-        $pageNav = new Pagenate($count, $page, PAGE_LIMIT);
-        $pageNav->setURL('list.php', array('id' => $list->getId()));
-        $showing=$pageNav->showing().' '.__('list items');
-        ?>
-    <?php }
-        else $showing = __('Add a few initial items to the list');
-    ?>
-        <tr>
-            <th colspan="5">
-                <em><?php echo $showing; ?></em>
-            </th>
-        </tr>
-        <tr>
-            <th></th>
-            <th><?php echo __('Value'); ?></th>
-            <?php
-            if (!$list || $list->hasAbbrev()) { ?>
-            <th><?php echo __(/* Short for 'abbreviation' */ 'Abbrev'); ?> <em style="display:inline">&mdash;
-                <?php echo __('abbreviations and such'); ?></em></th>
-            <?php
-            } ?>
-            <th><?php echo __('Disabled'); ?></th>
-            <th><?php echo __('Delete'); ?></th>
-        </tr>
-    </thead>
-
-    <tbody <?php if ($info['sort_mode'] == 'SortCol') { ?>
-            class="sortable-rows" data-sort="sort-"<?php } ?>>
-        <?php
-        if ($list) {
-            $icon = ($info['sort_mode'] == 'SortCol')
-                ? '<i class="icon-sort"></i>&nbsp;' : '';
-        foreach ($list->getAllItems() as $i) {
-            $id = $i->getId(); ?>
-        <tr class="<?php if (!$i->isEnabled()) echo 'disabled'; ?>">
-            <td><?php echo $icon; ?>
-                <input type="hidden" name="sort-<?php echo $id; ?>"
-                value="<?php echo $i->getSortOrder(); ?>"/></td>
-            <td><input type="text" size="40" name="value-<?php echo $id; ?>"
-                data-translate-tag="<?php echo $i->getTranslateTag('value'); ?>"
-                value="<?php echo $i->getValue(); ?>"/>
-                <?php if ($list->hasProperties()) { ?>
-                   <a class="action-button field-config"
-                       style="overflow:inherit"
-                       href="#list/<?php
-                        echo $list->getId(); ?>/item/<?php
-                        echo $id ?>/properties"
-                       id="item-<?php echo $id; ?>"
-                    ><?php
-                        echo sprintf('<i class="icon-edit" %s></i> ',
-                                $i->getConfiguration()
-                                ? '': 'style="color:red; font-weight:bold;"');
-                        echo __('Properties');
-                   ?></a>
-                <?php
-                }
-
-                if ($errors["value-$id"])
-                    echo sprintf('<br><span class="error">%s</span>',
-                            $errors["value-$id"]);
-                ?>
-            </td>
-            <?php
-            if ($list->hasAbbrev()) { ?>
-            <td><input type="text" size="30" name="abbrev-<?php echo $id; ?>"
-                value="<?php echo $i->getAbbrev(); ?>"/></td>
-            <?php
-            } ?>
-            <td>
-                <?php
-                if (!$i->isDisableable())
-                     echo '<i class="icon-ban-circle"></i>';
-                else
-                    echo sprintf('<input type="checkbox" name="disable-%s"
-                            %s %s />',
-                            $id,
-                            !$i->isEnabled() ? ' checked="checked" ' : '',
-                            (!$i->isEnabled() && !$i->isEnableable()) ? ' disabled="disabled" ' : ''
-                            );
-                ?>
-            </td>
-            <td>
-                <?php
-                if (!$i->isDeletable())
-                    echo '<i class="icon-ban-circle"></i>';
-                else
-                    echo sprintf('<input type="checkbox" name="delete-item-%s">', $id);
-
-                ?>
-            </td>
-        </tr>
-    <?php }
-    }
-
-    if (!$list || $list->allowAdd()) {
-       for ($i=0; $i<$newcount; $i++) { ?>
-        <tr>
-            <td><?php echo $icon; ?> <em>+</em>
-                <input type="hidden" name="sort-new-<?php echo $i; ?>"/></td>
-            <td><input type="text" size="40" name="value-new-<?php echo $i; ?>"/></td>
-            <?php
-            if (!$list || $list->hasAbbrev()) { ?>
-            <td><input type="text" size="30" name="abbrev-new-<?php echo $i; ?>"/></td>
-            <?php
-            } ?>
-            <td>&nbsp;</td>
-            <td>&nbsp;</td>
-        </tr>
-    <?php
-       }
-    }?>
-    </tbody>
-    </table>
-</div>
+<?php
+    $pjax_container = '#items';
+    include STAFFINC_DIR . 'templates/list-items.tmpl.php'; ?>
 </div>
 <p class="centered">
     <input type="submit" name="submit" value="<?php echo $submit_text; ?>">
@@ -342,14 +230,46 @@ $info=Format::htmlchars(($errors && $_POST) ? array_merge($info,$_POST) : $info)
 
 <script type="text/javascript">
 $(function() {
-    $('a.field-config').click( function(e) {
+    $(document).on('click', 'a.field-config', function(e) {
         e.preventDefault();
         var $id = $(this).attr('id');
         var url = 'ajax.php/'+$(this).attr('href').substr(1);
-        $.dialog(url, [201], function (xhr) {
-            $('a#'+$id+' i').removeAttr('style');
+        $.dialog(url, [201], function (xhr, resp) {
+          var json = $.parseJSON(resp);
+          if (json && json.success) {
+            if (json.id && json.row) {
+              $('#list-item-' + json.id).replaceWith(json.row);
+            }
+            else {
+              $.pjax.reload('#pjax-container');
+            }
+          }
         });
         return false;
     });
+    $(document).on('click', 'a.items-action', function(e) {
+        e.preventDefault();
+        var ids = [];
+        $('form#save :checkbox.mass:checked').each(function() {
+            ids.push($(this).val());
+        });
+        if (ids.length && confirm(__('You sure?'))) {
+            $.ajax({
+              url: 'ajax.php/' + $(this).attr('href').substr(1),
+              type: 'POST',
+              data: {count:ids.length, ids:ids},
+              dataType: 'json',
+              success: function(json) {
+                if (json.success) {
+                  if (window.location.search.indexOf('a=items') != -1)
+                    $.pjax.reload('#items');
+                  else
+                    $.pjax.reload('#pjax-container');
+                }
+              }
+            });
+        }
+        return false;
+    });
 });
 </script>
diff --git a/include/staff/footer.inc.php b/include/staff/footer.inc.php
index 5abc0997369e1e00e9e3d79bc98ae92b671438ca..9f2ee556c48cc1ff95de18e93be5c3a61196bfb7 100644
--- a/include/staff/footer.inc.php
+++ b/include/staff/footer.inc.php
@@ -43,10 +43,11 @@ if(is_object($thisstaff) && $thisstaff->isStaff()) { ?>
 <script type="text/javascript">
 if ($.support.pjax) {
   $(document).on('click', 'a', function(event) {
-    if (!$(this).hasClass('no-pjax')
-        && !$(this).closest('.no-pjax').length
-        && $(this).attr('href')[0] != '#')
-      $.pjax.click(event, {container: $('#pjax-container'), timeout: 2000});
+    var $this = $(this);
+    if (!$this.hasClass('no-pjax')
+        && !$this.closest('.no-pjax').length
+        && $this.attr('href')[0] != '#')
+      $.pjax.click(event, {container: $this.data('pjaxContainer') || $('#pjax-container'), timeout: 2000});
   })
 }
 </script>
diff --git a/include/staff/templates/list-import.tmpl.php b/include/staff/templates/list-import.tmpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..b3c38a99d66b83973de5459acc6285769e02f625
--- /dev/null
+++ b/include/staff/templates/list-import.tmpl.php
@@ -0,0 +1,85 @@
+<h3><?php echo $info['title']; ?></h3>
+<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b>
+<hr/>
+<?php
+if ($info['error']) {
+    echo sprintf('<p id="msg_error">%s</p>', $info['error']);
+} elseif ($info['warn']) {
+    echo sprintf('<p id="msg_warning">%s</p>', $info['warn']);
+} elseif ($info['msg']) {
+    echo sprintf('<p id="msg_notice">%s</p>', $info['msg']);
+} ?>
+<ul class="tabs" id="user-import-tabs">
+    <li class="active"><a href="#copy-paste"
+        ><i class="icon-edit"></i>&nbsp;<?php echo __('Copy Paste'); ?></a></li>
+    <li><a href="#upload"
+        ><i class="icon-fixed-width icon-cloud-upload"></i>&nbsp;<?php echo __('Upload'); ?></a></li>
+</ul>
+
+<form action="<?php echo $info['action']; ?>" method="post" enctype="multipart/form-data"
+    onsubmit="javascript:
+    if ($(this).find('[name=import]').val()) {
+        $(this).attr('action', '<?php echo $info['upload_url']; ?>');
+        $(document).unbind('submit.dialog');
+    }">
+<?php echo csrf_token();
+if ($org_id) { ?>
+    <input type="hidden" name="id" value="<?php echo $org_id; ?>"/>
+<?php } ?>
+<div id="user-import-tabs_container">
+<div class="tab_content" id="copy-paste" style="margin:5px;">
+<h2 style="margin-bottom:10px"><?php echo __('Value and Abbreviation'); ?></h2>
+<p><?php echo __(
+'Enter one name and abbreviation per line.'); ?><br/><em><?php echo __(
+'To import items with properties, use the Upload tab.'); ?></em>
+</p>
+<textarea name="pasted" style="display:block;width:100%;height:8em;padding:5px"
+    placeholder="<?php echo __('e.g. My Location, MY'); ?>">
+<?php echo $info['pasted']; ?>
+</textarea>
+</div>
+
+<div class="hidden tab_content" id="upload" style="margin:5px;">
+<h2 style="margin-bottom:10px"><?php echo __('Import a CSV File'); ?></h2>
+<p>
+<em><?php echo __(
+'Use the columns shown in the table below. To add more properties, use the Properties tab.  Only properties with `variable` defined can be imported.'); ?>
+</p>
+<table class="list"><tr>
+<?php
+    $fields = array('Value', 'Abbreviation');
+    $data = array(
+        array('Value' => __('My Location'), 'Abbreviation' => 'MY')
+    );
+    foreach ($list->getConfigurationForm()->getFields() as $f)
+        if ($f->get('name'))
+            $fields[] = $f->get('label');
+    foreach ($fields as $f) { ?>
+        <th><?php echo mb_convert_case($f, MB_CASE_TITLE); ?></th>
+<?php } ?>
+</tr>
+<?php
+    foreach ($data as $d) {
+        foreach ($fields as $f) {
+            ?><td><?php
+            if (isset($d[$f])) echo $d[$f];
+            ?></td><?php
+        }
+    } ?>
+</tr></table>
+<br/>
+<input type="file" name="import"/>
+</div>
+
+    <hr>
+    <p class="full-width">
+        <span class="buttons pull-left">
+            <input type="reset" value="<?php echo __('Reset'); ?>">
+            <input type="button" name="cancel" class="close"  value="<?php
+            echo __('Cancel'); ?>">
+        </span>
+        <span class="buttons pull-right">
+            <input type="submit" value="<?php echo __('Import Items'); ?>">
+        </span>
+     </p>
+</form>
diff --git a/include/staff/templates/list-item-properties.tmpl.php b/include/staff/templates/list-item-properties.tmpl.php
index def77747a2bea226a3e73bece150c380aea696bd..706c9484cb171e959788ce13a5c5cedb0624008e 100644
--- a/include/staff/templates/list-item-properties.tmpl.php
+++ b/include/staff/templates/list-item-properties.tmpl.php
@@ -1,63 +1,52 @@
-    <h3><?php echo __('Item Properties'); ?> &mdash; <?php echo $item->getValue() ?></h3>
-    <a class="close" href=""><i class="icon-remove-circle"></i></a>
-    <hr/>
-    <form method="post" action="#list/<?php
-            echo $list->getId(); ?>/item/<?php
-            echo $item->getId(); ?>/properties">
-        <?php
-        echo csrf_token();
-        $config = $item->getConfiguration();
-        $internal = $item->isInternal();
-        $form = $item->getConfigurationForm();
-        echo $form->getMedia();
-        foreach ($form->getFields() as $f) {
-            ?>
-            <div class="custom-field" id="field<?php
-                echo $f->getWidget()->id; ?>"
-                <?php
-                if (!$f->isVisible()) echo 'style="display:none;"'; ?>>
-            <div class="field-label">
-            <label for="<?php echo $f->getWidget()->name; ?>"
-                style="vertical-align:top;padding-top:0.2em">
-                <?php echo Format::htmlchars($f->getLocal('label')); ?>:</label>
-                <?php
-                if (!$internal && $f->isEditable() && $f->get('hint')) { ?>
-                    <br /><em style="color:gray;display:inline-block"><?php
-                        echo Format::htmlchars($f->get('hint')); ?></em>
-                <?php
-                } ?>
-            </div><div>
-            <?php
-            if ($internal && !$f->isEditable())
-                $f->render(array('mode'=>'view'));
-            else {
-                $f->render();
-                if ($f->get('required')) { ?>
-                    <font class="error">*</font>
-                <?php
-                }
-            }
-            ?>
-            </div>
-            <?php
-            foreach ($f->errors() as $e) { ?>
-                <div class="error"><?php echo $e; ?></div>
-            <?php } ?>
-            </div>
-            <?php
-        }
-        ?>
-        </table>
-        <hr>
-        <p class="full-width">
-            <span class="buttons pull-left">
-                <input type="reset" value="<?php echo __('Reset'); ?>">
-                <input type="button" value="<?php echo __('Cancel'); ?>" class="close">
-            </span>
-            <span class="buttons pull-right">
-                <input type="submit" value="<?php echo __('Save'); ?>">
-            </span>
-         </p>
-    </form>
-    <div class="clear"></div>
+<h3><?php echo $list->getName(); ?> &mdash; <?php
+    echo $item ? $item->getValue() : __('Add New List Item'); ?></h3>
+<a class="close" href=""><i class="icon-remove-circle"></i></a>
+<hr/>
 
+<ul class="tabs" id="item_tabs">
+    <li class="active">
+        <a href="#value"><i class="icon-reorder"></i>
+        <?php echo __('Value'); ?></a>
+    </li>
+    <li><a href="#properties"><i class="icon-asterisk"></i>
+        <?php echo __('Item Properties'); ?></a>
+    </li>
+</ul>
+
+<form method="post" id="item_tabs_container" action="<?php echo $action; ?>">
+    <?php
+    echo csrf_token();
+    $internal = $item ? $item->isInternal() : false;
+?>
+
+<div class="tab_content" id="value">
+<?php
+    $form = $item_form;
+    include 'dynamic-form-simple.tmpl.php';
+?>
+</div>
+
+<div class="tab_content hidden" id="properties">
+<?php
+    $form = $item ? $item->getConfigurationForm($_POST ?: null)
+        : $list->getConfigurationForm();
+    include 'dynamic-form-simple.tmpl.php';
+?>
+</div>
+
+<hr>
+<p class="full-width">
+    <span class="buttons pull-left">
+        <input type="reset" value="<?php echo __('Reset'); ?>">
+        <input type="button" value="<?php echo __('Cancel'); ?>" class="close">
+    </span>
+    <span class="buttons pull-right">
+        <input type="submit" value="<?php echo __('Save'); ?>">
+    </span>
+ </p>
+</form>
+
+<script type="text/javascript">
+   // Make translatable fields translatable
+   $('input[data-translate-tag], textarea[data-translate-tag]').translatable();
+</script>
diff --git a/include/staff/templates/list-item-row.tmpl.php b/include/staff/templates/list-item-row.tmpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..267cb1e5e7e0ca7a2d1666bf8c1993ab93b82f76
--- /dev/null
+++ b/include/staff/templates/list-item-row.tmpl.php
@@ -0,0 +1,39 @@
+<?php
+    $id = $item->getId(); ?>
+    <tr id="list-item-<?php echo $id; ?>" class="<?php if (!$item->isEnabled()) echo 'disabled'; ?>">
+        <td nowrap><?php echo $icon; ?>
+            <input type="hidden" name="sort-<?php echo $id; ?>"
+            value="<?php echo $item->getSortOrder(); ?>"/>
+            <input type="checkbox" value="<?php echo $id; ?>" class="mass nowarn"/>
+        </td>
+        <td>
+            <a class="field-config"
+               style="overflow:inherit"
+               href="#list/<?php
+                echo $list->getId(); ?>/item/<?php
+                echo $id ?>/update"
+               id="item-<?php echo $id; ?>"
+            ><?php
+                echo sprintf('<i class="icon-edit" %s></i> ',
+                        $item->getConfiguration()
+                        ? '': 'style="color:red; font-weight:bold;"');
+            ?>
+            <?php echo Format::htmlchars($item->getValue()); ?>
+            <?php
+            if ($list->hasAbbrev() && ($A = $item->getAbbrev())) { ?>
+                ( <?php echo Format::htmlchars($A); ?> )
+            <?php
+            } ?>
+<?php           if ($errors["value-$id"])
+                echo sprintf('<div class="error">%s</div>',
+                        $errors["value-$id"]);
+            ?>
+            </a>
+        </td>
+<?php $props = $item->getConfiguration();
+    foreach ($prop_fields as $F) { ?>
+        <td style="max-width: 20%"><span class="truncate"><?php
+        echo $F->display($props[$F->get('id')]);
+        ?></span></td>
+<?php } ?>
+    </tr>
diff --git a/include/staff/templates/list-items.tmpl.php b/include/staff/templates/list-items.tmpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..e57a8bf09fd5265c675447728c2aeb880e45e074
--- /dev/null
+++ b/include/staff/templates/list-items.tmpl.php
@@ -0,0 +1,127 @@
+    <?php if ($list) {
+        $page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1;
+        $count = $list->getNumItems();
+        $pageNav = new Pagenate($count, $page, PAGE_LIMIT);
+        if ($list->getSortMode() == 'SortCol')
+            $pageNav->setSlack(1);
+        $pageNav->setURL('lists.php?id='.$list->getId().'&a=items');
+        $showing=$pageNav->showing().' '.__('list items');
+        ?>
+    <?php }
+        else $showing = __('Add a few initial items to the list');
+    ?>
+    <div style="margin: 5px 0">
+    <div class="pull-left">
+        <input type="search" size="25" id="search" value="<?php
+            echo Format::htmlchars($_POST['search']); ?>"/>
+        <button type="submit" onclick="javascript:
+            event.preventDefault();
+            $.pjax({type: 'POST', data: { search: $('#search').val() }, container: '#pjax-container'});
+            return false;
+"><?php echo __('Search'); ?></button>
+        <?php if ($_POST['search']) { ?>
+        <a href="#" onclick="javascript:
+            $.pjax.reload('#pjax-container'); return false; "
+            ><i class="icon-remove-sign"></i> <?php
+                echo __('clear'); ?></a>
+        <?php } ?>
+    </div>
+    <?php if ($list) { ?>
+    <div class="pull-right">
+        <em style="display:inline-block; padding-bottom: 3px;"><?php echo $showing; ?></em>
+        <?php if ($list->allowAdd()) { ?>
+        <a class="action-button field-config"
+            href="#list/<?php
+            echo $list->getId(); ?>/item/add">
+            <i class="icon-plus-sign"></i>
+            <?php echo __('Add New Item'); ?>
+        </a>
+        <a class="action-button field-config"
+            href="#list/<?php
+            echo $list->getId(); ?>/import">
+            <i class="icon-upload"></i>
+            <?php echo __('Import Items'); ?>
+        </a>
+        <?php } ?>
+        <span class="action-button pull-right" data-dropdown="#action-dropdown-more">
+            <i class="icon-caret-down pull-right"></i>
+            <span ><i class="icon-cog"></i> <?php echo __('More');?></span>
+        </span>
+        <div id="action-dropdown-more" class="action-dropdown anchor-right">
+            <ul>
+                <li><a class="items-action" href="#list/<?php echo $list->getId(); ?>/delete">
+                    <i class="icon-trash icon-fixed-width"></i>
+                    <?php echo __('Delete'); ?></a></li>
+                <li><a class="items-action" href="#list/<?php echo $list->getId(); ?>/disable">
+                    <i class="icon-ban-circle icon-fixed-width"></i>
+                    <?php echo __('Disable'); ?></a></li>
+                <li><a class="items-action" href="#list/<?php echo $list->getId(); ?>/enable">
+                    <i class="icon-ok-sign icon-fixed-width"></i>
+                    <?php echo __('Enable'); ?></a></li>
+            </ul>
+        </div>
+    </div>
+    <?php } ?>
+
+    <div class="clear"></div>
+    </div>
+
+
+<?php
+$prop_fields = array();
+if ($list) {
+    foreach ($list->getConfigurationForm()->getFields() as $f) {
+        if (in_array($f->get('type'), array('text', 'datetime', 'phone')))
+            $prop_fields[] = $f;
+        if (strpos($f->get('type'), 'list-') === 0)
+            $prop_fields[] = $f;
+
+        // 4 property columns max
+        if (count($prop_fields) == 4)
+            break;
+    }
+}
+?>
+
+    <table class="form_table fixed" width="940" border="0" cellspacing="0" cellpadding="2">
+    <thead>
+        <tr>
+            <th width="24" nowrap></th>
+            <th><?php echo __('Value'); ?></th>
+<?php foreach ($prop_fields as $F) { ?>
+            <th><?php echo $F->getLocal('label'); ?></th>
+<?php } ?>
+        </tr>
+    </thead>
+
+    <tbody <?php if (!isset($_POST['search']) && $list && $list->get('sort_mode') == 'SortCol') { ?>
+            class="sortable-rows" data-sort="sort-"<?php } ?>>
+        <?php
+        if ($list) {
+            $icon = ($list->get('sort_mode') == 'SortCol')
+                ? '<i class="icon-sort"></i>&nbsp;' : '';
+            $items = $list->getAllItems();
+            if ($_POST['search']) {
+                $items->filter(Q::any(array(
+                    'value__contains'=>$_POST['search'],
+                    'extra__contains'=>$_POST['search'],
+                    'properties__contains'=>$_POST['search'],
+                )));
+                $search = true;
+            }
+            $items = $pageNav->paginate($items);
+            // Emit a marker for the first sort offset ?>
+            <input type="hidden" id="sort-offset" value="<?php echo
+                max($items[0]->sort, $pageNav->getStart()); ?>"/>
+<?php
+            foreach ($items as $item) {
+                include STAFFINC_DIR . 'templates/list-item-row.tmpl.php';
+            }
+        } ?>
+    </tbody>
+    </table>
+<?php if ($pageNav && $pageNav->getNumPages()) { ?>
+    <div><?php echo __('Page').':'.$pageNav->getPageLinks('items', $pjax_container); ?></div>
+<?php } ?>
+</div>
+
diff --git a/include/staff/templates/simple-form.tmpl.php b/include/staff/templates/simple-form.tmpl.php
index 2191e474de62a90d55e15ceb5ac9a3ed691e9272..705592fcf516635c4f9148d12716d638f7710014 100644
--- a/include/staff/templates/simple-form.tmpl.php
+++ b/include/staff/templates/simple-form.tmpl.php
@@ -8,7 +8,7 @@
         <div class="form-field"><?php
         if (!$field->isBlockLevel()) { ?>
             <div class="<?php if ($field->isRequired()) echo 'required';
-                ?>" style="display:inline-block;width:260px;">
+                ?>" style="display:inline-block;width:27%;">
                 <?php echo Format::htmlchars($field->getLocal('label')); ?>:
             <?php if ($field->isRequired()) { ?>
                 <span class="error">*</span>
@@ -20,7 +20,7 @@
                 ?></div>
 <?php       } ?>
             </div>
-            <div style="display:inline-block;max-width:700px"><?php
+            <div style="display:inline-block;max-width:73%"><?php
         }
         $field->render($options);
         foreach ($field->errors() as $e) { ?>
diff --git a/include/staff/templates/tickets.tmpl.php b/include/staff/templates/tickets.tmpl.php
index ce48d471b659a6ae2b0cb2fb95bc9a646bd90066..e70d3e29cd537338d5feb20b6f5f070acdf60de9 100644
--- a/include/staff/templates/tickets.tmpl.php
+++ b/include/staff/templates/tickets.tmpl.php
@@ -85,7 +85,7 @@ if ($results) { ?>
 <?php csrf_token(); ?>
  <input type="hidden" name="a" value="mass_process" >
  <input type="hidden" name="do" id="action" value="" >
- <table class="list" border="0" cellspacing="1" cellpadding="2" width="940">
+ <table class="list fixed" border="0" cellspacing="1" cellpadding="2" width="940">
     <thead>
         <tr>
             <?php
@@ -99,11 +99,11 @@ if ($results) { ?>
             <th width="300"><?php echo __('Subject'); ?></th>
             <?php
             if ($user) { ?>
-            <th width="200"><?php echo __('Department'); ?></th>
-            <th width="200"><?php echo __('Assignee'); ?></th>
+            <th width="100"><?php echo __('Department'); ?></th>
+            <th width="100"><?php echo __('Assignee'); ?></th>
             <?php
             } else { ?>
-            <th width="400"><?php echo __('User'); ?></th>
+            <th width="200"><?php echo __('User'); ?></th>
             <?php
             } ?>
         </tr>
@@ -119,15 +119,15 @@ if ($results) { ?>
 
         $assigned='';
         if ($row['staff_id'])
-            $assigned=sprintf('<span class="Icon staffAssigned">%s</span>',Format::truncate($row['staff'],40));
+            $assigned=sprintf('<span class="truncate Icon staffAssigned">%s</span>',$row['staff']);
         elseif ($row['team_id'])
-            $assigned=sprintf('<span class="Icon teamAssigned">%s</span>',Format::truncate($row['team'],40));
+            $assigned=sprintf('<span class="truncate Icon teamAssigned">%s</span>',$row['team']);
         else
             $assigned=' ';
 
         $status = ucfirst($row['status']);
         $tid=$row['number'];
-        $subject = Format::htmlchars(Format::truncate($row['subject'],40));
+        $subject = Format::htmlchars($row['subject']);
         $threadcount=$row['thread_count'];
         ?>
         <tr id="<?php echo $row['ticket_id']; ?>">
@@ -147,7 +147,8 @@ if ($results) { ?>
                 data-preview="#tickets/<?php echo $row['ticket_id']; ?>/preview"><?php echo $tid; ?></a></td>
             <td align="center" nowrap><?php echo Format::datetime($row['effective_date']); ?></td>
             <td><?php echo $status; ?></td>
-            <td><a <?php if ($flag) { ?> class="Icon <?php echo $flag; ?>Ticket" title="<?php echo ucfirst($flag); ?> Ticket" <?php } ?>
+            <td><a class="truncate <?php if ($flag) { ?> Icon <?php echo $flag; ?>Ticket" title="<?php echo ucfirst($flag); ?> Ticket<?php } ?>"
+                style="max-width: 80%; max-width: calc(100% - 86px);"
                 href="tickets.php?id=<?php echo $row['ticket_id']; ?>"><?php echo $subject; ?></a>
                  <?php
                     if ($threadcount>1)
@@ -161,7 +162,7 @@ if ($results) { ?>
             </td>
             <?php
             if ($user) { ?>
-            <td><?php echo Format::truncate($row['department'], 40); ?></td>
+            <td><span class="truncate"><?php echo $row['department']; ?></td>
             <td>&nbsp;<?php echo $assigned; ?></td>
             <?php
             } else { ?>
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index 0560eaf00ce70eacee1048f45dd9903a13368843..d16b43523050e3a12e75f73b472954880c42e209 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -403,8 +403,8 @@ $tcount = $ticket->getThreadEntries($types)->count();
                     <span class="pull-left">
                     <span style="display:inline-block"><?php
                         echo Format::datetime($entry->created);?></span>
-                    <span style="display:inline-block;padding:0 1em" class="faded title"><?php
-                        echo Format::truncate($entry->title, 100); ?></span>
+                    <span style="display:inline-block;padding:0 1em;max-width: 500px" class="faded title truncate"><?php
+                        echo $entry->title; ?></span>
                     </span>
                 <div class="pull-right">
 <?php           if ($entry->hasActions()) {
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index f0d4a6b2d5cecc791158f4af4fd646397442f2b5..f8013df254662376ad4546e6d10d3c0cf115813e 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -226,15 +226,15 @@ $_SESSION[':Q:tickets'] = $tickets;
  <input type="hidden" name="do" id="action" value="" >
  <input type="hidden" name="status" value="<?php echo
  Format::htmlchars($_REQUEST['status'], true); ?>" >
- <table class="list" border="0" cellspacing="1" cellpadding="2" width="940">
+ <table class="list fixed" border="0" cellspacing="1" cellpadding="2" width="940">
     <thead>
         <tr>
             <?php if ($thisstaff->canManageTickets()) { ?>
-	        <th width="8px">&nbsp;</th>
+	        <th width="12px">&nbsp;</th>
             <?php } ?>
 	        <th width="70">
                 <?php echo __('Ticket'); ?></th>
-	        <th width="70">
+	        <th width="100">
                 <?php echo $date_header ?: __('Date'); ?></th>
 	        <th width="280">
                 <?php echo __('Subject'); ?></th>
@@ -290,17 +290,17 @@ $_SESSION[':Q:tickets'] = $tickets;
                 $dept = Dept::getLocalById($T['dept_id'], 'name', $T['dept__name']);
                 if($showassigned) {
                     if($T['staff_id'])
-                        $lc=sprintf('<span class="Icon staffAssigned">%s</span>',Format::truncate((string) new PersonsName($T['staff__firstname'].' '.$T['staff__lastname']),40));
+                        $lc=sprintf('<span class="Icon staffAssigned truncate">%s</span>',(string) new PersonsName($T['staff__firstname'].' '.$T['staff__lastname']));
                     elseif($T['team_id'])
                         $lc=sprintf('<span class="Icon teamAssigned">%s</span>',
-                            Format::truncate(Team::getLocalById($T['team_id'], 'name', $T['team__name']),40));
+                            Team::getLocalById($T['team_id'], 'name', $T['team__name']));
                     else
                         $lc=' ';
                 }else{
-                    $lc=Format::truncate($dept,40);
+                    $lc='<span class="truncate">'.Format::htmlchars($dept).'</span>';
                 }
                 $tid=$T['number'];
-                $subject = Format::truncate($subject_field->display($subject_field->to_php($T['cdata__subject'])),40);
+                $subject = $subject_field->display($subject_field->to_php($T['cdata__subject']));
                 $threadcount=$T['thread_count'];
                 if(!strcasecmp($T['status__state'],'open') && !$T['isanswered'] && !$T['lock__staff_id']) {
                     $tid=sprintf('<b>%s</b>',$tid);
@@ -326,7 +326,9 @@ $_SESSION[':Q:tickets'] = $tickets;
                     ><?php echo $tid; ?></a></td>
                 <td align="center" nowrap><?php echo Format::datetime($T[$date_col ?: 'lastupdate']); ?></td>
                 <td><a <?php if ($flag) { ?> class="Icon <?php echo $flag; ?>Ticket" title="<?php echo ucfirst($flag); ?> Ticket" <?php } ?>
-                    href="tickets.php?id=<?php echo $T['ticket_id']; ?>"><?php echo $subject; ?></a>
+                    style="max-width: 80%; max-width: calc(100% - 86px);"
+                    href="tickets.php?id=<?php echo $T['ticket_id']; ?>"><span
+                    class="truncate"><?php echo $subject; ?></span></a>
                      <?php
                         if ($threadcount>1)
                             echo "<small>($threadcount)</small>&nbsp;".'<i
@@ -337,8 +339,10 @@ $_SESSION[':Q:tickets'] = $tickets;
                             echo '<i class="icon-fixed-width icon-paperclip"></i>&nbsp;';
                     ?>
                 </td>
-                <td nowrap>&nbsp;<?php $un = new PersonsName($T['user__name']); echo Format::htmlchars(
-                        Format::truncate($un, 22, strpos($un, '@'))); ?>&nbsp;</td>
+                <td nowrap><span class="truncate"><?php
+                    $un = new PersonsName($T['user__name']);
+                        echo Format::htmlchars($un);
+                ?></td>
                 <?php
                 if($search && !$status){
                     $displaystatus=TicketStatus::getLocalById($T['status_id'], 'value', $T['status__name']);
diff --git a/scp/ajax.php b/scp/ajax.php
index 140085b3910165086b67e1e9f4d61428777569e2..ed9e37345c16c29766b223c3d5a5da37ec02a1f0 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -64,8 +64,15 @@ $dispatcher = patterns('',
         url_get('^action/(?P<type>\w+)/config$', 'getFilterActionForm')
     )),
     url('^/list/', patterns('ajax.forms.php:DynamicFormsAjaxAPI',
-        url_get('^(?P<list>\w+)/item/(?P<id>\d+)/properties$', 'getListItemProperties'),
-        url_post('^(?P<list>\w+)/item/(?P<id>\d+)/properties$', 'saveListItemProperties')
+        url_get('^(?P<list>\w+)/items$', 'getListItems'),
+        url_get('^(?P<list>\w+)/item/(?P<id>\d+)/update$', 'getListItem'),
+        url_post('^(?P<list>\w+)/item/(?P<id>\d+)/update$', 'saveListItem'),
+        url('^(?P<list>\w+)/item/add$', 'addListItem'),
+        url('^(?P<list>\w+)/import$', 'importListItems'),
+        url('^(?P<list>\w+)/manage$', 'massManageListItems'),
+        url_post('^(?P<list>\w+)/delete$', 'deleteItems'),
+        url_post('^(?P<list>\w+)/disable$', 'disableItems'),
+        url_post('^(?P<list>\w+)/enable$', 'undisableItems')
     )),
     url('^/report/overview/', patterns('ajax.reports.php:OverviewReportAjaxAPI',
         # Send
diff --git a/scp/css/scp.css b/scp/css/scp.css
index e687b82be86bc6700eb385246ce3f4118a182caa..68188bee7044962c36c077f62de59da1250092e1 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -641,19 +641,18 @@ a.print {
 
 table.fixed {
     table-layout: fixed;
-    border-collapse: collapse;
     width: 100%;
 }
-table.fixed > thead > tr > th,
-table.fixed > thead > tr > td,
-table.fixed > tbody > tr > td,
-table.fixed > tr > td {
+table.fixed > thead > tr > th:not([width]),
+table.fixed > thead > tr > td:not([width]),
+table.fixed > tbody > tr > td:not([width]),
+table.fixed > tr > td:not([width]) {
     width: 180px;
 }
-table.fixed > thead > tr > th + th,
-table.fixed > thead > tr > td + td,
-table.fixed > tbody > tr > td + td,
-table.fixed > tr > td + td {
+table.fixed > thead > tr > th + th:not([width]),
+table.fixed > thead > tr > td + td:not([width]),
+table.fixed > tbody > tr > td + td:not([width]),
+table.fixed > tr > td + td:not([width]) {
     width: auto;
 }
 
@@ -2084,6 +2083,8 @@ button a:hover {
 }
 .truncate {
     width: auto;
+    display: inline-block;
+    max-width: 100%;
     white-space: nowrap;
     overflow: hidden;
     text-overflow: ellipsis;
diff --git a/scp/js/scp.js b/scp/js/scp.js
index edb2cf0c0ccf6750c3a9b58584ab3b0a1dbd1687..5e49989761d3b37bcb72fd58291c9a5cf0737e12 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -152,8 +152,8 @@ var scp_prep = function() {
         }
     };
 
-    $("form#save :input").change(function() {
-        warnOnLeave($(this));
+    $("form#save :input[name]").change(function() {
+        if (!$(this).is('.nowarn')) warnOnLeave($(this));
     });
 
     $("form#save :input[type=reset]").click(function() {
@@ -431,10 +431,11 @@ var scp_prep = function() {
        'helper': fixHelper,
        'cursor': 'move',
        'stop': function(e, ui) {
-           var attr = ui.item.parent('tbody').data('sort');
+           var attr = ui.item.parent('tbody').data('sort'),
+               offset = parseInt($('#sort-offset').val(), 10) || 0;
            warnOnLeave(ui.item);
            $('input[name^='+attr+']', ui.item.parent('tbody')).each(function(i, el) {
-               $(el).val(i+1);
+               $(el).val(i + 1 + offset);
            });
        }
    });
@@ -587,7 +588,7 @@ $.dialog = function (url, codes, cb, options) {
                         $.toggleOverlay(false);
                         $popup.hide();
                         $('div.body', $popup).empty();
-                        if(cb) cb(xhr);
+                        if(cb) cb(xhr, resp);
                     } else {
                         try {
                             var json = $.parseJSON(resp);
diff --git a/scp/lists.php b/scp/lists.php
index 5247a0643c36e1aed9c9b8b73c6ff75661aa0dff..628e60b059e73c564c5e23f5e10b7fbf2c0eb1ed 100644
--- a/scp/lists.php
+++ b/scp/lists.php
@@ -21,7 +21,6 @@ if ($criteria) {
 }
 
 $errors = array();
-$max_isort = 0;
 
 if($_POST) {
     switch(strtolower($_POST['do'])) {
@@ -30,36 +29,15 @@ if($_POST) {
                 $errors['err']=sprintf(__('%s: Unknown or invalid ID.'),
                     __('custom list'));
             elseif ($list->update($_POST, $errors)) {
-                // Update items
-                $items = array();
-                foreach ($list->getAllItems() as $item) {
-                    $id = $item->getId();
-                    if ($_POST["delete-item-$id"] == 'on' && $item->isDeletable()) {
-                        $item->delete();
-                        continue;
-                    }
-
-                    $ht = array(
-                            'value' => $_POST["value-$id"],
-                            'abbrev' => $_POST["abbrev-$id"],
-                            'sort' => $_POST["sort-$id"],
-                            );
-                    $value = mb_strtolower($ht['value']);
-                    if (!$value)
-                        $errors["value-$id"] = __('Value required');
-                    elseif (in_array($value, $items))
-                        $errors["value-$id"] = __('Value already in-use');
-                    elseif ($item->update($ht, $errors)) {
-                        if ($_POST["disable-$id"] == 'on')
-                            $item->disable();
-                        elseif(!$item->isEnabled() && $item->isEnableable())
-                            $item->enable();
-
-                        $item->save();
-                        $items[] = $value;
+                // Update item sorting
+                if ($list->getSortMode() == 'SortCol') {
+                    foreach ($list->getAllItems() as $item) {
+                        $id = $item->getId();
+                        if (isset($_POST["sort-{$id}"])) {
+                            $item->sort = $_POST["sort-$id"];
+                            $item->save();
+                        }
                     }
-
-                    $max_isort = max($max_isort, $_POST["sort-$id"]);
                 }
 
                 // Update properties
@@ -154,19 +132,21 @@ if($_POST) {
                 }
             }
             break;
-    }
-
-    if ($list && $list->allowAdd()) {
-        for ($i=0; isset($_POST["sort-new-$i"]); $i++) {
-            if (!$_POST["value-new-$i"])
-                continue;
 
-            $list->addItem(array(
-                        'value' => $_POST["value-new-$i"],
-                        'abbrev' =>$_POST["abbrev-new-$i"],
-                        'sort' => $_POST["sort-new-$i"] ?: ++$max_isort,
-                        ), $errors);
-        }
+        case 'import-items':
+            if (!$list) {
+                $errors['err']=sprintf(__('%s: Unknown or invalid ID.'),
+                    __('custom list'));
+            }
+            else {
+                $status = $list->importFromPost($_FILES['import'] ?: $_POST['pasted']);
+                if (is_numeric($status))
+                    $msg = sprintf(__('Successfully imported %1$d %2$s.'), $status,
+                        _N('list item', 'list items', $status));
+                else
+                    $errors['err'] = $status;
+            }
+            break;
     }
 
     if ($form) {
@@ -179,6 +159,9 @@ if($_POST) {
                 'label' => $_POST["prop-label-new-$i"],
                 'type' => $_POST["type-new-$i"],
                 'name' => $_POST["name-new-$i"],
+                'flags' => DynamicFormField::FLAG_ENABLED
+                    | DynamicFormField::FLAG_AGENT_VIEW
+                    | DynamicFormField::FLAG_AGENT_EDIT,
             ));
             $field->setForm($form);
             if ($field->isValid())
@@ -193,6 +176,13 @@ if($_POST) {
 }
 
 $page='dynamic-lists.inc.php';
+if($list && !strcasecmp(@$_REQUEST['a'],'items') && isset($_SERVER['HTTP_X_PJAX'])) {
+    $page='templates/list-items.tmpl.php';
+    $pjax_container = @$_SERVER['HTTP_X_PJAX_CONTAINER'];
+    require(STAFFINC_DIR.$page);
+    // Don't emit the header
+    return;
+}
 if($list || ($_REQUEST['a'] && !strcasecmp($_REQUEST['a'],'add'))) {
     $page='dynamic-list.inc.php';
     $ost->addExtraHeader('<meta name="tip-namespace" content="manage.custom_list" />',