diff --git a/config/vufind/config.ini b/config/vufind/config.ini
index 60ded5458f2a7328761a9f86209c5890f355b10b..24f8463989e855921c10d47e00a750c517ef9b8c 100644
--- a/config/vufind/config.ini
+++ b/config/vufind/config.ini
@@ -321,12 +321,12 @@ ils_encryption_key = false
 ; this).
 ;minimum_password_length = 4
 ;maximum_password_length = 32
-; Specify default limit of accepted characters in the password. Allowed values 
+; Specify default limit of accepted characters in the password. Allowed values
 ; are "numeric", "alphanumeric" or a regular expression
 ;password_pattern = "(?=.*\d)(?=.*[a-z])(?=.*[A-Z])"
-; Specify default hint about what the password may contain when using a regexp 
-; pattern. May be text or a translation key. The "numeric" and "alphanumeric" 
-; patterns have translated default hints. 
+; Specify default hint about what the password may contain when using a regexp
+; pattern. May be text or a translation key. The "numeric" and "alphanumeric"
+; patterns have translated default hints.
 ;password_hint = "Include both upper and lowercase letters and at least one number."
 
 ; Uncomment this line to switch on "privacy mode" in which no user information
@@ -1208,6 +1208,12 @@ preferred_service = "loan"
 ; preferred_service settings to be ignored.
 show_full_status = false
 
+; You can set this to the name of an alphabetic browse handler (see the
+; [AlphaBrowse_Types] section) in order to link call numbers displayed on the
+; holdings tab and in status messages to a specific browse list. Set to false
+; to disable call number linking.
+callnumber_handler = false
+
 ; This section controls the behavior of the Record module.
 [Record]
 ; Set this to true in order to enable "next" and "previous" links to navigate
diff --git a/module/VuFind/src/VuFind/Controller/AbstractRecord.php b/module/VuFind/src/VuFind/Controller/AbstractRecord.php
index 78d2949eeb1ce4b9a3e797b73a483105ce116fbc..dcc7a533362599d5dc3d082a9af6b7308ff2aea3 100644
--- a/module/VuFind/src/VuFind/Controller/AbstractRecord.php
+++ b/module/VuFind/src/VuFind/Controller/AbstractRecord.php
@@ -707,6 +707,10 @@ class AbstractRecord extends AbstractBase
             $view->scrollData = $this->resultScroller()->getScrollData($driver);
         }
 
+        $view->callnumberHandler = isset($config->Item_Status->callnumber_handler)
+            ? $config->Item_Status->callnumber_handler
+            : false;
+
         $view->setTemplate($ajax ? 'record/ajaxtab' : 'record/view');
         return $view;
     }
diff --git a/module/VuFind/src/VuFind/Controller/AjaxController.php b/module/VuFind/src/VuFind/Controller/AjaxController.php
index bde75701ac8104aa915f01e90ade21f13659ea97..def4c3902a10b85376e1328fd70815e5ee6085e9 100644
--- a/module/VuFind/src/VuFind/Controller/AjaxController.php
+++ b/module/VuFind/src/VuFind/Controller/AjaxController.php
@@ -224,7 +224,10 @@ class AjaxController extends AbstractBase
                 // If a full status display has been requested, append the HTML:
                 if ($showFullStatus) {
                     $current['full_status'] = $renderer->render(
-                        'ajax/status-full.phtml', ['statusItems' => $record]
+                        'ajax/status-full.phtml', [
+                            'statusItems' => $record,
+                            'callnumberHandler' => $this->getCallnumberHandler()
+                         ]
                     );
                 }
                 $current['record_number'] = array_search($current['id'], $ids);
@@ -295,7 +298,7 @@ class AjaxController extends AbstractBase
                 $list = $transList;
             }
             // All values mode?  Return comma-separated values:
-            return implode(', ', $list);
+            return implode(",\t", $list);
         } else {
             // Message mode?  Return the specified message, translated to the
             // appropriate language.
@@ -303,6 +306,26 @@ class AjaxController extends AbstractBase
         }
     }
 
