diff --git a/config/vufind/config.ini b/config/vufind/config.ini
index 6dc6836749a507fb924eb771a0743b0549356957..b89d88e845efc3b486a71b6c71c9251e47b91c19 100644
--- a/config/vufind/config.ini
+++ b/config/vufind/config.ini
@@ -563,9 +563,14 @@ authors         = Wikipedia
 ;HathiRights    = pd,ic-world,cc-by,cc-by-nd,cc-by-nc-nd,cc-by-nc,cc-by-nc-sa,cc-by-sa,cc-zero,und-world
 
 ; Possible GoogleBooks options full,partial,noview
-; Default is "full,partial" if unset here.
+; options can be set for each / either of link or tab
+; Link makes a button appear in search results / record view
+; Tab makes a tab with an embedded preview appear on record view
+; Default is "GoogleOptions['link'] = full,partial" if nothing
+; is set here.
 ; see code.google.com/apis/books/docs/dynamic-links.html#JSONformat
-;GoogleOptions  = full,partial
+;GoogleOptions['link']  = full,partial
+;GoogleOptions['tab']  = partial
 
 ; OpenLibrary currently offers the same options/default as GoogleBooks (above):
 ;OpenLibraryOptions  = full,partial
diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php
index e6e1dc422d0ade8a7128d754692b495df7f1fe6f..5252ef2bcbda265d5314e62ab02eade009e47aa1 100644
--- a/module/VuFind/config/module.config.php
+++ b/module/VuFind/config/module.config.php
@@ -427,6 +427,7 @@ $config = array(
                     'map' => 'VuFind\RecordTab\Factory::getMap',
                     'reviews' => 'VuFind\RecordTab\Factory::getReviews',
                     'usercomments' => 'VuFind\RecordTab\Factory::getUserComments',
+                    'preview' => 'VuFind\RecordTab\Factory::getPreview',
                 ),
                 'invokables' => array(
                     'description' => 'VuFind\RecordTab\Description',
@@ -577,6 +578,7 @@ $config = array(
                     'Holdings' => 'HoldingsILS', 'Description' => 'Description',
                     'TOC' => 'TOC', 'UserComments' => 'UserComments',
                     'Reviews' => 'Reviews', 'Excerpt' => 'Excerpt',
+                    'Preview' => 'preview',
                     'HierarchyTree' => 'HierarchyTree', 'Map' => 'Map',
                     'Details' => 'StaffViewArray',
                 ),
@@ -587,6 +589,7 @@ $config = array(
                     'Holdings' => 'HoldingsILS', 'Description' => 'Description',
                     'TOC' => 'TOC', 'UserComments' => 'UserComments',
                     'Reviews' => 'Reviews', 'Excerpt' => 'Excerpt',
+                    'Preview' => 'preview',
                     'HierarchyTree' => 'HierarchyTree', 'Map' => 'Map',
                     'Details' => 'StaffViewMARC',
                 ),
diff --git a/module/VuFind/src/VuFind/RecordTab/AbstractBase.php b/module/VuFind/src/VuFind/RecordTab/AbstractBase.php
index 6e38dfa9f6e5e117e5b76b6e0826b3084e97d449..fad767f1ffea70be9ae61b82b7182f51e6d562e8 100644
--- a/module/VuFind/src/VuFind/RecordTab/AbstractBase.php
+++ b/module/VuFind/src/VuFind/RecordTab/AbstractBase.php
@@ -63,6 +63,17 @@ abstract class AbstractBase implements TabInterface
         return true;
     }
 
+    /**
+     * Is this tab initially visible?
+     *
+     * @return bool
+     */
+    public function isVisible()
+    {
+        // Assume visible by default; subclasses may add rules.
+        return true;
+    }
+
     /**
      * Set the record driver
      *
@@ -112,4 +123,4 @@ abstract class AbstractBase implements TabInterface
     {
         return $this->request;
     }
-}
\ No newline at end of file
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/Factory.php b/module/VuFind/src/VuFind/RecordTab/Factory.php
index c82f0ad0e8065b63a982a0514e2082a64ddd6761..b48e57cb2e58cef8bbf82b176fdbe8dc8c8a6640 100644
--- a/module/VuFind/src/VuFind/RecordTab/Factory.php
+++ b/module/VuFind/src/VuFind/RecordTab/Factory.php
@@ -199,4 +199,36 @@ class Factory
             || ($cfg->Social->comments && $cfg->Social->comments !== 'disabled');
         return new UserComments($enabled);
     }
-}
\ No newline at end of file
+
+    /**
+     * Factory for Preview tab plugin.
+     *
+     * @param ServiceManager $sm Service manager.
+     *
+     * @return Preview
+     */
+    public static function getPreview(ServiceManager $sm)
+    {
+        $cfg = $sm->getServiceLocator()->get('VuFind\Config')->get('config');
+        // currently only active if config [content] [previews] contains google
+        // and googleoptions[tab] is not empty.
+        $active = false;
+        if (isset($cfg->Content->previews)) {
+            $content_previews = explode(
+                ',', strtolower(str_replace(' ', '', $cfg->Content->previews))
+            );
+            if (in_array('google', $content_previews)
+                && isset($cfg->Content->GoogleOptions)
+            ) {
+                $g_options = $cfg->Content->GoogleOptions;
+                if (isset($g_options->tab)) {
+                    $tabs = explode(',', $g_options->tab);
+                    if (count($tabs) > 0) {
+                        $active = true;
+                    }
+                }
+            }
+        }
+        return new Preview($active);
+    }
+}
diff --git a/module/VuFind/src/VuFind/RecordTab/Preview.php b/module/VuFind/src/VuFind/RecordTab/Preview.php
new file mode 100644
index 0000000000000000000000000000000000000000..81bc0fdef8e4b58e9aa4322f5ca4120c24843266
--- /dev/null
+++ b/module/VuFind/src/VuFind/RecordTab/Preview.php
@@ -0,0 +1,96 @@
+<?php
+/**
+ * Embedded Preview tab
+ *
+ * PHP version 5
+ *
+ * Copyright (C) Villanova University 2010.
+ *
+ * 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 VuFind2
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:record_tabs Wiki
+ */
+namespace VuFind\RecordTab;
+
+/**
+ * Embedded Preview tab
+ *
+ * @category VuFind2
+ * @package  RecordTabs
+ * @author   Demian Katz <demian.katz@villanova.edu>
+ * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
+ * @link     http://vufind.org/wiki/vufind2:record_tabs Wiki
+ */
+class Preview extends AbstractBase
+{
+    /**
+     * Configuration
+     *
+     * @var \Zend\Config\Config
+     */
+    protected $config = null;
+
+    /**
+     * Is this tab active?
+     *
+     * @var bool
+     */
+    protected $active = false;
+
+    /**
+     * Constructor
+     *
+     * @param bool $active Is this tab active?
+     */
+    public function __construct($active)
+    {
+        $this->active = $active;
+    }
+
+    /**
+     * Get the on-screen description for this tab.
+     *
+     * @return string
+     */
+    public function getDescription()
+    {
+        return 'Preview';
+    }
+
+    /**
+     * Is this tab active?
+     *
+     * @return bool
+     */
+    public function isActive()
+    {
+        return $this->active;
+    }
+
+    /**
+     * Is this tab initially visible?
+     *
+     * @return bool
+     */
+    public function isVisible()
+    {
+        // in this case there is no downside to keeping it hidden
+        // until there is content
+        return false;
+    }
+}
diff --git a/module/VuFind/src/VuFind/View/Helper/Root/Record.php b/module/VuFind/src/VuFind/View/Helper/Root/Record.php
index 187a5af66e5d900a462e32028bacab020846718a..aeaab9a50e14240255f802dfd552d057d556d2ba 100644
--- a/module/VuFind/src/VuFind/View/Helper/Root/Record.php
+++ b/module/VuFind/src/VuFind/View/Helper/Root/Record.php
@@ -234,18 +234,74 @@ class Record extends AbstractHelper
     }
 
     /**
-     * Render previews of the item if configured.
+     * Render previews (data and link) of the item if configured.
      *
      * @return string
      */
     public function getPreviews()
+    {
+        return $this->getPreviewData() . $this->getPreviewLink();
+    }
+
+    /**
+     * Render data needed to get previews.
+     *
+     * @return string
+     */
+    public function getPreviewData()
     {
         return $this->renderTemplate(
-            'preview.phtml',
+            'previewdata.phtml',
             array('driver' => $this->driver, 'config' => $this->config)
         );
     }
 
