diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index 9e8bce892bc6230dff7c9cfb920f2d26198e3ca4..c27914de5f49b603284a60d2604409c0291e0e27 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -1428,5 +1428,36 @@ function refer($tid, $target=null) {
 
         include STAFFINC_DIR . 'templates/task-view.tmpl.php';
     }
+
+
+    function export($id) {
+        global $thisstaff;
+
+        if (is_numeric($id))
+            $queue = SavedSearch::lookup($id);
+        else
+            $queue = AdhocSearch::load($id);
+
+        if (!$thisstaff)
+            Http::response(403, 'Agent login is required');
+        elseif (!$queue || !$queue->checkAccess($thisstaff))
+            Http::response(404, 'No such saved queue');
+
+        if ($_POST && is_array($_POST['fields'])) {
+            // Cache export preferences
+            $id = $queue->getId();
+            $_SESSION['Export:Q'.$id]['fields'] = $_POST['fields'];
+            $_SESSION['Export:Q'.$id]['filename'] = $_POST['filename'];
+            $_SESSION['Export:Q'.$id]['delimiter'] = $_POST['delimiter'];
+
+            if ($queue->isSaved() && isset($_POST['save-changes']))
+               $queue->updateExports(array_flip($_POST['fields']));
+
+            Http::response(201, 'Export Ready');
+        }
+
+        include STAFFINC_DIR . 'templates/queue-export.tmpl.php';
+
+    }
 }
 ?>
diff --git a/include/class.export.php b/include/class.export.php
index 6b7b4be5d54319e6f886046540f0b1daf31f4070..1ca9e3f47e1ef84878b89bd9da33c647439336c0 100644
--- a/include/class.export.php
+++ b/include/class.export.php
@@ -40,7 +40,8 @@ class Export {
     #      SQL is exported, but for something like tickets, we will need to
     #      export attached messages, reponses, and notes, as well as
     #      attachments associated with each, ...
-    static function dumpTickets($sql, $target=array(), $how='csv') {
+    static function dumpTickets($sql, $target=array(), $how='csv',
+            $options=array()) {
         // Add custom fields to the $sql statement
         $cdata = $fields = array();
         foreach (TicketForm::getInstance()->getFields() as $f) {
@@ -90,13 +91,15 @@ class Export {
                     }
                 }
                 return $record;
-            })
+            },
+            'delimiter' => @$options['delimiter'])
             );
     }
 