+    /**
+     * Based on settings and the number of callnumbers, return callnumber handler
+     * Use callnumbers before pickValue is run.
+     *
+     * @param array  $list           Array of callnumbers.
+     * @param string $displaySetting config.ini setting -- first, all or msg
+     *
+     * @return string
+     */
+    protected function getCallnumberHandler($list = null, $displaySetting = null)
+    {
+        if ($displaySetting == 'msg' && count($list) > 1) {
+            return false;
+        }
+        $config = $this->getConfig();
+        return isset($config->Item_Status->callnumber_handler)
+            ? $config->Item_Status->callnumber_handler
+            : false;
+    }
+
     /**
      * Reduce an array of service names to a human-readable string.
      *
@@ -376,6 +399,10 @@ class AjaxController extends AbstractBase
             }
         }
 
+        $callnumberHandler = $this->getCallnumberHandler(
+            $callNumbers, $callnumberSetting
+        );
+
         // Determine call number string based on findings:
         $callNumber = $this->pickValue(
             $callNumbers, $callnumberSetting, 'Multiple Call Numbers'
@@ -406,7 +433,8 @@ class AjaxController extends AbstractBase
             'reserve_message' => $record[0]['reserve'] == 'Y'
                 ? $this->translate('on_reserve')
                 : $this->translate('Not On Reserve'),
-            'callnumber' => htmlentities($callNumber, ENT_COMPAT, 'UTF-8')
+            'callnumber' => htmlentities($callNumber, ENT_COMPAT, 'UTF-8'),
+            'callnumber_handler' => $callnumberHandler
         ];
     }
 
@@ -449,6 +477,9 @@ class AjaxController extends AbstractBase
         foreach ($locations as $location => $details) {
             $locationCallnumbers = array_unique($details['callnumbers']);
             // Determine call number string based on findings:
+            $callnumberHandler = $this->getCallnumberHandler(
+                $locationCallnumbers, $callnumberSetting
+            );
             $locationCallnumbers = $this->pickValue(
                 $locationCallnumbers, $callnumberSetting, 'Multiple Call Numbers'
             );
@@ -462,7 +493,8 @@ class AjaxController extends AbstractBase
                 'callnumbers' =>
                     htmlentities($locationCallnumbers, ENT_COMPAT, 'UTF-8'),
                 'status_unknown' => isset($details['status_unknown'])
-                    ? $details['status_unknown'] : false
+                    ? $details['status_unknown'] : false,
+                'callnumber_handler' => $callnumberHandler
             ];
             $locationList[] = $locationInfo;
         }
diff --git a/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/CallnumberBrowseTest.php b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/CallnumberBrowseTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..eefd7248af69b9aeae68f95098824b6e2e487d34
--- /dev/null
+++ b/module/VuFind/tests/integration-tests/src/VuFindTest/Mink/CallnumberBrowseTest.php
@@ -0,0 +1,276 @@
+<?php
+/**
+ * Mink search actions test class.
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2011.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * @category VuFind
+ * @package  Tests
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org Main Page
+ */
+namespace VuFindTest\Mink;
+
+/**
+ * Mink search actions test class.
+ *
+ * @category VuFind
+ * @package  Tests
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     https://vufind.org Main Page
+ */
+class CallnumberBrowseTest extends \VuFindTest\Unit\MinkTestCase
+{
+    protected $id = 'testdeweybrowse';
+
+    /**
+     * Standard setup method.
+     *
+     * @return void
+     */
+    public function setUp()
+    {
+        // Give up if we're not running in CI:
+        if (!$this->continuousIntegrationRunning()) {
+            return $this->markTestSkipped('Continuous integration not running.');
+        }
+    }
+
+    /**
+     * Search for the specified query.
+     *
+     * @param string $query Search term(s)
+     *
+     * @return \Behat\Mink\Element\Element
+     */
+    protected function performSearch($query, $page = false)
+    {
+        $session = $this->getMinkSession();
+        $session->visit($this->getVuFindUrl() . '/Search/Home');
+        $page = $session->getPage();
+        $this->findCss($page, '.searchForm [name="lookfor"]')->setValue($query);
+        $this->findCss($page, '.btn.btn-primary')->click();
+        $this->snooze();
+        return $page;
+    }
+
+    /**
+     * Set config for callnumber tests
+     * Sets callnumber_handler to false
+     *
+     * @param string $nos  multiple_call_nos setting
+     * @param string $locs multiple_locations setting
+     *
+     * @return void
+     */
+    protected function changeCallnumberSettings($nos, $locs, $full = false)
+    {
+        $this->changeConfigs(
+            [
+                'config' => [
+                    'Catalog' => ['driver' => 'Sample'],
+                    'Item_Status' => [
+                        'multiple_call_nos' => $nos,
+                        'multiple_locations' => $locs,
+                        'callnumber_handler' => false,
+                        'show_full_status' => $full
+                    ]
+                ]
+            ]
+        );
+    }
+
+    /**
+     * Checks if a link has the correct callnumber and setting
+     *
+     * @param \Behat\Mink\Element\Element $link link element
+     * @param string                      $type dewey or lcc
+     *
+     * @return void
+     */
+    protected function checkLink($link, $type)
+    {
+        $this->assertTrue(is_object($link));
+        $href = $link->getAttribute('href');
+        $this->assertContains($type, $href);
+        $this->assertNotEquals('', $link->getText());
+        $this->assertContains($link->getText(), $href);
+    }
+
+    protected function setupMultipleCallnumbers()
+    {
+        $this->changeConfigs([
+            'config' => [
+                'Catalog' => ['driver' => 'Demo']
+            ],
+            'Demo' => [
+                'Holdings' => [
+                    $this->id => json_encode([
+                        ['callnumber' => 'CallNumberOne', 'location' => 'Villanova'],
+                        ['callnumber' => 'CallNumberTwo', 'location' => 'Villanova'],
+                        ['callnumber' => 'CallNumberThree', 'location' => 'Phobos'],
+                        ['callnumber' => 'CallNumberFour', 'location' => 'Phobos']
+                    ])
+                ]
+            ]
+        ]);
+    }
+
+    /**
+     * Sets callnumber_handler to true
+     *
+     * @param string                      $type        dewey or lcc
+     * @param \Behat\Mink\Element\Element $page        page element
+     * @param boolean                     $expectLinks links on multiple?
+     *
+     * @return void
+     */
+    protected function activateAndTestLinks($type, $page, $expectLinks)
+    {
+        // Single callnumbers (Sample)
+        $this->changeConfigs([
+            'config' => [
+                'Catalog' => ['driver' => 'Sample'],
+                'Item_Status' => ['callnumber_handler' => $type]
+            ]
+        ]);
+        $this->getMinkSession()->reload();
+        $this->snooze();
+        $link = $page->find('css', '.callnumber a,.groupCallnumber a,.fullCallnumber a');
+        $this->checkLink($link, $type);
+
+        // Multiple callnumbers
+        $this->setupMultipleCallnumbers();
+        $this->getMinkSession()->reload();
+        $this->snooze();
+        $link = $page->find('css', '.callnumber a,.groupCallnumber a,.fullCallnumber a');
+        if ($expectLinks) {
+            $this->checkLink($link, $type);
+            // TODO
+            // if 'all'
+            // - refresh until multiple
+            // - test multiple
+            // else
+        } else {
+            $this->assertTrue(is_null($link));
+        }
+    }
+
+    /**
+     * Sets callnumber_handler to true
+     *
+     * @param string  $nos         multiple_call_nos setting
+     * @param string  $locs        multiple_locations setting
+     * @param boolean $expectLinks whether or not links are expected for multiple callnumbers in this config
+     *
+     * @return void
+     */
+    protected function validateSetting($nos = 'first', $locs = 'msg', $expectLinks = true, $full = false)
+    {
+        $this->changeCallnumberSettings($nos, $locs, $full);
+        $page = $this->performSearch('id:' . $this->id);
+        // No link
+        $link = $page->find('css', '.callnumber a,.groupCallnumber a,.fullCallnumber a');
+        $this->assertTrue(is_null($link));
+        // With dewey links
+        $this->activateAndTestLinks('dewey', $page, $expectLinks);
+        // With lcc links
+        $this->activateAndTestLinks('lcc', $page, $expectLinks);
+    }
+
+    /**
+     * Test with multiple_call_nos set to first
+     * and multiple_locations set to msg
+     *
+     * @return void
+     */
+    public function testFirstAndMsg()
+    {
+        $this->changeConfigs([
+            'config' => ['Item_Status' => ['show_full_status' => false]]
+        ]);
+        $this->validateSetting('first');
+    }
+
+    /**
+     * Test with multiple_call_nos set to first
+     * and multiple_locations set to msg
+     *
+     * @return void
+     */
+    public function testAllAndMsg()
+    {
+        $this->validateSetting('all');
+    }
+
+    /**
+     * Test with multiple_call_nos set to first
+     * and multiple_locations set to msg
+     *
+     * @return void
+     */
+    public function testMsgAndMsg()
+    {
+        $this->validateSetting('msg', 'msg', false);
+    }
+
+    /**
+     * Test with multiple_call_nos set to first
+     * and multiple_locations set to group
+     *
+     * @return void
+     */
+    public function testFirstAndGroup()
+    {
+        $this->validateSetting('first', 'group');
+    }
+
+    /**
+     * Test with multiple_call_nos set to all
+     * and multiple_locations set to msg
+     *
+     * @return void
+     */
+    public function testAllAndGroup()
+    {
+        $this->validateSetting('all', 'group');
+    }
+
+    /**
+     * Test with multiple_call_nos set to msg
+     * and multiple_locations set to group
+     *
+     * @return void
+     */
+    public function testMsgAndGroup()
+    {
+        $this->validateSetting('msg', 'group', false);
+    }
+
+    /**
+     * Test with show_full_status set to true
+     *
+     * @return void
+     */
+    public function testStatusFull()
+    {
+        $this->validateSetting('first', 'msg', true, true);
+    }
+}
diff --git a/themes/bootstrap3/js/check_item_statuses.js b/themes/bootstrap3/js/check_item_statuses.js
index 985427d4e5a39c9b40a14bb7cbe8b5a935ae4156..48be5222529e5ff50ec73d20fd7c592a3abb9deb 100644
--- a/themes/bootstrap3/js/check_item_statuses.js
+++ b/themes/bootstrap3/js/check_item_statuses.js
@@ -1,4 +1,14 @@
 /*global VuFind */