+    /**
+     * Render links to previews of the item if configured.
+     *
+     * @return string
+     */
+    public function getPreviewLink()
+    {
+        return $this->renderTemplate(
+            'previewlink.phtml',
+            array('driver' => $this->driver, 'config' => $this->config)
+        );
+    }
+
+    /**
+     * collects ISBN, LCCN, and OCLC numbers to use in calling preview APIs
+     *
+     * @return array
+     */
+    public function getPreviewIds()
+    {
+        // Extract identifiers from record driver if it supports appropriate methods:
+        $isbn = is_callable(array($this->driver, 'getCleanISBN'))
+            ? $this->driver->getCleanISBN() : '';
+        $lccn = is_callable(array($this->driver, 'getLCCN'))
+            ? $this->driver->getLCCN() : '';
+        $oclc = is_callable(array($this->driver, 'getOCLC'))
+            ? $this->driver->getOCLC() : array();
+
+        // Turn identifiers into class names to communicate with jQuery logic:
+        $idClasses = array();
+        if (!empty($isbn)) {
+            $idClasses[] = 'ISBN' . $isbn;
+        }
+        if (!empty($lccn)) {
+            $idClasses[] = 'LCCN' . $lccn;
+        }
+        if (!empty($oclc)) {
+            foreach ($oclc as $oclcNum) {
+                if (!empty($oclcNum)) {
+                    $idClasses[] = 'OCLC' . $oclcNum;
+                }
+            }
+        }
+        return $idClasses;
+    }
+
     /**
      * Get the name of the controller used by the record route.
      *
@@ -479,4 +535,4 @@ class Record extends AbstractHelper
 
         return array_map($formatLink, $urls);
     }
-}
\ No newline at end of file
+}
diff --git a/themes/blueprint/css/styles.css b/themes/blueprint/css/styles.css
index c616511ecebb53ffb7f4417ad65869ad4ca542a3..ab317398a4b45e6cb7946076a4fae9b0417e3646 100644
--- a/themes/blueprint/css/styles.css
+++ b/themes/blueprint/css/styles.css
@@ -2375,3 +2375,8 @@ div.handle {
 #viz-instructions {
   padding-top:600px;
 }
+#gbsViewer {
+  margin:25px 0px 50px 25px;
+  width: 90%;
+  height: 600px;
+}
diff --git a/themes/blueprint/js/embedGBS.js b/themes/blueprint/js/embedGBS.js
new file mode 100644
index 0000000000000000000000000000000000000000..a6dfa1abfcd5c06b83a9e7efd9c569275e0023a3
--- /dev/null
+++ b/themes/blueprint/js/embedGBS.js
@@ -0,0 +1,12 @@
+// we don't need to wait for dom ready since lang is in the dom root
+var lang = document.documentElement.getAttribute('lang');
+google.load("books", "0", {"language":lang});
+
+function initialize() {
+  var bibkeys = getBibKeyString().split(/\s+/);
+  var viewer = new google.books.DefaultViewer(document.getElementById('gbsViewer'));
+  viewer.load(bibkeys);
+}
+
+google.setOnLoadCallback(initialize);
+
diff --git a/themes/blueprint/js/preview.js b/themes/blueprint/js/preview.js
index 70871aa87161f4da88e89e5de624b64f2f2bfb87..5046319500f22fce22285de7e7a8544799271e0f 100644
--- a/themes/blueprint/js/preview.js
+++ b/themes/blueprint/js/preview.js
@@ -1,12 +1,18 @@
 // functions to get rights codes for previews
 function getHathiOptions() {
-    return $('[class*="hathiPreviewDiv"]').attr("class").split('__')[1].split(',');
+    return $('[class*="hathiPreviewSpan"]').attr("class").split('__')[1].split(',');
 }
 function getGoogleOptions() {
-    return $('[class*="googlePreviewDiv"]').attr("class").split('__')[1].split(',');
+    var opts_temp = $('[class*="googlePreviewSpan"]').attr("class").split('__')[1].split(';');
+    var options = {};
+    for (key in opts_temp) {
+        var arr = opts_temp[key].split(':');
+        options[arr[0]] = arr[1].split(',');
+    }
+    return options;
 }
 function getOLOptions() {
-    return $('[class*="olPreviewDiv"]').attr("class").split('__')[1].split(',');
+    return $('[class*="olPreviewSpan"]').attr("class").split('__')[1].split(',');
 }
 
 function getHTPreviews(skeys) {
@@ -37,10 +43,7 @@ function applyPreviewUrl($link, url) {
         .parents('a').attr('href', url);
 }
 
-function processBookInfo(booksInfo, previewClass) {
-    // assign the correct rights string depending on source
-    var viewOptions = (previewClass == 'previewGBS')
-        ? getGoogleOptions() : getOLOptions();
+function processBookInfo(booksInfo, previewClass, viewOptions) {
     for (var bibkey in booksInfo) {
         var bookInfo = booksInfo[bibkey];
         if (bookInfo) {
@@ -54,11 +57,27 @@ function processBookInfo(booksInfo, previewClass) {
 }
 
 function processGBSBookInfo(booksInfo) {
-    processBookInfo(booksInfo, 'previewGBS');
+    var viewOptions = getGoogleOptions();
+    if (viewOptions['link'] && viewOptions['link'].length > 0) {
+        processBookInfo(booksInfo, 'previewGBS', viewOptions['link']);
+    }
+    if (viewOptions['tab'] && viewOptions['tab'].length > 0) {
+        // check for "embeddable: true" in bookinfo
+        for (var bibkey in booksInfo) {
+            var bookInfo = booksInfo[bibkey];
+            if (bookInfo) {
+                if (viewOptions['tab'].indexOf(bookInfo.preview)>= 0
+                && (bookInfo.embeddable)) {
+                    // make tab visible
+                    $('ul.recordTabs li.hidden a#Preview').parent().removeClass('hidden');
+                }
+            }
+        }
+    }
 }
 
 function processOLBookInfo(booksInfo) {
-    processBookInfo(booksInfo, 'previewOL');
+    processBookInfo(booksInfo, 'previewOL', getOLOptions());
 }
 
 function processHTBookInfo(booksInfo) {
@@ -116,17 +135,21 @@ function setIndexOf() {
     };
 }
 
-function getBookPreviews() {
+function getBibKeyString() {
     var skeys = '';
     $('.previewBibkeys').each(function(){
         skeys += $(this).attr('class');
     });
-    skeys = skeys.replace(/previewBibkeys/g, '').replace(/^\s+|\s+$/g, '');
+    return skeys.replace(/previewBibkeys/g, '').replace(/^\s+|\s+$/g, '');
+}
+
+function getBookPreviews() {
+    var skeys = getBibKeyString();
     var bibkeys = skeys.split(/\s+/);
     var script;
 
     // fetch Google preview if enabled
-    if ($('.previewGBS').length > 0) {
+    if ($('[class*="googlePreviewSpan"]').length > 0) {
         // checks if query string might break URI limit - if not, run as normal
         if (bibkeys.length <= 150){
             script = 'https://encrypted.google.com/books?jscmd=viewapi&bibkeys='
@@ -151,14 +174,14 @@ function getBookPreviews() {
     }
 
     // fetch OpenLibrary preview if enabled
-    if ($('.previewOL').length > 0) {
+    if ($('[class*="olPreviewSpan"]').length > 0) {
         script = 'http://openlibrary.org/api/books?bibkeys='
             + bibkeys.join(',') + '&callback=processOLBookInfo';
         $.getScript(script);
     }
 
     // fetch HathiTrust preview if enabled
-    if ($('.previewHT').length > 0) {
+    if ($('[class*="hathiPreviewSpan"]').length > 0) {
         getHTPreviews(skeys);
     }
 }
@@ -168,4 +191,4 @@ $(document).ready(function() {
         setIndexOf();
     }
     getBookPreviews();
-});
\ No newline at end of file
+});
diff --git a/themes/blueprint/templates/RecordDriver/AbstractBase/preview.phtml b/themes/blueprint/templates/RecordDriver/AbstractBase/preview.phtml
deleted file mode 100644
index be5d9b2e41e1fe96f0478edfea608f949bcf299d..0000000000000000000000000000000000000000
--- a/themes/blueprint/templates/RecordDriver/AbstractBase/preview.phtml
+++ /dev/null
@@ -1,88 +0,0 @@
-<?
-    $previews = isset($this->config->Content->previews)
-        ? explode(',', $this->config->Content->previews) : array();
-    if (!empty($previews)) {
-        // Extract identifiers from record driver if it supports appropriate methods:
-        $isbn = is_callable(array($this->driver, 'getCleanISBN'))
-            ? $this->driver->getCleanISBN() : '';
-        $lccn = is_callable(array($this->driver, 'getLCCN'))
-            ? $this->driver->getLCCN() : '';
-        $oclc = is_callable(array($this->driver, 'getOCLC'))
-            ? $this->driver->getOCLC() : array();
-
-        // Turn identifiers into class names to communicate with jQuery logic:
-        $idClasses = array();
-        if (!empty($isbn)) {
-            $idClasses[] = 'ISBN' . $isbn;
-        }
-        if (!empty($lccn)) {
-            $idClasses[] = 'LCCN' . $lccn;
-        }
-        if (!empty($oclc)) {
-            foreach ($oclc as $oclcNum) {
-                if (!empty($oclcNum)) {
-                    $idClasses[] = 'OCLC' . $oclcNum;
-                }
-            }
-        }
-
-        // If we found at least one identifier, we can build the placeholder HTML:
-        $html = '';
-        if (!empty($idClasses)) {
-            // Convert to string:
-            $idClasses = implode(' ', $idClasses);
-
-            // Loop through configured options and build appropriate HTML:
-            foreach ($previews as $current) {
-                switch (trim(strtolower($current))) {
-                case 'google':
-                    $name = 'Google Books';
-                    $divClass = 'googlePreviewDiv';
-                    $linkClass = 'previewGBS';
-                    $icon = 'https://www.google.com/intl/en/googlebooks/images/gbs_preview_button1.png';
-                    $options = isset($this->config->Content->GoogleOptions)
-                        ? str_replace(' ', '', $this->config->Content->GoogleOptions)
-                        : "full,partial";
-                    break;
-                case 'openlibrary':
-                    $name = 'Open Library';
-                    $divClass = 'olPreviewDiv';
-                    $linkClass = 'previewOL';
-                    $icon = $this->imageLink('preview_ol.gif');
-                    $options = isset($this->config->Content->OpenLibraryOptions)
-                        ? str_replace(' ', '', $this->config->Content->OpenLibraryOptions)
-                        : "full,partial";
-                    break;
-                case 'hathitrust':
-                    $name = 'HathiTrust';
-                    $divClass = 'hathiPreviewDiv';
-                    $linkClass = 'previewHT';
-                    $icon = $this->imageLink('preview_ht.gif');
-                    $options = isset($this->config->Content->HathiRights)
-                        ? str_replace(' ', '', $this->config->Content->HathiRights)
-                        : "pd,ic-world";
-                    break;
-                default:
-                    $name = $divClass = $linkClass = $icon = $options = false;
-                    break;
-                }
-                if ($name) {
-                    $title = $this->transEsc('Preview from') . ' ' . $name;
-                    $html .= '<div class="' . $divClass . '__' . $options . '">'
-                        . '<a title="' . $title . '" class="hide ' . $linkClass . ' ' . $idClasses . '" target="_blank">'
-                        . '<img src="' . $icon . '" alt="' . $this->transEsc('Preview') . '" />'
-                        . '</a>'
-                        . '</div>';
-                }
-            }
-
-            // If we built some HTML, we should load the supporting Javascript and
-            // add the necessary identifier code:
-            if (!empty($html)) {
-                $html .= '<span class="previewBibkeys ' . $idClasses . '"></span>';
-                $this->headScript()->appendFile("preview.js");
-                echo $html;
-            }
-        }
-    }
-?>
diff --git a/themes/blueprint/templates/RecordDriver/AbstractBase/previewdata.phtml b/themes/blueprint/templates/RecordDriver/AbstractBase/previewdata.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..6b9cb54ea1fd589852cfd05b738943cbd293b6b7
--- /dev/null
+++ b/themes/blueprint/templates/RecordDriver/AbstractBase/previewdata.phtml
@@ -0,0 +1,74 @@
+<?
+    $previews = isset($this->config->Content->previews)
+        ? explode(',', $this->config->Content->previews) : array();
+    if (!empty($previews)) {
+        $idClasses = $this->record($this->driver)->getPreviewIds();
+
+        // If we found at least one identifier, we can insert the data
+        $html = '';
+        if (!empty($idClasses)) {
+            // Convert to string:
+            $idClasses = implode(' ', $idClasses);
+
+            // Loop through configured options and build appropriate HTML:
+            foreach ($previews as $current) {
+                switch (trim(strtolower($current))) {
+                case 'google':
+                    $spanClass = 'googlePreviewSpan';
+                    // specify link vs. tab
+                    $link_options = '';
+                    if (isset($this->config->Content->GoogleOptions->link)
+                            && $this->config->Content->GoogleOptions->link) {
+                        $link_options = 'link:' . strtolower(str_replace(' ', '',
+                            $this->config->Content->GoogleOptions->link));
+                    }
+                    $tab_options = '';
+                    if (isset($this->config->Content->GoogleOptions->tab)
+                            && $this->config->Content->GoogleOptions->tab) {
+                        $tab_options = 'tab:' . strtolower(str_replace(' ', '',
+                            $this->config->Content->GoogleOptions->tab));
+                    }
+                    $options = ($link_options && $tab_options)
+                        ? "$link_options;$tab_options"
+                        : "$link_options$tab_options";
+                    // maintain previous behavior and default
+                    if (!$link_options && !$tab_options) {
+                        $options = 'link:full,partial';
+                        if (isset($this->config->Content->GoogleOptions)
+                              && is_string($this->config->Content->GoogleOptions)) {
+                            $options = 'link:' . strtolower(str_replace(' ', '',
+                                $this->config->Content->GoogleOptions));
+                        }
+                    }
+                    break;
+                case 'openlibrary':
+                    $spanClass = 'olPreviewSpan';
+                    $options = isset($this->config->Content->OpenLibraryOptions)
+                        ? str_replace(' ', '', $this->config->Content->OpenLibraryOptions)
+                        : "full,partial";
+                    break;
+                case 'hathitrust':
+                    $spanClass = 'hathiPreviewSpan';
+                    $options = isset($this->config->Content->HathiRights)
+                        ? str_replace(' ', '', $this->config->Content->HathiRights)
+                        : "pd,ic-world";
+                    break;
+                default:
+                    $spanClass = $options = false;
+                    break;
+                }
+                if ($spanClass) {
+                    $html .= '<span class="' . $spanClass . '__' . $options . '"></span>';
+                }
+            }
+
+            // If we built some HTML, we should load the supporting Javascript and
+            // add the necessary identifier code:
+            if (!empty($html)) {
+                $html .= '<span class="previewBibkeys ' . $idClasses . '"></span>';
+                $this->headScript()->appendFile("preview.js");
+                echo $html;
+            }
+        }
+    }
+?>
diff --git a/themes/blueprint/templates/RecordDriver/AbstractBase/previewlink.phtml b/themes/blueprint/templates/RecordDriver/AbstractBase/previewlink.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..04697b44c87b119d88135396686394a2a0e9c83e
--- /dev/null
+++ b/themes/blueprint/templates/RecordDriver/AbstractBase/previewlink.phtml
@@ -0,0 +1,53 @@
+<?
+    $previews = isset($this->config->Content->previews)
+        ? explode(',', $this->config->Content->previews) : array();
+    if (!empty($previews)) {
+        $idClasses = $this->record($this->driver)->getPreviewIds();
+        // If we found at least one identifier, we can build the placeholder HTML:
+        $html = '';
+        if (!empty($idClasses)) {
+            // Convert to string:
+            $idClasses = implode(' ', $idClasses);
+
+            // Loop through previews and build appropriate HTML:
+            foreach ($previews as $current) {
+                switch (trim(strtolower($current))) {
+                case 'google':
+                    $name = 'Google Books';
+                    $divClass = 'googlePreviewDiv';
+                    $linkClass = 'previewGBS';
+                    $icon = 'https://www.google.com/intl/en/googlebooks/images/gbs_preview_button1.png';
+                    break;
+                case 'openlibrary':
+                    $name = 'Open Library';
+                    $divClass = 'olPreviewDiv';
+                    $linkClass = 'previewOL';
+                    $icon = $this->imageLink('preview_ol.gif');
+                    break;
+                case 'hathitrust':
+                    $name = 'HathiTrust';
+                    $divClass = 'hathiPreviewDiv';
+                    $linkClass = 'previewHT';
+                    $icon = $this->imageLink('preview_ht.gif');
+                    break;
+                default:
+                    $name = $divClass = $linkClass = $icon = false;
+                    break;
+                }
+                if ($name) {
+                    $title = $this->transEsc('Preview from') . ' ' . $name;
+                    $html .= '<div class="' . $divClass . '">'
+                        . '<a title="' . $title . '" class="hide ' . $linkClass . ' ' . $idClasses . '" target="_blank">'
+                        . '<img src="' . $icon . '" alt="' . $this->transEsc('Preview') . '" />'
+                        . '</a>'
+                        . '</div>';
+                }
+            }
+
+            // javascript included in previewdata template
+            if (!empty($html)) {
+                echo $html;
+            }
+        }
+    }
+?>
diff --git a/themes/blueprint/templates/RecordDriver/SolrDefault/core.phtml b/themes/blueprint/templates/RecordDriver/SolrDefault/core.phtml
index 3ff4a669ec20e2d8aabe688114fa5867c871c6cc..8cffef3f4d1342e93a490d3434de86565920f733 100644
--- a/themes/blueprint/templates/RecordDriver/SolrDefault/core.phtml
+++ b/themes/blueprint/templates/RecordDriver/SolrDefault/core.phtml
@@ -245,6 +245,10 @@
     </div>
   <? endif; ?>
 
+    <? // if you have a preview tab but want to move or remove the preview link
+       // from this area of the record view, this can be split into
+       // getPreviewData() (should stay here) and
+       // getPreviewLink() (can go in your desired tab) ?>
   <?=$this->record($this->driver)->getPreviews()?>
 </div>
 
diff --git a/themes/blueprint/templates/RecordTab/preview.phtml b/themes/blueprint/templates/RecordTab/preview.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..36d3301a1bd0d6dd4d4db875ccfc511260646404
--- /dev/null
+++ b/themes/blueprint/templates/RecordTab/preview.phtml
@@ -0,0 +1,10 @@
+<?
+    // Set page title.
+    $this->headTitle($this->translate('Preview') . ': ' . $this->driver->getBreadcrumb());
+
+    // load the embedded preview javascript file
+    $this->headScript()->appendFile('https://www.google.com/jsapi');
+    $this->headScript()->appendFile('embedGBS.js');
+?>
+<div id="gbsViewer" ></div>
+
diff --git a/themes/blueprint/templates/record/view.phtml b/themes/blueprint/templates/record/view.phtml
index 91aa7d79b9e3edd0228451df8771d5d0276a3895..fdb2529382a24c2b01dfd28186ceae806f749d10 100644
--- a/themes/blueprint/templates/record/view.phtml
+++ b/themes/blueprint/templates/record/view.phtml
@@ -39,8 +39,11 @@
              $this->layout()->breadcrumbs .= '<span>&gt;</span><em>' . $this->transEsc($desc) . '</em>';
              $activeTabObj = $obj;
          }
+        $tab_classes = array();
+        if ($isCurrent) $tab_classes[] = 'active';
+        if (!$obj->isVisible()) $tab_classes[] = 'hidden';
       ?>
-      <li<?=$isCurrent ? ' class="active"' : ''?>>
+      <li<?=count($tab_classes) > 0 ? ' class="' . implode(' ', $tab_classes) . '"' : ''?>>
         <a href="<?=$this->recordLink()->getTabUrl($this->driver, $tab)?>#tabnav"><?=$this->transEsc($desc)?></a>
       </li>
       <? endforeach; ?>
diff --git a/themes/bootstrap/css/screen.css b/themes/bootstrap/css/screen.css
index 53bb7dc68415e73cacb9f654fa995f48df5ec229..0e79c4d3018ebb412bd8b23214f9b62de9e19749 100644
--- a/themes/bootstrap/css/screen.css
+++ b/themes/bootstrap/css/screen.css
@@ -74,4 +74,9 @@ div.xml.collapsed > div { display:none;margin-left:2px }
   .result .middle {
     width:75%;
   }
-}
\ No newline at end of file
+}
+#gbsViewer {
+  margin:25px 0px 50px 25px;
+  width: 90%;
+  height: 600px;
+}
diff --git a/themes/bootstrap/js/embedGBS.js b/themes/bootstrap/js/embedGBS.js
new file mode 100644
index 0000000000000000000000000000000000000000..a6dfa1abfcd5c06b83a9e7efd9c569275e0023a3
--- /dev/null
+++ b/themes/bootstrap/js/embedGBS.js
@@ -0,0 +1,12 @@
+// we don't need to wait for dom ready since lang is in the dom root
+var lang = document.documentElement.getAttribute('lang');
+google.load("books", "0", {"language":lang});
+
+function initialize() {
+  var bibkeys = getBibKeyString().split(/\s+/);
+  var viewer = new google.books.DefaultViewer(document.getElementById('gbsViewer'));
+  viewer.load(bibkeys);
+}
+
+google.setOnLoadCallback(initialize);
+
diff --git a/themes/bootstrap/js/preview.js b/themes/bootstrap/js/preview.js
index 087581fe6f62ef7548903f50bd21326773484c97..b4a7c9b176fa394a8f023d37091e6406c42ea39e 100644
--- a/themes/bootstrap/js/preview.js
+++ b/themes/bootstrap/js/preview.js
@@ -1,12 +1,18 @@
 // functions to get rights codes for previews
 function getHathiOptions() {
-    return $('[class*="hathiPreviewDiv"]').attr("class").split('__')[1].split(',');
+    return $('[class*="hathiPreviewSpan"]').attr("class").split('__')[1].split(',');
 }
 function getGoogleOptions() {
-    return $('[class*="googlePreviewDiv"]').attr("class").split('__')[1].split(',');
+    var opts_temp = $('[class*="googlePreviewSpan"]').attr("class").split('__')[1].split(';');
+    var options = {};
+    for (key in opts_temp) {
+        var arr = opts_temp[key].split(':');
+        options[arr[0]] = arr[1].split(',');
+    }
+    return options;
 }
 function getOLOptions() {
-    return $('[class*="olPreviewDiv"]').attr("class").split('__')[1].split(',');
+    return $('[class*="olPreviewSpan"]').attr("class").split('__')[1].split(',');
 }
 
 function getHTPreviews(skeys) {
@@ -37,10 +43,7 @@ function applyPreviewUrl($link, url) {
         .parents('a').attr('href', url);
 }
 
-function processBookInfo(booksInfo, previewClass) {
-    // assign the correct rights string depending on source
-    var viewOptions = (previewClass == 'previewGBS')
-        ? getGoogleOptions() : getOLOptions();
+function processBookInfo(booksInfo, previewClass, viewOptions) {
     for (var bibkey in booksInfo) {
         var bookInfo = booksInfo[bibkey];
         if (bookInfo) {
@@ -54,11 +57,27 @@ function processBookInfo(booksInfo, previewClass) {
 }
 
 function processGBSBookInfo(booksInfo) {
-    processBookInfo(booksInfo, 'previewGBS');
+    var viewOptions = getGoogleOptions();
+    if (viewOptions['link'] && viewOptions['link'].length > 0) {
+        processBookInfo(booksInfo, 'previewGBS', viewOptions['link']);
+    }
+    if (viewOptions['tab'] && viewOptions['tab'].length > 0) {
+        // check for "embeddable: true" in bookinfo
+        for (var bibkey in booksInfo) {
+            var bookInfo = booksInfo[bibkey];
+            if (bookInfo) {
+                if (viewOptions['tab'].indexOf(bookInfo.preview)>= 0
+                && (bookInfo.embeddable)) {
+                    // make tab visible
+                    $('ul.recordTabs li.hidden a#Preview').parent().removeClass('hidden');
+                }
+            }
+        }
+    }
 }
 
 function processOLBookInfo(booksInfo) {
-    processBookInfo(booksInfo, 'previewOL');
+    processBookInfo(booksInfo, 'previewOL', getOLOptions());
 }
 
 function processHTBookInfo(booksInfo) {
@@ -116,17 +135,21 @@ function setIndexOf() {
     };
 }
 
-function getBookPreviews() {
+function getBibKeyString() {
     var skeys = '';
     $('.previewBibkeys').each(function(){
         skeys += $(this).attr('class');
     });
-    skeys = skeys.replace(/previewBibkeys/g, '').replace(/^\s+|\s+$/g, '');
+    return skeys.replace(/previewBibkeys/g, '').replace(/^\s+|\s+$/g, '');
+}
+
+function getBookPreviews() {
+    var skeys = getBibKeyString();
     var bibkeys = skeys.split(/\s+/);
     var script;
 
     // fetch Google preview if enabled
-    if ($('.previewGBS').length > 0) {
+    if ($('[class*="googlePreviewSpan"]').length > 0) {
         // checks if query string might break URI limit - if not, run as normal
         if (bibkeys.length <= 150){
             script = 'https://encrypted.google.com/books?jscmd=viewapi&bibkeys='
@@ -151,14 +174,14 @@ function getBookPreviews() {
     }
 
     // fetch OpenLibrary preview if enabled
-    if ($('.previewOL').length > 0) {
+    if ($('[class*="olPreviewSpan"]').length > 0) {
         script = 'http://openlibrary.org/api/books?bibkeys='
             + bibkeys.join(',') + '&callback=processOLBookInfo';
         $.getScript(script);
     }
 
     // fetch HathiTrust preview if enabled
-    if ($('.previewHT').length > 0) {
+    if ($('[class*="hathiPreviewSpan"]').length > 0) {
         getHTPreviews(skeys);
     }
 }
@@ -168,4 +191,4 @@ $(document).ready(function() {
         setIndexOf();
     }
     getBookPreviews();
-});
\ No newline at end of file
+});
diff --git a/themes/bootstrap/templates/RecordDriver/AbstractBase/preview.phtml b/themes/bootstrap/templates/RecordDriver/AbstractBase/preview.phtml
deleted file mode 100644
index 3a671672624eb704b9464d9cf751669274bd2a40..0000000000000000000000000000000000000000
--- a/themes/bootstrap/templates/RecordDriver/AbstractBase/preview.phtml
+++ /dev/null
@@ -1,88 +0,0 @@
-<?
-    $previews = isset($this->config->Content->previews)
-        ? explode(',', $this->config->Content->previews) : array();
-    if (!empty($previews)) {
-        // Extract identifiers from record driver if it supports appropriate methods:
-        $isbn = is_callable(array($this->driver, 'getCleanISBN'))
-            ? $this->driver->getCleanISBN() : '';
-        $lccn = is_callable(array($this->driver, 'getLCCN'))
-            ? $this->driver->getLCCN() : '';
-        $oclc = is_callable(array($this->driver, 'getOCLC'))
-            ? $this->driver->getOCLC() : array();
-
-        // Turn identifiers into class names to communicate with jQuery logic:
-        $idClasses = array();
-        if (!empty($isbn)) {
-            $idClasses[] = 'ISBN' . $isbn;
-        }
-        if (!empty($lccn)) {
-            $idClasses[] = 'LCCN' . $lccn;
-        }
-        if (!empty($oclc)) {
-            foreach ($oclc as $oclcNum) {
-                if (!empty($oclcNum)) {
-                    $idClasses[] = 'OCLC' . $oclcNum;
-                }
-            }
-        }
-
-        // If we found at least one identifier, we can build the placeholder HTML:
-        $html = '';
-        if (!empty($idClasses)) {
-            // Convert to string:
-            $idClasses = implode(' ', $idClasses);
-
-            // Loop through configured options and build appropriate HTML:
-            foreach ($previews as $current) {
-                switch (trim(strtolower($current))) {
-                case 'google':
-                    $name = 'Google Books';
-                    $divClass = 'googlePreviewDiv';
-                    $linkClass = 'previewGBS';
-                    $icon = 'https://www.google.com/intl/en/googlebooks/images/gbs_preview_button1.png';
-                    $options = isset($this->config->Content->GoogleOptions)
-                        ? str_replace(' ', '', $this->config->Content->GoogleOptions)
-                        : "full,partial";
-                    break;
-                case 'openlibrary':
-                    $name = 'Open Library';
-                    $divClass = 'olPreviewDiv';
-                    $linkClass = 'previewOL';
-                    $icon = $this->imageLink('preview_ol.gif');
-                    $options = isset($this->config->Content->OpenLibraryOptions)
-                        ? str_replace(' ', '', $this->config->Content->OpenLibraryOptions)
-                        : "full,partial";
-                    break;
-                case 'hathitrust':
-                    $name = 'HathiTrust';
-                    $divClass = 'hathiPreviewDiv';
-                    $linkClass = 'previewHT';
-                    $icon = $this->imageLink('preview_ht.gif');
-                    $options = isset($this->config->Content->HathiRights)
-                        ? str_replace(' ', '', $this->config->Content->HathiRights)
-                        : "pd,ic-world";
-                    break;
-                default:
-                    $name = $divClass = $linkClass = $icon = $options = false;
-                    break;
-                }
-                if ($name) {
-                    $title = $this->transEsc('Preview from') . ' ' . $name;
-                    $html .= '<div class="' . $divClass . '__' . $options . '">'
-                        . '<a title="' . $title . '" class="hide ' . $linkClass . ' ' . $idClasses . '" target="_blank">'
-                        . '<img src="' . $icon . '" alt="' . $this->transEsc('Preview') . '" />'
-                        . '</a>'
-                        . '</div>';
-                }
-            }
-
-            // If we built some HTML, we should load the supporting Javascript and
-            // add the necessary identifier code:
-            if (!empty($html)) {
-                $html .= '<span class="previewBibkeys ' . $idClasses . '"></span>';
-                $this->headScript()->appendFile("preview.js");
-                echo $html;
-            }
-        }
-    }
-?>
\ No newline at end of file
diff --git a/themes/bootstrap/templates/RecordDriver/AbstractBase/previewdata.phtml b/themes/bootstrap/templates/RecordDriver/AbstractBase/previewdata.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..6b9cb54ea1fd589852cfd05b738943cbd293b6b7
--- /dev/null
+++ b/themes/bootstrap/templates/RecordDriver/AbstractBase/previewdata.phtml
@@ -0,0 +1,74 @@
+<?
+    $previews = isset($this->config->Content->previews)
+        ? explode(',', $this->config->Content->previews) : array();
+    if (!empty($previews)) {
+        $idClasses = $this->record($this->driver)->getPreviewIds();
+
+        // If we found at least one identifier, we can insert the data
+        $html = '';
+        if (!empty($idClasses)) {
+            // Convert to string:
+            $idClasses = implode(' ', $idClasses);
+
+            // Loop through configured options and build appropriate HTML:
+            foreach ($previews as $current) {
+                switch (trim(strtolower($current))) {
+                case 'google':
+                    $spanClass = 'googlePreviewSpan';
+                    // specify link vs. tab
+                    $link_options = '';
+                    if (isset($this->config->Content->GoogleOptions->link)
+                            && $this->config->Content->GoogleOptions->link) {
+                        $link_options = 'link:' . strtolower(str_replace(' ', '',
+                            $this->config->Content->GoogleOptions->link));
+                    }
+                    $tab_options = '';
+                    if (isset($this->config->Content->GoogleOptions->tab)
+                            && $this->config->Content->GoogleOptions->tab) {
+                        $tab_options = 'tab:' . strtolower(str_replace(' ', '',
+                            $this->config->Content->GoogleOptions->tab));
+                    }
+                    $options = ($link_options && $tab_options)
+                        ? "$link_options;$tab_options"
+                        : "$link_options$tab_options";
+                    // maintain previous behavior and default
+                    if (!$link_options && !$tab_options) {
+                        $options = 'link:full,partial';
+                        if (isset($this->config->Content->GoogleOptions)
+                              && is_string($this->config->Content->GoogleOptions)) {
+                            $options = 'link:' . strtolower(str_replace(' ', '',
+                                $this->config->Content->GoogleOptions));
+                        }
+                    }
+                    break;
+                case 'openlibrary':
+                    $spanClass = 'olPreviewSpan';
+                    $options = isset($this->config->Content->OpenLibraryOptions)
+                        ? str_replace(' ', '', $this->config->Content->OpenLibraryOptions)
+                        : "full,partial";
+                    break;
+                case 'hathitrust':
+                    $spanClass = 'hathiPreviewSpan';
+                    $options = isset($this->config->Content->HathiRights)
+                        ? str_replace(' ', '', $this->config->Content->HathiRights)
+                        : "pd,ic-world";
+                    break;
+                default:
+                    $spanClass = $options = false;
+                    break;
+                }
+                if ($spanClass) {
+                    $html .= '<span class="' . $spanClass . '__' . $options . '"></span>';
+                }
+            }
+
+            // If we built some HTML, we should load the supporting Javascript and
+            // add the necessary identifier code:
+            if (!empty($html)) {
+                $html .= '<span class="previewBibkeys ' . $idClasses . '"></span>';
+                $this->headScript()->appendFile("preview.js");
+                echo $html;
+            }
+        }
+    }
+?>
diff --git a/themes/bootstrap/templates/RecordDriver/AbstractBase/previewlink.phtml b/themes/bootstrap/templates/RecordDriver/AbstractBase/previewlink.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..04697b44c87b119d88135396686394a2a0e9c83e
--- /dev/null
+++ b/themes/bootstrap/templates/RecordDriver/AbstractBase/previewlink.phtml
@@ -0,0 +1,53 @@
+<?
+    $previews = isset($this->config->Content->previews)
+        ? explode(',', $this->config->Content->previews) : array();
+    if (!empty($previews)) {
+        $idClasses = $this->record($this->driver)->getPreviewIds();
+        // If we found at least one identifier, we can build the placeholder HTML:
+        $html = '';
+        if (!empty($idClasses)) {
+            // Convert to string:
+            $idClasses = implode(' ', $idClasses);
+
+            // Loop through previews and build appropriate HTML:
+            foreach ($previews as $current) {
+                switch (trim(strtolower($current))) {
+                case 'google':
+                    $name = 'Google Books';
+                    $divClass = 'googlePreviewDiv';
+                    $linkClass = 'previewGBS';
+                    $icon = 'https://www.google.com/intl/en/googlebooks/images/gbs_preview_button1.png';
+                    break;
+                case 'openlibrary':
+                    $name = 'Open Library';
+                    $divClass = 'olPreviewDiv';
+                    $linkClass = 'previewOL';
+                    $icon = $this->imageLink('preview_ol.gif');
+                    break;
+                case 'hathitrust':
+                    $name = 'HathiTrust';
+                    $divClass = 'hathiPreviewDiv';
+                    $linkClass = 'previewHT';
+                    $icon = $this->imageLink('preview_ht.gif');
+                    break;
+                default:
+                    $name = $divClass = $linkClass = $icon = false;
+                    break;
+                }
+                if ($name) {
+                    $title = $this->transEsc('Preview from') . ' ' . $name;
+                    $html .= '<div class="' . $divClass . '">'
+                        . '<a title="' . $title . '" class="hide ' . $linkClass . ' ' . $idClasses . '" target="_blank">'
+                        . '<img src="' . $icon . '" alt="' . $this->transEsc('Preview') . '" />'
+                        . '</a>'
+                        . '</div>';
+                }
+            }
+
+            // javascript included in previewdata template
+            if (!empty($html)) {
+                echo $html;
+            }
+        }
+    }
+?>
diff --git a/themes/bootstrap/templates/RecordDriver/SolrDefault/core.phtml b/themes/bootstrap/templates/RecordDriver/SolrDefault/core.phtml
index 3bb7c8c9e94f9b691b3bc6ee42d4ce69a6570d4e..6a82417790c26994f10e1c9ffc4031db5a8d084e 100644
--- a/themes/bootstrap/templates/RecordDriver/SolrDefault/core.phtml
+++ b/themes/bootstrap/templates/RecordDriver/SolrDefault/core.phtml
@@ -20,6 +20,10 @@
       <? endif; ?>
     </div>
 
+    <? // if you have a preview tab but want to move or remove the preview link
+       // from this area of the record view, this can be split into
+       // getPreviewData() (should stay here) and
+       // getPreviewLink() (can go in your desired tab) ?>
     <?=$this->record($this->driver)->getPreviews()?>
   </div>
 
diff --git a/themes/bootstrap/templates/RecordTab/preview.phtml b/themes/bootstrap/templates/RecordTab/preview.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..36d3301a1bd0d6dd4d4db875ccfc511260646404
--- /dev/null
+++ b/themes/bootstrap/templates/RecordTab/preview.phtml
@@ -0,0 +1,10 @@
+<?
+    // Set page title.
+    $this->headTitle($this->translate('Preview') . ': ' . $this->driver->getBreadcrumb());
+
+    // load the embedded preview javascript file
+    $this->headScript()->appendFile('https://www.google.com/jsapi');
+    $this->headScript()->appendFile('embedGBS.js');
+?>
+<div id="gbsViewer" ></div>
+
diff --git a/themes/bootstrap/templates/record/view.phtml b/themes/bootstrap/templates/record/view.phtml
index fb851a904ca74d462ba0fd02f246b8aa76c4dfd4..3ab11cb53037ff644951cbc04d00e5a7796defeb 100644
--- a/themes/bootstrap/templates/record/view.phtml
+++ b/themes/bootstrap/templates/record/view.phtml
@@ -55,8 +55,11 @@
           $this->layout()->breadcrumbs .= '<li class="active"><span class="divider">&gt;</span>' . $this->transEsc($desc) . '</li>';
           $activeTabObj = $obj;
         }
+        $tab_classes = array();
+        if ($isCurrent) $tab_classes[] = 'active';
+        if (!$obj->isVisible()) $tab_classes[] = 'hidden';
       ?>
-      <li<?=$isCurrent ? ' class="active"' : ''?>>
+      <li<?=count($tab_classes) > 0 ? ' class="' . implode(' ', $tab_classes) . '"' : ''?>>
         <a id="<?=$tab ?>" href="<?=$this->recordLink()->getTabUrl($this->driver, $tab)?>#tabnav"><?=$this->transEsc($desc)?></a>
       </li>
       <? endforeach; ?>
diff --git a/themes/bootstrap3/css/bootstrap-custom.css b/themes/bootstrap3/css/bootstrap-custom.css
index 60bf359fd168721998f8cc6c3010b23762f55b67..30551c1cafa21c1a51159cdf5cd59b9c11aa2b29 100644
--- a/themes/bootstrap3/css/bootstrap-custom.css
+++ b/themes/bootstrap3/css/bootstrap-custom.css
@@ -178,4 +178,9 @@ label.list-group-item {
 .table {
   word-wrap: break-word;
   table-layout: fixed;
-}
\ No newline at end of file
+}
+#gbsViewer {
+  margin:25px 0px 50px 25px;
+  width: 90%;
+  height: 600px;
+}
diff --git a/themes/bootstrap3/js/embedGBS.js b/themes/bootstrap3/js/embedGBS.js
new file mode 100644
index 0000000000000000000000000000000000000000..a6dfa1abfcd5c06b83a9e7efd9c569275e0023a3
--- /dev/null
+++ b/themes/bootstrap3/js/embedGBS.js
@@ -0,0 +1,12 @@
+// we don't need to wait for dom ready since lang is in the dom root
+var lang = document.documentElement.getAttribute('lang');
+google.load("books", "0", {"language":lang});
+
+function initialize() {
+  var bibkeys = getBibKeyString().split(/\s+/);
+  var viewer = new google.books.DefaultViewer(document.getElementById('gbsViewer'));
+  viewer.load(bibkeys);
+}
+
+google.setOnLoadCallback(initialize);
+
diff --git a/themes/bootstrap3/js/preview.js b/themes/bootstrap3/js/preview.js
index 91c188b785446e2ce691a8be98d26616474dee5b..3515cbb3cdf5100b20499028c3d8efa07bc45eed 100644
--- a/themes/bootstrap3/js/preview.js
+++ b/themes/bootstrap3/js/preview.js
@@ -1,12 +1,18 @@
 // functions to get rights codes for previews
 function getHathiOptions() {
-    return $('[class*="hathiPreviewDiv"]').attr("class").split('__')[1].split(',');
+    return $('[class*="hathiPreviewSpan"]').attr("class").split('__')[1].split(',');
 }
 function getGoogleOptions() {
-    return $('[class*="googlePreviewDiv"]').attr("class").split('__')[1].split(',');
+    var opts_temp = $('[class*="googlePreviewSpan"]').attr("class").split('__')[1].split(';');
+    var options = {};
+    for (key in opts_temp) {
+        var arr = opts_temp[key].split(':');
+        options[arr[0]] = arr[1].split(',');
+    }
+    return options;
 }
 function getOLOptions() {
-    return $('[class*="olPreviewDiv"]').attr("class").split('__')[1].split(',');
+    return $('[class*="olPreviewSpan"]').attr("class").split('__')[1].split(',');
 }
 
 function getHTPreviews(skeys) {
@@ -37,10 +43,7 @@ function applyPreviewUrl($link, url) {
         .parents('a').attr('href', url);
 }
 
-function processBookInfo(booksInfo, previewClass) {
-    // assign the correct rights string depending on source
-    var viewOptions = (previewClass == 'previewGBS')
-        ? getGoogleOptions() : getOLOptions();
+function processBookInfo(booksInfo, previewClass, viewOptions) {
     for (var bibkey in booksInfo) {
         var bookInfo = booksInfo[bibkey];
         if (bookInfo) {
@@ -54,11 +57,27 @@ function processBookInfo(booksInfo, previewClass) {
 }
 
 function processGBSBookInfo(booksInfo) {
-    processBookInfo(booksInfo, 'previewGBS');
+    var viewOptions = getGoogleOptions();
+    if (viewOptions['link'] && viewOptions['link'].length > 0) {
+        processBookInfo(booksInfo, 'previewGBS', viewOptions['link']);
+    }
+    if (viewOptions['tab'] && viewOptions['tab'].length > 0) {
+        // check for "embeddable: true" in bookinfo
+        for (var bibkey in booksInfo) {
+            var bookInfo = booksInfo[bibkey];
+            if (bookInfo) {
+                if (viewOptions['tab'].indexOf(bookInfo.preview)>= 0
+                && (bookInfo.embeddable)) {
+                    // make tab visible
+                    $('ul.recordTabs li.hidden a#preview').parent().removeClass('hidden');
+                }
+            }
+        }
+    }
 }
 
 function processOLBookInfo(booksInfo) {
-    processBookInfo(booksInfo, 'previewOL');
+    processBookInfo(booksInfo, 'previewOL', getOLOptions());
 }
 
 function processHTBookInfo(booksInfo) {
@@ -116,17 +135,21 @@ function setIndexOf() {
     };
 }
 
-function getBookPreviews() {
+function getBibKeyString() {
     var skeys = '';
     $('.previewBibkeys').each(function(){
         skeys += $(this).attr('class');
     });
-    skeys = skeys.replace(/previewBibkeys/g, '').replace(/^\s+|\s+$/g, '');
+    return skeys.replace(/previewBibkeys/g, '').replace(/^\s+|\s+$/g, '');
+}
+
+function getBookPreviews() {
+    var skeys = getBibKeyString();
     var bibkeys = skeys.split(/\s+/);
     var script;
 
     // fetch Google preview if enabled
-    if ($('.previewGBS').length > 0) {
+    if ($('[class*="googlePreviewSpan"]').length > 0) {
         // checks if query string might break URI limit - if not, run as normal
         if (bibkeys.length <= 150){
             script = 'https://encrypted.google.com/books?jscmd=viewapi&bibkeys='
@@ -151,14 +174,14 @@ function getBookPreviews() {
     }
 
     // fetch OpenLibrary preview if enabled
-    if ($('.previewOL').length > 0) {
+    if ($('[class*="olPreviewSpan"]').length > 0) {
         script = 'http://openlibrary.org/api/books?bibkeys='
             + bibkeys.join(',') + '&callback=processOLBookInfo';
         $.getScript(script);
     }
 
     // fetch HathiTrust preview if enabled
-    if ($('.previewHT').length > 0) {
+    if ($('[class*="hathiPreviewSpan"]').length > 0) {
         getHTPreviews(skeys);
     }
 }
@@ -168,4 +191,4 @@ $(document).ready(function() {
         setIndexOf();
     }
     getBookPreviews();
-});
\ No newline at end of file
+});
diff --git a/themes/bootstrap3/templates/RecordDriver/AbstractBase/preview.phtml b/themes/bootstrap3/templates/RecordDriver/AbstractBase/preview.phtml
deleted file mode 100644
index a50c1ad614534d7235ee85a6b6cecea5d056c959..0000000000000000000000000000000000000000
--- a/themes/bootstrap3/templates/RecordDriver/AbstractBase/preview.phtml
+++ /dev/null
@@ -1,88 +0,0 @@
-<?
-    $previews = isset($this->config->Content->previews)
-        ? explode(',', $this->config->Content->previews) : array();
-    if (!empty($previews)) {
-        // Extract identifiers from record driver if it supports appropriate methods:
-        $isbn = is_callable(array($this->driver, 'getCleanISBN'))
-            ? $this->driver->getCleanISBN() : '';
-        $lccn = is_callable(array($this->driver, 'getLCCN'))
-            ? $this->driver->getLCCN() : '';
-        $oclc = is_callable(array($this->driver, 'getOCLC'))
-            ? $this->driver->getOCLC() : array();
-
-        // Turn identifiers into class names to communicate with jQuery logic:
-        $idClasses = array();
-        if (!empty($isbn)) {
-            $idClasses[] = 'ISBN' . $isbn;
-        }
-        if (!empty($lccn)) {
-            $idClasses[] = 'LCCN' . $lccn;
-        }
-        if (!empty($oclc)) {
-            foreach ($oclc as $oclcNum) {
-                if (!empty($oclcNum)) {
-                    $idClasses[] = 'OCLC' . $oclcNum;
-                }
-            }
-        }
-
-        // If we found at least one identifier, we can build the placeholder HTML:
-        $html = '';
-        if (!empty($idClasses)) {
-            // Convert to string:
-            $idClasses = implode(' ', $idClasses);
-
-            // Loop through configured options and build appropriate HTML:
-            foreach ($previews as $current) {
-                switch (trim(strtolower($current))) {
-                case 'google':
-                    $name = 'Google Books';
-                    $divClass = 'googlePreviewDiv';
-                    $linkClass = 'previewGBS';
-                    $icon = 'https://www.google.com/intl/en/googlebooks/images/gbs_preview_button1.png';
-                    $options = isset($this->config->Content->GoogleOptions)
-                        ? str_replace(' ', '', $this->config->Content->GoogleOptions)
-                        : "full,partial";
-                    break;
-                case 'openlibrary':
-                    $name = 'Open Library';
-                    $divClass = 'olPreviewDiv';
-                    $linkClass = 'previewOL';
-                    $icon = $this->imageLink('preview_ol.gif');
-                    $options = isset($this->config->Content->OpenLibraryOptions)
-                        ? str_replace(' ', '', $this->config->Content->OpenLibraryOptions)
-                        : "full,partial";
-                    break;
-                case 'hathitrust':
-                    $name = 'HathiTrust';
-                    $divClass = 'hathiPreviewDiv';
-                    $linkClass = 'previewHT';
-                    $icon = $this->imageLink('preview_ht.gif');
-                    $options = isset($this->config->Content->HathiRights)
-                        ? str_replace(' ', '', $this->config->Content->HathiRights)
-                        : "pd,ic-world";
-                    break;
-                default:
-                    $name = $divClass = $linkClass = $icon = $options = false;
-                    break;
-                }
-                if ($name) {
-                    $title = $this->transEsc('Preview from') . ' ' . $name;
-                    $html .= '<div class="' . $divClass . '__' . $options . '">'
-                        . '<a title="' . $title . '" class="hidden ' . $linkClass . ' ' . $idClasses . '" target="_blank">'
-                        . '<img src="' . $icon . '" alt="' . $this->transEsc('Preview') . '" />'
-                        . '</a>'
-                        . '</div>';
-                }
-            }
-
-            // If we built some HTML, we should load the supporting Javascript and
-            // add the necessary identifier code:
-            if (!empty($html)) {
-                $html .= '<span class="previewBibkeys ' . $idClasses . '"></span>';
-                $this->headScript()->appendFile("preview.js");
-                echo $html;
-            }
-        }
-    }
-?>
\ No newline at end of file
diff --git a/themes/bootstrap3/templates/RecordDriver/AbstractBase/previewdata.phtml b/themes/bootstrap3/templates/RecordDriver/AbstractBase/previewdata.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..6b9cb54ea1fd589852cfd05b738943cbd293b6b7
--- /dev/null
+++ b/themes/bootstrap3/templates/RecordDriver/AbstractBase/previewdata.phtml
@@ -0,0 +1,74 @@
+<?
+    $previews = isset($this->config->Content->previews)
+        ? explode(',', $this->config->Content->previews) : array();
+    if (!empty($previews)) {
+        $idClasses = $this->record($this->driver)->getPreviewIds();
+
+        // If we found at least one identifier, we can insert the data
+        $html = '';
+        if (!empty($idClasses)) {
+            // Convert to string:
+            $idClasses = implode(' ', $idClasses);
+
+            // Loop through configured options and build appropriate HTML:
+            foreach ($previews as $current) {
+                switch (trim(strtolower($current))) {
+                case 'google':
+                    $spanClass = 'googlePreviewSpan';
+                    // specify link vs. tab
+                    $link_options = '';
+                    if (isset($this->config->Content->GoogleOptions->link)
+                            && $this->config->Content->GoogleOptions->link) {
+                        $link_options = 'link:' . strtolower(str_replace(' ', '',
+                            $this->config->Content->GoogleOptions->link));
+                    }
+                    $tab_options = '';
+                    if (isset($this->config->Content->GoogleOptions->tab)
+                            && $this->config->Content->GoogleOptions->tab) {
+                        $tab_options = 'tab:' . strtolower(str_replace(' ', '',
+                            $this->config->Content->GoogleOptions->tab));
+                    }
+                    $options = ($link_options && $tab_options)
+                        ? "$link_options;$tab_options"
+                        : "$link_options$tab_options";
+                    // maintain previous behavior and default
+                    if (!$link_options && !$tab_options) {
+                        $options = 'link:full,partial';
+                        if (isset($this->config->Content->GoogleOptions)
+                              && is_string($this->config->Content->GoogleOptions)) {
+                            $options = 'link:' . strtolower(str_replace(' ', '',
+                                $this->config->Content->GoogleOptions));
+                        }
+                    }
+                    break;
+                case 'openlibrary':
+                    $spanClass = 'olPreviewSpan';
+                    $options = isset($this->config->Content->OpenLibraryOptions)
+                        ? str_replace(' ', '', $this->config->Content->OpenLibraryOptions)
+                        : "full,partial";
+                    break;
+                case 'hathitrust':
+                    $spanClass = 'hathiPreviewSpan';
+                    $options = isset($this->config->Content->HathiRights)
+                        ? str_replace(' ', '', $this->config->Content->HathiRights)
+                        : "pd,ic-world";
+                    break;
+                default:
+                    $spanClass = $options = false;
+                    break;
+                }
+                if ($spanClass) {
+                    $html .= '<span class="' . $spanClass . '__' . $options . '"></span>';
+                }
+            }
+
+            // If we built some HTML, we should load the supporting Javascript and
+            // add the necessary identifier code:
+            if (!empty($html)) {
+                $html .= '<span class="previewBibkeys ' . $idClasses . '"></span>';
+                $this->headScript()->appendFile("preview.js");
+                echo $html;
+            }
+        }
+    }
+?>
diff --git a/themes/bootstrap3/templates/RecordDriver/AbstractBase/previewlink.phtml b/themes/bootstrap3/templates/RecordDriver/AbstractBase/previewlink.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..a62f62391eb489d4c1e9e4fe69703fe2d32ec10c
--- /dev/null
+++ b/themes/bootstrap3/templates/RecordDriver/AbstractBase/previewlink.phtml
@@ -0,0 +1,53 @@
+<?
+    $previews = isset($this->config->Content->previews)
+        ? explode(',', $this->config->Content->previews) : array();
+    if (!empty($previews)) {
+        $idClasses = $this->record($this->driver)->getPreviewIds();
+        // If we found at least one identifier, we can build the placeholder HTML:
+        $html = '';
+        if (!empty($idClasses)) {
+            // Convert to string:
+            $idClasses = implode(' ', $idClasses);
+
+            // Loop through previews and build appropriate HTML:
+            foreach ($previews as $current) {
+                switch (trim(strtolower($current))) {
+                case 'google':
+                    $name = 'Google Books';
+                    $divClass = 'googlePreviewDiv';
+                    $linkClass = 'previewGBS';
+                    $icon = 'https://www.google.com/intl/en/googlebooks/images/gbs_preview_button1.png';
+                    break;
+                case 'openlibrary':
+                    $name = 'Open Library';
+                    $divClass = 'olPreviewDiv';
+                    $linkClass = 'previewOL';
+                    $icon = $this->imageLink('preview_ol.gif');
+                    break;
+                case 'hathitrust':
+                    $name = 'HathiTrust';
+                    $divClass = 'hathiPreviewDiv';
+                    $linkClass = 'previewHT';
+                    $icon = $this->imageLink('preview_ht.gif');
+                    break;
+                default:
+                    $name = $divClass = $linkClass = $icon = false;
+                    break;
+                }
+                if ($name) {
+                    $title = $this->transEsc('Preview from') . ' ' . $name;
+                    $html .= '<div class="' . $divClass . '">'
+                        . '<a title="' . $title . '" class="hidden ' . $linkClass . ' ' . $idClasses . '" target="_blank">'
+                        . '<img src="' . $icon . '" alt="' . $this->transEsc('Preview') . '" />'
+                        . '</a>'
+                        . '</div>';
+                }
+            }
+
+            // javascript included in previewdata template
+            if (!empty($html)) {
+                echo $html;
+            }
+        }
+    }
+?>
diff --git a/themes/bootstrap3/templates/RecordDriver/SolrDefault/core.phtml b/themes/bootstrap3/templates/RecordDriver/SolrDefault/core.phtml
index 9cc3c180f69a414405c25be74ebf0018271d49db..befb71502b16436b93cf914b97a9835c247c9386 100644
--- a/themes/bootstrap3/templates/RecordDriver/SolrDefault/core.phtml
+++ b/themes/bootstrap3/templates/RecordDriver/SolrDefault/core.phtml
@@ -20,6 +20,10 @@
       <? endif; ?>
     </div>
 
+    <? // if you have a preview tab but want to move or remove the preview link
+       // from this area of the record view, this can be split into
+       // getPreviewData() (should stay here) and
+       // getPreviewLink() (can go in your desired tab) ?>
     <?=$this->record($this->driver)->getPreviews()?>
   </div>
 
diff --git a/themes/bootstrap3/templates/RecordTab/preview.phtml b/themes/bootstrap3/templates/RecordTab/preview.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..554efc456cf62ebcaf2e61181a01afff864bbf58
--- /dev/null
+++ b/themes/bootstrap3/templates/RecordTab/preview.phtml
@@ -0,0 +1,9 @@
+<?
+    // Set page title.
+    $this->headTitle($this->translate('Preview') . ': ' . $this->driver->getBreadcrumb());
+
+    // load the embedded preview javascript file
+    $this->headScript()->appendFile('https://www.google.com/jsapi');
+    $this->headScript()->appendFile('embedGBS.js');
+?>
+<div id="gbsViewer" ></div>
diff --git a/themes/bootstrap3/templates/record/view.phtml b/themes/bootstrap3/templates/record/view.phtml
index 37bad15412ef4653d896c2faca734c1cc8830c93..b3c119736d33f913f0d2b226a51337f5009e6526 100644
--- a/themes/bootstrap3/templates/record/view.phtml
+++ b/themes/bootstrap3/templates/record/view.phtml
@@ -59,8 +59,11 @@
           $this->layout()->breadcrumbs .= '<li class="active">' . $this->transEsc($desc) . '</li>';
           $activeTabObj = $obj;
         }
+        $tab_classes = array();
+        if ($isCurrent) $tab_classes[] = 'active';
+        if (!$obj->isVisible()) $tab_classes[] = 'hidden';
       ?>
-      <li<?=$isCurrent ? ' class="active"' : ''?>>
+      <li<?=count($tab_classes) > 0 ? ' class="' . implode(' ', $tab_classes) . '"' : ''?>>
         <a id="<?=strtolower($tab) ?>" href="<?=$this->recordLink()->getTabUrl($this->driver, $tab)?>#tabnav"><?=$this->transEsc($desc)?></a>
       </li>
       <? endforeach; ?>
diff --git a/themes/jquerymobile/templates/RecordTab/preview.phtml b/themes/jquerymobile/templates/RecordTab/preview.phtml
new file mode 100644
index 0000000000000000000000000000000000000000..1d088d2ec8b3a9984fbedeb5abd8f421c493cda6
--- /dev/null
+++ b/themes/jquerymobile/templates/RecordTab/preview.phtml
@@ -0,0 +1 @@
+<!-- not supported in mobile interface -->
\ No newline at end of file
diff --git a/themes/jquerymobile/templates/record/header-navbar.phtml b/themes/jquerymobile/templates/record/header-navbar.phtml
index aecd12dddad819d83f104c7f33e93f7687a4e911..9e0f0463b259129aa84918417d53b1b3015ba23d 100644
--- a/themes/jquerymobile/templates/record/header-navbar.phtml
+++ b/themes/jquerymobile/templates/record/header-navbar.phtml
@@ -8,6 +8,10 @@
   <div data-role="navbar">
     <ul>
       <? foreach ($tabs as $tab => $obj): ?>
+        <?
+          /* Initially invisible tabs are not supported in this theme; just skip them! */
+          if (!$obj->isVisible()) continue;
+        ?>
         <li>
           <a rel="external"<?=(strtolower(isset($this->activeTab) ? $this->activeTab : '') == strtolower($tab)) ? ' class="ui-btn-active"' : ''?> href="<?=$this->recordLink()->getTabUrl($this->driver, $tab)?>"><?=$this->transEsc($obj->getDescription())?></a>
         </li>