-    static  function saveTickets($sql, $fields, $filename, $how='csv') {
+    static  function saveTickets($sql, $fields, $filename, $how='csv',
+            $options=array()) {
         Http::download($filename, "text/$how");
-        self::dumpTickets($sql, $fields, $how);
+        self::dumpTickets($sql, $fields, $how, $options);
         exit;
     }
 
@@ -316,16 +319,17 @@ class ResultSetExporter {
 
 class CsvResultsExporter extends ResultSetExporter {
 
-    function dump() {
 
-        if (!$this->output)
-             $this->output = fopen('php://output', 'w');
+    function getDelimiter() {
+
+        if (isset($this->options['delimiter']))
+            return $this->options['delimiter'];
 
         // Detect delimeter from the current locale settings. For locales
         // which use comma (,) as the decimal separator, the semicolon (;)
         // should be used as the field separator
         $delimiter = ',';
-        if (class_exists('NumberFormatter')) {
+        if (!$this->options['delimiter'] && class_exists('NumberFormatter')) {
             $nf = NumberFormatter::create(Internationalization::getCurrentLocale(),
                 NumberFormatter::DECIMAL);
             $s = $nf->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL);
@@ -333,6 +337,17 @@ class CsvResultsExporter extends ResultSetExporter {
                 $delimiter = ';';
         }
 
+        return $delimiter;
+    }
+
+
+    function dump() {
+
+        if (!$this->output)
+             $this->output = fopen('php://output', 'w');
+
+
+        $delimiter = $this->getDelimiter();
         // Output a UTF-8 BOM (byte order mark)
         fputs($this->output, chr(0xEF) . chr(0xBB) . chr(0xBF));
         fputcsv($this->output, $this->getHeaders(), $delimiter);
diff --git a/include/class.queue.php b/include/class.queue.php
index c2a5487a1aa950e41472fd76a81a8085cc3a95bb..740673590b3a7531b9d284985d916021e41d43b6 100644
--- a/include/class.queue.php
+++ b/include/class.queue.php
@@ -711,7 +711,7 @@ class CustomQueue extends VerySimpleModel {
         ));
     }
 
-    function export($filename, $format) {
+    function export($options=array()) {
 
         if (!($query=$this->getBasicQuery()))
             return false;
@@ -719,7 +719,31 @@ class CustomQueue extends VerySimpleModel {
         if (!($fields=$this->getExportFields()))
             return false;
 
-        return Export::saveTickets($query, $fields, $filename, $format);
+
+        $filename = sprintf('%s Tickets-%s.csv',
+                $this->getName(),
+                strftime('%Y%m%d'));
+        // See if we have cached export preference
+        if (isset($_SESSION['Export:Q'.$this->getId()])) {
+            $opts = $_SESSION['Export:Q'.$this->getId()];
+            if (isset($opts['fields']))
+                $fields = array_intersect_key($fields,
+                        array_flip($opts['fields']));
+            if (isset($opts['filename'])
+                    && ($parts = pathinfo($opts['filename']))) {
+                $filename = $opts['filename'];
+                if (strcasecmp($parts['extension'], 'csv') !=0)
+                    $filename ="$filename.csv";
+            }
+
+            if (isset($opts['delimiter']))
+                $options['delimiter'] = $opts['delimiter'];
+
+        }
+
+
+        return Export::saveTickets($query, $fields, $filename, 'csv',
+                $options);
     }
 
     /**
@@ -917,6 +941,59 @@ class CustomQueue extends VerySimpleModel {
         $this->clearFlag(self::FLAG_DISABLED);
     }
 
+    function updateExports($fields, $save=true) {
+
+        if (!$fields)
+            return false;
+
+        $order = array_keys($fields);
+        // Filter exportable fields
+        if (!($fields = array_intersect_key($this->getExportableFields(), $fields)))
+            return false;
+
+        $new = $fields;
+        foreach ($this->exports as $f) {
+            $key = $f->getPath();
+            if (!isset($fields[$key])) {
+                $this->exports->remove($f);
+                continue;
+            }
+
+            $info = $fields[$key];
+            if (is_array($info))
+                $heading = $info['heading'];
+            else
+                $heading = $info;
+
+            $f->set('heading', $heading);
+            $f->set('sort', array_search($key, $order)+1);
+            unset($new[$key]);
+        }
+
+        foreach ($new as $k => $field) {
+            if (is_array($field))
+                $heading = $field['heading'];
+            else
+                $heading = $field;
+
+            $f = QueueExport::create(array(
+                        'path' => $k,
+                        'heading' => $heading,
+                        'sort' => array_search($k, $order)+1));
+            $this->exports->add($f);
+        }
+
+        $this->exports->sort(function($f) { return $f->sort; });
+
+        if (!count($this->exports) && $this->parent)
+            $this->hasFlag(self::FLAG_INHERIT_EXPORT);
+
+        if ($save)
+            $this->exports->saveAll();
+
+        return true;
+    }
+
     function update($vars, &$errors=array()) {
         // Set basic search information
         if (!$vars['name'])
@@ -1001,29 +1078,7 @@ class CustomQueue extends VerySimpleModel {
         // Update export fields for the queue
         if (isset($vars['exports']) &&
                  !$this->hasFlag(self::FLAG_INHERIT_EXPORT)) {
-            $new = $vars['exports'];
-            $order = array_keys($new);
-            foreach ($this->exports as $f) {
-                $key = $f->getPath();
-                if (!isset($vars['exports'][$key])) {
-                    $this->exports->remove($f);
-                    continue;
-                }
-                $info = $vars['exports'][$key];
-                $f->set('heading', $info['heading']);
-                $f->set('sort',array_search($key, $order));
-                unset($new[$key]);
-            }
-
-            foreach($new as $k => $field) {
-                $f = QueueExport::create(array(
-                            //'queue_id' => $this->getId(),
-                            'path' => $k,
-                            'heading' => $field['heading'],
-                            'sort' => array_search($k, $order)));
-                $this->exports->add($f);
-            }
-            $this->exports->sort(function($f) { return $f->sort; });
+            $this->updateExports($vars['exports'], false);
         }
 
         if (!count($this->exports) && $this->parent)
diff --git a/include/class.search.php b/include/class.search.php
index c2e4f93d1f1e03c226b65eec1c87cad27ec78852..e454da06adb0ad90319e41a5c9187ac70ea44586 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -652,6 +652,10 @@ class SavedSearch extends CustomQueue {
     // Override the ORM relationship to force no children
     private $children = false;
 
+    function isSaved() {
+        return true;
+    }
+
     static function forStaff(Staff $agent) {
         return static::objects()->filter(Q::any(array(
             'staff_id' => $agent->getId(),
@@ -679,9 +683,38 @@ class SavedSearch extends CustomQueue {
 
 class AdhocSearch
 extends SavedSearch {
+
+    function isSaved() {
+        return false;
+    }
+
+    function checkAccess($staff) {
+        return true;
+    }
+
     function getName() {
         return $this->title ?: $this->describeCriteria();
     }
+
+    function load($key) {
+
+        if (strpos($key, 'adhoc') === 0)
+            list(, $key) = explode(',', $key, 2);
+
+        if (!$key
+                || !isset($_SESSION['advsearch'])
+                || !($config=$_SESSION['advsearch'][$key]))
+            return null;
+
+       $queue = new AdhocSearch(array(
+                   'id' => "adhoc,$key",
+                   'root' => 'T',
+                   'title' => __('Advanced Search'),
+                ));
+       $queue->config = $config;
+
+       return $queue;
+    }
 }
 
 class AdvancedSearchForm extends SimpleForm {
diff --git a/include/staff/templates/advanced-search.tmpl.php b/include/staff/templates/advanced-search.tmpl.php
index c2cd1c1d442a833807b9adbcf6aeb8c4d4c36d0a..5cee3f051097b552c880c0dcd551d60427fed2ee 100644
--- a/include/staff/templates/advanced-search.tmpl.php
+++ b/include/staff/templates/advanced-search.tmpl.php
@@ -51,7 +51,7 @@ foreach ($queues as $id => $name) {
 
 <div class="tab_content" id="criteria">
   <div class="flex row">
-    <div class="span12">
+    <div class="span12" style="overflow-y: scroll; height:100%;">
 <?php if ($parent) { ?>
       <div class="faded" style="margin-bottom: 1em">
       <div>
@@ -69,12 +69,14 @@ foreach ($queues as $id => $name) {
 
 </div>
 
-<div class="tab_content hidden" id="columns">
+<div class="tab_content hidden" id="columns" style="overflow-y: scroll;
+height:100%;">
     <?php
     include STAFFINC_DIR . "templates/queue-columns.tmpl.php";
     ?>
 </div>
-<div class="tab_content hidden" id="fields">
+<div class="tab_content hidden" id="fields" style="overflow-y: scroll;
+height:auto;">
     <?php
     include STAFFINC_DIR . "templates/queue-fields.tmpl.php";  ?>
 </div>
diff --git a/include/staff/templates/queue-export.tmpl.php b/include/staff/templates/queue-export.tmpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..c2659bceac67bfb837c710a1fce1f3af815e7b33
--- /dev/null
+++ b/include/staff/templates/queue-export.tmpl.php
@@ -0,0 +1,132 @@
+<?php
+$qname = $queue->getName() ?:  __('Tickets Export');
+
+// Session cache
+$cache = $_SESSION['Export:Q'.$queue->getId()];
+if (isset($cache['filename']))
+    $filename = $cache['filename'];
+else
+    $filename = trim(sprintf('%s Tickets - %s.csv',
+            Format::htmlchars($queue->getName() ?: ''),
+            strftime('%Y%m%d')));
+
+if (isset($cache['delimiter']))
+    $delimiter = $cache['delimiter'];
+else
+    $delimiter = ''; //TODO: get user's preference (browswer settings)
+
+$fields = $queue->getExportFields(false) ?: array();
+if (isset($cache['fields']) && $fields)
+    $fields = array_intersect_key($fields, array_flip($cache['fields']));
+
+?>
+<div id="tickets-export">
+<h3 class="drag-handle"><?php echo Format::htmlchars($qname); ?></h3>
+<a class="close" href=""><i class="icon-remove-circle"></i></a>
+<hr/>
+<form action="#tickets/export/<?php echo $queue->getId(); ?>" method="post" name="export">
+  <div style="overflow-y: auto; height:400px; margin-bottom:5px;">
+  <table class="table">
+      <tbody>
+        <tr class="header">
+          <td><small><i class="icon-caret-down"></i>&nbsp;<?php echo
+          sprintf('%s <strong class="faded">( <span id="fields-count">%d</span> %s )</strong>',
+            __('Check columns to export'),
+            count($fields),
+            __('selected'));
+          ?> </small></td>
+        </tr>
+      </tbody>
+      <tbody class="sortable-rows" id="fields">
+        <?php
+        foreach ($queue->getExportableFields() as $path  => $label) {
+         echo sprintf('<tr style="display: table-row;">
+                <td><i class="faded-more
+                icon-sort"></i>&nbsp;&nbsp;<label><input
+                type="checkbox" name="fields[]" value="%s" %s>
+                &nbsp;&nbsp;<span>%s</span></label><td></tr>',
+                $path,
+                isset($fields[$path]) ? 'checked="checked"' : '',
+                @$fields[$path] ?: $label);
+        } ?>
+      </tbody>
+  </table>
+  </div>
+  <?php
+  if ($queue->isSaved()) { ?>
+  <div id="save-changes" class="hidden" style="padding-top:5px; border-top: 1px dotted #ddd;">
+    <span><i class="icon-bell-alt" style="color:red;"></i>&nbsp;
+     <label><input type="checkbox" name='save-changes' >&nbsp;Save export preference changes</label> </span>
+  </div>
+  <?php
+  } ?>
+  <div style="margin-top:10px;"><small><a href="#"
+    id="more"><i class="icon-caret-right"></i>&nbsp;<?php echo __('Advanced CSV Options'); ?></a></small></div>
+  <div id="more-options" class="hidden" style="padding:5px; border-top: 1px dotted #777;">
+      <div><span class="faded" style="width:60px;"><?php echo __('Filename'); ?>: </span><input
+        name="filename" type="text" size="40"
+        value="<?php echo Format::htmlchars($filename); ?>"></div>
+      <div><span class="faded" style="width:60px;"><?php echo __('Delimiter'); ?>: </span><input
+        name="csv-delimiter" type="text" maxlength="1"  size=10
+        value="<?php echo $delimiter; ?>"
+        placeholder=", (Comma)" maxlength="1" /></div>
+  </div>
+  <p class="full-width">
+    <span class="buttons pull-left">
+        <input type="reset"  id="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 __('Export'); ?>">
+    </span>
+   </p>
+</form>
+</div>
+<div class="clear"></div>
+<script>
++function() {
+   // Return a helper with preserved width of cells
+   var fixHelper = function(e, ui) {
+      ui.children().each(function() {
+          $(this).width($(this).width());
+      });
+      return ui;
+   };
+   // Sortable tables for dynamic forms objects
+   $('.sortable-rows').sortable({
+       'helper': fixHelper,
+       'cursor': 'move',
+       'stop': function(e, ui) {
+            $('div#save-changes').fadeIn();
+       }
+   });
+
+   $('#more').click(function() {
+        var more = $(this);
+        $('#more-options').slideToggle('fast', function(){
+           if ($(this).is(":hidden"))
+            more.find('i').removeClass('icon-caret-down').addClass('icon-caret-right');
+           else
+             more.find('i').removeClass('icon-caret-right').addClass('icon-caret-down');
+        });
+        return false;
+    });
+
+   $(document).on('change', 'tbody#fields input:checkbox', function (e) {
+       var f = $(this).closest('form');
+       var count = $("input[name='fields[]']:checked", f).length;
+       $('div#save-changes', f).fadeIn();
+       $('span#fields-count', f).html(count);
+     });
+
+   $(document).on('click', 'input#reset', function(e) {
+        var f = $(this).closest('form');
+        $('input.save-changes', f).prop('checked', false);
+        $('span#fields-count', f).html(<?php echo count($fields); ?>);
+        $('div#save-changes', f).hide();
+    });
+
+}();
+</script>
diff --git a/include/staff/templates/queue-tickets.tmpl.php b/include/staff/templates/queue-tickets.tmpl.php
index 0c8fe8c9c617be7969f386f52ceb65adb3a2876b..9c45da9370a20658185faffd0c22120a2caae588 100644
--- a/include/staff/templates/queue-tickets.tmpl.php
+++ b/include/staff/templates/queue-tickets.tmpl.php
@@ -296,15 +296,24 @@ foreach ($tickets as $T) {
       <span class="faded pull-right"><?php echo $pageNav->showing(); ?></span>
 <?php
         echo __('Page').':'.$pageNav->getPageLinks().'&nbsp;';
-        echo sprintf('<a class="export-csv no-pjax" href="?%s">%s</a>',
-                Http::build_query(array(
-                        'a' => 'export', 'queue' => $_REQUEST['queue'],
-                        'status' => $_REQUEST['status'])),
-                __('Export'));
         ?>
+        <a href="#tickets/export/<?php echo $queue->getId(); ?>" id="queue-export" class="no-pjax"
+            ><?php echo __('Export'); ?></a>
         <i class="help-tip icon-question-sign" href="#export"></i>
     </div>
 <?php
     } ?>
-
 </form>
+<script type="text/javascript">
+$(function() {
+    $(document).on('click', 'a#queue-export', function(e) {
+        e.preventDefault();
+        var url = 'ajax.php/'+$(this).attr('href').substr(1)
+        $.dialog(url, 201, function (xhr) {
+            window.location.href = '?a=export&queue=<?php echo $queue->getId(); ?>';
+            return false;
+         });
+        return false;
+    });
+});
+</script>
diff --git a/scp/ajax.php b/scp/ajax.php
index cdb5fa561107fca49e874137ae3adc02a938f412..bc23cb1ebca158bc75d7386a72b51496374dcbb1 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -170,6 +170,8 @@ $dispatcher = patterns('',
         url('^(?P<tid>\d+)/refer(?:/(?P<to>\w+))?$', 'refer'),
         url('^(?P<tid>\d+)/referrals$', 'referrals'),
         url('^(?P<tid>\d+)/claim$', 'claim'),
+        url('^export/(?P<id>\d+)$', 'export'),
+        url('^export/adhoc,(?P<key>[\w=/+]+)$', 'export'),
         url('^search', patterns('ajax.search.php:SearchAjaxAPI',
             url_get('^$', 'getAdvancedSearchDialog'),
             url_post('^$', 'doSearch'),
diff --git a/scp/tickets.php b/scp/tickets.php
index 18a7e0721dd267eb96fd17e00132ffe1a7155127..841a5df79b30ce7680332212cfe8d44b9cca9fad 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -102,13 +102,8 @@ if (!$ticket) {
             reset($_SESSION['advsearch']);
             $key = key($_SESSION['advsearch']);
         }
-        // XXX: De-duplicate and simplify this code
-        $queue = new AdhocSearch(array(
-            'id' => "adhoc,$key",
-            'root' => 'T',
-            'title' => __('Advanced Search'),
-        ));
-        $queue->config = $_SESSION['advsearch'][$key];
+
+        $queue = AdhocSearch::load($key);
     }
 
     // Make the current queue sticky
@@ -541,9 +536,7 @@ if($ticket) {
         $inc = 'ticket-open.inc.php';
     elseif ($_REQUEST['a'] == 'export' && $queue) {
         // XXX: Check staff access?
-        $filename = sprintf('%s Tickets-%s', $queue->getName(),
-                strftime('%Y%m%d'));
-        if (!$queue->export($filename, 'csv'))
+        if (!$queue->export())
             $errors['err'] = __('Unable to export results.')
                 .' '.__('Internal error occurred');
     } elseif ($queue) {