+function linkCallnumbers(callnumber, callnumber_handler) {
+  if (callnumber_handler) {
+    var cns = callnumber.split(',\t');
+    for (var i = 0; i < cns.length; i++) {
+      cns[i] = '<a href="' + VuFind.path + '/Alphabrowse/Home?source=' + encodeURI(callnumber_handler) + '&amp;from=' + encodeURI(cns[i]) + '">' + cns[i] + '</a>';
+    }
+    return cns.join(',\t');
+  }
+  return callnumber;
+}
 
 function checkItemStatuses(_container) {
   var container = _container || $('body');
@@ -75,18 +85,18 @@ function checkItemStatuses(_container) {
           locationListHTML += '</div>';
           locationListHTML += '<div class="groupCallnumber">';
           locationListHTML += (result.locationList[x].callnumbers)
-               ? result.locationList[x].callnumbers : '';
+               ? linkCallnumbers(result.locationList[x].callnumbers, result.locationList[x].callnumber_handler) : '';
           locationListHTML += '</div>';
         }
         item.find('.locationDetails').removeClass('hidden');
         item.find('.locationDetails').empty().append(locationListHTML);
       } else {
         // Default case -- load call number and location into appropriate containers:
-        item.find('.callnumber').empty().append(result.callnumber + '<br/>');
+        item.find('.callnumber').empty().append(linkCallnumbers(result.callnumber, result.callnumber_handler) + '<br/>');
         item.find('.location').empty().append(
           result.reserve === 'true'
-          ? result.reserve_message
-          : result.location
+            ? result.reserve_message
+            : result.location
         );
       }
     });
diff --git a/themes/bootstrap3/templates/RecordTab/holdingsils.phtml b/themes/bootstrap3/templates/RecordTab/holdingsils.phtml
index e8fc341cbe7b3720328e92bda515df2c879ebe8a..0501484acce98ca713cb4dca1fd86d5b313c7430 100644
--- a/themes/bootstrap3/templates/RecordTab/holdingsils.phtml
+++ b/themes/bootstrap3/templates/RecordTab/holdingsils.phtml
@@ -68,7 +68,12 @@
     <th><?=$this->transEsc("Call Number")?>: </th>
     <td width="50%">
       <? foreach ($callNos as $callNo): ?>
-        <?=$this->escapeHtml($callNo)?><br />
+        <? if ($this->callnumberHandler): ?>
+          <a href="<?=$this->url('alphabrowse-home') ?>?source=<?=$this->escapeHtmlAttr($this->callnumberHandler) ?>&amp;from=<?=$this->escapeHtmlAttr($callNo) ?>"><?=$this->escapeHtml($callNo)?></a>
+        <? else: ?>
+          <?=$this->escapeHtml($callNo)?>
+        <? endif; ?>
+        <br />
       <? endforeach; ?>
     </td>
   </tr>
diff --git a/themes/bootstrap3/templates/ajax/status-full.phtml b/themes/bootstrap3/templates/ajax/status-full.phtml
index 992a216479e43f17b9ba980706dcab09304eeb10..a04420f3b63f42ea6a9a6737b5433a825845fadb 100644
--- a/themes/bootstrap3/templates/ajax/status-full.phtml
+++ b/themes/bootstrap3/templates/ajax/status-full.phtml
@@ -7,7 +7,7 @@
   <? $i = 0; foreach ($this->statusItems as $item): ?>
     <? if (++$i == 5) break; // Show no more than 5 items ?>
     <tr>
-      <td>
+      <td class="fullLocation">
         <? $locationText = $this->transEsc('location_' . $item['location'], array(), $item['location']); ?>
         <? if (isset($item['locationhref']) && $item['locationhref']): ?>
           <a href="<?=$item['locationhref']?>" target="_blank"><?=$locationText?></a>
@@ -15,8 +15,14 @@
           <?=$locationText?>
         <? endif; ?>
       </td>
-      <td><?=$this->escapeHtml($item['callnumber'])?></td>
-      <td>
+      <td class="fullCallnumber">
+        <? if ($this->callnumberHandler): ?>
+          <a href="<?=$this->url('alphabrowse-home') ?>?source=<?=$this->escapeHtmlAttr($this->callnumberHandler) ?>&amp;from=<?=$this->escapeHtmlAttr($item['callnumber']) ?>"><?=$this->escapeHtml($item['callnumber'])?></a>
+        <? else: ?>
+          <?=$this->escapeHtml($item['callnumber'])?>
+        <? endif; ?>
+      </td>
+      <td class="fullAvailability">
         <? if (isset($item['use_unknown_message']) && $item['use_unknown_message']): ?>
           <span><?=$this->transEsc("status_unknown_message")?></span>
         <? elseif ($item['availability']): ?>
diff --git a/themes/jquerymobile/js/check_item_statuses.js b/themes/jquerymobile/js/check_item_statuses.js
index 61cbf563ec354d4b33851ff96ff49f4ede90bf73..158e94419bbbeae6a61b82a6a7330fedc5757cc9 100644
--- a/themes/jquerymobile/js/check_item_statuses.js
+++ b/themes/jquerymobile/js/check_item_statuses.js
@@ -1,4 +1,14 @@
 /*global path*/
+function linkCallnumbers(callnumber, callnumber_handler) {
+  if (callnumber_handler) {
+    var cns = callnumber.split(',\t');
+    for (var i = 0; i < cns.length; i++) {
+      cns[i] = '<a href="' + path + '/Alphabrowse/Home?source=' + encodeURI(callnumber_handler) + '&amp;from=' + encodeURI(cns[i]) + '">' + cns[i] + '</a>';
+    }
+    return cns.join(',\t');
+  }
+  return callnumber;
+}
 
 function checkItemStatuses() {
     var id = $.map($('.ajaxItemId'), function(i) {
@@ -27,7 +37,7 @@ function checkItemStatuses() {
                             item.find('.status').hide();
                         } else {
                             // Default case -- load call number and location into appropriate containers:
-                            item.find('.callnumber').empty().append(result.callnumber);
+                            item.find('.callnumber').empty().append(linkCallnumbers(result.callnumber, result.callnumber_handler));
                             item.find('.location').empty().append(
                                 result.reserve == 'true'
                                 ? result.reserve_message
diff --git a/themes/jquerymobile/templates/RecordTab/holdingsils.phtml b/themes/jquerymobile/templates/RecordTab/holdingsils.phtml
index e9b2f5aa640d22704fec72f049a7942357775a45..3da187abb51a5ea1de8ecc724c674fd13056b832 100644
--- a/themes/jquerymobile/templates/RecordTab/holdingsils.phtml
+++ b/themes/jquerymobile/templates/RecordTab/holdingsils.phtml
@@ -56,7 +56,12 @@
     <th><?=$this->transEsc("Call Number")?>: </th>
     <td>
       <? foreach ($callNos as $callNo): ?>
-        <?=$this->escapeHtml($callNo)?><br />
+        <? if ($this->callnumberHandler): ?>
+          <a href="<?=$this->url('alphabrowse-home') ?>?source=<?=$this->escapeHtmlAttr($this->callnumberHandler) ?>&amp;from=<?=$this->escapeHtmlAttr($callNo) ?>"><?=$this->escapeHtml($callNo)?></a>
+        <? else: ?>
+          <?=$this->escapeHtml($callNo)?>
+        <? endif; ?>
+        <br />
       <? endforeach; ?>
     </td>
   </tr>