[open-ils-commits] [GIT] Evergreen ILS branch master updated. 0af2c2526ca73ea8e50acd647f483018c2731d9b

Evergreen Git git at git.evergreen-ils.org
Wed Feb 25 12:16:57 EST 2015


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  0af2c2526ca73ea8e50acd647f483018c2731d9b (commit)
       via  6de17d3c74b274c96e077538bf0af4890ce9b679 (commit)
       via  988cfd0941bbb2582c5796fbf1d46150f177b99b (commit)
       via  d0b04cb7d863129907adb0b00df45cb24f5bb304 (commit)
       via  8b54b723dadf87b2ca058499bf1a8d832c9fa70b (commit)
       via  4829da8c5ad114f38295044efb45cf51a4ab9ab3 (commit)
       via  6924c6dc8f0057d561113e83973146559b3bf80b (commit)
       via  68c50a6d1c2d6e5efb6c3548a10851bb6cefe58e (commit)
       via  41eb6d1b678aa5e6724ca1a80675b432e0b52a03 (commit)
       via  591d4092dfd1bb8c343413af5f9ed9a3aed43f07 (commit)
       via  75b7a3b3d658376d2e4f1125a2e24617d8dd9479 (commit)
       via  5616ce84394fb142545fe92fb1a9407ca97a7d2c (commit)
       via  a9a3c4338e0e941529e424a57b0a4ba78aaedcf5 (commit)
       via  3c1a03e5fc3a2480041e52399892e4abe2efd6a5 (commit)
       via  96efbaef736cff91829ca196c2eb19cfe8c3682a (commit)
       via  d69daf1e7ac4bd41c186224fcf66a180fb088524 (commit)
       via  fd2b7b48a17613bbdfd3de240e4fd6bc54dc6e37 (commit)
       via  1b16bcf78e898166fe5603353e9a27c22afae428 (commit)
       via  d1e3c82aa48a7aef4663d0ed1139535198fee225 (commit)
       via  b787314e7a5ddf07c73903bc2ce2da3c98c4c17d (commit)
       via  c678719f0b404091cf17f533714305a1d90b36a3 (commit)
       via  520abb8d737aa0f9816f83ee0cc8c04e43553c45 (commit)
       via  cc0436de3ceffc3f265a5ffc8dcd2a5bd62a3fdb (commit)
       via  79893c1d9b43744c3e8a8ae99445753e9ccbc940 (commit)
       via  c03bbc226c2f5346f459f5513f025fb749a63f88 (commit)
       via  aa0e52b253d866e3b4400ba79eca64d1fc1403da (commit)
       via  cb44f54f562f3077249e0206da2a96af3f171810 (commit)
       via  5eaa585a6371537124988026a8332ae406eead74 (commit)
       via  444eb1599fe6fd9bd9666a29d4802a7dbe598411 (commit)
       via  eda33b0387c30e41378741d0e696bb593630364a (commit)
       via  50ac8524e2fd5e326f3a942cc672425a088aea6d (commit)
       via  ec079a18611e8ccbea55bbb79dced247415e7a9b (commit)
       via  d8c3b3e30c8a3c34088e443b096bb2362e4f35ce (commit)
       via  c62601137eceaeca988cf2c0da24157af076e788 (commit)
       via  1c9b48f215c582c260d0d3b838cc86cf872dcecc (commit)
       via  c0ae713a572bb89d57c72645d81a8ab8aaf75d95 (commit)
       via  a89ae52d90e3598bca4e35fed896298c528f334c (commit)
       via  2df05bef0da32c34d1b6f7e79fe7b8c8a4184ed5 (commit)
       via  e659af829ee95020bcb02eccf93811cea9d708f2 (commit)
       via  18c1e4c59b4a5379b8e896eedfd5be021079fc34 (commit)
       via  7cb6a97ba64916c37c9ae036dbdb4cb99bce5d41 (commit)
       via  cd5de415d4e9124b8c8e38814e34b933ac9c54d9 (commit)
       via  757c6c6c4f11efb499dc2c294eeef409523f5e77 (commit)
       via  3e001d054b60d3082b03931f25905f221bc949f9 (commit)
       via  aafdbdfd4e3ab8f7c41a6a3e1cf7f3a399facecc (commit)
       via  f17ec353a236ae0880defc0ca4cd0625bd2246aa (commit)
       via  51c1061a6271bd746732760ce1eb2162666049dc (commit)
       via  54c3f600fc98bfd1a703e4ab92a139322c158f59 (commit)
       via  b640e16f63bb7cc93696373af9b48c0e1ae3139b (commit)
       via  30f4c7b5032885c622eaf7719a50b2541e6556b7 (commit)
       via  a171d5984e3b05ad9642188dea3eaff7727f733d (commit)
       via  567982c3b7a03d4a8ffed692b4032d7dfc720b95 (commit)
       via  a6b1295c0a505fcc9b6c68acbb441531b8c4f2db (commit)
       via  a304398ea54448ac5c1a5419776d65b3fe9a8bb2 (commit)
       via  9e09624986a8ede16d8591b066f215fef6de2ec9 (commit)
       via  7081631586ae6ad3df5e62a5e151cfdf15130fc5 (commit)
       via  d9cce1c07e2a46afbe4ea3b9ddeb233efb9a047c (commit)
       via  b3f9abee16e07d87a78dbd3850477697030f03e6 (commit)
       via  cde17e137d1084475fc1a5756348dc49bc47624b (commit)
       via  bdb574f5e4dd7244bbc27aa6fee11dee240d79c8 (commit)
       via  72830baa57aa4e6a81b5b96a545fa95b661b3352 (commit)
       via  4ccbf980e1f2eadf9f99e015e7ac3d4765d046f2 (commit)
       via  afa3b5f9a08bb0fa8178bae4f0f3396f98d72d98 (commit)
       via  ca7710651c2d89078c961804db76a464c8b33cb6 (commit)
       via  b149ca10bf198aa3fd00546e3f43a9a8b25a9138 (commit)
       via  91e08b415b5d19aaac56c2ba0fea427654cbf346 (commit)
       via  ad7e1b91f8c433dd737da404d8ba1f24183ac32b (commit)
       via  9a0705241adb554fd33788056b6395ced99b5144 (commit)
       via  17b0f48cf1ec49049bdb6762448040c967e235bf (commit)
       via  06e505f2fee20e1b99c5087368a6f5c03022c1f2 (commit)
       via  8fe98678d6e34e7b415148bb6fd5494d01a9bf6d (commit)
       via  4cec7e69222e96c390fdb393be4a88793fd73605 (commit)
       via  0ad7e3945d90cf0ab0a919e82ae49e96d477467e (commit)
       via  258b8e00578cd93fc7a4f9d5555f8167f96acd7c (commit)
       via  191c1426e8ac03cf00b02e995be51b32e313a665 (commit)
       via  46c3f0be689d3aad8c42b8bd68b6b3fd17d517d9 (commit)
       via  e7c26bb5e792d8004064f69288743abd9cc556d9 (commit)
       via  4025380f9e8121364d6661d75a6c229272c431a2 (commit)
       via  c24efb21fc8ffadba2154a90c05c6cffdc737a59 (commit)
       via  3b87bd33ce8eeae7ea75d37cfe695839e7778967 (commit)
       via  601f011a113d14faaeac7d0f33201c61fc7348ae (commit)
       via  71d31a63ab15580192a52b29d2a35aadfa8235e2 (commit)
      from  8bd8a1c212efe04ee9d32687e8cee092859e18fd (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 0af2c2526ca73ea8e50acd647f483018c2731d9b
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Feb 20 11:59:23 2015 -0500

    LP#1402797 Open item status in a new tab
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/opac/parts/record/copy_table.tt2 b/Open-ILS/src/templates/opac/parts/record/copy_table.tt2
index 1c09536..eeb8643 100644
--- a/Open-ILS/src/templates/opac/parts/record/copy_table.tt2
+++ b/Open-ILS/src/templates/opac/parts/record/copy_table.tt2
@@ -106,7 +106,7 @@ END; # FOREACH bib
                 [% copy_info.barcode | html -%]
                 [% IF ctx.is_staff %]
                   [%- IF ctx.is_browser_staff %]
-                    <a target="_top" href="[% ctx.base_path %]/staff/cat/item/[% copy_info.id %]">[% l('view') %]</a>
+                    <a target="_blank" href="[% ctx.base_path %]/staff/cat/item/[% copy_info.id %]">[% l('view') %]</a>
                     [% IF ctx.has_perm('UPDATE_COPY', copy_info.circ_lib) 
                         OR ctx.has_perm('UPDATE_COPY', copy_info.call_number_owning_lib) %]
                         <span> | </span>

commit 6de17d3c74b274c96e077538bf0af4890ce9b679
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Feb 20 10:59:17 2015 -0500

    LP#1402797 Allow (and use) a default cancel cause for holds by passing around a scalar instead of an object
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/share/t_cancel_hold_dialog.tt2 b/Open-ILS/src/templates/staff/circ/share/t_cancel_hold_dialog.tt2
index 81417f2..ed56d77 100644
--- a/Open-ILS/src/templates/staff/circ/share/t_cancel_hold_dialog.tt2
+++ b/Open-ILS/src/templates/staff/circ/share/t_cancel_hold_dialog.tt2
@@ -15,7 +15,7 @@
         <div class="col-md-8">
           <select class="form-control" id="hold-cancel-reason"
             ng-model="args.cancel_reason"
-            ng-options="reason.label() for reason in args.cancel_reasons">
+            ng-options="reason.id() as reason.label() for reason in args.cancel_reasons">
           </select>
         </div>
       </div>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
index e3e6206..5c0ac9d 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
@@ -85,7 +85,7 @@ function($modal , $q , egCore , egConfirmDialog , egAlertDialog) {
                             egCore.net.request(
                                 'open-ils.circ', 'open-ils.circ.hold.cancel',
                                 egCore.auth.token(), hold_id,
-                                $scope.args.cancel_reason.id(), 
+                                $scope.args.cancel_reason,
                                 $scope.args.note
                             ).then(function(resp) {
                                 if (evt = egCore.evt.parse(resp)) {

commit 988cfd0941bbb2582c5796fbf1d46150f177b99b
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Feb 19 15:54:38 2015 -0500

    LP#1402797 Do not allow workstations as org units that cannot have user
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2
index 3a14bbe..8ceb795 100644
--- a/Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/workstation/t_splash.tt2
@@ -75,6 +75,7 @@
           <eg-org-selector 
             selected="contextOrg"
             hidden-test="wsOrgHidden">
+            disable-test="cant_have_users">
           </eg-org-selector>
         </div>
         <input type='text' class='form-control'  
diff --git a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
index 51a4240..ae771ec 100644
--- a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
+++ b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js
@@ -85,6 +85,8 @@ function($scope , $window , $location , egCore , egConfirmDialog) {
         .then(function() { $scope.defaultWS = $scope.selectedWS });
     }
 
+    $scope.cant_have_users = function (id) { return !egCore.org.CanHaveUsers(id); };
+
     // redirect the user to the login page using the current
     // workstation as the workstation URL param
     $scope.useWS = function() {

commit d0b04cb7d863129907adb0b00df45cb24f5bb304
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Feb 17 17:14:23 2015 -0500

    LP#1402797 Test value directly, and invert test for the a-disabled test
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/share/t_hold_edit_pickup_lib.tt2 b/Open-ILS/src/templates/staff/circ/share/t_hold_edit_pickup_lib.tt2
index b70b9ce..513bfa1 100644
--- a/Open-ILS/src/templates/staff/circ/share/t_hold_edit_pickup_lib.tt2
+++ b/Open-ILS/src/templates/staff/circ/share/t_hold_edit_pickup_lib.tt2
@@ -10,7 +10,7 @@
     <div class="form-group">
       <div class="col-md-4">[% l('Select Library:') %]</div>
       <div class="col-md-8">
-        <eg-org-selector disable-test="can_be_pickup" selected="args.org_unit"></eg-org-selector>
+        <eg-org-selector disable-test="cant_be_pickup" selected="args.org_unit"></eg-org-selector>
       </div>
     </div>
   </div>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
index af255b2..e3e6206 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
@@ -198,7 +198,7 @@ function($modal , $q , egCore , egConfirmDialog , egAlertDialog) {
             controller : 
                 ['$scope', '$modalInstance',
                 function($scope, $modalInstance) {
-                    $scope.can_be_pickup = egCore.org.CanHaveUsers;
+                    $scope.cant_be_pickup = function (id) { return !egCore.org.CanHaveUsers(id); };
                     $scope.args = {};
                     $scope.ok = function() { 
                         var vals = hold_ids.map(function(hold_id) {
diff --git a/Open-ILS/web/js/ui/default/staff/services/org.js b/Open-ILS/web/js/ui/default/staff/services/org.js
index dd89d82..dcf1d52 100644
--- a/Open-ILS/web/js/ui/default/staff/services/org.js
+++ b/Open-ILS/web/js/ui/default/staff/services/org.js
@@ -43,18 +43,18 @@ function($q,  egEnv,  egAuth,  egNet) {
 
     // tests that a node can have users
     service.CanHaveUsers = function(node_or_id) {
-        return Boolean(service
+	return service
             .get(node_or_id)
             .ou_type()
-            .can_have_users());
+            .can_have_users() == 't';
     }
 
     // tests that a node can have volumes
     service.CanHaveVolumes = function(node_or_id) {
-        return Boolean(service
+        return service
             .get(node_or_id)
             .ou_type()
-            .can_have_vols());
+            .can_have_vols() == 't';
     }
 
     // list of org_unit objects  or IDs for me + descendants

commit 8b54b723dadf87b2ca058499bf1a8d832c9fa70b
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Feb 17 17:13:32 2015 -0500

    LP#1402797 Move aDisabled from patron/app to services/ui so it can be used everywhere
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
index 70247fd..f80486f 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
@@ -1566,28 +1566,3 @@ function($scope , $routeParams , $window , $location , egCore) {
     $scope.user_perms_url = url;
 }])
 
-.directive('aDisabled', function() {
-    return {
-        compile: function(tElement, tAttrs, transclude) {
-            //Disable ngClick
-            tAttrs["ngClick"] = ("ng-click", "!("+tAttrs["aDisabled"]+") && ("+tAttrs["ngClick"]+")");
-
-            //Toggle "disabled" to class when aDisabled becomes true
-            return function (scope, iElement, iAttrs) {
-                scope.$watch(iAttrs["aDisabled"], function(newValue) {
-                    if (newValue !== undefined) {
-                        iElement.toggleClass("disabled", newValue);
-                    }
-                });
-
-                //Disable href on click
-                iElement.on("click", function(e) {
-                    if (scope.$eval(iAttrs["aDisabled"])) {
-                        e.preventDefault();
-                    }
-                });
-            };
-        }
-    };
-})
-
diff --git a/Open-ILS/web/js/ui/default/staff/services/ui.js b/Open-ILS/web/js/ui/default/staff/services/ui.js
index ca79115..25312c6 100644
--- a/Open-ILS/web/js/ui/default/staff/services/ui.js
+++ b/Open-ILS/web/js/ui/default/staff/services/ui.js
@@ -181,6 +181,31 @@ function($modal, $interpolate) {
     return service;
 }])
 
+.directive('aDisabled', function() {
+    return {
+        restrict : 'A',
+        compile: function(tElement, tAttrs, transclude) {
+            //Disable ngClick
+            tAttrs["ngClick"] = ("ng-click", "!("+tAttrs["aDisabled"]+") && ("+tAttrs["ngClick"]+")");
+
+            //Toggle "disabled" to class when aDisabled becomes true
+            return function (scope, iElement, iAttrs) {
+                scope.$watch(iAttrs["aDisabled"], function(newValue) {
+                    if (newValue !== undefined) {
+                        iElement.toggleClass("disabled", newValue);
+                    }
+                });
+
+                //Disable href on click
+                iElement.on("click", function(e) {
+                    if (scope.$eval(iAttrs["aDisabled"])) {
+                        e.preventDefault();
+                    }
+                });
+            };
+        }
+    };
+})
 
 /**
  * Nested org unit selector modeled as a Bootstrap dropdown button.
@@ -196,12 +221,12 @@ function($modal, $interpolate) {
             // Each org unit is passed into this function and, for
             // any org units where the response value is true, the
             // org unit will not be added to the selector.
-            hiddenTest : '&',
+            hiddenTest : '=',
 
             // Each org unit is passed into this function and, for
             // any org units where the response value is true, the
             // org unit will not be available for selection.
-            disableTest : '&',
+            disableTest : '=',
 
             // Caller can either $watch(selected, ..) or register an
             // onchange handler.
@@ -220,7 +245,7 @@ function($modal, $interpolate) {
            + '</button>'
            + '<ul class="dropdown-menu">'
              + '<li ng-repeat="org in orgList" ng-hide="hiddenTest(org.id)">'
-               + '<a href ng-click="orgChanged(org)" ng-disabled="disableTest(org.id)" '
+               + '<a href ng-click="orgChanged(org)" a-disabled="disableTest(org.id)" '
                  + 'style="padding-left: {{org.depth * 10 + 5}}px">'
                  + '{{org.shortname}}'
                + '</a>'

commit 4829da8c5ad114f38295044efb45cf51a4ab9ab3
Author: Mike Rylander <mrylander at gmail.com>
Date:   Sun Feb 1 17:05:29 2015 -0500

    LP#1402797 Attempt to implement disabling tests for orgs in the magic dropdown
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/share/t_hold_edit_pickup_lib.tt2 b/Open-ILS/src/templates/staff/circ/share/t_hold_edit_pickup_lib.tt2
index 895cda0..b70b9ce 100644
--- a/Open-ILS/src/templates/staff/circ/share/t_hold_edit_pickup_lib.tt2
+++ b/Open-ILS/src/templates/staff/circ/share/t_hold_edit_pickup_lib.tt2
@@ -10,7 +10,7 @@
     <div class="form-group">
       <div class="col-md-4">[% l('Select Library:') %]</div>
       <div class="col-md-8">
-        <eg-org-selector selected="args.org_unit"></eg-org-selector>
+        <eg-org-selector disable-test="can_be_pickup" selected="args.org_unit"></eg-org-selector>
       </div>
     </div>
   </div>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
index 1ea2dc5..af255b2 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
@@ -198,7 +198,8 @@ function($modal , $q , egCore , egConfirmDialog , egAlertDialog) {
             controller : 
                 ['$scope', '$modalInstance',
                 function($scope, $modalInstance) {
-                    $scope.args = {}
+                    $scope.can_be_pickup = egCore.org.CanHaveUsers;
+                    $scope.args = {};
                     $scope.ok = function() { 
                         var vals = hold_ids.map(function(hold_id) {
                             return {
diff --git a/Open-ILS/web/js/ui/default/staff/services/org.js b/Open-ILS/web/js/ui/default/staff/services/org.js
index 8ff7137..dd89d82 100644
--- a/Open-ILS/web/js/ui/default/staff/services/org.js
+++ b/Open-ILS/web/js/ui/default/staff/services/org.js
@@ -41,6 +41,22 @@ function($q,  egEnv,  egAuth,  egNet) {
         return nodes;
     };
 
+    // tests that a node can have users
+    service.CanHaveUsers = function(node_or_id) {
+        return Boolean(service
+            .get(node_or_id)
+            .ou_type()
+            .can_have_users());
+    }
+
+    // tests that a node can have volumes
+    service.CanHaveVolumes = function(node_or_id) {
+        return Boolean(service
+            .get(node_or_id)
+            .ou_type()
+            .can_have_vols());
+    }
+
     // list of org_unit objects  or IDs for me + descendants
     service.descendants = function(node_or_id, as_id) {
         var node = service.get(node_or_id);
diff --git a/Open-ILS/web/js/ui/default/staff/services/ui.js b/Open-ILS/web/js/ui/default/staff/services/ui.js
index 35e6887..ca79115 100644
--- a/Open-ILS/web/js/ui/default/staff/services/ui.js
+++ b/Open-ILS/web/js/ui/default/staff/services/ui.js
@@ -196,7 +196,12 @@ function($modal, $interpolate) {
             // Each org unit is passed into this function and, for
             // any org units where the response value is true, the
             // org unit will not be added to the selector.
-            hiddenTest : '=',
+            hiddenTest : '&',
+
+            // Each org unit is passed into this function and, for
+            // any org units where the response value is true, the
+            // org unit will not be available for selection.
+            disableTest : '&',
 
             // Caller can either $watch(selected, ..) or register an
             // onchange handler.
@@ -215,7 +220,7 @@ function($modal, $interpolate) {
            + '</button>'
            + '<ul class="dropdown-menu">'
              + '<li ng-repeat="org in orgList" ng-hide="hiddenTest(org.id)">'
-               + '<a href ng-click="orgChanged(org)"'
+               + '<a href ng-click="orgChanged(org)" ng-disabled="disableTest(org.id)" '
                  + 'style="padding-left: {{org.depth * 10 + 5}}px">'
                  + '{{org.shortname}}'
                + '</a>'

commit 6924c6dc8f0057d561113e83973146559b3bf80b
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Feb 17 16:25:30 2015 -0500

    LP#1402797 clean up action rows and styling
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2 b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
index 1d4dbba..958e823 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
@@ -1,17 +1,12 @@
 <div>
-  <div ng-if="bre">
-    <div class="row col-md-12 pad-vert marcfastitemadd">
-        <input id="mfiacn" type="text" placeholder="[% l('Call Number:') %]" ng-model="fast_item_callnumber"/>
-        <input id="mfiabc" type="text" placeholder="[% l('Barcode') %]" ng-model="fast_item_barcode"/>
-        <button class="btn btn-default" ng-click="saveFastItem()">Add Item</button>
-      </div>
-    </div>
+  <div ng-if="bre" class="row col-md-12 pad-vert marcfastitemadd">
+    <input id="mfiacn" type="text" placeholder="[% l('Call Number:') %]" ng-model="fast_item_callnumber"/>
+    <input id="mfiabc" type="text" placeholder="[% l('Barcode') %]" ng-model="fast_item_barcode"/>
+    <button class="btn btn-default" ng-click="saveFastItem()">Add Item</button>
   </div>
-  <div class="marcffeditor">
-    <div class="row col-md-12 pad-vert marctypesource">
-      <div class="col-md-2"><label>Record Type:</label> {{calculated_record_type}}</div>
-      <div ng-if="bre" class="col-md-2"><eg-marc-edit-bibsource/></div>
-    </div>
+  <div class="marcffeditor pad-vert row col-md-12 marctypesource">
+    <div class="col-md-2"><label>Record Type:</label> {{calculated_record_type}}</div>
+    <div ng-if="bre" class="col-md-2"><eg-marc-edit-bibsource/></div>
   </div>
   <div class="marcrecord pad-vert">
     <div>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
index f6f2e05..46a658b 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
@@ -161,7 +161,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     '/></span>'+
                   '</span>',
         scope: { field: "=", subfield: "=", onKeydown: '=' },
-        replace: false
+        replace: true
     }
 })
 
@@ -179,7 +179,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                       'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}i{{indNumber}}"'+
                       '/></span>',
         scope: { ind : '=', field: '=', onKeydown: '=', indNumber: '@' },
-        replace: false,
+        replace: true
     }
 })
 
@@ -197,7 +197,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                       'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}tag"'+
                       '/></span>',
         scope: { tag : '=', field: '=', onKeydown: '=' },
-        replace: false
+        replace: true
     }
 })
 
@@ -211,7 +211,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     '<span><eg-marc-edit-ind field="field" ind="field.ind2" on-keydown="onKeydown" ind-number="2"/></span>'+
                     '<span><eg-marc-edit-subfield ng-repeat="subfield in field.subfields" subfield="subfield" field="field" on-keydown="onKeydown"/></span>'+
                   '</div>',
-        scope: { field: "=", onKeydown: '=' }
+        scope: { field: "=", onKeydown: '=' },
+        replace: true
     }
 })
 
@@ -269,7 +270,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
     return {
         templateUrl : './cat/share/t_marcedit',
         restrict: 'E',
-        replace: false,
+        replace: true,
         scope: { recordId : '=', recordType : '@', maxUndo : '@' },
         link: function (scope, element, attrs) {
 
@@ -786,7 +787,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 .directive("egMarcEditBibsource", ['$timeout',function ($timeout) {
     return {
         restrict: 'E',
-        replace: false,
+        replace: true,
         template: '<span class="nullable">'+
                     '<select class="form-control" ng-model="bib_source" ng-options="s.id() as s.source() for s in bib_sources">'+
                       '<option value="">Select a Source</option>'+

commit 68c50a6d1c2d6e5efb6c3548a10851bb6cefe58e
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Feb 17 13:30:57 2015 -0500

    LP#1402797 Add (stub) fast item add, and (working) bib source selection
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2 b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
new file mode 100644
index 0000000..1d4dbba
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
@@ -0,0 +1,37 @@
+<div>
+  <div ng-if="bre">
+    <div class="row col-md-12 pad-vert marcfastitemadd">
+        <input id="mfiacn" type="text" placeholder="[% l('Call Number:') %]" ng-model="fast_item_callnumber"/>
+        <input id="mfiabc" type="text" placeholder="[% l('Barcode') %]" ng-model="fast_item_barcode"/>
+        <button class="btn btn-default" ng-click="saveFastItem()">Add Item</button>
+      </div>
+    </div>
+  </div>
+  <div class="marcffeditor">
+    <div class="row col-md-12 pad-vert marctypesource">
+      <div class="col-md-2"><label>Record Type:</label> {{calculated_record_type}}</div>
+      <div ng-if="bre" class="col-md-2"><eg-marc-edit-bibsource/></div>
+    </div>
+  </div>
+  <div class="marcrecord pad-vert">
+    <div>
+      <eg-marc-edit-leader record="record" on-keydown="onKeydown"/>
+    </div>
+    <div>
+      <eg-marc-edit-controlfield 
+        ng-repeat="field in controlfields" 
+        field="field" on-keydown="onKeydown"
+        id="r{{field.record.subfield('901','c')[1]}}f{{field.position}}"
+      />
+    </div>
+    <div>
+      <eg-marc-edit-datafield 
+        ng-repeat="field in datafields" 
+        field="field" on-keydown="onKeydown" 
+        id="r{{field.record.subfield('901','c')[1]}}f{{field.position}}"
+      />
+    </div>
+  </div>
+  <button class="btn btn-default" ng-click="saveRecord()">Save</button>
+  <button class="btn btn-default" ng-click="seeBreaker()">Breaker</button>
+</div>
diff --git a/Open-ILS/src/templates/staff/css/cat.css.tt2 b/Open-ILS/src/templates/staff/css/cat.css.tt2
index 2d653a8..ba4f41a 100644
--- a/Open-ILS/src/templates/staff/css/cat.css.tt2
+++ b/Open-ILS/src/templates/staff/css/cat.css.tt2
@@ -1,5 +1,9 @@
 .marcrecord {
-    background-color: lightgrey;
+    //background-color: lightgrey;
+}
+
+.marcfastitemadd, .marctypesource {
+    border-bottom: solid thin gray;
 }
 
 .marcedit {
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
index 000587c..f6f2e05 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
@@ -267,23 +267,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 /// TODO: fixed field editor and such
 .directive("egMarcEditRecord", function () {
     return {
-        template: '<form ng-submit="saveRecord()">'+
-                  '<div class="marcrecord">'+
-                    '<div><eg-marc-edit-leader record="record" on-keydown="onKeydown"/></div>'+
-                    '<div><eg-marc-edit-controlfield '+
-                        'ng-repeat="field in controlfields" '+
-                        'field="field" on-keydown="onKeydown"'+
-                        'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}"'+
-                        '/></div>'+
-                    '<div><eg-marc-edit-datafield '+
-                        'ng-repeat="field in datafields" '+
-                        'field="field" on-keydown="onKeydown" '+
-                        'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}"'+
-                        '/></div>'+
-                  '</div>'+
-                  '<button class="btn btn-default" type="submit">Save</button>'+
-                  '</form>'+
-                  '<button class="btn btn-default" ng-click="seeBreaker()">Breaker</button>',
+        templateUrl : './cat/share/t_marcedit',
         restrict: 'E',
         replace: false,
         scope: { recordId : '=', recordType : '@', maxUndo : '@' },
@@ -304,6 +288,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         controller : ['$timeout','$scope','egCore',
             function ( $timeout , $scope , egCore ) {
 
+                $scope.bib_source = null;
                 $scope.record_type = $scope.recordType || 'bre';
                 $scope.max_undo = $scope.maxUndo || 100;
                 $scope.record_undo_stack = [];
@@ -648,9 +633,15 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                         $scope.in_redo = true;
                         $scope[$scope.record_type] = rec;
                         $scope.record = new MARC.Record({ marcxml : $scope[$scope.record_type].marc() });
+                        $scope.calculated_record_type = $scope.record.recordType();
                         $scope.controlfields = $scope.record.fields.filter(function(f){ return f.isControlfield() });
                         $scope.datafields = $scope.record.fields.filter(function(f){ return !f.isControlfield() });
                         $scope.save_stack_depth = $scope.record_undo_stack.length;
+
+                        if ($scope.record_type == 'bre') {
+                            $scope.bib_source = $scope[$scope.record_type].source();
+                        }
+
                     }).then(setCaret);
                 }
 
@@ -792,4 +783,32 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
     }
 })
 
+.directive("egMarcEditBibsource", ['$timeout',function ($timeout) {
+    return {
+        restrict: 'E',
+        replace: false,
+        template: '<span class="nullable">'+
+                    '<select class="form-control" ng-model="bib_source" ng-options="s.id() as s.source() for s in bib_sources">'+
+                      '<option value="">Select a Source</option>'+
+                    '</select>'+
+                  '</span>',
+        controller: ['$scope','egCore',
+            function ($scope , egCore) {
+
+                egCore.pcrud.retrieveAll('cbs', {}, {atomic : true})
+                    .then(function(list) { $scope.bib_sources = list; });
+
+                $scope.$watch('bib_source',
+                    function(newVal, oldVal) {
+                        if (newVal !== oldVal) {
+                            $scope.bre.source(newVal);
+                        }
+                    }
+                );
+
+            }
+        ]
+    }
+}])
+
 ;

commit 41eb6d1b678aa5e6724ca1a80675b432e0b52a03
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 16 16:52:59 2015 -0500

    LP#1402797 Mangle the 005 when saving, and support any record_entry type
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
index f6eab15..000587c 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
@@ -286,7 +286,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                   '<button class="btn btn-default" ng-click="seeBreaker()">Breaker</button>',
         restrict: 'E',
         replace: false,
-        scope: { recordId : '=', maxUndo : '@' },
+        scope: { recordId : '=', recordType : '@', maxUndo : '@' },
         link: function (scope, element, attrs) {
 
             element.bind('click', function(e) {;
@@ -304,6 +304,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         controller : ['$timeout','$scope','egCore',
             function ( $timeout , $scope , egCore ) {
 
+                $scope.record_type = $scope.recordType || 'bre';
                 $scope.max_undo = $scope.maxUndo || 100;
                 $scope.record_undo_stack = [];
                 $scope.record_redo_stack = [];
@@ -642,11 +643,11 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 
                 function loadRecord() {
                     return egCore.pcrud.retrieve(
-                        'bre', $scope.recordId
+                        $scope.record_type, $scope.recordId
                     ).then(function(rec) {
                         $scope.in_redo = true;
-                        $scope.bre = rec;
-                        $scope.record = new MARC.Record({ marcxml : $scope.bre.marc() });
+                        $scope[$scope.record_type] = rec;
+                        $scope.record = new MARC.Record({ marcxml : $scope[$scope.record_type].marc() });
                         $scope.controlfields = $scope.record.fields.filter(function(f){ return f.isControlfield() });
                         $scope.datafields = $scope.record.fields.filter(function(f){ return !f.isControlfield() });
                         $scope.save_stack_depth = $scope.record_undo_stack.length;
@@ -730,9 +731,10 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                 };
 
                 $scope.saveRecord = function () {
-                    $scope.bre.marc($scope.record.toXmlString());
+                    $scope.mangle_005();
+                    $scope[$scope.record_type].marc($scope.record.toXmlString());
                     return egCore.pcrud.update(
-                        $scope.bre
+                        $scope[$scope.record_type]
                     ).then(loadRecord);
                 };
 
@@ -751,6 +753,40 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                 if ($scope.recordId)
                     loadRecord();
 
+                $scope.mangle_005 = function () {
+                    var now = new Date();
+                    var y = now.getUTCFullYear();
+                
+                    var m = now.getUTCMonth() + 1;
+                    if (m < 10) m = '0' + m;
+                
+                    var d = now.getUTCDate();
+                    if (d < 10) d = '0' + d;
+                
+                    var H = now.getUTCHours();
+                    if (H < 10) H = '0' + H;
+                
+                    var M = now.getUTCMinutes();
+                    if (M < 10) M = '0' + M;
+                
+                    var S = now.getUTCSeconds();
+                    if (S < 10) S = '0' + S;
+                
+                    var stamp = '' + y + m + d + H + M + S + '.0';
+                    var f = $scope.record.field('005',true)[0];
+                    if (f) {
+                        f.data = stamp;
+                    } else {
+                        $scope.record.insertOrderedFields(
+                            new MARC.Field({
+                                tag : '005',
+                                data: stamp
+                            })
+                        );
+                    }
+                
+                }
+
             }
         ]          
     }

commit 591d4092dfd1bb8c343413af5f9ed9a3aed43f07
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 16 16:12:24 2015 -0500

    LP#1402797 handle up/down arrow (navigate) and ctrl + up/down (duplicate field)
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
index a127aa6..f6eab15 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
@@ -501,6 +501,111 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 
                         event_return = false;
 
+                    } else if (event.keyCode == 38) {
+                        if (event.ctrlKey) { // copy the field up
+                            var index_field = event.data.scope.field.position;
+
+                            var field_obj;
+                            if (event.data.scope.field.isControlfield()) {
+                                field_obj = new MARC.Field({
+                                    tag : event.data.scope.field.tag,
+                                    data : event.data.scope.field.data
+                                });
+                            } else {
+                                var sf_clone = [];
+                                for (var i in event.data.scope.field.subfields) {
+                                    sf_clone.push(event.data.scope.field.subfields[i].slice());
+                                }
+                                field_obj = new MARC.Field({
+                                    tag : event.data.scope.field.tag,
+                                    ind1 : event.data.scope.field.ind1,
+                                    ind2 : event.data.scope.field.ind2,
+                                    subfields : sf_clone
+                                });
+                            }
+
+
+                            event.data.scope.field.record.insertFieldsBefore(
+                                event.data.scope.field,
+                                field_obj
+                            );
+
+                            $scope.current_event_target = 'r' + $scope.recordId +
+                                                          'f' + index_field + 'tag';
+
+                            $scope.current_event_target_cursor_pos = 0;
+                            $scope.current_event_target_cursor_pos_end = 3;
+                            $scope.force_render = true;
+
+                            $timeout(function(){$scope.$digest()}).then(setCaret);
+
+                        } else { // jump to prev field
+                            if (event.data.scope.field.position > 0) {
+                                $timeout(function(){
+                                    $scope.current_event_target_cursor_pos = 0;
+                                    $scope.current_event_target_cursor_pos_end = 0;
+                                    $scope.current_event_target = 'r' + $scope.recordId +
+                                                                  'f' + (event.data.scope.field.position - 1) +
+                                                                  'tag';
+                                }).then(setCaret);
+                            }
+                        }
+
+                        event_return = false;
+
+                    } else if (event.keyCode == 40) { // down arrow...
+                        if (event.ctrlKey) { // copy the field down
+
+                            var index_field = event.data.scope.field.position;
+                            var new_field = index_field + 1;
+
+                            var field_obj;
+                            if (event.data.scope.field.isControlfield()) {
+                                field_obj = new MARC.Field({
+                                    tag : event.data.scope.field.tag,
+                                    data : event.data.scope.field.data
+                                });
+                            } else {
+                                var sf_clone = [];
+                                for (var i in event.data.scope.field.subfields) {
+                                    sf_clone.push(event.data.scope.field.subfields[i].slice());
+                                }
+                                field_obj = new MARC.Field({
+                                    tag : event.data.scope.field.tag,
+                                    ind1 : event.data.scope.field.ind1,
+                                    ind2 : event.data.scope.field.ind2,
+                                    subfields : sf_clone
+                                });
+                            }
+
+                            event.data.scope.field.record.insertFieldsAfter(
+                                event.data.scope.field,
+                                field_obj
+                            );
+
+                            $scope.current_event_target = 'r' + $scope.recordId +
+                                                          'f' + new_field + 'tag';
+
+                            $scope.current_event_target_cursor_pos = 0;
+                            $scope.current_event_target_cursor_pos_end = 3;
+                            $scope.force_render = true;
+
+                            $timeout(function(){$scope.$digest()}).then(setCaret);
+
+                        } else { // jump to next field
+                            if (event.data.scope.field.record.fields[event.data.scope.field.position + 1]) {
+                                $timeout(function(){
+                                    $scope.current_event_target_cursor_pos = 0;
+                                    $scope.current_event_target_cursor_pos_end = 0;
+                                    $scope.current_event_target = 'r' + $scope.recordId +
+                                                                  'f' + (event.data.scope.field.position + 1) +
+                                                                  'tag';
+                                }).then(setCaret);
+                            }
+                        }
+
+                        event_return = false;
+
                     } else { // Assumes only marc editor elements have IDs that can trigger this event handler.
                         $scope.current_event_target = $(event.target).attr('id');
                         if ($scope.current_event_target) {
@@ -516,16 +621,20 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 
                 function setCaret() {
                     if ($scope.current_event_target) {
+                        console.log("Putting caret in " + $scope.current_event_target);
                         if (!$scope.current_event_target_cursor_pos_end)
                             $scope.current_event_target_cursor_pos_end = $scope.current_event_target_cursor_pos
 
                         var element = $('#'+$scope.current_event_target).get(0);
                         if (element) {
                             element.focus();
-                            element.setSelectionRange(
-                                $scope.current_event_target_cursor_pos,
-                                $scope.current_event_target_cursor_pos_end
-                            );
+                            if (element.setSelectionRange) {
+                                element.setSelectionRange(
+                                    $scope.current_event_target_cursor_pos,
+                                    $scope.current_event_target_cursor_pos_end
+                                );
+                            }
+                            $scope.current_event_cursor_pos_end = null;
                             $scope.current_event_target = null;
                         }
                     }

commit 75b7a3b3d658376d2e4f1125a2e24617d8dd9479
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 16 16:11:54 2015 -0500

    LP#1402797 canonicalize field.postion after inserting or removing fields
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/marcrecord.js b/Open-ILS/web/js/ui/default/staff/marcrecord.js
index 6041ddb..1d97be3 100644
--- a/Open-ILS/web/js/ui/default/staff/marcrecord.js
+++ b/Open-ILS/web/js/ui/default/staff/marcrecord.js
@@ -205,13 +205,10 @@ var MARC = {
                           record : me,
                           tag    : cf.attr('tag'),
                           data   : cf.text(),
-                          position: ind
                     })
                 )
             });
 
-            var cfield_count = me.fields.length + 1;
-
             $('datafield', mxml).each(function (ind) {
                 var df=$(this);
                 me.fields.push(
@@ -220,7 +217,6 @@ var MARC = {
                         tag       : df.attr('tag'),
                         ind1      : df.attr('ind1'),
                         ind2      : df.attr('ind2'),
-                        position  : ind + cfield_count,
                         subfields : $('subfield', df).map(
                             function (i, sf) {
                                 return [[ $(sf).attr('code'), $(sf).text(), i ]];
@@ -229,6 +225,11 @@ var MARC = {
                     })
                 )
             });
+
+            for (var j = 0; j < this.fields.length; j++) {
+                this.fields[j].position = j;
+            }
+
             me.ready = true;
 
         },
@@ -297,7 +298,6 @@ var MARC = {
                                 record : me,
                                 tag    : line_tag(current_line),
                                 data   : cf_line_data(current_line).replace('\\',' ','g'),
-                                position: ind
                             })
                         );
                     }
@@ -317,7 +317,6 @@ var MARC = {
                                 tag       : line_tag(current_line),
                                 ind1      : df_ind1(current_line),
                                 ind2      : df_ind2(current_line),
-                                position  : ind,
                                 subfields : sf_list.map( function (sf, i) {
                                                 var sf_data = sf.substring(1);
                                                 if (me.delimiter == '$') sf_data = sf_data.replace(/\{dollar\}/g, '$');
@@ -328,6 +327,10 @@ var MARC = {
                 }
             });
 
+            for (var j = 0; j < this.fields.length; j++) {
+                this.fields[j].position = j;
+            }
+
             me.ready = true;
             return this;
         },

commit 5616ce84394fb142545fe92fb1a9407ca97a7d2c
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 16 12:08:40 2015 -0500

    LP#1402797 insert 00[678], building 008 from record data
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
index 20a793d..a127aa6 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
@@ -317,6 +317,14 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                 $scope.onKeydown = function (event) {
                     var event_return = true;
 
+                    console.log(
+                        'keydown: which='+event.which+
+                        ', ctrlKey='+event.ctrlKey+
+                        ', shiftKey='+event.shiftKey+
+                        ', altKey='+event.altKey+
+                        ', metaKey='+event.altKey
+                    );
+
                     if (event.which == 89 && event.ctrlKey) { // ctrl+y, redo
                         event_return = $scope.processRedo();
                     } else if (event.which == 90 && event.ctrlKey) { // ctrl+z, undo
@@ -366,6 +374,56 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 
                         event_return = false;
 
+                    } else if (event.which == 117 && event.shiftKey) { // shift + F6, insert 006
+                        event.data.scope.field.record.insertOrderedFields(
+                            new MARC.Field({
+                                tag : '006',
+                                data : '                                        '
+                            })
+                        );
+
+                        $scope.force_render = true;
+                        $timeout(function(){$scope.$digest()}).then(setCaret);
+
+                        event_return = false;
+
+                    } else if (event.which == 118 && event.shiftKey) { // shift + F7, insert 007
+                        event.data.scope.field.record.insertOrderedFields(
+                            new MARC.Field({
+                                tag : '007',
+                                data : '                                        '
+                            })
+                        );
+
+                        $scope.force_render = true;
+                        $timeout(function(){$scope.$digest()}).then(setCaret);
+
+                        event_return = false;
+
+                    } else if (event.which == 119 && event.shiftKey) { // shift + F8, insert/replace 008
+                        var new_008_data = event.data.scope.field.record.generate008();
+
+
+                        var old_008s = event.data.scope.field.record.field('008',true);
+                        old_008s.forEach(function(o) {
+                            var domnode = $('#r'+o.record.subfield('901','c')[1] + 'f' + o.position);
+                            domnode.scope().$destroy();
+                            domnode.remove();
+                            event.data.scope.field.record.deleteFields(o);
+                        });
+
+                        event.data.scope.field.record.insertOrderedFields(
+                            new MARC.Field({
+                                tag : '008',
+                                data : new_008_data
+                            })
+                        );
+
+                        $scope.force_render = true;
+                        $timeout(function(){$scope.$digest()}).then(setCaret);
+
+                        event_return = false;
+
                     } else if (event.which == 13 && event.ctrlKey) { // ctrl+enter, insert datafield
 
                         var element = $(event.target);
@@ -376,10 +434,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                         event.data.scope.field.record.insertFieldsAfter(
                             event.data.scope.field,
                             new MARC.Field({
-                                record : event.data.scope.field.record,
                                 tag : '999',
-                                subfields : [[' ','',0]],
-                                position : new_field
+                                subfields : [[' ','',0]]
                             })
                         );
 
diff --git a/Open-ILS/web/js/ui/default/staff/marcrecord.js b/Open-ILS/web/js/ui/default/staff/marcrecord.js
index 77f989b..6041ddb 100644
--- a/Open-ILS/web/js/ui/default/staff/marcrecord.js
+++ b/Open-ILS/web/js/ui/default/staff/marcrecord.js
@@ -23,30 +23,85 @@ var MARC = {
     Record : function(kwargs) {
         if (!kwargs) kwargs = {};
 
+        this.generate008 = function () {
+            var f;
+            var s;
+            var orig008 = '                                        ';
+            var now = new Date();
+            var y = now.getUTCFullYear().toString().substr(2,2);
+            var m = now.getUTCMonth() + 1;
+            if (m < 10) m = '0' + m;
+            var d = now.getUTCDate();
+            if (d < 10) d = '0' + d;
+
+
+            if (f = this.field('008',true)[0]) {
+                orig008 = f.data;
+            }
+        
+            /* lang code from 041a */
+            var lang = orig008.substr(35, 3);
+            
+            if (f = this.field('041',true)[0]) {
+                if (s = f.subfield('a',true)[0]) {
+                    if(s[1]) lang = s[1];
+                }
+            }
+        
+            /* country code from 044a */
+            var country = orig008.substr(15, 3);
+            if (f = this.field('044',true)[0]) {
+                if (s = f.subfield('a',true)[0]) {
+                    if(s[1]) country = s[1];
+                }
+            }
+            while (country.length < 3) country = country + ' ';
+            if (country.length > 3) country = country.substr(0,3);
+        
+            /* date1 from 260c */
+            var date1 = now.getUTCFullYear().toString();
+            if (f = this.field('260',true)[0]) {
+                if (s = f.subfield('c',true)[0]) {
+                    if (s[1]) {
+                        var tmpd = s[1];
+                        tmpd = tmpd.replace(/[^0-9]/g, '');
+                        if (tmpd.match(/^\d\d\d\d/)) {
+                            date1 = tmpd.substr(0, 4);
+                        }
+                    }
+                }
+            }
+        
+            var date2 = orig008.substr(11, 4);
+            var datetype = orig008.substr(6, 1);
+            var modded = orig008.substr(38, 1);
+            var catsrc = orig008.substr(39, 1);
+        
+            return '' + y + m + d + datetype + date1 + date2 + country + '                 ' + lang + modded + catsrc;
+        
+        }
+
         this.title = function () { return this.subfield('245','a')[1] }
 
-        this.field = function (spec) {
+        this.field = function (spec, wantarray) {
             var list = this.fields.filter(function (f) {
                 if (f.tag.match(spec)) return true;
                 return false;
             });
 
-            if (list.length == 1) return list[0];
+            if (!wantarray && list.length == 1) return list[0];
             return list;
         }
 
         this.subfield = function (spec, code) {
-            var f = this.field(spec);
-            if (Array.isArray(f)) {
-                if (!f.length) return f;
-                f = f[0];
-            }
-            return f.subfield(code)
+            var f = this.field(spec, true)[0];
+            if (f) return f.subfield(code)
+            return null;
         }
 
         this.appendFields = function () {
             var me = this;
-            Array.prototype.slice.call(arguments).forEach( function (f) { me.fields.push( f ) } );
+            Array.prototype.slice.call(arguments).forEach( function (f) { f.position = me.fields.length; me.fields.push( f ) } );
         }
 
         this.deleteField = function (f) { return this.deleteFields(f) },
@@ -71,18 +126,17 @@ var MARC = {
             var args = Array.prototype.slice.call(arguments);
             args.splice(0,1);
             var me = this;
-            var done = false;
             for (var j = 0; j < this.fields.length; j++) {
-                if (!done && target === this.fields[j]) {
+                if (target === this.fields[j]) {
                     args.forEach( function (f) {
                         f.record = me;
-                        f.position = j++;
-                        me.fields.splice(j,0,f);
+                        me.fields.splice(j++,0,f);
                     });
-                    j++;
-                    done = true;
+                    break;
                 }
-                if (done && this.fields[j]) this.fields[j].position += args.length - 1;
+            }
+            for (var j = 0; j < this.fields.length; j++) {
+                this.fields[j].position = j;
             }
         }
 
@@ -90,25 +144,23 @@ var MARC = {
             var args = Array.prototype.slice.call(arguments);
             args.splice(0,1);
             var me = this;
-            var done = false;
             for (var j = 0; j < this.fields.length; j++) {
-                if (!done && target === this.fields[j]) {
+                if (target === this.fields[j]) {
                     args.forEach( function (f) {
                         f.record = me;
-                        f.position = ++j;
-                        me.fields.splice(j,0,f);
+                        me.fields.splice(++j,0,f);
                     });
-                    j++;
-                    done = true;
+                    break;
                 }
-                if (done && this.fields[j]) this.fields[j].position += args.length - 1;
+            }
+            for (var j = 0; j < this.fields.length; j++) {
+                this.fields[j].position = j;
             }
         }
 
         this.deleteFields = function () {
             var me = this;
             var counter = 0;
-            var done = false;
             for ( var i in arguments ) {
                 var f = arguments[i];
                 for (var j = 0; j < me.fields.length; j++) {
@@ -116,12 +168,13 @@ var MARC = {
                         me.fields[j].record = null;
                         me.fields.splice(j,1);
                         counter++
-                        j++;
-                        done = true;
+                        break;
                     }
-                    if (done) this.fields[j].position -= 1;
                 }
             }
+            for (var j = 0; j < this.fields.length; j++) {
+                this.fields[j].position = j;
+            }
             return counter;
         }
 
@@ -494,11 +547,11 @@ var MARC = {
     Field : function (kwargs) {
         if (!kwargs) kwargs = {};
 
-        this.subfield = function (code) {
+        this.subfield = function (code, wantarray) {
             var list = this.subfields.filter( function (s) {
                 if (s[0] == code) return true; return false;
             });
-            if (list.length == 1) return list[0];
+            if (!wantarray && list.length == 1) return list[0];
             return list;
         }
 

commit a9a3c4338e0e941529e424a57b0a4ba78aaedcf5
Author: Mike Rylander <mrylander at gmail.com>
Date:   Sun Feb 15 20:06:50 2015 -0500

    LP#1402797 Improve styling; marcrecord bug fixes; implement field/subfield insert and remove
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/css/cat.css.tt2 b/Open-ILS/src/templates/staff/css/cat.css.tt2
index 7370b92..2d653a8 100644
--- a/Open-ILS/src/templates/staff/css/cat.css.tt2
+++ b/Open-ILS/src/templates/staff/css/cat.css.tt2
@@ -7,16 +7,32 @@
     border-collapse: collapse;
     border: solid thin gray;
     padding: 0px 5px 0px 5px;
+    margin: 0px;
 }
 
 input.marcedit:focus {
     background-color: lightcyan;
 }
 
+.marcsfvalue {
+    border-left: 0px !important;
+}
+
 .marcsfcode {
     color: blue;
     font-weight: normal;
     text-align: center;
+    padding-left: 0px !important;
+    border-left: 0px !important;
+    border-right: 0px !important;
+}
+
+.marcsfcodedelimiter {
+    color: blue;
+    font-weight: normal;
+    text-align: center;
+    padding-right: 0px !important;
+    border-right: 0px !important;
 }
 
 .marctag, .marcind {
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
index bb5279a..20a793d 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
@@ -123,7 +123,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         ],
         link: function (scope, element, attrs) {
 
-            if (scope.onKeydown) element.bind('keydown', scope.onKeydown);
+            if (scope.onKeydown) element.bind('keydown', {scope : scope}, scope.onKeydown);
 
             element.bind('change', function (e) { element.size = scope.max || parseInt(scope.content.length * 1.1) });
 
@@ -138,11 +138,11 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         transclude: true,
         restrict: 'E',
         template: '<span>'+
-                    '<span class="marcsfcode"><label class="marcedit"'+
+                    '<span><label class="marcedit marcsfcodedelimiter"'+
                         'for="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}s{{subfield[2]}}code" '+
                         '>‡</label><eg-marc-edit-editable '+
                         'itype="sfc" '+
-                        'class="marcedit" '+
+                        'class="marcedit marcsf marcsfcode" '+
                         'field="field" '+
                         'subfield="subfield" '+
                         'content="subfield[0]" '+
@@ -152,7 +152,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     '/></span>'+
                     '<span><eg-marc-edit-editable '+
                         'itype="sfv" '+
-                        'class="marcedit marcsfvalue" '+
+                        'class="marcedit marcsf marcsfvalue" '+
                         'field="field" '+
                         'subfield="subfield" '+
                         'content="subfield[1]" '+
@@ -270,8 +270,16 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         template: '<form ng-submit="saveRecord()">'+
                   '<div class="marcrecord">'+
                     '<div><eg-marc-edit-leader record="record" on-keydown="onKeydown"/></div>'+
-                    '<div><eg-marc-edit-controlfield ng-repeat="field in controlfields" field="field" on-keydown="onKeydown"/></div>'+
-                    '<div><eg-marc-edit-datafield ng-repeat="field in datafields" field="field" on-keydown="onKeydown"/></div>'+
+                    '<div><eg-marc-edit-controlfield '+
+                        'ng-repeat="field in controlfields" '+
+                        'field="field" on-keydown="onKeydown"'+
+                        'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}"'+
+                        '/></div>'+
+                    '<div><eg-marc-edit-datafield '+
+                        'ng-repeat="field in datafields" '+
+                        'field="field" on-keydown="onKeydown" '+
+                        'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}"'+
+                        '/></div>'+
                   '</div>'+
                   '<button class="btn btn-default" type="submit">Save</button>'+
                   '</form>'+
@@ -279,6 +287,20 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         restrict: 'E',
         replace: false,
         scope: { recordId : '=', maxUndo : '@' },
+        link: function (scope, element, attrs) {
+
+            element.bind('click', function(e) {;
+                scope.current_event_target = $(e.target).attr('id');
+                if (scope.current_event_target) {
+                    console.log('Recording click event on ' + scope.current_event_target);
+                    scope.current_event_target_cursor_pos =
+                        e.target.selectionDirection=='backward' ?
+                            e.target.selectionStart :
+                            e.target.selectionEnd;
+                }
+            });
+
+        },
         controller : ['$timeout','$scope','egCore',
             function ( $timeout , $scope , egCore ) {
 
@@ -299,6 +321,130 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                         event_return = $scope.processRedo();
                     } else if (event.which == 90 && event.ctrlKey) { // ctrl+z, undo
                         event_return = $scope.processUndo();
+                    } else if ((event.which == 68 || event.which == 73) && event.ctrlKey) { // ctrl+d or ctrl+i, insert subfield
+
+                        var element = $(event.target);
+                        var new_sf, index_sf, move_data;
+
+                        if (element.hasClass('marcsfvalue')) {
+                            index_sf = event.data.scope.subfield[2];
+                            new_sf = index_sf + 1;
+
+                            var start = event.target.selectionStart;
+                            var end = event.target.selectionEnd - event.target.selectionStart ?
+                                    event.target.selectionEnd :
+                                    event.target.value.length;
+
+                            move_data = event.target.value.substring(start,end);
+
+                        } else if (element.hasClass('marcsfcode')) {
+                            index_sf = event.data.scope.subfield[2];
+                            new_sf = index_sf + 1;
+                        } else if (element.hasClass('marctag') || element.hasClass('marcind')) {
+                            index_sf = 0;
+                            new_sf = index_sf;
+                        }
+
+                        $scope.current_event_target = 'r' + $scope.recordId +
+                                                      'f' + event.data.scope.field.position + 
+                                                      's' + new_sf + 'code';
+
+                        event.data.scope.field.subfields.forEach(function(sf) {
+                            if (sf[2] >= new_sf) sf[2]++;
+                            if (sf[2] == index_sf) sf[1] = event.target.value.substring(0,start) + event.target.value.substring(end);
+                        });
+                        event.data.scope.field.subfields.splice(
+                            new_sf,
+                            0,
+                            [' ', move_data, new_sf ]
+                        );
+
+                        $scope.current_event_target_cursor_pos = 0;
+                        $scope.current_event_target_cursor_pos_end = 1;
+
+                        $timeout(function(){$scope.$digest()}).then(setCaret);
+
+                        event_return = false;
+
+                    } else if (event.which == 13 && event.ctrlKey) { // ctrl+enter, insert datafield
+
+                        var element = $(event.target);
+
+                        var index_field = event.data.scope.field.position;
+                        var new_field = index_field + 1;
+
+                        event.data.scope.field.record.insertFieldsAfter(
+                            event.data.scope.field,
+                            new MARC.Field({
+                                record : event.data.scope.field.record,
+                                tag : '999',
+                                subfields : [[' ','',0]],
+                                position : new_field
+                            })
+                        );
+
+                        $scope.current_event_target = 'r' + $scope.recordId +
+                                                      'f' + new_field + 'tag';
+
+                        $scope.current_event_target_cursor_pos = 0;
+                        $scope.current_event_target_cursor_pos_end = 3;
+                        $scope.force_render = true;
+
+                        $timeout(function(){$scope.$digest()}).then(setCaret);
+
+                        event_return = false;
+
+                    } else if (event.which == 46 && event.ctrlKey) { // ctrl+del, remove field
+
+                        var del_field = event.data.scope.field.position;
+
+                        var domnode = $('#r'+event.data.scope.field.record.subfield('901','c')[1] + 'f' + del_field);
+
+                        event.data.scope.field.record.deleteFields(
+                            event.data.scope.field
+                        );
+
+                        domnode.scope().$destroy();
+                        domnode.remove();
+
+                        $scope.current_event_target = 'r' + $scope.recordId +
+                                                      'f' + del_field + 'tag';
+
+                        $scope.current_event_target_cursor_pos = 0;
+                        $scope.current_event_target_cursor_pos_end = 0
+                        $scope.force_render = true;
+
+                        $timeout(function(){$scope.$digest()}).then(setCaret);
+
+                        event_return = false;
+
+                    } else if (event.which == 46 && event.shiftKey && $(event.target).hasClass('marcsf')) { // shift+del, remove subfield
+
+                        var sf = event.data.scope.subfield[2] - 1;
+                        if (sf == -1) sf = 0;
+
+                        event.data.scope.field.deleteExactSubfields(
+                            event.data.scope.subfield
+                        );
+
+                        if (!event.data.scope.field.subfields[sf]) {
+                            $scope.current_event_target = 'r' + $scope.recordId +
+                                                          'f' + event.data.scope.field.position + 
+                                                          'tag';
+                        } else {
+                            $scope.current_event_target = 'r' + $scope.recordId +
+                                                          'f' + event.data.scope.field.position + 
+                                                          's' + sf + 'value';
+                        }
+
+                        $scope.current_event_target_cursor_pos = 0;
+                        $scope.current_event_target_cursor_pos_end = 0;
+                        $scope.force_render = true;
+
+                        $timeout(function(){$scope.$digest()}).then(setCaret);
+
+                        event_return = false;
+
                     } else { // Assumes only marc editor elements have IDs that can trigger this event handler.
                         $scope.current_event_target = $(event.target).attr('id');
                         if ($scope.current_event_target) {
@@ -314,13 +460,18 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 
                 function setCaret() {
                     if ($scope.current_event_target) {
+                        if (!$scope.current_event_target_cursor_pos_end)
+                            $scope.current_event_target_cursor_pos_end = $scope.current_event_target_cursor_pos
+
                         var element = $('#'+$scope.current_event_target).get(0);
-                        element.focus();
-                        element.setSelectionRange(
-                            $scope.current_event_target_cursor_pos,
-                            $scope.current_event_target_cursor_pos
-                        );
-                        $scope.current_event_target = null;
+                        if (element) {
+                            element.focus();
+                            element.setSelectionRange(
+                                $scope.current_event_target_cursor_pos,
+                                $scope.current_event_target_cursor_pos_end
+                            );
+                            $scope.current_event_target = null;
+                        }
                     }
                 }
 
@@ -345,6 +496,12 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                             pos: $scope.current_event_target_cursor_pos
                         });
 
+                        if ($scope.force_render) {
+                            $scope.controlfields = $scope.record.fields.filter(function(f){ return f.isControlfield() });
+                            $scope.datafields = $scope.record.fields.filter(function(f){ return !f.isControlfield() });
+                            $scope.force_render = false;
+                        }
+
                         if ($scope.record_undo_stack.length != $scope.save_stack_depth) {
                             console.log('should get a listener... does not');
                             $('body').on('beforeunload', function(){
diff --git a/Open-ILS/web/js/ui/default/staff/marcrecord.js b/Open-ILS/web/js/ui/default/staff/marcrecord.js
index 22e5354..77f989b 100644
--- a/Open-ILS/web/js/ui/default/staff/marcrecord.js
+++ b/Open-ILS/web/js/ui/default/staff/marcrecord.js
@@ -71,14 +71,18 @@ var MARC = {
             var args = Array.prototype.slice.call(arguments);
             args.splice(0,1);
             var me = this;
+            var done = false;
             for (var j = 0; j < this.fields.length; j++) {
-                if (target === this.fields[j]) {
-                    j--;
+                if (!done && target === this.fields[j]) {
                     args.forEach( function (f) {
-                        me.fields.splice(j++,0,f);
+                        f.record = me;
+                        f.position = j++;
+                        me.fields.splice(j,0,f);
                     });
-                    break;
+                    j++;
+                    done = true;
                 }
+                if (done && this.fields[j]) this.fields[j].position += args.length - 1;
             }
         }
 
@@ -86,28 +90,36 @@ var MARC = {
             var args = Array.prototype.slice.call(arguments);
             args.splice(0,1);
             var me = this;
+            var done = false;
             for (var j = 0; j < this.fields.length; j++) {
-                if (target === this.fields[j]) {
+                if (!done && target === this.fields[j]) {
                     args.forEach( function (f) {
-                        me.fields.splice(j++,0,f);
+                        f.record = me;
+                        f.position = ++j;
+                        me.fields.splice(j,0,f);
                     });
-                    break;
+                    j++;
+                    done = true;
                 }
+                if (done && this.fields[j]) this.fields[j].position += args.length - 1;
             }
         }
 
         this.deleteFields = function () {
             var me = this;
             var counter = 0;
+            var done = false;
             for ( var i in arguments ) {
                 var f = arguments[i];
                 for (var j = 0; j < me.fields.length; j++) {
                     if (f === me.fields[j]) {
                         me.fields[j].record = null;
-                        me.fields.splice(j,0);
+                        me.fields.splice(j,1);
                         counter++
-                        break;
+                        j++;
+                        done = true;
                     }
+                    if (done) this.fields[j].position -= 1;
                 }
             }
             return counter;
@@ -498,6 +510,27 @@ var MARC = {
             }
         }
 
+        this.deleteExactSubfields = function () {
+            var me = this;
+            var counter = 0;
+            var done = false;
+            for ( var i in arguments ) {
+                var f = arguments[i];
+                for (var j = 0; j < me.subfields.length; j++) {
+                    if (f === me.subfields[j]) {
+                        me.subfields.splice(j,1);
+                        counter++
+                        j++;
+                        done = true;
+                    }
+                    if (done && me.subfields[j])
+                        me.subfields[j][2] -= 1;
+                }
+            }
+            return counter;
+        }
+
+
         this.deleteSubfields = function (c) {
             return this.deleteSubfield( { code : c } );
         }

commit 3c1a03e5fc3a2480041e52399892e4abe2efd6a5
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Feb 13 18:13:44 2015 -0500

    LP#1402797 Start styling marc editor via css
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/base.tt2 b/Open-ILS/src/templates/staff/base.tt2
index ce9cc5e..9f92d3d 100644
--- a/Open-ILS/src/templates/staff/base.tt2
+++ b/Open-ILS/src/templates/staff/base.tt2
@@ -14,6 +14,7 @@
     [% ELSE %]
     <link rel="stylesheet" href="[% WEB_BUILD_PATH %]/css/evergreen-staff-client-deps.[% EVERGREEN_VERSION %].min.css" />
     [% END %]
+    <link rel="stylesheet" href="[% ctx.base_path %]/staff/css/cat.css" />
     <link rel="stylesheet" href="[% ctx.base_path %]/staff/css/style.css" />
     <link rel="stylesheet" href="[% ctx.base_path %]/staff/css/print.css" type="text/css" media="print" />
   </head>
diff --git a/Open-ILS/src/templates/staff/css/cat.css.tt2 b/Open-ILS/src/templates/staff/css/cat.css.tt2
new file mode 100644
index 0000000..7370b92
--- /dev/null
+++ b/Open-ILS/src/templates/staff/css/cat.css.tt2
@@ -0,0 +1,120 @@
+.marcrecord {
+    background-color: lightgrey;
+}
+
+.marcedit {
+    background-color: lightgrey;
+    border-collapse: collapse;
+    border: solid thin gray;
+    padding: 0px 5px 0px 5px;
+}
+
+input.marcedit:focus {
+    background-color: lightcyan;
+}
+
+.marcsfcode {
+    color: blue;
+    font-weight: normal;
+    text-align: center;
+}
+
+.marctag, .marcind {
+    text-align: center;
+}
+
+.marcEditableControlfield {
+    text-align: left;
+}
+
+.marcSubfieldCode, .marcInd1, .marcInd2, .marcTag {
+    width: 1em;
+}
+
+.marcSubfieldDelimiter, .marcSubfieldCode {
+    color: blue;
+}
+
+.marcInd1, .marcInd2, .marcTag {
+    text-align: center;
+}
+
+.marcSubfield {
+    min-width: 1em;
+}
+
+.tooltip {
+    /* width: 10em; */
+}
+
+caption {
+    font-weight: bold;
+}
+
+grid row, grid column {
+    border-collapse: collapse;
+    border: solid thin gray;
+    vertical-align: bottom;
+}
+
+grid column {
+    background-color: lightgray; 
+    padding: 2px;
+}
+
+grid#leaderGrid textbox {
+    min-width: 1em;
+}
+
+grid#leaderGrid label {
+    font-weight: bold;
+}
+
+grid#leaderGrid label, grid#leaderGrid textbox {
+    visibility: hidden;
+}
+
+grid#leaderGrid[type="BKS"] *[set~="BKS"], grid#leaderGrid[type="BKS"] *[set~="BKS"] + textbox {
+    visibility: visible;
+}
+
+grid#leaderGrid[type="SER"] *[set~="SER"], grid#leaderGrid[type="SER"] *[set~="SER"] + textbox {
+    visibility: visible;
+}
+
+grid#leaderGrid[type="VIS"] *[set~="VIS"], grid#leaderGrid[type="VIS"] *[set~="VIS"] + textbox {
+    visibility: visible;
+}
+
+grid#leaderGrid[type="MIX"] *[set~="MIX"], grid#leaderGrid[type="MIX"] *[set~="MIX"] + textbox {
+    visibility: visible;
+}
+
+grid#leaderGrid[type="MAP"] *[set~="MAP"], grid#leaderGrid[type="MAP"] *[set~="MAP"] + textbox {
+    visibility: visible;
+}
+
+grid#leaderGrid[type="SCO"] *[set~="SCO"], grid#leaderGrid[type="SCO"] *[set~="SCO"] + textbox {
+    visibility: visible;
+}
+
+grid#leaderGrid[type="REC"] *[set~="REC"], grid#leaderGrid[type="REC"] *[set~="REC"] + textbox {
+    visibility: visible;
+}
+
+grid#leaderGrid[type="COM"] *[set~="COM"], grid#leaderGrid[type="COM"] *[set~="COM"] + textbox {
+    visibility: visible;
+}
+
+grid#leaderGrid[type="AUT"] *[set~="AUT"], grid#leaderGrid[type="AUT"] *[set~="AUT"] + textbox {
+    visibility: visible;
+}
+
+grid#leaderGrid[type="MFHD"] *[set~="MFHD"], grid#leaderGrid[type="MFHD"] *[set~="MFHD"] + textbox {
+    visibility: visible;
+}
+
+grid[name="-none-"] * label { color: black; }
+
+.marcValidated { color: black; }
+.marcUnvalidated { color: red; }
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
index 7580a14..bb5279a 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
@@ -138,9 +138,11 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         transclude: true,
         restrict: 'E',
         template: '<span>'+
-                    '<span><eg-marc-edit-editable '+
+                    '<span class="marcsfcode"><label class="marcedit"'+
+                        'for="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}s{{subfield[2]}}code" '+
+                        '>‡</label><eg-marc-edit-editable '+
                         'itype="sfc" '+
-                        'class="marcsfcode" '+
+                        'class="marcedit" '+
                         'field="field" '+
                         'subfield="subfield" '+
                         'content="subfield[0]" '+
@@ -150,7 +152,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     '/></span>'+
                     '<span><eg-marc-edit-editable '+
                         'itype="sfv" '+
-                        'class="marcsfvalue" '+
+                        'class="marcedit marcsfvalue" '+
                         'field="field" '+
                         'subfield="subfield" '+
                         'content="subfield[1]" '+
@@ -169,6 +171,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         restrict: 'E',
         template: '<span><eg-marc-edit-editable '+
                       'itype="ind" '+
+                      'class="marcedit marcind" '+
                       'field="field" '+
                       'content="ind" '+
                       'max="1" '+
@@ -186,6 +189,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         restrict: 'E',
         template: '<span><eg-marc-edit-editable '+
                       'itype="tag" '+
+                      'class="marcedit marctag" '+
                       'field="field" '+
                       'content="tag" '+
                       'max="3" '+
@@ -202,9 +206,9 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         transclude: true,
         restrict: 'E',
         template: '<div>'+
-                    '<span><eg-marc-edit-tag class="marctag" field="field" tag="field.tag" on-keydown="onKeydown"/></span>'+
-                    '<span><eg-marc-edit-ind class="marcind" field="field" ind="field.ind1" on-keydown="onKeydown" ind-number="1"/></span>'+
-                    '<span><eg-marc-edit-ind class="marcind" field="field" ind="field.ind2" on-keydown="onKeydown" ind-number="2"/></span>'+
+                    '<span><eg-marc-edit-tag field="field" tag="field.tag" on-keydown="onKeydown"/></span>'+
+                    '<span><eg-marc-edit-ind field="field" ind="field.ind1" on-keydown="onKeydown" ind-number="1"/></span>'+
+                    '<span><eg-marc-edit-ind field="field" ind="field.ind2" on-keydown="onKeydown" ind-number="2"/></span>'+
                     '<span><eg-marc-edit-subfield ng-repeat="subfield in field.subfields" subfield="subfield" field="field" on-keydown="onKeydown"/></span>'+
                   '</div>',
         scope: { field: "=", onKeydown: '=' }
@@ -216,11 +220,11 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         transclude: true,
         restrict: 'E',
         template: '<div>'+
-                    '<span><eg-marc-edit-tag class="marctag" field="field" tag="field.tag" on-keydown="onKeydown"/></span>'+
+                    '<span><eg-marc-edit-tag field="field" tag="field.tag" on-keydown="onKeydown"/></span>'+
                     '<span><eg-marc-edit-editable '+
                       'itype="cfld" '+
                       'field="field" '+
-                      'class="marcdata" '+
+                      'class="marcedit marcdata" '+
                       'content="field.data" '+
                       'on-keydown="onKeydown" '+
                       'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}data"'+
@@ -236,14 +240,14 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         restrict: 'E',
         template: '<div>'+
                     '<span><eg-marc-edit-editable '+
-                      'class="marctag" '+
+                      'class="marcedit marctag" '+
                       'content="tag" '+
                       'on-keydown="onKeydown" '+
                       'id="leadertag" '+
                       'disabled="disabled"'+
                       '/></span>'+
                     '<span><eg-marc-edit-editable '+
-                      'class="marcdata" '+
+                      'class="marcedit marcdata" '+
                       'itype="ldr" '+
                       'max="{{record.leader.length}}" '+
                       'content="record.leader" '+

commit 96efbaef736cff91829ca196c2eb19cfe8c3682a
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Feb 13 13:17:32 2015 -0500

    LP#1402797 Code layout cleanup
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
index ae699b2..7580a14 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
@@ -36,14 +36,20 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
     return {
         restrict: 'E',
         replace: true,
-        template: '<input style="font-family: \'Lucida Console\', Monaco, monospace;" ng-model="content" size="{{content.length * 1.1}}" maxlength="{{max}}" class="" type="text"/>',
+        template: '<input '+
+                      'style="font-family: \'Lucida Console\', Monaco, monospace;" '+
+                      'ng-model="content" '+
+                      'size="{{content.length * 1.1}}" '+
+                      'maxlength="{{max}}" '+
+                      'class="" '+
+                      'type="text" '+
+                  '/>',
         scope: {
             field: '=',
             onKeydown: '=',
             subfield: '=',
             content: '=',
             contextItemContainer: '@',
-            idPath: '=',
             max: '@',
             itype: '@'
         },
@@ -161,7 +167,14 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
     return {
         transclude: true,
         restrict: 'E',
-        template: '<span><eg-marc-edit-editable itype="ind" field="field" content="ind" max="1" on-keydown="onKeydown" id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}i{{indNumber}}"/></span>',
+        template: '<span><eg-marc-edit-editable '+
+                      'itype="ind" '+
+                      'field="field" '+
+                      'content="ind" '+
+                      'max="1" '+
+                      'on-keydown="onKeydown" '+
+                      'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}i{{indNumber}}"'+
+                      '/></span>',
         scope: { ind : '=', field: '=', onKeydown: '=', indNumber: '@' },
         replace: false,
     }
@@ -171,7 +184,14 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
     return {
         transclude: true,
         restrict: 'E',
-        template: '<span><eg-marc-edit-editable itype="tag" field="field" content="tag" max="3" on-keydown="onKeydown" id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}tag"/></span>',
+        template: '<span><eg-marc-edit-editable '+
+                      'itype="tag" '+
+                      'field="field" '+
+                      'content="tag" '+
+                      'max="3" '+
+                      'on-keydown="onKeydown" '+
+                      'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}tag"'+
+                      '/></span>',
         scope: { tag : '=', field: '=', onKeydown: '=' },
         replace: false
     }
@@ -197,7 +217,14 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         restrict: 'E',
         template: '<div>'+
                     '<span><eg-marc-edit-tag class="marctag" field="field" tag="field.tag" on-keydown="onKeydown"/></span>'+
-                    '<span><eg-marc-edit-editable itype="cfld" field="field" class="marcdata" content="field.data" on-keydown="onKeydown" id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}data"/></span>'+
+                    '<span><eg-marc-edit-editable '+
+                      'itype="cfld" '+
+                      'field="field" '+
+                      'class="marcdata" '+
+                      'content="field.data" '+
+                      'on-keydown="onKeydown" '+
+                      'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}data"'+
+                      '/></span>'+
                   '</div>',
         scope: { field: "=", onKeydown: '=' }
     }
@@ -208,8 +235,21 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         transclude: true,
         restrict: 'E',
         template: '<div>'+
-                    '<span><eg-marc-edit-editable class="marctag" content="tag" on-keydown="onKeydown" id="leadertag" disabled="disabled"/></span>'+
-                    '<span><eg-marc-edit-editable class="marcdata" itype="ldr" max="{{record.leader.length}}" content="record.leader" id="r{{record.subfield(\'901\',\'c\')[1]}}leaderdata" on-keydown="onKeydown"/></span>'+
+                    '<span><eg-marc-edit-editable '+
+                      'class="marctag" '+
+                      'content="tag" '+
+                      'on-keydown="onKeydown" '+
+                      'id="leadertag" '+
+                      'disabled="disabled"'+
+                      '/></span>'+
+                    '<span><eg-marc-edit-editable '+
+                      'class="marcdata" '+
+                      'itype="ldr" '+
+                      'max="{{record.leader.length}}" '+
+                      'content="record.leader" '+
+                      'id="r{{record.subfield(\'901\',\'c\')[1]}}leaderdata" '+
+                      'on-keydown="onKeydown"'+
+                      '/></span>'+
                   '</div>',
         controller : ['$scope',
             function ( $scope ) {

commit d69daf1e7ac4bd41c186224fcf66a180fb088524
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Feb 11 19:47:59 2015 -0500

    LP#1402797 Should prompt when there is unsaved data, but does not.  More to do...
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
index 6b517ab..ae699b2 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
@@ -244,6 +244,9 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                 $scope.in_undo = false;
                 $scope.in_redo = false;
                 $scope.record = new MARC.Record();
+                $scope.save_stack_depth = 0;
+                $scope.controlfields = [];
+                $scope.datafields = [];
 
                 $scope.onKeydown = function (event) {
                     var event_return = true;
@@ -286,6 +289,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                         $scope.record = new MARC.Record({ marcxml : $scope.bre.marc() });
                         $scope.controlfields = $scope.record.fields.filter(function(f){ return f.isControlfield() });
                         $scope.datafields = $scope.record.fields.filter(function(f){ return !f.isControlfield() });
+                        $scope.save_stack_depth = $scope.record_undo_stack.length;
                     }).then(setCaret);
                 }
 
@@ -296,6 +300,15 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                             target: $scope.current_event_target,
                             pos: $scope.current_event_target_cursor_pos
                         });
+
+                        if ($scope.record_undo_stack.length != $scope.save_stack_depth) {
+                            console.log('should get a listener... does not');
+                            $('body').on('beforeunload', function(){
+                                return 'There is unsaved data in this record.'
+                            });
+                        } else {
+                            $('body').off('beforeunload');
+                        }
                     }
 
                     if ($scope.record_undo_stack.length > $scope.max_undo)
@@ -369,10 +382,6 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     }
                 );
 
-
-                $scope.controlfields = [];
-                $scope.datafields = [];
-
                 if ($scope.recordId)
                     loadRecord();
 

commit fd2b7b48a17613bbdfd3de240e4fd6bc54dc6e37
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Feb 11 19:23:32 2015 -0500

    LP#1402797 Global undo/redo stack stack
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
index 8f53a54..6b517ab 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
@@ -25,7 +25,6 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                             $scope.$parent.$parent.content = replace_with
                         })
                     }, 0);
-                    console.log('well, replaced it');
                     $($element).parent().css({display: 'none'});
                 }
             }
@@ -36,16 +35,17 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 .directive("egMarcEditEditable", ['$timeout', '$compile', '$document', function ($timeout, $compile, $document) {
     return {
         restrict: 'E',
-        replace: false,
-        transclude: true,
+        replace: true,
         template: '<input style="font-family: \'Lucida Console\', Monaco, monospace;" ng-model="content" size="{{content.length * 1.1}}" maxlength="{{max}}" class="" type="text"/>',
         scope: {
             field: '=',
+            onKeydown: '=',
             subfield: '=',
             content: '=',
             contextItemContainer: '@',
+            idPath: '=',
             max: '@',
-            type: '@'
+            itype: '@'
         },
         controller : ['$scope',
             function ( $scope ) {
@@ -71,6 +71,10 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     if ($scope.context_menu_element) {
                         console.log('Reshowing context menu...');
                         $($scope.context_menu_element).css({ display: 'block', top: event.pageY, left: event.pageX });
+                        $('body').on('click.context_menu',function() {
+                            $($scope.context_menu_element).css('display','none');
+                            $('body').off('click.context_menu');
+                        });
                         return false;
                     }
 
@@ -83,26 +87,26 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                             '</ul>';
             
                         var tnode = angular.element(tmpl);
-                        console.log('... got element ...');
-
                         $document.find('body').append(tnode);
-                        console.log('... attached to DOM ...');
 
                         $(tnode).css({
                             display: 'block',
                             top: event.pageY,
                             left: event.pageX
                         });
-                        console.log('... displayed ...');
 
                         $scope.context_menu_element = tnode;
-                        console.log('... captured for later ...');
 
                         $timeout(function() {
                             var e = $compile(tnode)($scope);
-                            console.log('... compiled: ' + e);
                         }, 0);
 
+
+                        $('body').on('click.context_menu',function() {
+                            $(tnode).css('display','none');
+                            $('body').off('click.context_menu');
+                        });
+
                         return false;
                     }
             
@@ -113,7 +117,10 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         ],
         link: function (scope, element, attrs) {
 
+            if (scope.onKeydown) element.bind('keydown', scope.onKeydown);
+
             element.bind('change', function (e) { element.size = scope.max || parseInt(scope.content.length * 1.1) });
+
             if (scope.contextItemContainer && angular.isArray(scope[scope.contextItemContainer]))
                 element.bind('contextmenu', scope.showContext);
         }
@@ -122,71 +129,94 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 
 .directive("egMarcEditSubfield", function () {
     return {
+        transclude: true,
         restrict: 'E',
         template: '<span>'+
-                    '<span><eg-marc-edit-editable type="sfc" class="marcsfcode" field="field" subfield="subfield" content="subfield[0]" max="1"/></span>'+
-                    '<span><eg-marc-edit-editable type="sfv" class="marcsfvalue" field="field" subfield="subfield" content="subfield[1]"/></span>'+
+                    '<span><eg-marc-edit-editable '+
+                        'itype="sfc" '+
+                        'class="marcsfcode" '+
+                        'field="field" '+
+                        'subfield="subfield" '+
+                        'content="subfield[0]" '+
+                        'max="1" '+
+                        'on-keydown="onKeydown" '+
+                        'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}s{{subfield[2]}}code" '+
+                    '/></span>'+
+                    '<span><eg-marc-edit-editable '+
+                        'itype="sfv" '+
+                        'class="marcsfvalue" '+
+                        'field="field" '+
+                        'subfield="subfield" '+
+                        'content="subfield[1]" '+
+                        'on-keydown="onKeydown" '+
+                        'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}s{{subfield[2]}}value" '+
+                    '/></span>'+
                   '</span>',
-        scope: { field: "=", subfield: "=" },
+        scope: { field: "=", subfield: "=", onKeydown: '=' },
         replace: false
     }
 })
 
 .directive("egMarcEditInd", function () {
     return {
+        transclude: true,
         restrict: 'E',
-        template: '<span><eg-marc-edit-editable type="ind" field="field" content="ind" max="1"/></span>',
-        scope: { ind : '=', field: '=' },
+        template: '<span><eg-marc-edit-editable itype="ind" field="field" content="ind" max="1" on-keydown="onKeydown" id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}i{{indNumber}}"/></span>',
+        scope: { ind : '=', field: '=', onKeydown: '=', indNumber: '@' },
         replace: false,
     }
 })
 
 .directive("egMarcEditTag", function () {
     return {
+        transclude: true,
         restrict: 'E',
-        template: '<span><eg-marc-edit-editable type="tag" field="field" content="tag" max="3"/></span>',
-        scope: { tag : '=', field: '=' },
+        template: '<span><eg-marc-edit-editable itype="tag" field="field" content="tag" max="3" on-keydown="onKeydown" id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}tag"/></span>',
+        scope: { tag : '=', field: '=', onKeydown: '=' },
         replace: false
     }
 })
 
 .directive("egMarcEditDatafield", function () {
     return {
+        transclude: true,
         restrict: 'E',
         template: '<div>'+
-                    '<span><eg-marc-edit-tag class="marctag" field="field" tag="field.tag"/></span>'+
-                    '<span><eg-marc-edit-ind class="marcind" field="field" ind="field.ind1"/></span>'+
-                    '<span><eg-marc-edit-ind class="marcind" field="field" ind="field.ind2"/></span>'+
-                    '<span><eg-marc-edit-subfield ng-repeat="subfield in field.subfields" subfield="subfield" field="field"/></span>'+
+                    '<span><eg-marc-edit-tag class="marctag" field="field" tag="field.tag" on-keydown="onKeydown"/></span>'+
+                    '<span><eg-marc-edit-ind class="marcind" field="field" ind="field.ind1" on-keydown="onKeydown" ind-number="1"/></span>'+
+                    '<span><eg-marc-edit-ind class="marcind" field="field" ind="field.ind2" on-keydown="onKeydown" ind-number="2"/></span>'+
+                    '<span><eg-marc-edit-subfield ng-repeat="subfield in field.subfields" subfield="subfield" field="field" on-keydown="onKeydown"/></span>'+
                   '</div>',
-        scope: { field: "=" }
+        scope: { field: "=", onKeydown: '=' }
     }
 })
 
 .directive("egMarcEditControlfield", function () {
     return {
+        transclude: true,
         restrict: 'E',
         template: '<div>'+
-                    '<span><eg-marc-edit-tag class="marctag" field="field" tag="field.tag"/></span>'+
-                    '<span><eg-marc-edit-editable type="cfld" field="field" class="marcdata" content="field.data"/></span>'+
+                    '<span><eg-marc-edit-tag class="marctag" field="field" tag="field.tag" on-keydown="onKeydown"/></span>'+
+                    '<span><eg-marc-edit-editable itype="cfld" field="field" class="marcdata" content="field.data" on-keydown="onKeydown" id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}data"/></span>'+
                   '</div>',
-        scope: { field: "=" }
+        scope: { field: "=", onKeydown: '=' }
     }
 })
 
 .directive("egMarcEditLeader", function () {
     return {
+        transclude: true,
         restrict: 'E',
         template: '<div>'+
-                    '<span><eg-marc-edit-editable class="marctag" content="tag"/></span>'+
-                    '<span><eg-marc-edit-editable class="marcdata" type="ldr" max="{{record.leader.length}}" content="record.leader"/></span>'+
+                    '<span><eg-marc-edit-editable class="marctag" content="tag" on-keydown="onKeydown" id="leadertag" disabled="disabled"/></span>'+
+                    '<span><eg-marc-edit-editable class="marcdata" itype="ldr" max="{{record.leader.length}}" content="record.leader" id="r{{record.subfield(\'901\',\'c\')[1]}}leaderdata" on-keydown="onKeydown"/></span>'+
                   '</div>',
         controller : ['$scope',
             function ( $scope ) {
                 $scope.tag = 'LDR';
             }
         ],
-        scope: { record: "=" }
+        scope: { record: "=", onKeydown: '=' }
     }
 })
 
@@ -195,31 +225,131 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
     return {
         template: '<form ng-submit="saveRecord()">'+
                   '<div class="marcrecord">'+
-                    '<div><eg-marc-edit-leader record="record"/></div>'+
-                    '<div><eg-marc-edit-controlfield ng-repeat="field in controlfields" field="field"/></div>'+
-                    '<div><eg-marc-edit-datafield ng-repeat="field in datafields" field="field"/></div>'+
+                    '<div><eg-marc-edit-leader record="record" on-keydown="onKeydown"/></div>'+
+                    '<div><eg-marc-edit-controlfield ng-repeat="field in controlfields" field="field" on-keydown="onKeydown"/></div>'+
+                    '<div><eg-marc-edit-datafield ng-repeat="field in datafields" field="field" on-keydown="onKeydown"/></div>'+
                   '</div>'+
                   '<button class="btn btn-default" type="submit">Save</button>'+
                   '</form>'+
                   '<button class="btn btn-default" ng-click="seeBreaker()">Breaker</button>',
         restrict: 'E',
         replace: false,
-        scope: { recordId : '=' },
-        controller : ['$scope','egCore',
-            function ( $scope , egCore ) {
+        scope: { recordId : '=', maxUndo : '@' },
+        controller : ['$timeout','$scope','egCore',
+            function ( $timeout , $scope , egCore ) {
+
+                $scope.max_undo = $scope.maxUndo || 100;
+                $scope.record_undo_stack = [];
+                $scope.record_redo_stack = [];
+                $scope.in_undo = false;
+                $scope.in_redo = false;
+                $scope.record = new MARC.Record();
+
+                $scope.onKeydown = function (event) {
+                    var event_return = true;
+
+                    if (event.which == 89 && event.ctrlKey) { // ctrl+y, redo
+                        event_return = $scope.processRedo();
+                    } else if (event.which == 90 && event.ctrlKey) { // ctrl+z, undo
+                        event_return = $scope.processUndo();
+                    } else { // Assumes only marc editor elements have IDs that can trigger this event handler.
+                        $scope.current_event_target = $(event.target).attr('id');
+                        if ($scope.current_event_target) {
+                            $scope.current_event_target_cursor_pos =
+                                event.target.selectionDirection=='backward' ?
+                                    event.target.selectionStart :
+                                    event.target.selectionEnd;
+                        }
+                    }
+
+                    return event_return;
+                };
+
+                function setCaret() {
+                    if ($scope.current_event_target) {
+                        var element = $('#'+$scope.current_event_target).get(0);
+                        element.focus();
+                        element.setSelectionRange(
+                            $scope.current_event_target_cursor_pos,
+                            $scope.current_event_target_cursor_pos
+                        );
+                        $scope.current_event_target = null;
+                    }
+                }
 
                 function loadRecord() {
                     return egCore.pcrud.retrieve(
                         'bre', $scope.recordId
                     ).then(function(rec) {
+                        $scope.in_redo = true;
                         $scope.bre = rec;
-                        $scope.record = new MARC.Record();
-                        $scope.record.fromXmlString( $scope.bre.marc() );
+                        $scope.record = new MARC.Record({ marcxml : $scope.bre.marc() });
                         $scope.controlfields = $scope.record.fields.filter(function(f){ return f.isControlfield() });
                         $scope.datafields = $scope.record.fields.filter(function(f){ return !f.isControlfield() });
-                    });
+                    }).then(setCaret);
                 }
 
+                $scope.$watch('record.toBreaker()', function (newVal, oldVal) {
+                    if (!$scope.in_undo && !$scope.in_redo && oldVal != newVal) {
+                        $scope.record_undo_stack.push({
+                            breaker: oldVal,
+                            target: $scope.current_event_target,
+                            pos: $scope.current_event_target_cursor_pos
+                        });
+                    }
+
+                    if ($scope.record_undo_stack.length > $scope.max_undo)
+                        $scope.record_undo_stack.shift();
+
+                    console.log('undo stack is ' + $scope.record_undo_stack.length + ' deep');
+                    $scope.in_redo = false;
+                    $scope.in_undo = false;
+                });
+
+                $scope.processUndo = function () {
+                    if ($scope.record_undo_stack.length) {
+                        $scope.in_undo = true;
+
+                        var undo_item = $scope.record_undo_stack.pop();
+                        $scope.record_redo_stack.push(undo_item);
+
+                        $scope.record = new MARC.Record({ marcbreaker : undo_item.breaker });
+                        $scope.controlfields = $scope.record.fields.filter(function(f){ return f.isControlfield() });
+                        $scope.datafields = $scope.record.fields.filter(function(f){ return !f.isControlfield() });
+
+                        $scope.current_event_target = undo_item.target;
+                        $scope.current_event_target_cursor_pos = undo_item.pos;
+                        console.log('Undo targeting ' + $scope.current_event_target + ' position ' + $scope.current_event_target_cursor_pos);
+
+                        $timeout(function(){$scope.$digest()}).then(setCaret);
+                        return false;
+                    }
+
+                    return true;
+                };
+
+                $scope.processRedo = function () {
+                    if ($scope.record_redo_stack.length) {
+                        $scope.in_redo = true;
+
+                        var redo_item = $scope.record_redo_stack.pop();
+                        $scope.record_undo_stack.push(redo_item);
+
+                        $scope.record = new MARC.Record({ marcbreaker : redo_item.breaker });
+                        $scope.controlfields = $scope.record.fields.filter(function(f){ return f.isControlfield() });
+                        $scope.datafields = $scope.record.fields.filter(function(f){ return !f.isControlfield() });
+
+                        $scope.current_event_target = redo_item.target;
+                        $scope.current_event_target_cursor_pos = redo_item.pos;
+                        console.log('Redo targeting ' + $scope.current_event_target + ' position ' + $scope.current_event_target_cursor_pos);
+
+                        $timeout(function(){$scope.$digest()}).then(setCaret);
+                        return false;
+                    }
+
+                    return true;
+                };
+
                 $scope.saveRecord = function () {
                     $scope.bre.marc($scope.record.toXmlString());
                     return egCore.pcrud.update(
diff --git a/Open-ILS/web/js/ui/default/staff/marcrecord.js b/Open-ILS/web/js/ui/default/staff/marcrecord.js
index 876540a..22e5354 100644
--- a/Open-ILS/web/js/ui/default/staff/marcrecord.js
+++ b/Open-ILS/web/js/ui/default/staff/marcrecord.js
@@ -116,13 +116,11 @@ var MARC = {
         // this.clone = function () { return dojo.clone(this) } // maybe implement later...
 
         this.fromXmlURL = function (url) {
-            this.ready   = false;
             var me = this;
             return $.get( // This is a Promise
                 url,
                 function (mxml) {
                     me.fromXmlDocument($('record', mxml)[0]);
-                    me.ready = true;
                     if (me.onLoad) me.onLoad();
             });
         },
@@ -135,18 +133,21 @@ var MARC = {
             var me = this;
             me.leader = $($('leader',mxml)[0]).text() || '00000cam a2200205Ka 4500';
 
-            $('controlfield', mxml).each(function () {
+            $('controlfield', mxml).each(function (ind) {
                 var cf=$(this);
                 me.fields.push(
                     new MARC.Field({
                           record : me,
                           tag    : cf.attr('tag'),
-                          data   : cf.text()
+                          data   : cf.text(),
+                          position: ind
                     })
                 )
             });
 
-            $('datafield', mxml).each(function () {
+            var cfield_count = me.fields.length + 1;
+
+            $('datafield', mxml).each(function (ind) {
                 var df=$(this);
                 me.fields.push(
                     new MARC.Field({
@@ -154,14 +155,16 @@ var MARC = {
                         tag       : df.attr('tag'),
                         ind1      : df.attr('ind1'),
                         ind2      : df.attr('ind2'),
+                        position  : ind + cfield_count,
                         subfields : $('subfield', df).map(
                             function (i, sf) {
-                                return [[ $(sf).attr('code'), $(sf).text() ]];
+                                return [[ $(sf).attr('code'), $(sf).text(), i ]];
                             }
                         ).get()
                     })
                 )
             });
+            me.ready = true;
 
         },
 
@@ -216,7 +219,7 @@ var MARC = {
             }
             
             var lines = marctxt.replace(/^=/gm,'').split('\n');
-            lines.forEach(function (current_line) {
+            lines.forEach(function (current_line, ind) {
 
                 if (current_line.match(/^#/)) {
                     // skip comment lines
@@ -228,7 +231,8 @@ var MARC = {
                             new MARC.Field({
                                 record : me,
                                 tag    : line_tag(current_line),
-                                data   : cf_line_data(current_line).replace('\\',' ','g')
+                                data   : cf_line_data(current_line).replace('\\',' ','g'),
+                                position: ind
                             })
                         );
                     }
@@ -248,16 +252,18 @@ var MARC = {
                                 tag       : line_tag(current_line),
                                 ind1      : df_ind1(current_line),
                                 ind2      : df_ind2(current_line),
-                                subfields : sf_list.map( function (sf) {
+                                position  : ind,
+                                subfields : sf_list.map( function (sf, i) {
                                                 var sf_data = sf.substring(1);
                                                 if (me.delimiter == '$') sf_data = sf_data.replace(/\{dollar\}/g, '$');
-                                                return [ sf.substring(0,1), sf_data ];
+                                                return [ sf.substring(0,1), sf_data, i ];
                                             })
                         })
                     );
                 }
             });
 
+            me.ready = true;
             return this;
         },
 
@@ -446,6 +452,7 @@ var MARC = {
             return val;
         }
 
+        this.ready = false;
         this.fields = [];
         this.delimiter = '\u2021';
         this.leader = '00000cam a2200205Ka 4500';
@@ -571,6 +578,7 @@ var MARC = {
         this.data = ''; // MARC data for a controlfield element
         this.subfields = []; // list of MARC subfields for a datafield element
 
+        this.position = kwargs.position;
         this.record = kwargs.record;
         this.tag = kwargs.tag;
         this.ind1 = kwargs.ind1 || ' ';

commit 1b16bcf78e898166fe5603353e9a27c22afae428
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Feb 11 08:49:12 2015 -0500

    LP#1402797 Make grid action context menu safe for multiple grids per page
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/services/grid.js b/Open-ILS/web/js/ui/default/staff/services/grid.js
index 7ba2f78..ccb9b40 100644
--- a/Open-ILS/web/js/ui/default/staff/services/grid.js
+++ b/Open-ILS/web/js/ui/default/staff/services/grid.js
@@ -484,50 +484,46 @@ angular.module('egGridMod',
                             + action.label + '" => ' + E + "\n" + E.stack);
                     }
 
-                    if ($scope.action_context_y || $scope.action_context_x)
-                        $scope.hideActionContextMenu();
+                    if ($scope.action_context_showing) $scope.hideActionContextMenu();
                 }
 
             }
 
             $scope.hideActionContextMenu = function () {
-                var menu_dom = $('body').find('.grid-action-dropdown')[0];
-                $(menu_dom).css({
+                $($scope.menu_dom).css({
                     display: '',
                     width: $scope.action_context_width,
                     top: $scope.action_context_y,
                     left: $scope.action_context_x
                 });
-                $($scope.action_context_parent).append(menu_dom);
+                $($scope.action_context_parent).append($scope.menu_dom);
                 $scope.action_context_oldy = $scope.action_context_oldx = 0;
-                $('body').unbind('click.remove_context_menu');
+                $('body').unbind('click.remove_context_menu_'+$scope.action_context_index);
                 $scope.action_context_showing = false;
             }
 
             $scope.action_context_showing = false;
             $scope.showActionContextMenu = function ($event) {
-                if (!grid.getSelectedItems().length) // Nothing selected, fire the click event
-                    $event.target.click();
 
-                var current_parent = $scope.grid_element;
-                if ($scope.action_context_showing) {
-                    current_parent = $('body');
-                }
+                // Have to gather these here, instead of inside link()
+                if (!$scope.menu_dom) $scope.menu_dom = $($scope.grid_element).find('.grid-action-dropdown')[0];
+                if (!$scope.action_context_parent) $scope.action_context_parent = $($scope.menu_dom).parent();
 
-                var menu_dom = $(current_parent).find('.grid-action-dropdown')[0];
+                if (!grid.getSelectedItems().length) // Nothing selected, fire the click event
+                    $event.target.click();
 
                 if (!$scope.action_context_showing) {
-                    $scope.action_context_width = $(menu_dom).css('width');
-                    $scope.action_context_y = $(menu_dom).css('top');
-                    $scope.action_context_x = $(menu_dom).css('left');
-                    $scope.action_context_parent = $(menu_dom).parent();
+                    $scope.action_context_width = $($scope.menu_dom).css('width');
+                    $scope.action_context_y = $($scope.menu_dom).css('top');
+                    $scope.action_context_x = $($scope.menu_dom).css('left');
                     $scope.action_context_showing = true;
+                    $scope.action_context_index = Math.floor((Math.random() * 1000) + 1);
 
-                    $('body').append($(menu_dom));
-                    $('body').bind('click.remove_context_menu', $scope.hideActionContextMenu);
+                    $('body').append($($scope.menu_dom));
+                    $('body').bind('click.remove_context_menu_'+$scope.action_context_index, $scope.hideActionContextMenu);
                 }
 
-                $(menu_dom).css({
+                $($scope.menu_dom).css({
                     display: 'block',
                     width: $scope.action_context_width,
                     top: $event.pageY,

commit d1e3c82aa48a7aef4663d0ed1139535198fee225
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Feb 10 12:24:14 2015 -0500

    LP#1402797 Allow forgive-fines on forced checkout
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/share/t_circ_exists_dialog.tt2 b/Open-ILS/src/templates/staff/circ/share/t_circ_exists_dialog.tt2
index 9c41ddd..8772d5f 100644
--- a/Open-ILS/src/templates/staff/circ/share/t_circ_exists_dialog.tt2
+++ b/Open-ILS/src/templates/staff/circ/share/t_circ_exists_dialog.tt2
@@ -21,12 +21,22 @@
         </div>
       </div>
       <div class="modal-footer">
-        <input ng-if="!sameUser" type="submit" class="btn btn-primary" 
-            value="[% l('Normal Checkin then Checkout') %]"/>
-        <input ng-if="sameUser" type="submit" class="btn btn-primary" 
-            value="[% l('Renew') %]"/>
-        <button class="btn btn-warning" 
-            ng-click="cancel($event)">[% l('Cancel') %]</button>
+        <div class="form-group row">
+          <div class="col-md-3">
+            <label for="forgive-checkbox">[% l('Forgive fines?') %]</label>
+          </div>
+          <div class="col-md-1">
+            <input type="checkbox" class="checkbox"
+              id="forgive-checkbox" ng-model="args.forgive_fines"/>
+          </div>
+          <div class="col-md-8">
+            <input ng-if="!sameUser" type="submit" class="btn btn-primary" 
+                value="[% l('Normal Checkin then Checkout') %]"/>
+            <input ng-if="sameUser" type="submit" class="btn btn-primary" 
+                value="[% l('Renew') %]"/>
+            <button class="btn btn-warning" 
+                ng-click="cancel($event)">[% l('Cancel') %]</button>
+        </div>
       </div>
     </div> <!-- modal-content -->
   </div> <!-- modal-dialog -->
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
index e4db7a2..eea4a18 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
@@ -850,24 +850,28 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
             controller: 
                        ['$scope','$modalInstance',
                 function($scope , $modalInstance) {
+                $scope.args = {forgive_fines : false};
                 $scope.circDate = openCirc.xact_start();
                 $scope.sameUser = sameUser;
-                $scope.ok = function() { $modalInstance.close() }
+                $scope.ok = function() { $modalInstance.close($scope.args) }
                 $scope.cancel = function($event) { 
                     $modalInstance.dismiss();
                     $event.preventDefault(); // form, avoid calling ok();
                 }
             }]
         }).result.then(
-            function() {
+            function(args) {
                 if (sameUser) {
+                    params.void_overdues = args.forgive_fines;
                     options.override = true;
                     return service.renew(params, options);
                 }
 
-                return service.checkin(
-                    {barcode : params.copy_barcode, noop : true}
-                ).then(function(checkin_resp) {
+                return service.checkin({
+                    barcode : params.copy_barcode,
+                    noop : true,
+                    void_overdues : args.forgive_fines
+                }).then(function(checkin_resp) {
                     if (checkin_resp.evt.textcode == 'SUCCESS') {
                         return service.checkout(params, options);
                     } else {

commit b787314e7a5ddf07c73903bc2ce2da3c98c4c17d
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Feb 10 10:48:11 2015 -0500

    LP#1402797 If no row is currently selected on right-click, select the clicked row
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/services/grid.js b/Open-ILS/web/js/ui/default/staff/services/grid.js
index 42330b1..7ba2f78 100644
--- a/Open-ILS/web/js/ui/default/staff/services/grid.js
+++ b/Open-ILS/web/js/ui/default/staff/services/grid.js
@@ -506,6 +506,9 @@ angular.module('egGridMod',
 
             $scope.action_context_showing = false;
             $scope.showActionContextMenu = function ($event) {
+                if (!grid.getSelectedItems().length) // Nothing selected, fire the click event
+                    $event.target.click();
+
                 var current_parent = $scope.grid_element;
                 if ($scope.action_context_showing) {
                     current_parent = $('body');

commit c678719f0b404091cf17f533714305a1d90b36a3
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 9 17:19:34 2015 -0500

    LP#1402797 Make grid context menu safer and more state-aware
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/services/grid.js b/Open-ILS/web/js/ui/default/staff/services/grid.js
index f5f1237..42330b1 100644
--- a/Open-ILS/web/js/ui/default/staff/services/grid.js
+++ b/Open-ILS/web/js/ui/default/staff/services/grid.js
@@ -501,17 +501,28 @@ angular.module('egGridMod',
                 $($scope.action_context_parent).append(menu_dom);
                 $scope.action_context_oldy = $scope.action_context_oldx = 0;
                 $('body').unbind('click.remove_context_menu');
+                $scope.action_context_showing = false;
             }
 
+            $scope.action_context_showing = false;
             $scope.showActionContextMenu = function ($event) {
-                var menu_dom = $($scope.grid_element).find('.grid-action-dropdown')[0];
-                $scope.action_context_width = $(menu_dom).css('width');
-                $scope.action_context_y = $(menu_dom).css('top');
-                $scope.action_context_x = $(menu_dom).css('left');
-                $scope.action_context_parent = $(menu_dom).parent();
-
-                $('body').append($(menu_dom));
-                $('body').bind('click.remove_context_menu', $scope.hideActionContextMenu);
+                var current_parent = $scope.grid_element;
+                if ($scope.action_context_showing) {
+                    current_parent = $('body');
+                }
+
+                var menu_dom = $(current_parent).find('.grid-action-dropdown')[0];
+
+                if (!$scope.action_context_showing) {
+                    $scope.action_context_width = $(menu_dom).css('width');
+                    $scope.action_context_y = $(menu_dom).css('top');
+                    $scope.action_context_x = $(menu_dom).css('left');
+                    $scope.action_context_parent = $(menu_dom).parent();
+                    $scope.action_context_showing = true;
+
+                    $('body').append($(menu_dom));
+                    $('body').bind('click.remove_context_menu', $scope.hideActionContextMenu);
+                }
 
                 $(menu_dom).css({
                     display: 'block',

commit 520abb8d737aa0f9816f83ee0cc8c04e43553c45
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 9 17:09:35 2015 -0500

    LP#1402797 Typo
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/share/t_circ_exists_dialog.tt2 b/Open-ILS/src/templates/staff/circ/share/t_circ_exists_dialog.tt2
index f0b37fd..9c41ddd 100644
--- a/Open-ILS/src/templates/staff/circ/share/t_circ_exists_dialog.tt2
+++ b/Open-ILS/src/templates/staff/circ/share/t_circ_exists_dialog.tt2
@@ -21,7 +21,7 @@
         </div>
       </div>
       <div class="modal-footer">
-        <input if="!sameUser" type="submit" class="btn btn-primary" 
+        <input ng-if="!sameUser" type="submit" class="btn btn-primary" 
             value="[% l('Normal Checkin then Checkout') %]"/>
         <input ng-if="sameUser" type="submit" class="btn btn-primary" 
             value="[% l('Renew') %]"/>

commit cc0436de3ceffc3f265a5ffc8dcd2a5bd62a3fdb
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 9 16:58:00 2015 -0500

    LP#1402797 use ng-options so that the default option works
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/share/t_new_message_dialog.tt2 b/Open-ILS/src/templates/staff/circ/share/t_new_message_dialog.tt2
index c6a8960..d588f8b 100644
--- a/Open-ILS/src/templates/staff/circ/share/t_new_message_dialog.tt2
+++ b/Open-ILS/src/templates/staff/circ/share/t_new_message_dialog.tt2
@@ -22,12 +22,11 @@
             </li>
           </ul>
         </div>
-        <div class="col-md-4 pull-right">
-          <select class="form-control" ng-model="args.custom_penalty">
-            <option disabled="disabled" ng-selected="args.custom_penalty = ''" value="">[% l('Penalty Type') %]</option>
-            <option ng-repeat="penalty in penalties" 
-              ng-selected="args.custom_penalty = penalty.id"
-              value="{{penalty.id()}}">{{penalty.label()}}</option>
+        <div class="col-md-4 pull-right nullable">
+          <select class="form-control"
+                  ng-model="args.custom_penalty"
+                  ng-options="penalty.id() as penalty.label() for penalty in penalties">
+            <option value="">[% l('Penalty Type') %]</option>
           </select>
         </div>
       </div>

commit 79893c1d9b43744c3e8a8ae99445753e9ccbc940
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 9 16:42:47 2015 -0500

    LP#1402797 Attach the context menu to the body for proper coordinate mapping
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/services/grid.js b/Open-ILS/web/js/ui/default/staff/services/grid.js
index f5fbc4e..f5f1237 100644
--- a/Open-ILS/web/js/ui/default/staff/services/grid.js
+++ b/Open-ILS/web/js/ui/default/staff/services/grid.js
@@ -491,7 +491,7 @@ angular.module('egGridMod',
             }
 
             $scope.hideActionContextMenu = function () {
-                var menu_dom = $($scope.grid_element).find('.grid-action-dropdown')[0];
+                var menu_dom = $('body').find('.grid-action-dropdown')[0];
                 $(menu_dom).css({
                     display: '',
                     width: $scope.action_context_width,
@@ -510,7 +510,7 @@ angular.module('egGridMod',
                 $scope.action_context_x = $(menu_dom).css('left');
                 $scope.action_context_parent = $(menu_dom).parent();
 
-                $($scope.grid_element).append($(menu_dom));
+                $('body').append($(menu_dom));
                 $('body').bind('click.remove_context_menu', $scope.hideActionContextMenu);
 
                 $(menu_dom).css({

commit c03bbc226c2f5346f459f5513f025fb749a63f88
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 9 16:33:47 2015 -0500

    LP#1402797 Grid row context menu from Actions dropdown
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/share/t_autogrid.tt2 b/Open-ILS/src/templates/staff/share/t_autogrid.tt2
index 7db04a9..03aa65e 100644
--- a/Open-ILS/src/templates/staff/share/t_autogrid.tt2
+++ b/Open-ILS/src/templates/staff/share/t_autogrid.tt2
@@ -69,7 +69,7 @@
       <button type="button" class="btn btn-default dropdown-toggle">
         [% l('Actions') %] <span class="caret"></span>                       
       </button>                                                              
-      <ul class="dropdown-menu pull-right">                                  
+      <ul class="dropdown-menu pull-right grid-action-dropdown">                                  
         <li ng-repeat="action in actions" ng-class="{divider: action.divider}" ng-hide="actionHide(action)">
           <a ng-if="!action.divider" href
             ng-click="actionLauncher(action)">{{action.label}}</a>
diff --git a/Open-ILS/web/js/ui/default/staff/services/grid.js b/Open-ILS/web/js/ui/default/staff/services/grid.js
index 3b8a467..f5fbc4e 100644
--- a/Open-ILS/web/js/ui/default/staff/services/grid.js
+++ b/Open-ILS/web/js/ui/default/staff/services/grid.js
@@ -78,6 +78,11 @@ angular.module('egGridMod',
             // load auto fields after eg-grid-field's so they are not clobbered
             scope.handleAutoFields();
             scope.collect();
+
+            scope.grid_element = element;
+            $(element)
+                .find('.eg-grid-content-body')
+                .bind('contextmenu', scope.showActionContextMenu);
         },
 
         controller : [
@@ -470,15 +475,52 @@ angular.module('egGridMod',
                 if (!action.handler) {
                     console.error(
                         'No handler specified for "' + action.label + '"');
-                    return;
-                }
+                } else {
 
-                try {
-                    action.handler(grid.getSelectedItems());
-                } catch(E) {
-                    console.error('Error executing handler for "' 
-                        + action.label + '" => ' + E + "\n" + E.stack);
+                    try {
+                        action.handler(grid.getSelectedItems());
+                    } catch(E) {
+                        console.error('Error executing handler for "' 
+                            + action.label + '" => ' + E + "\n" + E.stack);
+                    }
+
+                    if ($scope.action_context_y || $scope.action_context_x)
+                        $scope.hideActionContextMenu();
                 }
+
+            }
+
+            $scope.hideActionContextMenu = function () {
+                var menu_dom = $($scope.grid_element).find('.grid-action-dropdown')[0];
+                $(menu_dom).css({
+                    display: '',
+                    width: $scope.action_context_width,
+                    top: $scope.action_context_y,
+                    left: $scope.action_context_x
+                });
+                $($scope.action_context_parent).append(menu_dom);
+                $scope.action_context_oldy = $scope.action_context_oldx = 0;
+                $('body').unbind('click.remove_context_menu');
+            }
+
+            $scope.showActionContextMenu = function ($event) {
+                var menu_dom = $($scope.grid_element).find('.grid-action-dropdown')[0];
+                $scope.action_context_width = $(menu_dom).css('width');
+                $scope.action_context_y = $(menu_dom).css('top');
+                $scope.action_context_x = $(menu_dom).css('left');
+                $scope.action_context_parent = $(menu_dom).parent();
+
+                $($scope.grid_element).append($(menu_dom));
+                $('body').bind('click.remove_context_menu', $scope.hideActionContextMenu);
+
+                $(menu_dom).css({
+                    display: 'block',
+                    width: $scope.action_context_width,
+                    top: $event.pageY,
+                    left: $event.pageX
+                });
+
+                return false;
             }
 
             // returns the list of selected item objects

commit aa0e52b253d866e3b4400ba79eca64d1fc1403da
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Mon Feb 9 15:44:10 2015 -0500

    LP#1402797 add a Retrieve All These Patrons button
    
    in the Item Status -> Circ History List
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/item/t_circ_list_pane.tt2 b/Open-ILS/src/templates/staff/cat/item/t_circ_list_pane.tt2
index 9f7bbb5..05ee925 100644
--- a/Open-ILS/src/templates/staff/cat/item/t_circ_list_pane.tt2
+++ b/Open-ILS/src/templates/staff/cat/item/t_circ_list_pane.tt2
@@ -3,6 +3,16 @@
     [% l('Item has not circulated.') %]
   </div>
 </div>
+<div class="row" ng-show="circ_list.length">
+  <div class="flex-row">
+      <div class="flex-cell well">
+          <button class="btn btn-default" ng-click="retrieveAllPatrons()">
+            [% l('Retrieve All These Patrons') %]
+          </button>
+      </div>
+  </div>
+</div>
+
 
 <div class="row" ng-repeat="circ in circ_list">
   <div class="flex-row">
diff --git a/Open-ILS/web/js/ui/default/staff/cat/item/app.js b/Open-ILS/web/js/ui/default/staff/cat/item/app.js
index 82ed923..a7d08fb 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/item/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/item/app.js
@@ -210,8 +210,8 @@ function($scope , $q , $location , $timeout , egCore , egGridDataProvider , item
  * Detail view -- shows one copy
  */
 .controller('ViewCtrl', 
-       ['$scope','$q','$location','$routeParams','egCore','itemSvc','egBilling',
-function($scope , $q , $location , $routeParams , egCore , itemSvc , egBilling) {
+       ['$scope','$q','$location','$routeParams','$timeout','$window','egCore','itemSvc','egBilling',
+function($scope , $q , $location , $routeParams , $timeout , $window , egCore , itemSvc , egBilling) {
     var copyId = $routeParams.id;
     $scope.tab = $routeParams.tab || 'summary';
     $scope.context.page = 'detail';
@@ -366,6 +366,21 @@ function($scope , $q , $location , $routeParams , egCore , itemSvc , egBilling)
         });
     }
 
+    $scope.retrieveAllPatrons = function() {
+        var users = new Set();
+        angular.forEach($scope.circ_list.map(function(circ) { return circ.usr(); }),function(usr) {
+            users.add(usr);
+        });
+        users.forEach(function(usr) {
+            $timeout(function() {
+                var url = $location.absUrl().replace(
+                    /\/cat\/.*/,
+                    '/circ/patron/' + usr.id() + '/checkout');
+                $window.open(url, '_blank')
+            });
+        });
+    }
+
     function loadCircHistory() {
         $scope.circ_list = [];
 

commit cb44f54f562f3077249e0206da2a96af3f171810
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 9 14:54:53 2015 -0500

    LP#1402797 Add user-related fields to some hold interfaces
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2
index b21146d..c233b24 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2
@@ -80,7 +80,11 @@
     <eg-grid-field label="[% l('Queue Position') %]" path='queue_position' hidden></eg-grid-field>
     <eg-grid-field path='hold.*' parent-idl-class="ahr" hidden></eg-grid-field>
     <eg-grid-field path='copy.*' parent-idl-class="acp" hidden></eg-grid-field>
-    <eg-grid-field path='hold.usr.*' parent-idl-class="acp" hidden></eg-grid-field>
+    <eg-grid-field path='hold.usr.*' parent-idl-class="ahr" hidden></eg-grid-field>
+    <eg-grid-field path='hold.usr.card.*' parent-idl-class="ahr" hidden></eg-grid-field>
+    <eg-grid-field path='hold.requestor.*' parent-idl-class="ahr" hidden></eg-grid-field>
+    <eg-grid-field path='hold.requestor.card.*' parent-idl-class="ahr" hidden></eg-grid-field>
+
     <eg-grid-field path='volume.*' parent-idl-class="acn" hidden></eg-grid-field>
     <eg-grid-field path='mvr.*' parent-idl-class="mvr" hidden></eg-grid-field>
   </eg-grid>
diff --git a/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2 b/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2
index 8cea1d1..f803a1d 100644
--- a/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2
+++ b/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2
@@ -81,6 +81,10 @@
 
   <eg-grid-field label="[% l('Queue Position') %]" path='queue_position' hidden></eg-grid-field>
   <eg-grid-field path='hold.*' parent-idl-class="ahr" hidden></eg-grid-field>
+  <eg-grid-field path='hold.usr.*' parent-idl-class="ahr" hidden></eg-grid-field>
+  <eg-grid-field path='hold.usr.card.*' parent-idl-class="ahr" hidden></eg-grid-field>
+  <eg-grid-field path='hold.requestor.*' parent-idl-class="ahr" hidden></eg-grid-field>
+  <eg-grid-field path='hold.requestor.card.*' parent-idl-class="ahr" hidden></eg-grid-field>
   <eg-grid-field path='copy.*' parent-idl-class="acp" hidden></eg-grid-field>
   <eg-grid-field path='volume.*' parent-idl-class="acn" hidden></eg-grid-field>
   <eg-grid-field path='mvr.*' parent-idl-class="mvr" hidden></eg-grid-field>
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
index c2e63f5..bca5020 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
@@ -74,6 +74,8 @@
   <eg-grid-field path='copy.*' parent-idl-class="acp" hidden></eg-grid-field>
   <eg-grid-field path='hold.usr.*' parent-idl-class="ahr" hidden></eg-grid-field>
   <eg-grid-field path='hold.usr.card.*' parent-idl-class="ahr" hidden></eg-grid-field>
+  <eg-grid-field path='hold.requestor.*' parent-idl-class="ahr" hidden></eg-grid-field>
+  <eg-grid-field path='hold.requestor.card.*' parent-idl-class="ahr" hidden></eg-grid-field>
   <eg-grid-field path='volume.*' parent-idl-class="acn" hidden></eg-grid-field>
   <eg-grid-field path='mvr.*' parent-idl-class="mvr" hidden></eg-grid-field>
 </eg-grid>

commit 5eaa585a6371537124988026a8332ae406eead74
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 9 13:24:11 2015 -0500

    LP#1402797 Add missing columns to patron search result grid
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_search_results.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_search_results.tt2
index ac83e6d..56d3651 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_search_results.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_search_results.tt2
@@ -27,4 +27,5 @@
   <eg-grid-field label="[% ('Billing:County') %]" path='billing_address.county'></eg-grid-field>
   <eg-grid-field label="[% ('Billing:State') %]" path='billing_address.state'></eg-grid-field>
   <eg-grid-field label="[% ('Billing:Zip') %]" path='billing_address.post_code'></eg-grid-field>
+  <eg-grid-field path='*' ignore="id family_name first_given_name second_given_name dob create_date"></eg-grid-field>
 </eg-grid>

commit 444eb1599fe6fd9bd9666a29d4802a7dbe598411
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 9 13:06:10 2015 -0500

    LP#1402797 Remove Uncancel Hold action where not useful
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2
index 13d5bf9..b21146d 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_holds.tt2
@@ -50,8 +50,6 @@
       label="[% l('Find Another Target') %]"></eg-grid-action>
     <eg-grid-action handler="grid_actions.cancel_hold"
       label="[% l('Cancel Hold') %]"></eg-grid-action>
-    <eg-grid-action handler="grid_actions.uncancel_hold"
-      label="[% l('Uncancel Hold') %]"></eg-grid-action>
 
     <eg-grid-field label="[% l('Hold ID') %]" path='hold.id'></eg-grid-field>
     <eg-grid-field label="[% l('Current Copy') %]" 
diff --git a/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2 b/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2
index 33aee3e..8cea1d1 100644
--- a/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2
+++ b/Open-ILS/src/templates/staff/circ/holds/t_pull_list.tt2
@@ -53,8 +53,6 @@
     label="[% l('Find Another Target') %]"></eg-grid-action>
   <eg-grid-action handler="grid_actions.cancel_hold"
     label="[% l('Cancel Hold') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.uncancel_hold"
-    label="[% l('Uncancel Hold') %]"></eg-grid-action>
 
   <eg-grid-field label="[% l('Hold ID') %]" path='hold.id'></eg-grid-field>
   <eg-grid-field label="[% l('Current Copy') %]" 

commit eda33b0387c30e41378741d0e696bb593630364a
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 9 12:51:01 2015 -0500

    LP#1402797 Hide cancel/uncancel as appropriate
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
index 914c52f..c2e63f5 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
@@ -37,9 +37,9 @@
   <eg-grid-action divider="true"></eg-grid-action>
   <eg-grid-action handler="grid_actions.retarget"
     label="[% l('Find Another Target') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.cancel_hold"
+  <eg-grid-action handler="grid_actions.cancel_hold" hide="hide_cancel_hold"
     label="[% l('Cancel Hold') %]"></eg-grid-action>
-  <eg-grid-action handler="grid_actions.uncancel_hold"
+  <eg-grid-action handler="grid_actions.uncancel_hold" hide="hide_uncancel_hold"
     label="[% l('Uncancel Hold') %]"></eg-grid-action>
 
   <eg-grid-field label="[% l('Hold ID') %]" path='hold.id'></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/holds.js b/Open-ILS/web/js/ui/default/staff/circ/patron/holds.js
index c42a162..9c2101f 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/holds.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/holds.js
@@ -38,6 +38,14 @@ function($scope,  $q,  $routeParams,  egCore,  egUser,  patronSvc,
         provider.refresh();
     }
 
+    $scope.hide_cancel_hold = function(action) { 
+        return $scope.holds_display == 'alt';
+    }
+
+    $scope.hide_uncancel_hold = function(action) {
+        return !$scope.hide_cancel_hold();
+    }
+
     var provider = egGridDataProvider.instance({});
     $scope.gridDataProvider = provider;
 

commit 50ac8524e2fd5e326f3a942cc672425a088aea6d
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 9 12:50:44 2015 -0500

    LP#1402797 Teach the autogrid how to hide actions
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/share/t_autogrid.tt2 b/Open-ILS/src/templates/staff/share/t_autogrid.tt2
index 912b644..7db04a9 100644
--- a/Open-ILS/src/templates/staff/share/t_autogrid.tt2
+++ b/Open-ILS/src/templates/staff/share/t_autogrid.tt2
@@ -70,7 +70,7 @@
         [% l('Actions') %] <span class="caret"></span>                       
       </button>                                                              
       <ul class="dropdown-menu pull-right">                                  
-        <li ng-repeat="action in actions" ng-class="{divider: action.divider}">
+        <li ng-repeat="action in actions" ng-class="{divider: action.divider}" ng-hide="actionHide(action)">
           <a ng-if="!action.divider" href
             ng-click="actionLauncher(action)">{{action.label}}</a>
         </li>
diff --git a/Open-ILS/web/js/ui/default/staff/services/grid.js b/Open-ILS/web/js/ui/default/staff/services/grid.js
index 50c2786..3b8a467 100644
--- a/Open-ILS/web/js/ui/default/staff/services/grid.js
+++ b/Open-ILS/web/js/ui/default/staff/services/grid.js
@@ -457,6 +457,14 @@ angular.module('egGridMod',
                 return ''+item; 
             }
 
+            // fires the hide handler function for a context action
+            $scope.actionHide = function(action) {
+                if (!action.hide) {
+                    return false;
+                }
+                return action.hide(action);
+            }
+
             // fires the action handler function for a context action
             $scope.actionLauncher = function(action) {
                 if (!action.handler) {
@@ -896,10 +904,12 @@ angular.module('egGridMod',
         scope : {
             label   : '@', // Action label
             handler : '=',  // Action function handler
+            hide    : '=',
             divider : '='
         },
         link : function(scope, element, attrs, egGridCtrl) {
             egGridCtrl.addAction({
+                hide  : scope.hide,
                 label : scope.label,
                 divider : scope.divider,
                 handler : scope.handler

commit ec079a18611e8ccbea55bbb79dced247415e7a9b
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Fri Feb 6 17:25:10 2015 -0500

    LP#1402797 remove Group Actions from Group Member Details
    
    place "Move Another Patron To This Group" under Actions
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_group.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_group.tt2
index 8725652..aa273ea 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_group.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_group.tt2
@@ -12,11 +12,10 @@
 <eg-grid
   idl-class="au"
   sort="gridSort"
-  grid-controls="gridControls"
-  menu-label="[% l('Group Actions') %]">
+  grid-controls="gridControls">
 
-  <eg-grid-menu-item handler="moveToGroup"
-    label="[% l('Move Another Patron To This Group') %]"></eg-grid-menu-item>
+  <eg-grid-action handler="moveToGroup"
+    label="[% l('Move Another Patron To This Group') %]"></eg-grid-action>
 
   <eg-grid-action 
     label="[% l('Register a New Group Member by Cloning Selected Patron') %]" 

commit d8c3b3e30c8a3c34088e443b096bb2362e4f35ce
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Fri Feb 6 16:33:51 2015 -0500

    LP#1402797 change Retrieve Selected Patron
    
    to Retrieve Selected Patrons for Other -> Group Member Details interface
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_group.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_group.tt2
index 21bb4c2..8725652 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_group.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_group.tt2
@@ -28,7 +28,7 @@
   <eg-grid-action label="[% l("Move Selected Patrons to Another Patron's Group") %]" 
     handler="moveToAnotherGroup"></eg-grid-action>
 
-  <eg-grid-action label="[% l("Retrieve Selected Patron") %]" 
+  <eg-grid-action label="[% l("Retrieve Selected Patrons") %]"
     handler="retrieveSelected"></eg-grid-action>
 
   <eg-grid-field path="active"></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
index 14a9bb8..70247fd 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
@@ -1322,9 +1322,9 @@ function($scope,  $routeParams , $location , egCore , patronSvc , $modal) {
 }])
 
 .controller('PatronGroupCtrl',
-       ['$scope','$routeParams','$q','$window','$location','egCore',
+       ['$scope','$routeParams','$q','$window','$timeout','$location','egCore',
         'patronSvc','$modal','egPromptDialog','egConfirmDialog',
-function($scope,  $routeParams , $q , $window , $location , egCore ,
+function($scope,  $routeParams , $q , $window , $timeout,  $location , egCore ,
          patronSvc , $modal , egPromptDialog , egConfirmDialog) {
 
     var usr_id = $routeParams.id;
@@ -1483,10 +1483,14 @@ function($scope,  $routeParams , $q , $window , $location , egCore ,
 
     $scope.retrieveSelected = function(selected) {
         if (!selected.length) return;
-        var url = $location.absUrl().replace(
-            /\/patron\/.*/, 
-            '/patron/' + selected[0].id + '/checkout');
-        $window.open(url, '_blank').focus();
+        angular.forEach(selected, function(usr) {
+            $timeout(function() {
+                var url = $location.absUrl().replace(
+                    /\/patron\/.*/,
+                    '/patron/' + usr.id + '/checkout');
+                $window.open(url, '_blank')
+            });
+        });
     }
 
 }])

commit c62601137eceaeca988cf2c0da24157af076e788
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Feb 5 16:21:20 2015 -0500

    LP#1402797 Context menu infrastructure
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
index 8ba6b4a..8f53a54 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
@@ -4,12 +4,12 @@
 
 angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 
-.directive("egContextMenuItem", function () {
+.directive("egContextMenuItem", ['$timeout',function ($timeout) {
     return {
         restrict: 'E',
-        replace: false,
-        template: '<li ng-click="setContent(item.value,item.action)">{{label}}</li>',
-        scope: { item: '=' },
+        replace: true,
+        template: '<li><a ng-click="setContent(item.value,item.action)">{{item.label}}</a></li>',
+        scope: { item: '=', content: '=' },
         controller: ['$scope','$element',
             function ($scope , $element) {
                 if (!$scope.item.label) $scope.item.label = $scope.item.value;
@@ -18,64 +18,104 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                 }
 
                 $scope.setContent = function (v, a) {
-                    if (a) { v = a($scope,v); }
-                    $scope.$apply("item.value=v");
-                    $( $($scope.element).parent() ).parent().empty().remove();
-                    $scope.$parent.$destroy();
+                    var replace_with = v;
+                    if (a) replace_with = a($scope,$element,$scope.item.value,$scope.$parent.$parent.content);
+                    $timeout(function(){
+                        $scope.$parent.$parent.$apply(function(){
+                            $scope.$parent.$parent.content = replace_with
+                        })
+                    }, 0);
+                    console.log('well, replaced it');
+                    $($element).parent().css({display: 'none'});
                 }
             }
         ]
     }
-})
-
-.directive("egMarcEditEditable", ['$document', function ($document) {
-    function showContext(event) {
-        event.preventDefault();
-        var con = event.data.scope.contextitems;
-
-        if (angular.isArray(con)) { // we have a list of values or transforms
-            var tmpl = 
-            '<div class="dropdown" dropdown is-open="true">'+
-                '<ul class="dropdown-menu" role="menu">'+
-                    '<eg-context-menu-item ng-repeat="item in contextitems" item="item"/>'+
-                '</ul>'+
-            '</div>';
-
-            var el = $compile(tmpl)(event.data);
-            el.css({
-                postion: 'absolute',
-                top: event.pageY,
-                left: event.pageX
-            });
-
-            $document.append(el);
-        }
-    }
+}])
 
+.directive("egMarcEditEditable", ['$timeout', '$compile', '$document', function ($timeout, $compile, $document) {
     return {
         restrict: 'E',
         replace: false,
-        template: '<input style="font-family: \'Lucida Console\', Monaco, monospace;" ng-model="content" size="{{content.length * 1.1}}" maxlength="{{max}}" class="" type="text"/>',
         transclude: true,
+        template: '<input style="font-family: \'Lucida Console\', Monaco, monospace;" ng-model="content" size="{{content.length * 1.1}}" maxlength="{{max}}" class="" type="text"/>',
         scope: {
+            field: '=',
+            subfield: '=',
             content: '=',
-            //contextitems: '=',
+            contextItemContainer: '@',
             max: '@',
             type: '@'
         },
-//        controller : ['$scope',
-//            function ( $scope ) {
-//                $scope.minsize = $scope.max || $scope.content.length;
-//                if (!$scope.contextitems) $scope.contextitems = {};
-//            }
-//        ],
-        link: function (scope, element, attrs) {
+        controller : ['$scope',
+            function ( $scope ) {
+
+/* XXX Example, for testing.  We'll get this from the tag-table services for realz
+ *
+                if (!$scope.contextItemContainer) {
+                    $scope.contextItemContainer = "default_context";
+                    $scope[$scope.contextItemContainer] = [
+                        { value: 'a' },
+                        { value: 'b' },
+                        { value: 'c' },
+                    ];
+                }
+
+ *
+ */
 
-            element.bind('change', {}, function (e) { element.size = scope.max || scope.content.length * 1.1});
+                if ($scope.contextItemContainer)
+                    $scope.item_container = $scope[$scope.contextItemContainer];
+
+                $scope.showContext = function (event) {
+                    if ($scope.context_menu_element) {
+                        console.log('Reshowing context menu...');
+                        $($scope.context_menu_element).css({ display: 'block', top: event.pageY, left: event.pageX });
+                        return false;
+                    }
+
+                    if (angular.isArray($scope.item_container)) { // we have a list of values or transforms
+                        console.log('Showing context menu...');
+
+                        var tmpl = 
+                            '<ul class="dropdown-menu" role="menu">'+
+                                '<eg-context-menu-item ng-repeat="item in item_container" item="item" content="content"/>'+
+                            '</ul>';
+            
+                        var tnode = angular.element(tmpl);
+                        console.log('... got element ...');
+
+                        $document.find('body').append(tnode);
+                        console.log('... attached to DOM ...');
+
+                        $(tnode).css({
+                            display: 'block',
+                            top: event.pageY,
+                            left: event.pageX
+                        });
+                        console.log('... displayed ...');
+
+                        $scope.context_menu_element = tnode;
+                        console.log('... captured for later ...');
+
+                        $timeout(function() {
+                            var e = $compile(tnode)($scope);
+                            console.log('... compiled: ' + e);
+                        }, 0);
+
+                        return false;
+                    }
+            
+                    return true;
+                }
 
-            if (scope.contextitems && angular.isArray(scope.contextitems)) {
-                element.bind('context', { scope : scope, element : element }, showContext);
             }
+        ],
+        link: function (scope, element, attrs) {
+
+            element.bind('change', function (e) { element.size = scope.max || parseInt(scope.content.length * 1.1) });
+            if (scope.contextItemContainer && angular.isArray(scope[scope.contextItemContainer]))
+                element.bind('contextmenu', scope.showContext);
         }
     }
 }])
@@ -84,8 +124,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
     return {
         restrict: 'E',
         template: '<span>'+
-                    '<span><eg-marc-edit-editable type="sfc" class="marcsfcode" content="subfield[0]" max="1"/></span>'+
-                    '<span><eg-marc-edit-editable type="sfv" class="marcsfvalue" content="subfield[1]"/></span>'+
+                    '<span><eg-marc-edit-editable type="sfc" class="marcsfcode" field="field" subfield="subfield" content="subfield[0]" max="1"/></span>'+
+                    '<span><eg-marc-edit-editable type="sfv" class="marcsfvalue" field="field" subfield="subfield" content="subfield[1]"/></span>'+
                   '</span>',
         scope: { field: "=", subfield: "=" },
         replace: false
@@ -95,8 +135,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 .directive("egMarcEditInd", function () {
     return {
         restrict: 'E',
-        template: '<span><eg-marc-edit-editable type="ind" content="ind" max="1"/></span>',
-        scope: { ind : '=' },
+        template: '<span><eg-marc-edit-editable type="ind" field="field" content="ind" max="1"/></span>',
+        scope: { ind : '=', field: '=' },
         replace: false,
     }
 })
@@ -104,8 +144,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 .directive("egMarcEditTag", function () {
     return {
         restrict: 'E',
-        template: '<span><eg-marc-edit-editable type="tag" content="tag" max="3"/></span>',
-        scope: { tag : '=' },
+        template: '<span><eg-marc-edit-editable type="tag" field="field" content="tag" max="3"/></span>',
+        scope: { tag : '=', field: '=' },
         replace: false
     }
 })
@@ -114,9 +154,9 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
     return {
         restrict: 'E',
         template: '<div>'+
-                    '<span><eg-marc-edit-tag class="marctag" tag="field.tag"/></span>'+
-                    '<span><eg-marc-edit-ind class="marcind" ind="field.ind1"/></span>'+
-                    '<span><eg-marc-edit-ind class="marcind" ind="field.ind2"/></span>'+
+                    '<span><eg-marc-edit-tag class="marctag" field="field" tag="field.tag"/></span>'+
+                    '<span><eg-marc-edit-ind class="marcind" field="field" ind="field.ind1"/></span>'+
+                    '<span><eg-marc-edit-ind class="marcind" field="field" ind="field.ind2"/></span>'+
                     '<span><eg-marc-edit-subfield ng-repeat="subfield in field.subfields" subfield="subfield" field="field"/></span>'+
                   '</div>',
         scope: { field: "=" }
@@ -127,8 +167,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
     return {
         restrict: 'E',
         template: '<div>'+
-                    '<span><eg-marc-edit-tag class="marctag" tag="field.tag"/></span>'+
-                    '<span><eg-marc-edit-editable type="cfld" class="marcdata" content="field.data"/></span>'+
+                    '<span><eg-marc-edit-tag class="marctag" field="field" tag="field.tag"/></span>'+
+                    '<span><eg-marc-edit-editable type="cfld" field="field" class="marcdata" content="field.data"/></span>'+
                   '</div>',
         scope: { field: "=" }
     }
@@ -139,7 +179,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         restrict: 'E',
         template: '<div>'+
                     '<span><eg-marc-edit-editable class="marctag" content="tag"/></span>'+
-                    '<span><eg-marc-edit-editable class="marcdata" max="{{record.leader.length}}" content="record.leader"/></span>'+
+                    '<span><eg-marc-edit-editable class="marcdata" type="ldr" max="{{record.leader.length}}" content="record.leader"/></span>'+
                   '</div>',
         controller : ['$scope',
             function ( $scope ) {

commit 1c9b48f215c582c260d0d3b838cc86cf872dcecc
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Feb 4 22:46:20 2015 -0500

    LP#1402797 Initial MARC editor -- load, edit content, save, see the Breaker version
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/index.tt2 b/Open-ILS/src/templates/staff/cat/catalog/index.tt2
index 919b900..56a4ea9 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/index.tt2
@@ -9,6 +9,7 @@
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/record.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/marcedit.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/circ.js"></script>
 [% INCLUDE 'staff/circ/share/circ_strings.tt2' %]
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/holds.js"></script>
diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
index 29a6eb6..972fa62 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
@@ -2,6 +2,7 @@
 <div class="row pad-vert col-md-12 alert alert-info alert-less-pad strong-text-2">
   <span ng-if="record_tab == 'catalog'">[% l('Catalog') %]</span>
   <span ng-if="record_tab == 'marc_html'">[% l('MARC HTML') %]</span>
+  <span ng-if="record_tab == 'marc_edit'">[% l('Edit MARC') %]</span>
   <span ng-if="record_tab == 'holds'">[% l('Holds for Record') %]</span>
 </div>
 
@@ -22,6 +23,8 @@
       <ul class="dropdown-menu dropdown-menu-right" role="menu">
         <li><a href ng-click="set_record_tab('catalog')">
             [% l('OPAC View') %]</a></li>
+        <li><a href ng-click="set_record_tab('marc_edit')">
+            [% l('MARC Edit') %]</a></li>
         <li><a href ng-click="set_record_tab('marc_html')">
             [% l('MARC View') %]</a></li>
         <li class="divider"></li>
@@ -44,6 +47,9 @@
     <eg-embed-frame url="catalog_url" handlers="handlers" onchange="handle_page"></eg-embed-frame>
   </div>
   <!-- ng-if the remaining tabs so they can be instantiated on demand -->
+  <div ng-if="record_tab == 'marc_edit'">
+    <eg-marc-edit-record record-id="record_id"></eg-marc-edit-record>
+  </div>
   <div ng-if="record_tab == 'marc_html'">
     <eg-record-html record-id="record_id"></eg-record-html>
   </div>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
index 181a9ff..4cd81d7 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
@@ -7,7 +7,7 @@
  *
  */
 
-angular.module('egCatalogApp', ['ui.bootstrap','ngRoute','egCoreMod','egGridMod'])
+angular.module('egCatalogApp', ['ui.bootstrap','ngRoute','egCoreMod','egGridMod', 'egMarcMod'])
 
 .config(function($routeProvider, $locationProvider, $compileProvider) {
     $locationProvider.html5Mode(true);
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
new file mode 100644
index 0000000..8ba6b4a
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
@@ -0,0 +1,214 @@
+/**
+ *  A MARC editor...
+ */
+
+angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
+
+.directive("egContextMenuItem", function () {
+    return {
+        restrict: 'E',
+        replace: false,
+        template: '<li ng-click="setContent(item.value,item.action)">{{label}}</li>',
+        scope: { item: '=' },
+        controller: ['$scope','$element',
+            function ($scope , $element) {
+                if (!$scope.item.label) $scope.item.label = $scope.item.value;
+                if ($scope.item.divider) {
+                    $element.style.borderTop = 'solid 1px';
+                }
+
+                $scope.setContent = function (v, a) {
+                    if (a) { v = a($scope,v); }
+                    $scope.$apply("item.value=v");
+                    $( $($scope.element).parent() ).parent().empty().remove();
+                    $scope.$parent.$destroy();
+                }
+            }
+        ]
+    }
+})
+
+.directive("egMarcEditEditable", ['$document', function ($document) {
+    function showContext(event) {
+        event.preventDefault();
+        var con = event.data.scope.contextitems;
+
+        if (angular.isArray(con)) { // we have a list of values or transforms
+            var tmpl = 
+            '<div class="dropdown" dropdown is-open="true">'+
+                '<ul class="dropdown-menu" role="menu">'+
+                    '<eg-context-menu-item ng-repeat="item in contextitems" item="item"/>'+
+                '</ul>'+
+            '</div>';
+
+            var el = $compile(tmpl)(event.data);
+            el.css({
+                postion: 'absolute',
+                top: event.pageY,
+                left: event.pageX
+            });
+
+            $document.append(el);
+        }
+    }
+
+    return {
+        restrict: 'E',
+        replace: false,
+        template: '<input style="font-family: \'Lucida Console\', Monaco, monospace;" ng-model="content" size="{{content.length * 1.1}}" maxlength="{{max}}" class="" type="text"/>',
+        transclude: true,
+        scope: {
+            content: '=',
+            //contextitems: '=',
+            max: '@',
+            type: '@'
+        },
+//        controller : ['$scope',
+//            function ( $scope ) {
+//                $scope.minsize = $scope.max || $scope.content.length;
+//                if (!$scope.contextitems) $scope.contextitems = {};
+//            }
+//        ],
+        link: function (scope, element, attrs) {
+
+            element.bind('change', {}, function (e) { element.size = scope.max || scope.content.length * 1.1});
+
+            if (scope.contextitems && angular.isArray(scope.contextitems)) {
+                element.bind('context', { scope : scope, element : element }, showContext);
+            }
+        }
+    }
+}])
+
+.directive("egMarcEditSubfield", function () {
+    return {
+        restrict: 'E',
+        template: '<span>'+
+                    '<span><eg-marc-edit-editable type="sfc" class="marcsfcode" content="subfield[0]" max="1"/></span>'+
+                    '<span><eg-marc-edit-editable type="sfv" class="marcsfvalue" content="subfield[1]"/></span>'+
+                  '</span>',
+        scope: { field: "=", subfield: "=" },
+        replace: false
+    }
+})
+
+.directive("egMarcEditInd", function () {
+    return {
+        restrict: 'E',
+        template: '<span><eg-marc-edit-editable type="ind" content="ind" max="1"/></span>',
+        scope: { ind : '=' },
+        replace: false,
+    }
+})
+
+.directive("egMarcEditTag", function () {
+    return {
+        restrict: 'E',
+        template: '<span><eg-marc-edit-editable type="tag" content="tag" max="3"/></span>',
+        scope: { tag : '=' },
+        replace: false
+    }
+})
+
+.directive("egMarcEditDatafield", function () {
+    return {
+        restrict: 'E',
+        template: '<div>'+
+                    '<span><eg-marc-edit-tag class="marctag" tag="field.tag"/></span>'+
+                    '<span><eg-marc-edit-ind class="marcind" ind="field.ind1"/></span>'+
+                    '<span><eg-marc-edit-ind class="marcind" ind="field.ind2"/></span>'+
+                    '<span><eg-marc-edit-subfield ng-repeat="subfield in field.subfields" subfield="subfield" field="field"/></span>'+
+                  '</div>',
+        scope: { field: "=" }
+    }
+})
+
+.directive("egMarcEditControlfield", function () {
+    return {
+        restrict: 'E',
+        template: '<div>'+
+                    '<span><eg-marc-edit-tag class="marctag" tag="field.tag"/></span>'+
+                    '<span><eg-marc-edit-editable type="cfld" class="marcdata" content="field.data"/></span>'+
+                  '</div>',
+        scope: { field: "=" }
+    }
+})
+
+.directive("egMarcEditLeader", function () {
+    return {
+        restrict: 'E',
+        template: '<div>'+
+                    '<span><eg-marc-edit-editable class="marctag" content="tag"/></span>'+
+                    '<span><eg-marc-edit-editable class="marcdata" max="{{record.leader.length}}" content="record.leader"/></span>'+
+                  '</div>',
+        controller : ['$scope',
+            function ( $scope ) {
+                $scope.tag = 'LDR';
+            }
+        ],
+        scope: { record: "=" }
+    }
+})
+
+/// TODO: fixed field editor and such
+.directive("egMarcEditRecord", function () {
+    return {
+        template: '<form ng-submit="saveRecord()">'+
+                  '<div class="marcrecord">'+
+                    '<div><eg-marc-edit-leader record="record"/></div>'+
+                    '<div><eg-marc-edit-controlfield ng-repeat="field in controlfields" field="field"/></div>'+
+                    '<div><eg-marc-edit-datafield ng-repeat="field in datafields" field="field"/></div>'+
+                  '</div>'+
+                  '<button class="btn btn-default" type="submit">Save</button>'+
+                  '</form>'+
+                  '<button class="btn btn-default" ng-click="seeBreaker()">Breaker</button>',
+        restrict: 'E',
+        replace: false,
+        scope: { recordId : '=' },
+        controller : ['$scope','egCore',
+            function ( $scope , egCore ) {
+
+                function loadRecord() {
+                    return egCore.pcrud.retrieve(
+                        'bre', $scope.recordId
+                    ).then(function(rec) {
+                        $scope.bre = rec;
+                        $scope.record = new MARC.Record();
+                        $scope.record.fromXmlString( $scope.bre.marc() );
+                        $scope.controlfields = $scope.record.fields.filter(function(f){ return f.isControlfield() });
+                        $scope.datafields = $scope.record.fields.filter(function(f){ return !f.isControlfield() });
+                    });
+                }
+
+                $scope.saveRecord = function () {
+                    $scope.bre.marc($scope.record.toXmlString());
+                    return egCore.pcrud.update(
+                        $scope.bre
+                    ).then(loadRecord);
+                };
+
+                $scope.seeBreaker = function () {
+                    alert($scope.record.toBreaker());
+                };
+
+                $scope.$watch('recordId',
+                    function(newVal, oldVal) {
+                        if (newVal && newVal !== oldVal) {
+                            loadRecord();
+                        }
+                    }
+                );
+
+
+                $scope.controlfields = [];
+                $scope.datafields = [];
+
+                if ($scope.recordId)
+                    loadRecord();
+
+            }
+        ]          
+    }
+})
+
+;
diff --git a/Open-ILS/web/js/ui/default/staff/marcrecord.js b/Open-ILS/web/js/ui/default/staff/marcrecord.js
index 9dd0a23..876540a 100644
--- a/Open-ILS/web/js/ui/default/staff/marcrecord.js
+++ b/Open-ILS/web/js/ui/default/staff/marcrecord.js
@@ -118,7 +118,7 @@ var MARC = {
         this.fromXmlURL = function (url) {
             this.ready   = false;
             var me = this;
-            $.get(
+            return $.get( // This is a Promise
                 url,
                 function (mxml) {
                     me.fromXmlDocument($('record', mxml)[0]);
@@ -128,7 +128,7 @@ var MARC = {
         },
 
         this.fromXmlString = function (mxml) {
-                this.fromXmlDocument( $.parseXML( mxml ).find('record')[0] );
+                this.fromXmlDocument( $( $.parseXML( mxml ) ).find('record')[0] );
         },
 
         this.fromXmlDocument = function (mxml) {

commit c0ae713a572bb89d57c72645d81a8ab8aaf75d95
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 2 17:07:24 2015 -0500

    LP#1402797 Use jquery xml parser, and appendChild()
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/marcrecord.js b/Open-ILS/web/js/ui/default/staff/marcrecord.js
index 0802558..9dd0a23 100644
--- a/Open-ILS/web/js/ui/default/staff/marcrecord.js
+++ b/Open-ILS/web/js/ui/default/staff/marcrecord.js
@@ -167,12 +167,12 @@ var MARC = {
 
         this.toXmlDocument = function () {
 
-            var doc = DOMParser('<record xmlns="http://www.loc.gov/MARC21/slim"/>');
+            var doc = $.parseXML('<record xmlns="http://www.loc.gov/MARC21/slim"/>');
             var rec_node = $('record', doc)[0];
 
             var ldr = doc.createElementNS('http://www.loc.gov/MARC21/slim', 'leader');
             ldr.textContent = this.leader;
-            rec_node.append( ldr );
+            rec_node.appendChild( ldr );
 
             this.fields.forEach(function (f) {
                 var element = f.isControlfield() ? 'controlfield' : 'datafield';
@@ -192,7 +192,7 @@ var MARC = {
                     });
                 }
 
-                rec_node.append(f_node);
+                rec_node.appendChild(f_node);
             });
 
             return doc;

commit a89ae52d90e3598bca4e35fed896298c528f334c
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 2 16:50:28 2015 -0500

    LP#1402797 Initial commit and sourcing of the jquery-ified marcrecord.js module
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/base_js.tt2 b/Open-ILS/src/templates/staff/base_js.tt2
index 9cf9d69..8947ca3 100644
--- a/Open-ILS/src/templates/staff/base_js.tt2
+++ b/Open-ILS/src/templates/staff/base_js.tt2
@@ -3,6 +3,7 @@
 [% IF EXPAND_WEB_IMPORTS %]
 
 <!-- angular -->
+<script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/angular.min.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/angular-route.min.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/ui-bootstrap-tpls.min.js"></script>
diff --git a/Open-ILS/src/templates/staff/cat/catalog/index.tt2 b/Open-ILS/src/templates/staff/cat/catalog/index.tt2
index 9e799d4..919b900 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/index.tt2
@@ -5,6 +5,7 @@
 %]
 
 [% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/marcrecord.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/record.js"></script>
diff --git a/Open-ILS/web/js/ui/default/staff/marcrecord.js b/Open-ILS/web/js/ui/default/staff/marcrecord.js
new file mode 100644
index 0000000..0802558
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/marcrecord.js
@@ -0,0 +1,2270 @@
+/* ---------------------------------------------------------------------------
+ * Copyright (C) 2009-2015  Equinox Software, Inc.
+ * Mike Rylander <miker at esilibrary.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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.
+ * ---------------------------------------------------------------------------
+ */
+
+// !!! Head's up !!!
+// This module requires a /real/ jQuery be used with your
+// angularjs, so as to avoid using hand-rolled AJAX.
+
+var MARC = {
+
+    Record : function(kwargs) {
+        if (!kwargs) kwargs = {};
+
+        this.title = function () { return this.subfield('245','a')[1] }
+
+        this.field = function (spec) {
+            var list = this.fields.filter(function (f) {
+                if (f.tag.match(spec)) return true;
+                return false;
+            });
+
+            if (list.length == 1) return list[0];
+            return list;
+        }
+
+        this.subfield = function (spec, code) {
+            var f = this.field(spec);
+            if (Array.isArray(f)) {
+                if (!f.length) return f;
+                f = f[0];
+            }
+            return f.subfield(code)
+        }
+
+        this.appendFields = function () {
+            var me = this;
+            Array.prototype.slice.call(arguments).forEach( function (f) { me.fields.push( f ) } );
+        }
+
+        this.deleteField = function (f) { return this.deleteFields(f) },
+
+        this.insertOrderedFields = function () {
+            var me = this;
+            for (var i = 0; i < arguments.length; i++) {
+                var f = arguments[i];
+                var done = false;
+                for (var j = 0; j < this.fields.length; j++) {
+                    if (f.tag < this.fields[j].tag) {
+                        this.insertFieldsBefore(this.fields[j], f);
+                        done = true;
+                        break;
+                    }
+                }
+                if (!done) this.appendFields(f);
+            }
+        }
+
+        this.insertFieldsBefore = function (target) {
+            var args = Array.prototype.slice.call(arguments);
+            args.splice(0,1);
+            var me = this;
+            for (var j = 0; j < this.fields.length; j++) {
+                if (target === this.fields[j]) {
+                    j--;
+                    args.forEach( function (f) {
+                        me.fields.splice(j++,0,f);
+                    });
+                    break;
+                }
+            }
+        }
+
+        this.insertFieldsAfter = function (target) {
+            var args = Array.prototype.slice.call(arguments);
+            args.splice(0,1);
+            var me = this;
+            for (var j = 0; j < this.fields.length; j++) {
+                if (target === this.fields[j]) {
+                    args.forEach( function (f) {
+                        me.fields.splice(j++,0,f);
+                    });
+                    break;
+                }
+            }
+        }
+
+        this.deleteFields = function () {
+            var me = this;
+            var counter = 0;
+            for ( var i in arguments ) {
+                var f = arguments[i];
+                for (var j = 0; j < me.fields.length; j++) {
+                    if (f === me.fields[j]) {
+                        me.fields[j].record = null;
+                        me.fields.splice(j,0);
+                        counter++
+                        break;
+                    }
+                }
+            }
+            return counter;
+        }
+
+        // this.clone = function () { return dojo.clone(this) } // maybe implement later...
+
+        this.fromXmlURL = function (url) {
+            this.ready   = false;
+            var me = this;
+            $.get(
+                url,
+                function (mxml) {
+                    me.fromXmlDocument($('record', mxml)[0]);
+                    me.ready = true;
+                    if (me.onLoad) me.onLoad();
+            });
+        },
+
+        this.fromXmlString = function (mxml) {
+                this.fromXmlDocument( $.parseXML( mxml ).find('record')[0] );
+        },
+
+        this.fromXmlDocument = function (mxml) {
+            var me = this;
+            me.leader = $($('leader',mxml)[0]).text() || '00000cam a2200205Ka 4500';
+
+            $('controlfield', mxml).each(function () {
+                var cf=$(this);
+                me.fields.push(
+                    new MARC.Field({
+                          record : me,
+                          tag    : cf.attr('tag'),
+                          data   : cf.text()
+                    })
+                )
+            });
+
+            $('datafield', mxml).each(function () {
+                var df=$(this);
+                me.fields.push(
+                    new MARC.Field({
+                        record    : me,
+                        tag       : df.attr('tag'),
+                        ind1      : df.attr('ind1'),
+                        ind2      : df.attr('ind2'),
+                        subfields : $('subfield', df).map(
+                            function (i, sf) {
+                                return [[ $(sf).attr('code'), $(sf).text() ]];
+                            }
+                        ).get()
+                    })
+                )
+            });
+
+        },
+
+        this.toXmlDocument = function () {
+
+            var doc = DOMParser('<record xmlns="http://www.loc.gov/MARC21/slim"/>');
+            var rec_node = $('record', doc)[0];
+
+            var ldr = doc.createElementNS('http://www.loc.gov/MARC21/slim', 'leader');
+            ldr.textContent = this.leader;
+            rec_node.append( ldr );
+
+            this.fields.forEach(function (f) {
+                var element = f.isControlfield() ? 'controlfield' : 'datafield';
+                var f_node = doc.createElementNS( 'http://www.loc.gov/MARC21/slim', element );
+                f_node.setAttribute('tag', f.tag);
+                
+                if (f.isControlfield()) {
+                    if (f.data) f_node.textContent = f.data;
+                } else {
+                    f_node.setAttribute('ind1', f.indicator(1));
+                    f_node.setAttribute('ind2', f.indicator(2));
+                    f.subfields.forEach( function (sf) {
+                        var sf_node = doc.createElementNS('http://www.loc.gov/MARC21/slim', 'subfield');
+                        sf_node.setAttribute('code', sf[0]);
+                        sf_node.textContent = sf[1];
+                        f_node.appendChild(sf_node);
+                    });
+                }
+
+                rec_node.append(f_node);
+            });
+
+            return doc;
+        },
+
+        this.toXmlString = function () {
+            return (new XMLSerializer()).serializeToString( this.toXmlDocument() );
+        },
+
+        this.fromBreaker = function (marctxt) {
+            var me = this;
+
+            function cf_line_data (l) { return l.substring(4) || '' };
+            function df_line_data (l) { return l.substring(6) || '' };
+            function line_tag (l) { return l.substring(0,3) };
+            function df_ind1 (l) { return l.substring(4,5).replace('\\',' ') };
+            function df_ind2 (l) { return l.substring(5,6).replace('\\',' ') };
+            function isControlField (l) {
+                var x = line_tag(l);
+                return (x == 'LDR' || x < '010') ? true : false;
+            }
+            
+            var lines = marctxt.replace(/^=/gm,'').split('\n');
+            lines.forEach(function (current_line) {
+
+                if (current_line.match(/^#/)) {
+                    // skip comment lines
+                } else if (isControlField(current_line)) {
+                    if (line_tag(current_line) == 'LDR') {
+                        me.leader = cf_line_data(current_line) || '00000cam a2200205Ka 4500';
+                    } else {
+                        me.fields.push(
+                            new MARC.Field({
+                                record : me,
+                                tag    : line_tag(current_line),
+                                data   : cf_line_data(current_line).replace('\\',' ','g')
+                            })
+                        );
+                    }
+                } else {
+                    if (current_line.substring(4,5) == me.delimiter) // add delimiters if they were left out
+                        current_line = current_line.substring(0,3) + ' \\\\' + current_line.substring(4);
+
+                    var data = df_line_data(current_line);
+                    if (!(data.substring(0,1) == me.delimiter)) data = me.delimiter + 'a' + data;
+
+                    var sf_list = data.split(me.delimiter);
+                    sf_list.shift();
+
+                    me.fields.push(
+                        new MARC.Field({
+                                record    : me,
+                                tag       : line_tag(current_line),
+                                ind1      : df_ind1(current_line),
+                                ind2      : df_ind2(current_line),
+                                subfields : sf_list.map( function (sf) {
+                                                var sf_data = sf.substring(1);
+                                                if (me.delimiter == '$') sf_data = sf_data.replace(/\{dollar\}/g, '$');
+                                                return [ sf.substring(0,1), sf_data ];
+                                            })
+                        })
+                    );
+                }
+            });
+
+            return this;
+        },
+
+        this.toBreaker = function () {
+
+            var me = this;
+            var mtxt = '=LDR ' + this.leader + '\n';
+
+            mtxt += this.fields.map( function (f) {
+                if (f.isControlfield()) {
+                    if (f.data) return '=' + f.tag + ' ' + f.data.replace(' ','\\','g');
+                    return '=' + f.tag;
+                } else {
+                    return '=' + f.tag + ' ' +
+                        f.indicator(1).replace(' ','\\') + 
+                        f.indicator(2).replace(' ','\\') + 
+                        f.subfields.map( function (sf) {
+                            var sf_data = sf[1];
+                            if (me.delimiter == '$') sf_data = sf_data.replace(/\$/g, '{dollar}');
+                            return me.delimiter + sf[0] + sf_data;
+                        }).join('');
+                }
+            }).join('\n');
+
+            return mtxt;
+        }
+
+        this.recordType = function () {
+        
+            var _t = this.leader.substr(MARC.Record._ff_pos.Type.ldr.BKS.start, MARC.Record._ff_pos.Type.ldr.BKS.len);
+            var _b = this.leader.substr(MARC.Record._ff_pos.BLvl.ldr.BKS.start, MARC.Record._ff_pos.BLvl.ldr.BKS.len);
+        
+            for (var t in MARC.Record._recType) {
+                if (_t.match(MARC.Record._recType[t].Type) && _b.match(MARC.Record._recType[t].BLvl)) {
+                    return t;
+                }
+            }
+            return 'BKS'; // default
+        }
+        
+        this.videorecordingFormatName = function () {
+            var _7 = this.field('007').data;
+        
+            if (_7 && _7.match(/^v/)) {
+                var _v_e = _7.substr(
+                    MARC.Record._physical_characteristics.v.subfields.e.start,
+                    MARC.Record._physical_characteristics.v.subfields.e.len
+                );
+        
+                return MARC.Record._physical_characteristics.v.subfields.e.values[ _v_e ];
+            }
+        
+            return null;
+        }
+        
+        this.videorecordingFormatCode = function () {
+            var _7 = this.field('007').data;
+        
+            if (_7 && _7.match(/^v/)) {
+                return _7.substr(
+                    MARC.Record._physical_characteristics.v.subfields.e.start,
+                    MARC.Record._physical_characteristics.v.subfields.e.len
+                );
+            }
+        
+            return null;
+        }
+        
+        this.extractFixedField = function (field, dflt) {
+        if (!MARC.Record._ff_pos[field]) return null;
+        
+            var _l = this.leader;
+            var _8 = this.field('008').data;
+            var _6 = this.field('006').data;
+        
+            var rtype = this.recordType();
+        
+            var val;
+        
+            if (MARC.Record._ff_pos[field].ldr && _l) {
+                if (MARC.Record._ff_pos[field].ldr[rtype]) {
+                    val = _l.substr(
+                        MARC.Record._ff_pos[field].ldr[rtype].start,
+                        MARC.Record._ff_pos[field].ldr[rtype].len
+                    );
+                }
+            } else if (MARC.Record._ff_pos[field]._8 && _8) {
+                if (MARC.Record._ff_pos[field]._8[rtype]) {
+                    val = _8.substr(
+                        MARC.Record._ff_pos[field]._8[rtype].start,
+                        MARC.Record._ff_pos[field]._8[rtype].len
+                    );
+                }
+            }
+        
+            if (!val && MARC.Record._ff_pos[field]._6 && _6) {
+                if (MARC.Record._ff_pos[field]._6[rtype]) {
+                    val = _6.substr(
+                        MARC.Record._ff_pos[field]._6[rtype].start,
+                        MARC.Record._ff_pos[field]._6[rtype].len
+                    );
+                }
+            }
+    
+            if (!val && dflt) {
+                val = '';
+                var d;
+                var p;
+                if (MARC.Record._ff_pos[field].ldr && MARC.Record._ff_pos[field].ldr[rtype]) {
+                    d = MARC.Record._ff_pos[field].ldr[rtype].def;
+                    p = 'ldr';
+                }
+    
+                if (MARC.Record._ff_pos[field]._8 && MARC.Record._ff_pos[field]._8[rtype]) {
+                    d = MARC.Record._ff_pos[field]._8[rtype].def;
+                    p = '_8';
+                }
+    
+                if (!val && MARC.Record._ff_pos[field]._6 && MARC.Record._ff_pos[field]._6[rtype]) {
+                    d = MARC.Record._ff_pos[field]._6[rtype].def;
+                    p = '_6';
+                }
+    
+                if (p) {
+                    for (var j = 0; j < MARC.Record._ff_pos[field][p][rtype].len; j++) {
+                        val += d;
+                    }
+                } else {
+                    val = null;
+                }
+            }
+    
+            return val;
+        }
+    
+        this.setFixedField = function (field, value) {
+            if (!MARC.Record._ff_pos[field]) return null;
+        
+            var _l = this.leader;
+            var _8 = this.field('008').data;
+            var _6 = this.field('006').data;
+        
+            var rtype = this.recordType();
+        
+            var val;
+        
+            if (MARC.Record._ff_pos[field].ldr && _l) {
+                if (MARC.Record._ff_pos[field].ldr[rtype]) { // It's in the leader
+                    val = value.substr(0, MARC.Record._ff_pos[field].ldr[rtype].len);
+                    this.leader =
+                        _l.substring(0, MARC.Record._ff_pos[field].ldr[rtype].start) +
+                        val +
+                        _l.substring(
+                            MARC.Record._ff_pos[field].ldr[rtype].start
+                            + MARC.Record._ff_pos[field].ldr[rtype].len
+                        );
+                }
+            } else if (MARC.Record._ff_pos[field]._8 && _8) {
+                if (MARC.Record._ff_pos[field]._8[rtype]) { // Nope, it's in the 008
+                    val = value.substr(0, MARC.Record._ff_pos[field]._8[rtype].len);
+                    this.field('008').update(
+                        _8.substring(0, MARC.Record._ff_pos[field]._8[rtype].start) +
+                        val +
+                        _8.substring(
+                            MARC.Record._ff_pos[field]._8[rtype].start
+                            + MARC.Record._ff_pos[field]._8[rtype].len
+                        )
+                    );
+                }
+            }
+        
+            if (!val && MARC.Record._ff_pos[field]._6 && _6) {
+                if (MARC.Record._ff_pos[field]._6[rtype]) { // ok, maybe the 006?
+                    val = value.substr(0, MARC.Record._ff_pos[field]._6[rtype].len);
+                    this.field('006').update(
+                        _6.substring(0, MARC.Record._ff_pos[field]._6[rtype].start) +
+                        val +
+                        _6.substring(
+                            MARC.Record._ff_pos[field]._6[rtype].start
+                            + MARC.Record._ff_pos[field]._6[rtype].len
+                        )
+                    );
+                }
+            }
+    
+            return val;
+        }
+
+        this.fields = [];
+        this.delimiter = '\u2021';
+        this.leader = '00000cam a2200205Ka 4500';
+
+        if (kwargs.delimiter) this.delimiter = kwargs.delimiter;
+        if (kwargs.onLoad) this.onLoad = kwargs.onLoad;
+
+        if (kwargs.url) {
+            this.fromXmlURL(kwargs.url);
+        } else if (kwargs.marcxml) {
+            this.fromXmlString(kwargs.marcxml);
+            if (this.onLoad) this.onLoad();
+        } else if (kwargs.xml) {
+            this.fromXmlDocument(kwargs.xml);
+            if (this.onLoad) this.onLoad();
+        } else if (kwargs.marcbreaker) {
+            this.fromBreaker(kwargs.marcbreaker);
+            if (this.onLoad) this.onLoad();
+        }
+
+        if (kwargs.rtype == 'AUT') {
+            this.setFixedField('Type','z');
+        }
+
+    },
+
+    Field : function (kwargs) {
+        if (!kwargs) kwargs = {};
+
+        this.subfield = function (code) {
+            var list = this.subfields.filter( function (s) {
+                if (s[0] == code) return true; return false;
+            });
+            if (list.length == 1) return list[0];
+            return list;
+        }
+
+        this.addSubfields = function () {
+            for (var i = 0; i < arguments.length; i++) {
+                var code = arguments[i];
+                var value = arguments[++i];
+                this.subfields.push( [ code, value ] );
+            }
+        }
+
+        this.deleteSubfields = function (c) {
+            return this.deleteSubfield( { code : c } );
+        }
+
+        this.deleteSubfield = function (args) {
+            var me = this;
+            if (!Array.isArray( args.code )) {
+                args.code = [ args.code ];
+            }
+
+            if (args.pos && !Array.isArray( args.pos )) {
+                args.pos = [ args.pos ];
+            }
+
+            for (var i = 0; i < args.code.length; i++) {
+                var sub_pos = {};
+                for (var j = 0; j < me.subfields; j++) {
+                    if (me.subfields[j][0] == args.code[i]) {
+
+                        if (!sub_pos[args.code[i]]) sub_pos[args.code[j]] = 0;
+                        else sub_pos[args.code[i]]++;
+
+                        if (args.pos) {
+                            for (var k = 0; k < args.pos.length; k++) {
+                                if (sub_pos[args.code[i]] == args.pos[k]) me.subfields.splice(j,1);
+                            }
+                        } else if (args.match && me.subfields[j][1].match( args.match )) {
+                            me.subfields.splice(j,1);
+                        } else {
+                            me.subfields.splice(j,1);
+                        }
+                    }
+                }
+            }
+        }
+
+        this.update = function ( args ) {
+            if (this.isControlfield()) {
+                this.data = args;
+            } else {
+                if (args.ind1) this.ind1 = args.ind1;
+                if (args.ind2) this.ind2 = args.ind2;
+                if (args.tag) this.tag = args.tag;
+
+                for (var i in args) {
+                    if (i == 'tag' || i == 'ind1' || i == 'ind2') continue;
+                    var done = 0;
+                    this.subfields.forEach( function (f) {
+                        if (!done && f[0] == i) {
+                            f[1] = args[i];
+                            done = 1;
+                        }
+                    });
+                }
+            }
+        }
+
+        this.isControlfield = function () {
+            return this.tag < '010' ? true : false;
+        }
+
+        this.indicator = function (num, value) {
+            if (value !== undefined) {
+                if (num == 1) this.ind1 = value;
+                else if (num == 2) this.ind2 = value;
+                else { this.error = true; return null; }
+            }
+            if (num == 1) return this.ind1;
+            else if (num == 2) return this.ind2;
+            else { this.error = true; return null; }
+        }
+
+        this.error = false; 
+        this.record = null; // MARC record pointer
+        this.tag = ''; // MARC tag
+        this.ind1 = ' '; // MARC indicator 1
+        this.ind2 = ' '; // MARC indicator 2
+        this.data = ''; // MARC data for a controlfield element
+        this.subfields = []; // list of MARC subfields for a datafield element
+
+        this.record = kwargs.record;
+        this.tag = kwargs.tag;
+        this.ind1 = kwargs.ind1 || ' ';
+        this.ind2 = kwargs.ind2 || ' ';
+        this.data = kwargs.data;
+
+        if (kwargs.subfields) this.subfields = kwargs.subfields;
+        else this.subfields = [];
+
+    },
+
+    Batch : function(kwargs) {
+
+        this.parse = function () {
+            if (this.source instanceof Object ) { // assume an xml collection document
+                this.source = $('record', this.source);
+                this.type = 'xml';
+            } else if (this.source.match(/^\s*</)) { // this is xml text
+                this.source = $.parseXML( mxml ).find('record');
+                this.type = 'xml';
+            } else { // must be a marcbreaker doc. split on blank lines
+                this.source = this.source.split(/^$/);
+                this.type = 'marcbreaker';
+            }
+        }
+
+        this.fetchURL = function (u) {
+            var me = this;
+            $.get( u, function (mrc) {
+                me.source = mrc;
+                me.ready = true;
+            });
+        }
+
+        this.next = function () {
+            var chunk = this.source[this.current_record++];
+
+            if (chunk) {
+                var args = {};
+                args[this.type] = chunk;
+                if (this.delimiter) args.delimiter = this.delimiter;
+                return new MARC.Record(args);
+            }
+
+            return null;
+        }
+
+        this.ready = false;
+        this.records = [];
+        this.source = kwargs.source;
+        this.delimiter = kwargs.delimiter
+        this.current_record = 0;
+
+        if (this.source) this.ready = true;
+        if (!this.ready && kwargs.url) this.fetchURL( kwargs.url );
+
+        if (this.ready) this.parse();
+
+    }
+};
+
+MARC.Record._recType = {
+    BKS : { Type : /[at]{1}/,    BLvl : /[acdm]{1}/ },
+    SER : { Type : /[a]{1}/,    BLvl : /[bsi]{1}/ },
+    VIS : { Type : /[gkro]{1}/,    BLvl : /[abcdmsi]{1}/ },
+    MIX : { Type : /[p]{1}/,    BLvl : /[cdi]{1}/ },
+    MAP : { Type : /[ef]{1}/,    BLvl : /[abcdmsi]{1}/ },
+    SCO : { Type : /[cd]{1}/,    BLvl : /[abcdmsi]{1}/ },
+    REC : { Type : /[ij]{1}/,    BLvl : /[abcdmsi]{1}/ },
+    COM : { Type : /[m]{1}/,    BLvl : /[abcdmsi]{1}/ },
+    AUT : { Type : /[z]{1}/,    BLvl : /.{1}/ },
+    MFHD : { Type : /[uvxy]{1}/,  BLvl : /.{1}/ }
+};
+
+MARC.Record._ff_pos = {
+    AccM : {
+        _8 : {
+            SCO : {start: 24, len : 6, def : ' ' },
+            REC : {start: 24, len : 6, def : ' ' }
+        },
+        _6 : {
+            SCO : {start: 7, len : 6, def : ' ' },
+            REC : {start: 7, len : 6, def : ' ' }
+        }
+    },
+    Alph : {
+        _8 : {
+            SER : {start : 33, len : 1, def : ' ' }
+        },
+        _6 : {
+            SER : {start : 16, len : 1, def : ' ' }
+        }
+    },
+    Audn : {
+        _8 : {
+            BKS : {start : 22, len : 1, def : ' ' },
+            SER : {start : 22, len : 1, def : ' ' },
+            VIS : {start : 22, len : 1, def : ' ' },
+            SCO : {start : 22, len : 1, def : ' ' },
+            REC : {start : 22, len : 1, def : ' ' },
+            COM : {start : 22, len : 1, def : ' ' }
+        },
+        _6 : {
+            BKS : {start : 5, len : 1, def : ' ' },
+            SER : {start : 5, len : 1, def : ' ' },
+            VIS : {start : 5, len : 1, def : ' ' },
+            SCO : {start : 5, len : 1, def : ' ' },
+            REC : {start : 5, len : 1, def : ' ' },
+            COM : {start : 5, len : 1, def : ' ' }
+        }
+    },
+    Biog : {
+        _8 : {
+            BKS : {start : 34, len : 1, def : ' ' }
+        },
+        _6 : {
+            BKS : {start : 17, len : 1, def : ' ' }
+        }
+    },
+    BLvl : {
+        ldr : {
+            BKS : {start : 7, len : 1, def : 'm' },
+            SER : {start : 7, len : 1, def : 's' },
+            VIS : {start : 7, len : 1, def : 'm' },
+            MIX : {start : 7, len : 1, def : 'c' },
+            MAP : {start : 7, len : 1, def : 'm' },
+            SCO : {start : 7, len : 1, def : 'm' },
+            REC : {start : 7, len : 1, def : 'm' },
+            COM : {start : 7, len : 1, def : 'm' }
+        }
+    },
+    Comp : {
+        _8 : {
+            SCO : {start : 18, len : 2, def : 'uu'},
+            REC : {start : 18, len : 2, def : 'uu'}
+        },
+        _6 : {
+            SCO : {start : 1, len : 2, def : 'uu'},
+            REC : {start : 1, len : 2, def : 'uu'}
+        },
+    },
+    Conf : {
+        _8 : {
+            BKS : {start : 29, len : 1, def : '0' },
+            SER : {start : 29, len : 1, def : '0' }
+        },
+        _6 : {
+            BKS : {start : 11, len : 1, def : '0' },
+            SER : {start : 11, len : 1, def : '0' }
+        }
+    },
+    Cont : {
+        _8 : {
+            BKS : {start : 24, len : 4, def : ' ' },
+            SER : {start : 25, len : 3, def : ' ' }
+        },
+        _6 : {
+            BKS : {start : 7, len : 4, def : ' ' },
+            SER : {start : 8, len : 3, def : ' ' }
+        }
+    },
+    CrTp : {
+        _8 : {
+            MAP : {start: 25, len : 1, def : 'a' }
+        },
+        _6 : { 
+            MAP : {start : 8, len : 1, def : 'a' }
+        }
+    },
+    Ctrl : {
+        ldr : {
+            BKS : {start : 8, len : 1, def : ' ' },
+            SER : {start : 8, len : 1, def : ' ' },
+            VIS : {start : 8, len : 1, def : ' ' },
+            MIX : {start : 8, len : 1, def : ' ' },
+            MAP : {start : 8, len : 1, def : ' ' },
+            SCO : {start : 8, len : 1, def : ' ' },
+            REC : {start : 8, len : 1, def : ' ' },
+            COM : {start : 8, len : 1, def : ' ' }
+        }
+    },
+    Ctry : {
+            _8 : {
+                BKS : {start : 15, len : 3, def : ' ' },
+                SER : {start : 15, len : 3, def : ' ' },
+                VIS : {start : 15, len : 3, def : ' ' },
+                MIX : {start : 15, len : 3, def : ' ' },
+                MAP : {start : 15, len : 3, def : ' ' },
+                SCO : {start : 15, len : 3, def : ' ' },
+                REC : {start : 15, len : 3, def : ' ' },
+                COM : {start : 15, len : 3, def : ' ' }
+            }
+        },
+    Date1 : {
+        _8 : {
+            BKS : {start : 7, len : 4, def : ' ' },
+            SER : {start : 7, len : 4, def : ' ' },
+            VIS : {start : 7, len : 4, def : ' ' },
+            MIX : {start : 7, len : 4, def : ' ' },
+            MAP : {start : 7, len : 4, def : ' ' },
+            SCO : {start : 7, len : 4, def : ' ' },
+            REC : {start : 7, len : 4, def : ' ' },
+            COM : {start : 7, len : 4, def : ' ' }
+        }
+    },
+    Date2 : {
+        _8 : {
+            BKS : {start : 11, len : 4, def : ' ' },
+            SER : {start : 11, len : 4, def : '9' },
+            VIS : {start : 11, len : 4, def : ' ' },
+            MIX : {start : 11, len : 4, def : ' ' },
+            MAP : {start : 11, len : 4, def : ' ' },
+            SCO : {start : 11, len : 4, def : ' ' },
+            REC : {start : 11, len : 4, def : ' ' },
+            COM : {start : 11, len : 4, def : ' ' }
+        }
+    },
+    Desc : {
+        ldr : {
+            BKS : {start : 18, len : 1, def : ' ' },
+            SER : {start : 18, len : 1, def : ' ' },
+            VIS : {start : 18, len : 1, def : ' ' },
+            MIX : {start : 18, len : 1, def : ' ' },
+            MAP : {start : 18, len : 1, def : ' ' },
+            SCO : {start : 18, len : 1, def : ' ' },
+            REC : {start : 18, len : 1, def : ' ' },
+            COM : {start : 18, len : 1, def : ' ' }
+        }
+    },
+    DtSt : {
+        _8 : {
+            BKS : {start : 6, len : 1, def : ' ' },
+            SER : {start : 6, len : 1, def : 'c' },
+            VIS : {start : 6, len : 1, def : ' ' },
+            MIX : {start : 6, len : 1, def : ' ' },
+            MAP : {start : 6, len : 1, def : ' ' },
+            SCO : {start : 6, len : 1, def : ' ' },
+            REC : {start : 6, len : 1, def : ' ' },
+            COM : {start : 6, len : 1, def : ' ' }
+        }
+    },
+    ELvl : {
+        ldr : {
+            BKS : {start : 17, len : 1, def : ' ' },
+            SER : {start : 17, len : 1, def : ' ' },
+            VIS : {start : 17, len : 1, def : ' ' },
+            MIX : {start : 17, len : 1, def : ' ' },
+            MAP : {start : 17, len : 1, def : ' ' },
+            SCO : {start : 17, len : 1, def : ' ' },
+            REC : {start : 17, len : 1, def : ' ' },
+            COM : {start : 17, len : 1, def : ' ' },
+            AUT : {start : 17, len : 1, def : 'n' },
+            MFHD : {start : 17, len : 1, def : 'u' }
+        }
+    },
+    EntW : {
+        _8 : {
+            SER : {start : 24, len : 1, def : ' '}
+        },
+        _6 : {
+            SER : {start : 7, len : 1, def : ' '}
+        }
+    },
+    Fest : {
+        _8 : {
+            BKS : {start : 30, len : 1, def : '0' }
+        },
+        _6 : {
+            BKS : {start : 13, len : 1, def : '0' }
+        }
+    },
+    File : {
+        _8 : {
+            COM : {start: 26, len : 1, def : 'u' }
+        },
+        _6 : {
+            COM : {start: 9, len : 1, def : 'u' }
+        }
+    },
+    FMus : {
+        _8 : {
+            SCO : {start : 20, len : 1, def : 'u'},
+            REC : {start : 20, len : 1, def : 'n'}
+        },
+        _6 : {
+            SCO : {start : 3, len : 1, def : 'u'},
+            REC : {start : 3, len : 1, def : 'n'}
+        },
+    },
+    Form : {
+        _8 : {
+            BKS : {start : 23, len : 1, def : ' ' },
+            SER : {start : 23, len : 1, def : ' ' },
+            VIS : {start : 29, len : 1, def : ' ' },
+            MIX : {start : 23, len : 1, def : ' ' },
+            MAP : {start : 29, len : 1, def : ' ' },
+            SCO : {start : 23, len : 1, def : ' ' },
+            REC : {start : 23, len : 1, def : ' ' }
+        },
+        _6 : {
+            BKS : {start : 6, len : 1, def : ' ' },
+            SER : {start : 6, len : 1, def : ' ' },
+            VIS : {start : 12, len : 1, def : ' ' },
+            MIX : {start : 6, len : 1, def : ' ' },
+            MAP : {start : 12, len : 1, def : ' ' },
+            SCO : {start : 6, len : 1, def : ' ' },
+            REC : {start : 6, len : 1, def : ' ' }
+        }
+    },
+    Freq : {
+        _8 : {
+            SER : {start : 18, len : 1, def : ' '}
+        },
+        _6 : {
+            SER : {start : 1, len : 1, def : ' '}
+        }
+    },
+    GPub : {
+        _8 : {
+            BKS : {start : 28, len : 1, def : ' ' },
+            SER : {start : 28, len : 1, def : ' ' },
+            VIS : {start : 28, len : 1, def : ' ' },
+            MAP : {start : 28, len : 1, def : ' ' },
+            COM : {start : 28, len : 1, def : ' ' }
+        },
+        _6 : {
+            BKS : {start : 11, len : 1, def : ' ' },
+            SER : {start : 11, len : 1, def : ' ' },
+            VIS : {start : 11, len : 1, def : ' ' },
+            MAP : {start : 11, len : 1, def : ' ' },
+            COM : {start : 11, len : 1, def : ' ' }
+        }
+    },
+    Ills : {
+        _8 : {
+            BKS : {start : 18, len : 4, def : ' ' }
+        },
+        _6 : {
+            BKS : {start : 1, len : 4, def : ' ' }
+        }
+    },
+    Indx : {
+        _8 : {
+            BKS : {start : 31, len : 1, def : '0' },
+            MAP : {start : 31, len : 1, def : '0' }
+        },
+        _6 : {
+            BKS : {start : 14, len : 1, def : '0' },
+            MAP : {start : 14, len : 1, def : '0' }
+        }
+    },
+    Item : {
+        ldr : {
+            MFHD : {start : 18, len : 1, def : 'i' }
+        }
+    },
+    Lang : {
+        _8 : {
+            BKS : {start : 35, len : 3, def : ' ' },
+            SER : {start : 35, len : 3, def : ' ' },
+            VIS : {start : 35, len : 3, def : ' ' },
+            MIX : {start : 35, len : 3, def : ' ' },
+            MAP : {start : 35, len : 3, def : ' ' },
+            SCO : {start : 35, len : 3, def : ' ' },
+            REC : {start : 35, len : 3, def : ' ' },
+            COM : {start : 35, len : 3, def : ' ' }
+        }
+    },
+    LitF : {
+        _8 : {
+            BKS : {start : 33, len : 1, def : '0' }
+        },
+        _6 : {
+            BKS : {start : 16, len : 1, def : '0' }
+        }
+    },
+    LTxt : {
+        _8 : {
+            SCO : {start : 30, len : 2, def : 'n'},
+            REC : {start : 30, len : 2, def : ' '}
+        },
+        _6 : {
+            SCO : {start : 13, len : 2, def : 'n'},
+            REC : {start : 13, len : 2, def : ' '}
+        },
+    },
+    MRec : {
+        _8 : {
+            BKS : {start : 38, len : 1, def : ' ' },
+            SER : {start : 38, len : 1, def : ' ' },
+            VIS : {start : 38, len : 1, def : ' ' },
+            MIX : {start : 38, len : 1, def : ' ' },
+            MAP : {start : 38, len : 1, def : ' ' },
+            SCO : {start : 38, len : 1, def : ' ' },
+            REC : {start : 38, len : 1, def : ' ' },
+            COM : {start : 38, len : 1, def : ' ' }
+        }
+    },
+    Orig : {
+        _8 : {
+            SER : {start : 22, len : 1, def : ' '}
+        },
+        _6 : {
+            SER : {start: 5, len : 1, def: ' '}
+        }
+    },
+    Part : {
+        _8 : {
+            SCO : {start : 21, len : 1, def : ' '},
+            REC : {start : 21, len : 1, def : 'n'}
+        },
+        _6 : {
+            SCO : {start : 4, len : 1, def : ' '},
+            REC : {start : 4, len : 1, def : 'n'}
+        },
+    },
+    Proj : {
+        _8 : {
+            MAP : {start : 22, len : 2, def : ' ' }
+        },
+        _6 : {
+            MAP: {start : 5, len : 2, def : ' ' }
+        }
+    },
+    RecStat : {
+        ldr : {
+            BKS : {start : 5, len : 1, def : 'n' },
+            SER : {start : 5, len : 1, def : 'n' },
+            VIS : {start : 5, len : 1, def : 'n' },
+            MIX : {start : 5, len : 1, def : 'n' },
+            MAP : {start : 5, len : 1, def : 'n' },
+            SCO : {start : 5, len : 1, def : 'n' },
+            REC : {start : 5, len : 1, def : 'n' },
+            COM : {start : 5, len : 1, def : 'n' },
+            MFHD: {start : 5, len : 1, def : 'n' },
+            AUT : {start : 5, len : 1, def : 'n' }
+        }
+    },
+    Regl : {
+        _8 : {
+            SER : {start : 19, len : 1, def : ' '}
+        },
+        _6 : {
+            SER : {start : 2, len : 1, def : ' '}
+        }
+    },
+    Relf : {
+        _8 : {
+            MAP : {start: 18, len : 4, def : ' '}
+        },
+        _6 : {
+            MAP : {start: 1, len : 4, def : ' '}
+        }
+    },
+    'S/L' : {
+        _8 : {
+            SER : {start : 34, len : 1, def : '0' }
+        },
+        _6 : {
+            SER : {start : 17, len : 1, def : '0' }
+        }
+    },
+    SpFM : {
+        _8 : {
+            MAP : {start: 33, len : 2, def : ' ' }
+        },
+        _6 : {
+            MAP : {start: 16, len : 2, def : ' '}
+        }
+    },
+    Srce : {
+        _8 : {
+            BKS : {start : 39, len : 1, def : 'd' },
+            SER : {start : 39, len : 1, def : 'd' },
+            VIS : {start : 39, len : 1, def : 'd' },
+            SCO : {start : 39, len : 1, def : 'd' },
+            REC : {start : 39, len : 1, def : 'd' },
+            COM : {start : 39, len : 1, def : 'd' },
+            MFHD : {start : 39, len : 1, def : 'd' },
+            "AUT" : {"start" : 39, "len" : 1, "def" : 'd' }
+        }
+    },
+    SrTp : {
+        _8 : {
+            SER : {start : 21, len : 1, def : ' '}
+        },
+        _6 : {
+            SER : {start : 4, len : 1, def : ' '}
+        }
+    },
+    Tech : {
+        _8 : {
+            VIS : {start : 34, len : 1, def : ' '}
+        },
+        _6 : {
+            VIS : {start : 17, len : 1, def : ' '}
+        }
+    },
+    Time : {
+        _8 : {
+            VIS : {start : 18, len : 3, def : ' '}
+        },
+        _6 : {
+            VIS : {start : 1, len : 3, def : ' '}
+        }
+    },
+    TMat : {
+        _8 : {
+            VIS : {start : 33, len : 1, def : ' ' }
+        },
+        _6 : {
+            VIS : {start : 16, len : 1, def : ' ' }
+        }
+    },
+    TrAr : {
+        _8 : {
+            SCO : {start : 33, len : 1, def : ' ' },
+            REC : {start : 33, len : 1, def : 'n' }
+        },
+        _6 : {
+            SCO : {start : 16, len : 1, def : ' ' },
+            REC : {start : 16, len : 1, def : 'n' }
+        }
+    },
+    Type : {
+        ldr : {
+            BKS : {start : 6, len : 1, def : 'a' },
+            SER : {start : 6, len : 1, def : 'a' },
+            VIS : {start : 6, len : 1, def : 'g' },
+            MIX : {start : 6, len : 1, def : 'p' },
+            MAP : {start : 6, len : 1, def : 'e' },
+            SCO : {start : 6, len : 1, def : 'c' },
+            REC : {start : 6, len : 1, def : 'i' },
+            COM : {start : 6, len : 1, def : 'm' },
+            AUT : {start : 6, len : 1, def : 'z' },
+            MFHD : {start : 6, len : 1, def : 'y' }
+        }
+    },
+    "GeoDiv" : {
+         "_8" : {
+             "AUT" : {"start" : 6, "len" : 1, "def" : ' ' }
+         }
+     },
+     "Roman" : {
+         "_8" : {
+             "AUT" : {"start" : 7, "len" : 1, "def" : ' ' }
+         }
+     },
+     "CatLang" : {
+         "_8" : {
+             "AUT" : {"start" : 8, "len" : 1, "def" : ' ' }
+         }
+     },
+     "Kind" : {
+         "_8" : {
+             "AUT" : {"start" : 9, "len" : 1, "def" : ' ' }
+         }
+     },
+     "Rules" : {
+         "_8" : {
+             "AUT" : {"start" : 10, "len" : 1, "def" : ' ' }
+         }
+     },
+     "Subj" : {
+         "_8" : {
+             "AUT" : {"start" : 11, "len" : 1, "def" : ' ' }
+         }
+     },
+     "Series" : {
+         "_8" : {
+             "AUT" : {"start" : 12, "len" : 1, "def" : ' ' }
+         }
+     },
+     "SerNum" : {
+         "_8" : {
+             "AUT" : {"start" : 13, "len" : 1, "def" : ' ' }
+         }
+     },
+     "NameUse" : {
+         "_8" : {
+             "AUT" : {"start" : 14, "len" : 1, "def" : ' ' }
+         }
+     },
+     "SubjUse" : {
+         "_8" : {
+             "AUT" : {"start" : 15, "len" : 1, "def" : ' ' }
+         }
+     },
+     "SerUse" : {
+         "_8" : {
+             "AUT" : {"start" : 16, "len" : 1, "def" : ' ' }
+         }
+     },
+     "TypeSubd" : {
+         "_8" : {
+             "AUT" : {"start" : 17, "len" : 1, "def" : ' ' }
+         }
+     },
+     "GovtAgn" : {
+         "_8" : {
+             "AUT" : {"start" : 28, "len" : 1, "def" : ' ' }
+         }
+     },
+     "RefStatus" : {
+         "_8" : {
+             "AUT" : {"start" : 29, "len" : 1, "def" : ' ' }
+         }
+     },
+     "UpdStatus" : {
+         "_8" : {
+             "AUT" : {"start" : 31, "len" : 1, "def" : ' ' }
+         }
+     },
+     "Name" : {
+         "_8" : {
+             "AUT" : {"start" : 32, "len" : 1, "def" : ' ' }
+         }
+     },
+     "Status" : {
+         "_8" : {
+             "AUT" : {"start" : 33, "len" : 1, "def" : ' ' }
+         }
+     },
+     "ModRec" : {
+         "_8" : {
+             "AUT" : {"start" : 38, "len" : 1, "def" : ' ' }
+         }
+     },
+     "Source" : {
+         "_8" : {
+             "AUT" : {"start" : 39, "len" : 1, "def" : ' ' }
+         }
+     }
+};
+
+MARC.Record._physical_characteristics = {
+    c : {
+        label     : "Electronic Resource",
+        subfields : {
+            b : {    start : 1,
+                len   : 1,
+                label : "SMD",
+                values: {    a : "Tape Cartridge",
+                        b : "Chip cartridge",
+                        c : "Computer optical disk cartridge",
+                        f : "Tape cassette",
+                        h : "Tape reel",
+                        j : "Magnetic disk",
+                        m : "Magneto-optical disk",
+                        o : "Optical disk",
+                        r : "Remote",
+                        u : "Unspecified",
+                        z : "Other"
+                }
+            },
+            d : {    start : 3,
+                len   : 1,
+                label : "Color",
+                values: {    a : "One color",
+                        b : "Black-and-white",
+                        c : "Multicolored",
+                        g : "Gray scale",
+                        m : "Mixed",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            e : {    start : 4,
+                len   : 1,
+                label : "Dimensions",
+                values: {    a : "3 1/2 in.",
+                        e : "12 in.",
+                        g : "4 3/4 in. or 12 cm.",
+                        i : "1 1/8 x 2 3/8 in.",
+                        j : "3 7/8 x 2 1/2 in.",
+                        n : "Not applicable",
+                        o : "5 1/4 in.",
+                        u : "Unknown",
+                        v : "8 in.",
+                        z : "Other"
+                }
+            },
+            f : {    start : 5,
+                len   : 1,
+                label : "Sound",
+                values: {    ' ' : "No sound (Silent)",
+                        a   : "Sound",
+                        u   : "Unknown"
+                }
+            },
+            g : {    start : 6,
+                len   : 3,
+                label : "Image bit depth",
+                values: {    mmm   : "Multiple",
+                        nnn   : "Not applicable",
+                        '---' : "Unknown"
+                }
+            },
+            h : {    start : 9,
+                len   : 1,
+                label : "File formats",
+                values: {    a : "One file format",
+                        m : "Multiple file formats",
+                        u : "Unknown"
+                }
+            },
+            i : {    start : 10,
+                len   : 1,
+                label : "Quality assurance target(s)",
+                values: {    a : "Absent",
+                        n : "Not applicable",
+                        p : "Present",
+                        u : "Unknown"
+                }
+            },
+            j : {    start : 11,
+                len   : 1,
+                label : "Antecedent/Source",
+                values: {    a : "File reproduced from original",
+                        b : "File reproduced from microform",
+                        c : "File reproduced from electronic resource",
+                        d : "File reproduced from an intermediate (not microform)",
+                        m : "Mixed",
+                        n : "Not applicable",
+                        u : "Unknown"
+                }
+            },
+            k : {    start : 12,
+                len   : 1,
+                label : "Level of compression",
+                values: {    a : "Uncompressed",
+                        b : "Lossless",
+                        d : "Lossy",
+                        m : "Mixed",
+                        u : "Unknown"
+                }
+            },
+            l : {    start : 13,
+                len   : 1,
+                label : "Reformatting quality",
+                values: {    a : "Access",
+                        n : "Not applicable",
+                        p : "Preservation",
+                        r : "Replacement",
+                        u : "Unknown"
+                }
+            }
+        }
+    },
+    d : {
+        label     : "Globe",
+        subfields : {
+            b : {    start : 1,
+                len   : 1,
+                label : "SMD",
+                values: {    a : "Celestial globe",
+                        b : "Planetary or lunar globe",
+                        c : "Terrestrial globe",
+                        e : "Earth moon globe",
+                        u : "Unspecified",
+                        z : "Other"
+                }
+            },
+            d : {    start : 3,
+                len   : 1,
+                label : "Color",
+                values: {    a : "One color",
+                        c : "Multicolored"
+                }
+            },
+            e : {    start : 4,
+                len   : 1,
+                label : "Physical medium",
+                values: {    a : "Paper",
+                        b : "Wood",
+                        c : "Stone",
+                        d : "Metal",
+                        e : "Synthetics",
+                        f : "Skins",
+                        g : "Textile",
+                        p : "Plaster",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            f : {    start : 5,
+                len   : 1,
+                label : "Type of reproduction",
+                values: {    f : "Facsimile",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            }
+        }
+    },
+    a : {
+        label     : "Map",
+        subfields : {
+            b : {    start : 1,
+                len   : 1,
+                label : "SMD",
+                values: {    d : "Atlas",
+                        g : "Diagram",
+                        j : "Map",
+                        k : "Profile",
+                        q : "Model",
+                        r : "Remote-sensing image",
+                        s : "Section",
+                        u : "Unspecified",
+                        y : "View",
+                        z : "Other"
+                }
+            },
+            d : {    start : 3,
+                len   : 1,
+                label : "Color",
+                values: {    a : "One color",
+                        c : "Multicolored"
+                }
+            },
+            e : {    start : 4,
+                len   : 1,
+                label : "Physical medium",
+                values: {    a : "Paper",
+                        b : "Wood",
+                        c : "Stone",
+                        d : "Metal",
+                        e : "Synthetics",
+                        f : "Skins",
+                        g : "Textile",
+                        p : "Plaster",
+                        q : "Flexible base photographic medium, positive",
+                        r : "Flexible base photographic medium, negative",
+                        s : "Non-flexible base photographic medium, positive",
+                        t : "Non-flexible base photographic medium, negative",
+                        u : "Unknown",
+                        y : "Other photographic medium",
+                        z : "Other"
+                }
+            },
+            f : {    start : 5,
+                len   : 1,
+                label : "Type of reproduction",
+                values: {    f : "Facsimile",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            g : {    start : 6,
+                len   : 1,
+                label : "Production/reproduction details",
+                values: {    a : "Photocopy, blueline print",
+                        b : "Photocopy",
+                        c : "Pre-production",
+                        d : "Film",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            h : {    start : 7,
+                len   : 1,
+                label : "Positive/negative",
+                values: {    a : "Positive",
+                        b : "Negative",
+                        m : "Mixed",
+                        n : "Not applicable"
+                }
+            }
+        }
+    },
+    h : {
+        label     : "Microform",
+        subfields : {
+            b : {    start : 1,
+                len   : 1,
+                label : "SMD",
+                values: {    a : "Aperture card",
+                        b : "Microfilm cartridge",
+                        c : "Microfilm cassette",
+                        d : "Microfilm reel",
+                        e : "Microfiche",
+                        f : "Microfiche cassette",
+                        g : "Microopaque",
+                        u : "Unspecified",
+                        z : "Other"
+                }
+            },
+            d : {    start : 3,
+                len   : 1,
+                label : "Positive/negative",
+                values: {    a : "Positive",
+                        b : "Negative",
+                        m : "Mixed",
+                        u : "Unknown"
+                }
+            },
+            e : {    start : 4,
+                len   : 1,
+                label : "Dimensions",
+                values: {    a : "8 mm.",
+                        e : "16 mm.",
+                        f : "35 mm.",
+                        g : "70mm.",
+                        h : "105 mm.",
+                        l : "3 x 5 in. (8 x 13 cm.)",
+                        m : "4 x 6 in. (11 x 15 cm.)",
+                        o : "6 x 9 in. (16 x 23 cm.)",
+                        p : "3 1/4 x 7 3/8 in. (9 x 19 cm.)",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            f : {    start : 5,
+                len   : 4,
+                label : "Reduction ratio range/Reduction ratio",
+                values: {    a : "Low (1-16x)",
+                        b : "Normal (16-30x)",
+                        c : "High (31-60x)",
+                        d : "Very high (61-90x)",
+                        e : "Ultra (90x-)",
+                        u : "Unknown",
+                        v : "Reduction ratio varies"
+                }
+            },
+            g : {    start : 9,
+                len   : 1,
+                label : "Color",
+                values: {    b : "Black-and-white",
+                        c : "Multicolored",
+                        m : "Mixed",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            h : {    start : 10,
+                len   : 1,
+                label : "Emulsion on film",
+                values: {    a : "Silver halide",
+                        b : "Diazo",
+                        c : "Vesicular",
+                        m : "Mixed",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            i : {    start : 11,
+                len   : 1,
+                label : "Quality assurance target(s)",
+                values: {    a : "1st gen. master",
+                        b : "Printing master",
+                        c : "Service copy",
+                        m : "Mixed generation",
+                        u : "Unknown"
+                }
+            },
+            j : {    start : 12,
+                len   : 1,
+                label : "Base of film",
+                values: {    a : "Safety base, undetermined",
+                        c : "Safety base, acetate undetermined",
+                        d : "Safety base, diacetate",
+                        l : "Nitrate base",
+                        m : "Mixed base",
+                        n : "Not applicable",
+                        p : "Safety base, polyester",
+                        r : "Safety base, mixed",
+                        t : "Safety base, triacetate",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            }
+        }
+    },
+    m : {
+        label     : "Motion Picture",
+        subfields : {
+            b : {    start : 1,
+                len   : 1,
+                label : "SMD",
+                values: {    a : "Film cartridge",
+                        f : "Film cassette",
+                        r : "Film reel",
+                        u : "Unspecified",
+                        z : "Other"
+                }
+            },
+            d : {    start : 3,
+                len   : 1,
+                label : "Color",
+                values: {    b : "Black-and-white",
+                        c : "Multicolored",
+                        h : "Hand-colored",
+                        m : "Mixed",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            e : {    start : 4,
+                len   : 1,
+                label : "Motion picture presentation format",
+                values: {    a : "Standard sound aperture, reduced frame",
+                        b : "Nonanamorphic (wide-screen)",
+                        c : "3D",
+                        d : "Anamorphic (wide-screen)",
+                        e : "Other-wide screen format",
+                        f : "Standard. silent aperture, full frame",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            f : {    start : 5,
+                len   : 1,
+                label : "Sound on medium or separate",
+                values: {    a : "Sound on medium",
+                        b : "Sound separate from medium",
+                        u : "Unknown"
+                }
+            },
+            g : {    start : 6,
+                len   : 1,
+                label : "Medium for sound",
+                values: {    a : "Optical sound track on motion picture film",
+                        b : "Magnetic sound track on motion picture film",
+                        c : "Magnetic audio tape in cartridge",
+                        d : "Sound disc",
+                        e : "Magnetic audio tape on reel",
+                        f : "Magnetic audio tape in cassette",
+                        g : "Optical and magnetic sound track on film",
+                        h : "Videotape",
+                        i : "Videodisc",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            h : {    start : 7,
+                len   : 1,
+                label : "Dimensions",
+                values: {    a : "Standard 8 mm.",
+                        b : "Super 8 mm./single 8 mm.",
+                        c : "9.5 mm.",
+                        d : "16 mm.",
+                        e : "28 mm.",
+                        f : "35 mm.",
+                        g : "70 mm.",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            i : {    start : 8,
+                len   : 1,
+                label : "Configuration of playback channels",
+                values: {    k : "Mixed",
+                        m : "Monaural",
+                        n : "Not applicable",
+                        q : "Multichannel, surround or quadraphonic",
+                        s : "Stereophonic",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            j : {    start : 9,
+                len   : 1,
+                label : "Production elements",
+                values: {    a : "Work print",
+                        b : "Trims",
+                        c : "Outtakes",
+                        d : "Rushes",
+                        e : "Mixing tracks",
+                        f : "Title bands/inter-title rolls",
+                        g : "Production rolls",
+                        n : "Not applicable",
+                        z : "Other"
+                }
+            }
+        }
+    },
+    k : {
+        label     : "Non-projected Graphic",
+        subfields : {
+            b : {    start : 1,
+                len   : 1,
+                label : "SMD",
+                values: {    c : "Collage",
+                        d : "Drawing",
+                        e : "Painting",
+                        f : "Photo-mechanical print",
+                        g : "Photonegative",
+                        h : "Photoprint",
+                        i : "Picture",
+                        j : "Print",
+                        l : "Technical drawing",
+                        n : "Chart",
+                        o : "Flash/activity card",
+                        u : "Unspecified",
+                        z : "Other"
+                }
+            },
+            d : {    start : 3,
+                len   : 1,
+                label : "Color",
+                values: {    a : "One color",
+                        b : "Black-and-white",
+                        c : "Multicolored",
+                        h : "Hand-colored",
+                        m : "Mixed",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            e : {    start : 4,
+                len   : 1,
+                label : "Primary support material",
+                values: {    a : "Canvas",
+                        b : "Bristol board",
+                        c : "Cardboard/illustration board",
+                        d : "Glass",
+                        e : "Synthetics",
+                        f : "Skins",
+                        g : "Textile",
+                        h : "Metal",
+                        m : "Mixed collection",
+                        o : "Paper",
+                        p : "Plaster",
+                        q : "Hardboard",
+                        r : "Porcelain",
+                        s : "Stone",
+                        t : "Wood",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            f : {    start : 5,
+                len   : 1,
+                label : "Secondary support material",
+                values: {    a : "Canvas",
+                        b : "Bristol board",
+                        c : "Cardboard/illustration board",
+                        d : "Glass",
+                        e : "Synthetics",
+                        f : "Skins",
+                        g : "Textile",
+                        h : "Metal",
+                        m : "Mixed collection",
+                        o : "Paper",
+                        p : "Plaster",
+                        q : "Hardboard",
+                        r : "Porcelain",
+                        s : "Stone",
+                        t : "Wood",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            }
+        }
+    },
+    g : {
+        label     : "Projected Graphic",
+        subfields : {
+            b : {    start : 1,
+                len   : 1,
+                label : "SMD",
+                values: {    c : "Film cartridge",
+                        d : "Filmstrip",
+                        f : "Film filmstrip type",
+                        o : "Filmstrip roll",
+                        s : "Slide",
+                        t : "Transparency",
+                        z : "Other"
+                }
+            },
+            d : {    start : 3,
+                len   : 1,
+                label : "Color",
+                values: {    b : "Black-and-white",
+                        c : "Multicolored",
+                        h : "Hand-colored",
+                        m : "Mixed",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            e : {    start : 4,
+                len   : 1,
+                label : "Base of emulsion",
+                values: {    d : "Glass",
+                        e : "Synthetics",
+                        j : "Safety film",
+                        k : "Film base, other than safety film",
+                        m : "Mixed collection",
+                        o : "Paper",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            f : {    start : 5,
+                len   : 1,
+                label : "Sound on medium or separate",
+                values: {    a : "Sound on medium",
+                        b : "Sound separate from medium",
+                        u : "Unknown"
+                }
+            },
+            g : {    start : 6,
+                len   : 1,
+                label : "Medium for sound",
+                values: {    a : "Optical sound track on motion picture film",
+                        b : "Magnetic sound track on motion picture film",
+                        c : "Magnetic audio tape in cartridge",
+                        d : "Sound disc",
+                        e : "Magnetic audio tape on reel",
+                        f : "Magnetic audio tape in cassette",
+                        g : "Optical and magnetic sound track on film",
+                        h : "Videotape",
+                        i : "Videodisc",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            h : {    start : 7,
+                len   : 1,
+                label : "Dimensions",
+                values: {    a : "Standard 8 mm.",
+                        b : "Super 8 mm./single 8 mm.",
+                        c : "9.5 mm.",
+                        d : "16 mm.",
+                        e : "28 mm.",
+                        f : "35 mm.",
+                        g : "70 mm.",
+                        j : "2 x 2 in. (5 x 5 cm.)",
+                        k : "2 1/4 x 2 1/4 in. (6 x 6 cm.)",
+                        s : "4 x 5 in. (10 x 13 cm.)",
+                        t : "5 x 7 in. (13 x 18 cm.)",
+                        v : "8 x 10 in. (21 x 26 cm.)",
+                        w : "9 x 9 in. (23 x 23 cm.)",
+                        x : "10 x 10 in. (26 x 26 cm.)",
+                        y : "7 x 7 in. (18 x 18 cm.)",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            i : {    start : 8,
+                len   : 1,
+                label : "Secondary support material",
+                values: {    c : "Cardboard",
+                        d : "Glass",
+                        e : "Synthetics",
+                        h : "metal",
+                        j : "Metal and glass",
+                        k : "Synthetics and glass",
+                        m : "Mixed collection",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            }
+        }
+    },
+    r : {
+        label     : "Remote-sensing Image",
+        subfields : {
+            b : {    start : 1,
+                len   : 1,
+                label : "SMD",
+                values: { u : "Unspecified" }
+            },
+            d : {    start : 3,
+                len   : 1,
+                label : "Altitude of sensor",
+                values: {    a : "Surface",
+                        b : "Airborne",
+                        c : "Spaceborne",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            e : {    start : 4,
+                len   : 1,
+                label : "Attitude of sensor",
+                values: {    a : "Low oblique",
+                        b : "High oblique",
+                        c : "Vertical",
+                        n : "Not applicable",
+                        u : "Unknown"
+                }
+            },
+            f : {    start : 5,
+                len   : 1,
+                label : "Cloud cover",
+                values: {    0 : "0-09%",
+                        1 : "10-19%",
+                        2 : "20-29%",
+                        3 : "30-39%",
+                        4 : "40-49%",
+                        5 : "50-59%",
+                        6 : "60-69%",
+                        7 : "70-79%",
+                        8 : "80-89%",
+                        9 : "90-100%",
+                        n : "Not applicable",
+                        u : "Unknown"
+                }
+            },
+            g : {    start : 6,
+                len   : 1,
+                label : "Platform construction type",
+                values: {    a : "Balloon",
+                        b : "Aircraft-low altitude",
+                        c : "Aircraft-medium altitude",
+                        d : "Aircraft-high altitude",
+                        e : "Manned spacecraft",
+                        f : "Unmanned spacecraft",
+                        g : "Land-based remote-sensing device",
+                        h : "Water surface-based remote-sensing device",
+                        i : "Submersible remote-sensing device",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            h : {    start : 7,
+                len   : 1,
+                label : "Platform use category",
+                values: {    a : "Meteorological",
+                        b : "Surface observing",
+                        c : "Space observing",
+                        m : "Mixed uses",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            i : {    start : 8,
+                len   : 1,
+                label : "Sensor type",
+                values: {    a : "Active",
+                        b : "Passive",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            j : {    start : 9,
+                len   : 2,
+                label : "Data type",
+                values: {    nn : "Not applicable",
+                        uu : "Unknown",
+                        zz : "Other",
+                        aa : "Visible light",
+                        da : "Near infrared",
+                        db : "Middle infrared",
+                        dc : "Far infrared",
+                        dd : "Thermal infrared",
+                        de : "Shortwave infrared (SWIR)",
+                        df : "Reflective infrared",
+                        dv : "Combinations",
+                        dz : "Other infrared data",
+                        ga : "Sidelooking airborne radar (SLAR)",
+                        gb : "Synthetic aperture radar (SAR-single frequency)",
+                        gc : "SAR-multi-frequency (multichannel)",
+                        gd : "SAR-like polarization",
+                        ge : "SAR-cross polarization",
+                        gf : "Infometric SAR",
+                        gg : "Polarmetric SAR",
+                        gu : "Passive microwave mapping",
+                        gz : "Other microwave data",
+                        ja : "Far ultraviolet",
+                        jb : "Middle ultraviolet",
+                        jc : "Near ultraviolet",
+                        jv : "Ultraviolet combinations",
+                        jz : "Other ultraviolet data",
+                        ma : "Multi-spectral, multidata",
+                        mb : "Multi-temporal",
+                        mm : "Combination of various data types",
+                        pa : "Sonar-water depth",
+                        pb : "Sonar-bottom topography images, sidescan",
+                        pc : "Sonar-bottom topography, near-surface",
+                        pd : "Sonar-bottom topography, near-bottom",
+                        pe : "Seismic surveys",
+                        pz : "Other acoustical data",
+                        ra : "Gravity anomales (general)",
+                        rb : "Free-air",
+                        rc : "Bouger",
+                        rd : "Isostatic",
+                        sa : "Magnetic field",
+                        ta : "Radiometric surveys"
+                }
+            }
+        }
+    },
+    s : {
+        label     : "Sound Recording",
+        subfields : {
+            b : {    start : 1,
+                len   : 1,
+                label : "SMD",
+                values: {    d : "Sound disc",
+                        e : "Cylinder",
+                        g : "Sound cartridge",
+                        i : "Sound-track film",
+                        q : "Roll",
+                        s : "Sound cassette",
+                        t : "Sound-tape reel",
+                        u : "Unspecified",
+                        w : "Wire recording",
+                        z : "Other"
+                }
+            },
+            d : {    start : 3,
+                len   : 1,
+                label : "Speed",
+                values: {    a : "16 rpm",
+                        b : "33 1/3 rpm",
+                        c : "45 rpm",
+                        d : "78 rpm",
+                        e : "8 rpm",
+                        f : "1.4 mps",
+                        h : "120 rpm",
+                        i : "160 rpm",
+                        k : "15/16 ips",
+                        l : "1 7/8 ips",
+                        m : "3 3/4 ips",
+                        o : "7 1/2 ips",
+                        p : "15 ips",
+                        r : "30 ips",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            e : {    start : 4,
+                len   : 1,
+                label : "Configuration of playback channels",
+                values: {    m : "Monaural",
+                        q : "Quadraphonic",
+                        s : "Stereophonic",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            f : {    start : 5,
+                len   : 1,
+                label : "Groove width or pitch",
+                values: {    m : "Microgroove/fine",
+                        n : "Not applicable",
+                        s : "Coarse/standard",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            g : {    start : 6,
+                len   : 1,
+                label : "Dimensions",
+                values: {    a : "3 in.",
+                        b : "5 in.",
+                        c : "7 in.",
+                        d : "10 in.",
+                        e : "12 in.",
+                        f : "16 in.",
+                        g : "4 3/4 in. (12 cm.)",
+                        j : "3 7/8 x 2 1/2 in.",
+                        o : "5 1/4 x 3 7/8 in.",
+                        s : "2 3/4 x 4 in.",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            h : {    start : 7,
+                len   : 1,
+                label : "Tape width",
+                values: {    l : "1/8 in.",
+                        m : "1/4in.",
+                        n : "Not applicable",
+                        o : "1/2 in.",
+                        p : "1 in.",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            i : {    start : 8,
+                len   : 1,
+                label : "Tape configuration ",
+                values: {    a : "Full (1) track",
+                        b : "Half (2) track",
+                        c : "Quarter (4) track",
+                        d : "8 track",
+                        e : "12 track",
+                        f : "16 track",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            m : {    start : 12,
+                len   : 1,
+                label : "Special playback",
+                values: {    a : "NAB standard",
+                        b : "CCIR standard",
+                        c : "Dolby-B encoded, standard Dolby",
+                        d : "dbx encoded",
+                        e : "Digital recording",
+                        f : "Dolby-A encoded",
+                        g : "Dolby-C encoded",
+                        h : "CX encoded",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            n : {    start : 13,
+                len   : 1,
+                label : "Capture and storage",
+                values: {    a : "Acoustical capture, direct storage",
+                        b : "Direct storage, not acoustical",
+                        d : "Digital storage",
+                        e : "Analog electrical storage",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            }
+        }
+    },
+    f : {
+        label     : "Tactile Material",
+        subfields : {
+            b : {    start : 1,
+                len   : 1,
+                label : "SMD",
+                values: {    a : "Moon",
+                        b : "Braille",
+                        c : "Combination",
+                        d : "Tactile, with no writing system",
+                        u : "Unspecified",
+                        z : "Other"
+                }
+            },
+            d : {    start : 3,
+                len   : 2,
+                label : "Class of braille writing",
+                values: {    a : "Literary braille",
+                        b : "Format code braille",
+                        c : "Mathematics and scientific braille",
+                        d : "Computer braille",
+                        e : "Music braille",
+                        m : "Multiple braille types",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            e : {    start : 4,
+                len   : 1,
+                label : "Level of contraction",
+                values: {    a : "Uncontracted",
+                        b : "Contracted",
+                        m : "Combination",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            f : {    start : 6,
+                len   : 3,
+                label : "Braille music format",
+                values: {    a : "Bar over bar",
+                        b : "Bar by bar",
+                        c : "Line over line",
+                        d : "Paragraph",
+                        e : "Single line",
+                        f : "Section by section",
+                        g : "Line by line",
+                        h : "Open score",
+                        i : "Spanner short form scoring",
+                        j : "Short form scoring",
+                        k : "Outline",
+                        l : "Vertical score",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            g : {    start : 9,
+                len   : 1,
+                label : "Special physical characteristics",
+                values: {    a : "Print/braille",
+                        b : "Jumbo or enlarged braille",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            }
+        }
+    },
+    v : {
+        label     : "Videorecording",
+        subfields : {
+            b : {    start : 1,
+                len   : 1,
+                label : "SMD",
+                values: {     c : "Videocartridge",
+                        d : "Videodisc",
+                        f : "Videocassette",
+                        r : "Videoreel",
+                        u : "Unspecified",
+                        z : "Other"
+                }
+            },
+            d : {    start : 3,
+                len   : 1,
+                label : "Color",
+                values: {    b : "Black-and-white",
+                        c : "Multicolored",
+                        m : "Mixed",
+                        n : "Not applicable",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            e : {    start : 4,
+                len   : 1,
+                label : "Videorecording format",
+                values: {    a : "Beta",
+                        b : "VHS",
+                        c : "U-matic",
+                        d : "EIAJ",
+                        e : "Type C",
+                        f : "Quadruplex",
+                        g : "Laserdisc",
+                        h : "CED",
+                        i : "Betacam",
+                        j : "Betacam SP",
+                        k : "Super-VHS",
+                        m : "M-II",
+                        o : "D-2",
+                        p : "8 mm.",
+                        q : "Hi-8 mm.",
+                        u : "Unknown",
+                        v : "DVD",
+                        z : "Other"
+                }
+            },
+            f : {    start : 5,
+                len   : 1,
+                label : "Sound on medium or separate",
+                values: {    a : "Sound on medium",
+                        b : "Sound separate from medium",
+                        u : "Unknown"
+                }
+            },
+            g : {    start : 6,
+                len   : 1,
+                label : "Medium for sound",
+                values: {    a : "Optical sound track on motion picture film",
+                        b : "Magnetic sound track on motion picture film",
+                        c : "Magnetic audio tape in cartridge",
+                        d : "Sound disc",
+                        e : "Magnetic audio tape on reel",
+                        f : "Magnetic audio tape in cassette",
+                        g : "Optical and magnetic sound track on motion picture film",
+                        h : "Videotape",
+                        i : "Videodisc",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            h : {    start : 7,
+                len   : 1,
+                label : "Dimensions",
+                values: {    a : "8 mm.",
+                        m : "1/4 in.",
+                        o : "1/2 in.",
+                        p : "1 in.",
+                        q : "2 in.",
+                        r : "3/4 in.",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            },
+            i : {    start : 8,
+                len   : 1,
+                label : "Configuration of playback channel",
+                values: {    k : "Mixed",
+                        m : "Monaural",
+                        n : "Not applicable",
+                        q : "Multichannel, surround or quadraphonic",
+                        s : "Stereophonic",
+                        u : "Unknown",
+                        z : "Other"
+                }
+            }
+        }
+    }
+};
+

commit 2df05bef0da32c34d1b6f7e79fe7b8c8a4184ed5
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 2 14:55:17 2015 -0500

    LP#1402797 Remove DOB valid test for now; Change alert valid test (expressions must be simple)
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
index 8377ed7..2b92870 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
@@ -26,7 +26,7 @@
       <div class="col-md-5">[% l('Net Access') %]</div>
       <div class="col-md-7">{{patron().net_access_level().name()}}</div>
     </div>
-    <div class="row" ng-class="{'patron-summary-alert' : new Date(patron().dob()) > new Date()}">
+    <div class="row">
       <div class="col-md-5">[% l('Date of Birth') %]</div>
       <div class="col-md-7">{{patron().dob() | date:'shortDate'}}</div>
     </div>
@@ -138,7 +138,7 @@
     <div class="panel">
       <div class="panel-body">
         <fieldset>
-          <legend ng-class="{'patron-summary-alert': !new Boolean(addr.valid())}">
+          <legend ng-class="{'patron-summary-alert': addr.valid() == 'f'}">
             {{addr.address_type()}} 
             <a href class="pad-horiz patron-summary-act-link" 
               ng-click="print_address(addr)">[% l('(print)') %]</a>

commit e659af829ee95020bcb02eccf93811cea9d708f2
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 2 13:43:48 2015 -0500

    LP#1402797 Add Item Status to the circ menu
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2
index 35988d4..84c9e33 100644
--- a/Open-ILS/src/templates/staff/navbar.tt2
+++ b/Open-ILS/src/templates/staff/navbar.tt2
@@ -142,6 +142,12 @@
             </a>
           </li>
           <li>
+            <a href="./cat/item/search" target="_self">
+              <span class="glyphicon glyphicon-saved"></span>
+              <span>[% l('Item Status') %]</span>
+            </a>
+          </li>
+          <li>
             <a href="./cat/item/missing_pieces" target="_self">
               <span class="glyphicon glyphicon-th"></span>
               <span>[% l('Scan Item as Missing Pieces') %]</span>

commit 18c1e4c59b4a5379b8e896eedfd5be021079fc34
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 2 13:20:55 2015 -0500

    LP#1402797 Make the save box smaller to help avoid overlapping the input form
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/css/skin/default/register.css b/Open-ILS/web/css/skin/default/register.css
index 8b5736b..4c58059 100644
--- a/Open-ILS/web/css/skin/default/register.css
+++ b/Open-ILS/web/css/skin/default/register.css
@@ -12,7 +12,7 @@
     position: fixed;
     top:40px;
     right:30px;
-    width:300px;
+    width:200px;
     border:2px solid #d9e8f9;
     -moz-border-radius: 10px;
     font-weight: bold;

commit 7cb6a97ba64916c37c9ae036dbdb4cb99bce5d41
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 2 13:05:56 2015 -0500

    LP#1402797 Add DOB and address validity styling
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
index f88d539..8377ed7 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
@@ -26,9 +26,9 @@
       <div class="col-md-5">[% l('Net Access') %]</div>
       <div class="col-md-7">{{patron().net_access_level().name()}}</div>
     </div>
-    <div class="row">
+    <div class="row" ng-class="{'patron-summary-alert' : new Date(patron().dob()) > new Date()}">
       <div class="col-md-5">[% l('Date of Birth') %]</div>
-      <div class="col-md-7" ng-class="{patron-summary-alert : new Date(patron().dob()) > new Date()}">{{patron().dob() | date:'shortDate'}}</div>
+      <div class="col-md-7">{{patron().dob() | date:'shortDate'}}</div>
     </div>
     <div class="row">
       <div class="col-md-5">[% l('Last Activity') %]</div>
@@ -138,7 +138,7 @@
     <div class="panel">
       <div class="panel-body">
         <fieldset>
-          <legend>
+          <legend ng-class="{'patron-summary-alert': !new Boolean(addr.valid())}">
             {{addr.address_type()}} 
             <a href class="pad-horiz patron-summary-act-link" 
               ng-click="print_address(addr)">[% l('(print)') %]</a>

commit cd5de415d4e9124b8c8e38814e34b933ac9c54d9
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 2 12:31:46 2015 -0500

    LP#1402797 Add DOB to patron summary
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
index 8c803cf..f88d539 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_summary.tt2
@@ -27,6 +27,10 @@
       <div class="col-md-7">{{patron().net_access_level().name()}}</div>
     </div>
     <div class="row">
+      <div class="col-md-5">[% l('Date of Birth') %]</div>
+      <div class="col-md-7" ng-class="{patron-summary-alert : new Date(patron().dob()) > new Date()}">{{patron().dob() | date:'shortDate'}}</div>
+    </div>
+    <div class="row">
       <div class="col-md-5">[% l('Last Activity') %]</div>
       <div class="col-md-7">{{patron().usr_activity()[0].event_time() | date:'shortDate'}}</div>
     </div>

commit 757c6c6c4f11efb499dc2c294eeef409523f5e77
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 2 12:06:04 2015 -0500

    LP#1402797 Profile Group no longer causes second search to fail, but it does not yet propagate
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
index 4afa173..14a9bb8 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
@@ -771,9 +771,26 @@ function($scope,  $q,  $routeParams,  $timeout,  $window,  $location,  egCore,
         propagate = patronSvc.urlSearch.search;
     }
 
+    if (egCore.env.pgt) {
+        $scope.profiles = egCore.env.pgt.list;
+    } else {
+        egCore.pcrud.search('pgt', {parent : null}, 
+            {flesh : -1, flesh_fields : {pgt : ['children']}}
+        ).then(
+            function(tree) {
+                egCore.env.absorbTree(tree, 'pgt')
+                $scope.profiles = egCore.env.pgt.list;
+            }
+        );
+    }
+
     if (propagate) {
         // populate the search form with our cached / preexisting search info
         angular.forEach(propagate, function(val, key) {
+            if (key == 'profile')
+                val.value = $scope.profiles.filter(function(p) { p.id() == val.value })[0];
+            if (key == 'home_ou')
+                val.value = egCore.org.get(val.value);
             $scope.searchArgs[key] = val.value;
         });
     }
@@ -884,19 +901,6 @@ function($scope,  $q,  $routeParams,  $timeout,  $window,  $location,  egCore,
 
     $scope.patronSearchGridProvider = provider;
 
-    if (egCore.env.pgt) {
-        $scope.profiles = egCore.env.pgt.list;
-    } else {
-        egCore.pcrud.search('pgt', {parent : null}, 
-            {flesh : -1, flesh_fields : {pgt : ['children']}}
-        ).then(
-            function(tree) {
-                egCore.env.absorbTree(tree, 'pgt')
-                $scope.profiles = egCore.env.pgt.list;
-            }
-        );
-    }
-
     // determine the tree depth of the profile group
     $scope.pgt_depth = function(grp) {
         var d = 0;

commit 3e001d054b60d3082b03931f25905f221bc949f9
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 2 10:15:10 2015 -0500

    LP#1402797 Add "forget" mode for grid limit; Use in hold clearing mode
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
index 8ce83c4..f0f5be5 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
@@ -167,7 +167,7 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
 
         // we want to see all processed holds, so (effectively) remove
         // the grid limit.
-        $scope.gridControls.setLimit(1000); 
+        $scope.gridControls.setLimit(1000, true); 
 
         // initiate clear shelf and grab cache key
         egCore.net.request(
diff --git a/Open-ILS/web/js/ui/default/staff/services/grid.js b/Open-ILS/web/js/ui/default/staff/services/grid.js
index 8b2b441..50c2786 100644
--- a/Open-ILS/web/js/ui/default/staff/services/grid.js
+++ b/Open-ILS/web/js/ui/default/staff/services/grid.js
@@ -223,8 +223,8 @@ angular.module('egGridMod',
                     grid.collect();
                 }
 
-                controls.setLimit = function(limit) {
-                    if (grid.persistKey)
+                controls.setLimit = function(limit,forget) {
+                    if (!forget && grid.persistKey)
                         egCore.hatch.setLocalItem('eg.grid.' + grid.persistKey + '.limit', limit);
                     grid.limit = limit;
                 }

commit aafdbdfd4e3ab8f7c41a6a3e1cf7f3a399facecc
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Feb 2 10:14:36 2015 -0500

    LP#1402797 Pass the top-level class so that the proper tooltip can be generated
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
index 0761b41..914c52f 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
@@ -72,8 +72,8 @@
   <eg-grid-field label="[% l('Cancel Cause') %]"path='hold.cancel_cause.label' hidden></eg-grid-field>
   <eg-grid-field path='hold.*' parent-idl-class="ahr" ignore="current_copy" hidden></eg-grid-field>
   <eg-grid-field path='copy.*' parent-idl-class="acp" hidden></eg-grid-field>
-  <eg-grid-field path='hold.usr.*' parent-idl-class="au" hidden></eg-grid-field>
-  <eg-grid-field path='hold.usr.card.*' parent-idl-class="ac" hidden></eg-grid-field>
+  <eg-grid-field path='hold.usr.*' parent-idl-class="ahr" hidden></eg-grid-field>
+  <eg-grid-field path='hold.usr.card.*' parent-idl-class="ahr" hidden></eg-grid-field>
   <eg-grid-field path='volume.*' parent-idl-class="acn" hidden></eg-grid-field>
   <eg-grid-field path='mvr.*' parent-idl-class="mvr" hidden></eg-grid-field>
 </eg-grid>

commit f17ec353a236ae0880defc0ca4cd0625bd2246aa
Author: Mike Rylander <mrylander at gmail.com>
Date:   Sun Feb 1 16:07:10 2015 -0500

    LP#1402797 Focus the Print button
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/share/t_transit_dialog.tt2 b/Open-ILS/src/templates/staff/circ/share/t_transit_dialog.tt2
index 1491fb2..69d9271 100644
--- a/Open-ILS/src/templates/staff/circ/share/t_transit_dialog.tt2
+++ b/Open-ILS/src/templates/staff/circ/share/t_transit_dialog.tt2
@@ -57,7 +57,7 @@
     </div>
     <div class="modal-footer">
       <input type="button" class="btn btn-primary"
-        ng-click="print()" value="[% l('Print') %]"/>
+        ng-click="print()" focus-me="true" value="[% l('Print') %]"/>
       <input type="submit" class="btn btn-warning"
         ng-click="ok()" value="[% l('Do Not Print') %]"/>
     </div>

commit 51c1061a6271bd746732760ce1eb2162666049dc
Author: Mike Rylander <mrylander at gmail.com>
Date:   Sun Feb 1 15:55:39 2015 -0500

    LP#1402797 Make Strict Barcode checkbox sticky upon actual use
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
index dd3fe8f..d0407c6 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js
@@ -49,6 +49,9 @@ function($scope , $q , $window , $location , egCore , checkinSvc , egGridDataPro
     $scope.grid_persist_key = $scope.is_capture ? 
         'circ.checkin.capture' : 'circ.checkin.checkin';
 
+    egCore.hatch.getItem('circ.checkin.strict_barcode')
+        .then(function(sb){ $scope.strict_barcode = sb });
+
     egCore.org.settings([
         'ui.circ.suppress_checkin_popups' // add other settings as needed
     ]).then(function(set) {
@@ -143,6 +146,7 @@ function($scope , $q , $window , $location , egCore , checkinSvc , egGridDataPro
             }
         }
 
+        egCore.hatch.setItem('circ.checkin.strict_barcode', $scope.strict_barcode);
         var options = {
             check_barcode : $scope.strict_barcode,
             no_precat_alert : $scope.modifiers.no_precat_alert,
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js b/Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js
index 1dff72b..9806d8f 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/checkout.js
@@ -36,6 +36,9 @@ function($scope , $q , $modal , $routeParams , egCore , egUser , patronSvc ,
 
     $scope.using_hatch = egCore.hatch.usingHatch();
 
+    egCore.hatch.getItem('circ.checkout.strict_barcode')
+        .then(function(sb){ $scope.strict_barcode = sb });
+
     // avoid multiple, in-flight attempts on the same barcode
     var pending_barcodes = {};
 
@@ -110,6 +113,7 @@ function($scope , $q , $modal , $routeParams , egCore , egUser , patronSvc ,
         $scope.checkouts.unshift(row_item);
         $scope.gridDataProvider.refresh();
 
+        egCore.hatch.setItem('circ.checkout.strict_barcode', $scope.strict_barcode);
         var options = {check_barcode : $scope.strict_barcode};
 
         egCirc.checkout(params, options).then(
diff --git a/Open-ILS/web/js/ui/default/staff/circ/renew/app.js b/Open-ILS/web/js/ui/default/staff/circ/renew/app.js
index c64c66c..85ed294 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/renew/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/renew/app.js
@@ -32,6 +32,8 @@ angular.module('egRenewApp',
        ['$scope','$window','$location','egCore','egGridDataProvider','egCirc',
 function($scope , $window , $location , egCore , egGridDataProvider , egCirc) {
 
+    egCore.hatch.getItem('circ.renew.strict_barcode')
+        .then(function(sb){ $scope.strict_barcode = sb });
     $scope.focusBarcode = true;
     $scope.renewals = [];
 
@@ -90,6 +92,7 @@ function($scope , $window , $location , egCore , egGridDataProvider , egCirc) {
         $scope.renewals.unshift(row_item);
         $scope.gridDataProvider.refresh();
 
+        egCore.hatch.setItem('circ.renew.strict_barcode', $scope.strict_barcode);
         var options = {check_barcode : $scope.strict_barcode};
 
         egCirc.renew(params, options).then(

commit 54c3f600fc98bfd1a703e4ab92a139322c158f59
Author: Mike Rylander <mrylander at gmail.com>
Date:   Sun Feb 1 15:32:53 2015 -0500

    LP#1402797 Pre-force tab change.  There have been reports of the tab not changing on automatic redirect.
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
index cca86d0..4afa173 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
@@ -573,6 +573,7 @@ function($scope,  $q,  $location , $filter,  egCore,  egUser,  patronSvc) {
             && egCore.env.aous['ui.circ.show_billing_tab_on_bills']
             && !$location.path().match(/bills$/)) {
 
+            $scope.tab = 'bills';
             $location
                 .path('/circ/patron/' + patronSvc.current.id() + '/bills')
                 .search('card', null);

commit b640e16f63bb7cc93696373af9b48c0e1ae3139b
Author: Mike Rylander <mrylander at gmail.com>
Date:   Sun Feb 1 15:06:05 2015 -0500

    LP#1402797 Add Cancel Cause column to hold grid and flesh that object
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
index 89eb58d..0761b41 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
@@ -69,6 +69,7 @@
   <eg-grid-field label="[% l('Status') %]" path='status_string'></eg-grid-field>
 
   <eg-grid-field label="[% l('Queue Position') %]" path='queue_position' hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Cancel Cause') %]"path='hold.cancel_cause.label' hidden></eg-grid-field>
   <eg-grid-field path='hold.*' parent-idl-class="ahr" ignore="current_copy" hidden></eg-grid-field>
   <eg-grid-field path='copy.*' parent-idl-class="acp" hidden></eg-grid-field>
   <eg-grid-field path='hold.usr.*' parent-idl-class="au" hidden></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
index f1fa1fa..1ea2dc5 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
@@ -413,6 +413,9 @@ function($modal , $q , egCore , egConfirmDialog , egAlertDialog) {
         if (hold.requestor() && typeof hold.requestor() != 'object')
             egCore.pcrud.retrieve('au',hold.requestor()).then(function(u) { hold.requestor(u) });
 
+        if (hold.cancel_cause() && typeof hold.cancel_cause() != 'object')
+            egCore.pcrud.retrieve('ahrcc',hold.cancel_cause()).then(function(c) { hold.cancel_cause(c) });
+
         if (hold.usr() && typeof hold.usr() != 'object')
             egCore.pcrud.retrieve('au',hold.usr()).then(function(u) { hold.usr(u) });
 

commit 30f4c7b5032885c622eaf7719a50b2541e6556b7
Author: Mike Rylander <mrylander at gmail.com>
Date:   Sun Feb 1 14:46:00 2015 -0500

    LP#1402797 Default to staff-forced cancel cause
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
index b155085..f1fa1fa 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/holds.js
@@ -64,6 +64,7 @@ function($modal , $q , egCore , egConfirmDialog , egAlertDialog) {
                 ['$scope', '$modalInstance', 'cancel_reasons',
                 function($scope, $modalInstance, cancel_reasons) {
                     $scope.args = {
+                        cancel_reason : 5,
                         cancel_reasons : cancel_reasons,
                         num_holds : hold_ids.length
                     };

commit a171d5984e3b05ad9642188dea3eaff7727f733d
Author: Mike Rylander <mrylander at gmail.com>
Date:   Sun Feb 1 14:12:45 2015 -0500

    LP#1402797 Add Circulation Modifier to the column list (must add manually, as it is a link-type field)
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_items_out.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_items_out.tt2
index 912ada5..cc3a9d9 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_items_out.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_items_out.tt2
@@ -86,6 +86,7 @@
     </a>
   </eg-grid-field>
   <eg-grid-field path="*" hidden></eg-grid-field>
+  <eg-grid-field path="target_copy.circ_modifier" hidden></eg-grid-field>
   <eg-grid-field path="target_copy.*" hidden></eg-grid-field>
   <eg-grid-field path="target_copy.call_number.*" hidden></eg-grid-field>
   <eg-grid-field path="target_copy.call_number.record.*" hidden></eg-grid-field>

commit 567982c3b7a03d4a8ffed692b4032d7dfc720b95
Author: Mike Rylander <mrylander at gmail.com>
Date:   Sat Jan 31 14:51:21 2015 -0500

    LP#1402797 Make warnings more prominent, as in the XUL client
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/checkin/t_checkin.tt2 b/Open-ILS/src/templates/staff/circ/checkin/t_checkin.tt2
index c578efa..732b342 100644
--- a/Open-ILS/src/templates/staff/circ/checkin/t_checkin.tt2
+++ b/Open-ILS/src/templates/staff/circ/checkin/t_checkin.tt2
@@ -7,45 +7,43 @@
 
 <div class="row">
   <div class="col-md-12">
-    <div class="flex-row left-anchored">
-      <div ng-if="is_backdate()" class="alert-danger pad-all-min">
-        [% l('Backdated Check In [_1]', 
-          '{{checkinArgs.backdate | date:"shortDate"}}') %]
-      </div>
-      <div ng-if="modifiers.no_precat_alert" class="alert-danger pad-all-min">
-        [% l('Ignore Pre-Cataloged Items') %]
-      </div>
-      <div ng-if="modifiers.noop" class="alert-danger pad-all-min">
-        [% l('Suppress Holds and Transits') %]
-      </div>
-      <div ng-if="modifiers.void_overdues" class="alert-danger pad-all-min">
-        [% l('Amnesty Mode') %]
-      </div>
-      <div ng-if="modifiers.auto_print_holds_transits" 
-        class="alert-danger pad-all-min">
-        [% l('Auto-Print Hold and Transit Slips') %]
-      </div>
-      <div ng-if="modifiers.clear_expired" class="alert-danger pad-all-min">
-        [% l('Clear Holds Shelf') %]
-      </div>
-      <div ng-if="modifiers.retarget_holds" class="alert-danger pad-all-min">
-        <div ng-if="modifiers.retarget_holds_all">
-          [% l('Always Retarget Local Holds') %]
-        </div>
-        <div ng-if="!modifiers.retarget_holds_all">
-          [% l('Retarget Local Holds') %]
-        </div>
+    <div ng-if="is_backdate()" class="alert-danger pad-all-min">
+      [% l('Backdated Check In [_1]', 
+        '{{checkinArgs.backdate | date:"shortDate"}}') %]
+    </div>
+    <div ng-if="modifiers.no_precat_alert" class="alert-danger pad-all-min">
+      [% l('Ignore Pre-Cataloged Items') %]
+    </div>
+    <div ng-if="modifiers.noop" class="alert-danger pad-all-min">
+      [% l('Suppress Holds and Transits') %]
+    </div>
+    <div ng-if="modifiers.void_overdues" class="alert-danger pad-all-min">
+      [% l('Amnesty Mode') %]
+    </div>
+    <div ng-if="modifiers.auto_print_holds_transits" 
+      class="alert-danger pad-all-min">
+      [% l('Auto-Print Hold and Transit Slips') %]
+    </div>
+    <div ng-if="modifiers.clear_expired" class="alert-danger pad-all-min">
+      [% l('Clear Holds Shelf') %]
+    </div>
+    <div ng-if="modifiers.retarget_holds" class="alert-danger pad-all-min">
+      <div ng-if="modifiers.retarget_holds_all">
+        [% l('Always Retarget Local Holds') %]
       </div>
-      <div ng-if="modifiers.hold_as_transit" class="alert-danger pad-all-min">
-        [% l('Capture Local Holds As Transits') %]
+      <div ng-if="!modifiers.retarget_holds_all">
+        [% l('Retarget Local Holds') %]
       </div>
     </div>
+    <div ng-if="modifiers.hold_as_transit" class="alert-danger pad-all-min">
+      [% l('Capture Local Holds As Transits') %]
+    </div>
   </div>
 </div>
 
 <!-- checkin form -->
 <div class="row pad-vert">
-  <div class="col-md-4">
+  <div class="col-md-5">
     <form ng-submit="checkin(checkinArgs)" role="form" class="form-inline">
       <div class="input-group">
 
@@ -63,7 +61,7 @@
     </form>
   </div>
 
-  <div class="col-md-4">
+  <div class="col-md-3">
     <div ng-if="alert" class="col-md-12 alert-danger pad-all-min">
       <span ng-if="alert.already_checked_in">
         [% l('[_1] was already checked in.', '{{alert.already_checked_in}}') %]

commit a6b1295c0a505fcc9b6c68acbb441531b8c4f2db
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Jan 30 17:33:08 2015 -0500

    LP#1402797 Style search and clear buttons like action buttons to differentiate from text boxes
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_search.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_search.tt2
index bc4e92a..a4118b0 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_search.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_search.tt2
@@ -27,11 +27,11 @@
         </div>
 
         <div class="col-md-2" ng-mouseover="setLastFormElement()">
-          <input type="submit" class="btn btn-default" value="[% l('Search') %]"/>
+          <input type="submit" class="btn btn-primary" value="[% l('Search') %]"/>
         </div>
 
         <div class="col-md-2" ng-mouseover="setLastFormElement()">
-          <input type="reset" class="btn btn-default" ng-click="clearForm()" 
+          <input type="reset" class="btn btn-primary" ng-click="clearForm()" 
             value="[% l('Clear Form') %]"/>
         </div>
 

commit a304398ea54448ac5c1a5419776d65b3fe9a8bb2
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Jan 30 17:16:09 2015 -0500

    LP#1402797 Protect against chrome autofill as best we can in Verfiy Credentials
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_credentials.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_credentials.tt2
index 524cb7f..6355722 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_credentials.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_credentials.tt2
@@ -11,7 +11,7 @@
               for="verify-username">[% l('Username') %]</label>
             <div class="col-md-8">
               <input type="text" id="verify-username" class="form-control" 
-                focus-me="focusMe" ng-disabled="prepop"
+                focus-me="focusMe" ng-disabled="prepop" autocomplete="off"
                 placeholder="[% l('Username') %]" ng-model="username"/>
             </div>
           </div>
@@ -21,7 +21,7 @@
               for="verify-barcode">[% l('Barcode') %]</label>
             <div class="col-md-8">
               <input type="text" id="verify-barcode" class="form-control" 
-                ng-disabled="prepop"
+                ng-disabled="prepop" autocomplete="off"
                 placeholder="[% l('Barcode') %]" ng-model="barcode"/>
             </div>
           </div>
@@ -30,7 +30,7 @@
             <label class="col-md-4 control-label" 
               for="verify-password">[% l('Password') %]</label>
             <div class="col-md-8">
-              <input type="password" id="verify-password" class="form-control" 
+              <input autocomplete="off" type="password" id="verify-password" class="form-control" 
                 placeholder="[% l('Password') %]" ng-model="password"/>
             </div>
           </div>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
index f2f21a3..cca86d0 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
@@ -1167,10 +1167,14 @@ function($scope,  $routeParams , $location , egCore) {
     // called with a patron, pre-populate the form args
     $scope.initTab('other', $routeParams.id).then(
         function() {
-            if ($scope.patron()) {
+            if ($routeParams.id && $scope.patron()) {
                 $scope.prepop = true;
                 $scope.username = $scope.patron().usrname();
                 $scope.barcode = $scope.patron().card().barcode();
+            } else {
+                $scope.username = '';
+                $scope.barcode = '';
+                $scope.password = '';
             }
         }
     );

commit 9e09624986a8ede16d8591b066f215fef6de2ec9
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Jan 30 16:56:56 2015 -0500

    LP#1402797 Where possible, handle all circ events, not just the first one in the stack
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/share/t_event_override_dialog.tt2 b/Open-ILS/src/templates/staff/circ/share/t_event_override_dialog.tt2
index 5850ac5..7308145 100644
--- a/Open-ILS/src/templates/staff/circ/share/t_event_override_dialog.tt2
+++ b/Open-ILS/src/templates/staff/circ/share/t_event_override_dialog.tt2
@@ -7,11 +7,13 @@
     </h4>
   </div>
   <div class="modal-body">
-    <div class="panel panel-danger">
-      <div class="panel-heading">{{evt.textcode}}</div>
-      <div class="panel-body">
-        <div ng-if="copy_barcode" class="strong-text-2">{{copy_barcode}}</div>
-        {{evt.desc}}
+    <div ng-repeat="evt in events">
+      <div class="panel panel-danger">
+        <div class="panel-heading">{{evt.textcode}}</div>
+        <div class="panel-body">
+          <div ng-if="copy_barcode" class="strong-text-2">{{copy_barcode}}</div>
+          {{evt.desc}}
+        </div>
       </div>
     </div>
   </div>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
index ec1bcca..e4db7a2 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
@@ -124,7 +124,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
 
             ).then(function(evt) {
 
-                if (angular.isArray(evt)) evt = evt[0];
+                if (!angular.isArray(evt)) evt = [evt];
 
                 return service.flesh_response_data('checkout', evt, params, options)
                 .then(function() {
@@ -163,7 +163,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
 
             ).then(function(evt) {
 
-                if (angular.isArray(evt)) evt = evt[0];
+                if (!angular.isArray(evt)) evt = [evt];
 
                 return service.flesh_response_data(
                     'renew', evt, params, options)
@@ -203,7 +203,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
 
             ).then(function(evt) {
 
-                if (angular.isArray(evt)) evt = evt[0];
+                if (!angular.isArray(evt)) evt = [evt];
                 return service.flesh_response_data(
                     'checkin', evt, params, options)
                 .then(function() {
@@ -220,9 +220,9 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
     service.munge_resp_data = function(final_resp) {
         var data = final_resp.data = {};
 
-        if (!final_resp.evt) return;
+        if (!final_resp.evt[0]) return;
 
-        var payload = final_resp.evt.payload;
+        var payload = final_resp.evt[0].payload;
         if (!payload) return;
 
         data.circ = payload.circ;
@@ -235,10 +235,10 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
         data.transit = payload.transit;
         data.status = payload.status;
         data.message = payload.message;
-        data.title = final_resp.evt.title;
-        data.author = final_resp.evt.author;
-        data.isbn = final_resp.evt.isbn;
-        data.route_to = final_resp.evt.route_to;
+        data.title = final_resp.evt[0].title;
+        data.author = final_resp.evt[0].author;
+        data.isbn = final_resp.evt[0].isbn;
+        data.route_to = final_resp.evt[0].route_to;
 
         // for checkin, the mbts lives on the main circ
         if (payload.circ && payload.circ.billable_transaction())
@@ -265,14 +265,14 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
             // override attempt already made and failed.
             // NOTE: I don't think we'll ever get here, since the
             // override attempt should produce a perm failure...
-            console.debug('override failed: ' + evt.textcode);
+            angular.forEach(evt, function(e){ console.debug('override failed: ' + e.textcode); });
             return $q.reject();
 
         } 
 
-        if (service.auto_override_checkout_events[evt.textcode]) {
-            // user has already opted to override this type
-            // of event.  Re-run the checkout w/ override.
+        if (evt.filter(function(e){return !service.auto_override_checkout_events[e.textcode];}).length == 0) {
+            // user has already opted to override these type
+            // of events.  Re-run the checkout w/ override.
             options.override = true;
             return service.checkout(params, options);
         } 
@@ -281,11 +281,11 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
         // Some events offer a stock override dialog, while others
         // require additional context.
 
-        switch(evt.textcode) {
+        switch(evt[0].textcode) {
             case 'COPY_NOT_AVAILABLE':
-                return service.copy_not_avail_dialog(evt, params, options);
+                return service.copy_not_avail_dialog(evt[0], params, options);
             case 'COPY_ALERT_MESSAGE':
-                return service.copy_alert_dialog(evt, params, options, 'checkout');
+                return service.copy_alert_dialog(evt[0], params, options, 'checkout');
             default: 
                 return service.override_dialog(evt, params, options, 'checkout');
         }
@@ -297,15 +297,15 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
             // override attempt already made and failed.
             // NOTE: I don't think we'll ever get here, since the
             // override attempt should produce a perm failure...
-            console.debug('override failed: ' + evt.textcode);
+            angular.forEach(evt, function(e){ console.debug('override failed: ' + e.textcode); });
             return $q.reject();
 
         } 
 
         // renewal auto-overrides are the same as checkout
-        if (service.auto_override_checkout_events[evt.textcode]) {
-            // user has already opted to override this type
-            // of event.  Re-run the renew w/ override.
+        if (evt.filter(function(e){return !service.auto_override_checkout_events[e.textcode];}).length == 0) {
+            // user has already opted to override these type
+            // of events.  Re-run the renew w/ override.
             options.override = true;
             return service.renew(params, options);
         } 
@@ -314,9 +314,9 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
         // Some events offer a stock override dialog, while others
         // require additional context.
 
-        switch(evt.textcode) {
+        switch(evt[0].textcode) {
             case 'COPY_ALERT_MESSAGE':
-                return service.copy_alert_dialog(evt, params, options, 'renew');
+                return service.copy_alert_dialog(evt[0], params, options, 'renew');
             default: 
                 return service.override_dialog(evt, params, options, 'renew');
         }
@@ -329,14 +329,14 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
             // override attempt already made and failed.
             // NOTE: I don't think we'll ever get here, since the
             // override attempt should produce a perm failure...
-            console.debug('override failed: ' + evt.textcode);
+            angular.forEach(evt, function(e){ console.debug('override failed: ' + e.textcode); });
             return $q.reject();
 
         } 
 
         if (options.suppress_checkin_popups
-            && service.checkin_suppress_overrides.indexOf(evt.textcode) > -1) {
-            // Event is suppressed.  Re-run the checkin w/ override.
+            && evt.filter(function(e){return service.checkin_suppress_overrides.indexOf(e.textcode) == -1;}).length == 0) {
+            // Events are suppressed.  Re-run the checkin w/ override.
             options.override = true;
             return service.checkin(params, options);
         } 
@@ -345,9 +345,9 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
         // Some events offer a stock override dialog, while others
         // require additional context.
 
-        switch(evt.textcode) {
+        switch(evt[0].textcode) {
             case 'COPY_ALERT_MESSAGE':
-                return service.copy_alert_dialog(evt, params, options, 'checkin');
+                return service.copy_alert_dialog(evt[0], params, options, 'checkin');
             default: 
                 return service.override_dialog(evt, params, options, 'checkin');
         }
@@ -359,14 +359,14 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
         var final_resp = {evt : evt, params : params, options : options};
 
         // track the barcode regardless of whether it refers to a copy
-        evt.copy_barcode = params.copy_barcode;
+        angular.forEach(evt, function(e){ e.copy_barcode = params.copy_barcode; });
 
         // Overridable Events
-        if (service.renew_overridable_events.indexOf(evt.textcode) > -1) 
+        if (evt.filter(function(e){return service.renew_overridable_events.indexOf(e.textcode) > -1;}).length > 0)
             return service.handle_overridable_renew_event(evt, params, options);
 
         // Other events
-        switch (evt.textcode) {
+        switch (evt[0].textcode) {
             case 'SUCCESS':
                 return $q.when(final_resp);
 
@@ -376,22 +376,22 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
             case 'PATRON_ACCOUNT_EXPIRED':
             case 'CIRC_CLAIMS_RETURNED':
                 return service.exit_alert(
-                    egCore.strings[evt.textcode],
+                    egCore.strings[evt[0].textcode],
                     {barcode : params.copy_barcode}
                 );
 
             case 'PERM_FAILURE':
                 return service.exit_alert(
-                    egCore.strings[evt.textcode],
-                    {permission : evt.ilsperm}
+                    egCore.strings[evt[0].textcode],
+                    {permission : evt[0].ilsperm}
                 );
 
             default:
                 return service.exit_alert(
                     egCore.strings.CHECKOUT_FAILED_GENERIC, {
                         barcode : params.copy_barcode,
-                        textcode : evt.textcode,
-                        desc : evt.desc
+                        textcode : evt[0].textcode,
+                        desc : evt[0].desc
                     }
                 );
         }
@@ -403,14 +403,14 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
         var final_resp = {evt : evt, params : params, options : options};
 
         // track the barcode regardless of whether it refers to a copy
-        evt.copy_barcode = params.copy_barcode;
+        angular.forEach(evt, function(e){ e.copy_barcode = params.copy_barcode; });
 
         // Overridable Events
-        if (service.checkout_overridable_events.indexOf(evt.textcode) > -1) 
+        if (evt.filter(function(e){return service.checkout_overridable_events.indexOf(e.textcode) > -1;}).length > 0)
             return service.handle_overridable_checkout_event(evt, params, options);
 
         // Other events
-        switch (evt.textcode) {
+        switch (evt[0].textcode) {
             case 'SUCCESS':
                 return $q.when(final_resp);
 
@@ -428,22 +428,22 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
             case 'PATRON_ACCOUNT_EXPIRED':
             case 'CIRC_CLAIMS_RETURNED':
                 return service.exit_alert(
-                    egCore.strings[evt.textcode],
+                    egCore.strings[evt[0].textcode],
                     {barcode : params.copy_barcode}
                 );
 
             case 'PERM_FAILURE':
                 return service.exit_alert(
-                    egCore.strings[evt.textcode],
-                    {permission : evt.ilsperm}
+                    egCore.strings[evt[0].textcode],
+                    {permission : evt[0].ilsperm}
                 );
 
             default:
                 return service.exit_alert(
                     egCore.strings.CHECKOUT_FAILED_GENERIC, {
                         barcode : params.copy_barcode,
-                        textcode : evt.textcode,
-                        desc : evt.desc
+                        textcode : evt[0].textcode,
+                        desc : evt[0].desc
                     }
                 );
         }
@@ -492,7 +492,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
     service.flesh_response_data = function(action, evt, params, options) {
         var promises = [];
         var payload;
-        if (!evt || !(payload = evt.payload)) return $q.when();
+        if (!evt[0] || !(payload = evt[0].payload)) return $q.when();
 
         promises.push(service.flesh_copy_location(payload.copy));
         if (payload.copy) {
@@ -532,14 +532,14 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
         }
 
         // extract precat values
-        evt.title = payload.record ? payload.record.title() : 
-            (payload.copy ? payload.copy.dummy_title() : null);
+        angular.forEach(evt, function(e){ e.title = payload.record ? payload.record.title() : 
+            (payload.copy ? payload.copy.dummy_title() : null);});
 
-        evt.author = payload.record ? payload.record.author() : 
-            (payload.copy ? payload.copy.dummy_author() : null);
+        angular.forEach(evt, function(e){ e.author = payload.record ? payload.record.author() : 
+            (payload.copy ? payload.copy.dummy_author() : null);});
 
-        evt.isbn = payload.record ? payload.record.isbn() : 
-            (payload.copy ? payload.copy.dummy_isbn() : null);
+        angular.forEach(evt, function(e){ e.isbn = payload.record ? payload.record.isbn() : 
+            (payload.copy ? payload.copy.dummy_isbn() : null);});
 
         return $q.all(promises);
     }
@@ -599,14 +599,17 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
     // opens a dialog asking the user if they would like to override
     // the returned event.
     service.override_dialog = function(evt, params, options, action) {
+        if (!angular.isArray(evt)) evt = [evt];
         return $modal.open({
             templateUrl: './circ/share/t_event_override_dialog',
             controller: 
                 ['$scope', '$modalInstance', 
                 function($scope, $modalInstance) {
-                $scope.evt = evt;
-                $scope.auto_override = 
-                    service.checkout_auto_override_after_first.indexOf(evt.textcode) > -1;
+                $scope.events = evt;
+                $scope.auto_override =
+                    evt.filter(function(e){
+                        return service.checkout_auto_override_after_first.indexOf(evt.textcode) > -1;
+                    }).length > 0;
                 $scope.copy_barcode = params.copy_barcode; // may be null
                 $scope.ok = function() { $modalInstance.close() }
                 $scope.cancel = function ($event) { 
@@ -623,8 +626,10 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
                 }
 
                 // checkout/renew support override-after-first
-                if (service.checkout_auto_override_after_first.indexOf(evt.textcode) > -1)
-                    service.auto_override_checkout_events[evt.textcode] = true;
+                angular.forEach(evt, function(e){
+                    if (service.checkout_auto_override_after_first.indexOf(e.textcode) > -1)
+                        service.auto_override_checkout_events[e.textcode] = true;
+                });
 
                 return service[action](params, options);
             }
@@ -632,6 +637,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
     }
 
     service.copy_not_avail_dialog = function(evt, params, options) {
+        if (angular.isArray(evt)) evt = evt[0];
         return $modal.open({
             templateUrl: './circ/share/t_copy_not_avail_dialog',
             controller: 
@@ -736,6 +742,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
     // find the open transit for the given copy barcode; flesh the org
     // units locally.
     service.find_copy_transit = function(evt, params, options) {
+        if (angular.isArray(evt)) evt = evt[0];
 
         if (evt && evt.payload && evt.payload.transit)
             return $q.when(evt.payload.transit);
@@ -763,6 +770,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
     }
 
     service.copy_in_transit_dialog = function(evt, params, options) {
+        if (angular.isArray(evt)) evt = evt[0];
         return $modal.open({
             templateUrl: './circ/share/t_copy_in_transit_dialog',
             controller: 
@@ -811,6 +819,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
     }
 
     service.circ_exists_dialog = function(evt, params, options) {
+        if (angular.isArray(evt)) evt = evt[0];
 
         if (!evt.payload.old_circ) {
             return egCore.net.request(
@@ -1165,6 +1174,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
     // alert when copy location alert_message is set.
     // This does not affect processing, it only produces a click-through
     service.handle_checkin_loc_alert = function(evt, params, options) {
+        if (angular.isArray(evt)) evt = evt[0];
 
         var copy = evt && evt.payload ? evt.payload.copy : null;
 
@@ -1179,25 +1189,26 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
     }
 
     service.handle_checkin_resp = function(evt, params, options) {
+        if (!angular.isArray(evt)) evt = [evt];
 
         var final_resp = {evt : evt, params : params, options : options};
 
         var copy, hold, transit;
-        if (evt.payload) {
-            copy = evt.payload.copy;
-            hold = evt.payload.hold;
-            transit = evt.payload.transit;
+        if (evt[0].payload) {
+            copy = evt[0].payload.copy;
+            hold = evt[0].payload.hold;
+            transit = evt[0].payload.transit;
         }
 
         // track the barcode regardless of whether it's valid
-        evt.copy_barcode = params.copy_barcode;
+        angular.forEach(evt, function(e){ e.copy_barcode = params.copy_barcode; });
 
-        console.debug('checkin event ' + evt.textcode);
+        angular.forEach(evt, function(e){ console.debug('checkin event ' + e.textcode); });
 
-        if (service.checkin_overridable_events.indexOf(evt.textcode) > -1) 
+        if (evt.filter(function(e){return service.checkin_overridable_events.indexOf(e.textcode) > -1;}).length > 0)
             return service.handle_overridable_checkin_event(evt, params, options);
 
-        switch (evt.textcode) {
+        switch (evt[0].textcode) {
 
             case 'SUCCESS':
             case 'NO_CHANGE':
@@ -1220,10 +1231,10 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
                             if (hold.pickup_lib() == egCore.auth.user().ws_ou()) {
                                 // inform user if the item is on the local holds shelf
                             
-                                evt.route_to = egCore.strings.ROUTE_TO_HOLDS_SHELF;
+                                evt[0].route_to = egCore.strings.ROUTE_TO_HOLDS_SHELF;
                                 return service.route_dialog(
                                     './circ/share/t_hold_shelf_dialog', 
-                                    evt, params, options
+                                    evt[0], params, options
                                 ).then(function() { return final_resp });
 
                             } else {
@@ -1240,7 +1251,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
                         }
 
                     case 11: /* CATALOGING */
-                        evt.route_to = egCore.strings.ROUTE_TO_CATALOGING;
+                        evt[0].route_to = egCore.strings.ROUTE_TO_CATALOGING;
                         return $q.when(final_resp);
 
                     case 15: /* ON_RESERVATION_SHELF */
@@ -1256,7 +1267,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
             case 'ROUTE_ITEM':
                 return service.route_dialog(
                     './circ/share/t_transit_dialog', 
-                    evt, params, options
+                    evt[0], params, options
                 ).then(function() { return final_resp });
 
             case 'ASSET_COPY_NOT_FOUND':
@@ -1265,7 +1276,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
                     .result.then(function() {return final_resp});
 
             case 'ITEM_NOT_CATALOGED':
-                evt.route_to = egCore.strings.ROUTE_TO_CATALOGING;
+                evt[0].route_to = egCore.strings.ROUTE_TO_CATALOGING;
                 if (options.no_precat_alert) 
                     return $q.when(final_resp);
                 return egAlertDialog.open(
@@ -1273,7 +1284,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
                     .result.then(function() {return final_resp});
 
             default:
-                console.warn('unhandled checkin response : ' + evt.textcode);
+                console.warn('unhandled checkin response : ' + evt[0].textcode);
                 return $q.when(final_resp);
         }
     }
@@ -1281,6 +1292,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
     // collect transit, address, and hold info that's not already
     // included in responses.
     service.collect_route_data = function(tmpl, evt, params, options) {
+        if (angular.isArray(evt)) evt = evt[0];
         var promises = [];
         var data = {};
 
@@ -1313,6 +1325,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
     }
 
     service.route_dialog = function(tmpl, evt, params, options) {
+        if (angular.isArray(evt)) evt = evt[0];
 
         return service.collect_route_data(tmpl, evt, params, options)
         .then(function(data) {
@@ -1383,6 +1396,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
 
     // action == what action to take if the user confirms the alert
     service.copy_alert_dialog = function(evt, params, options, action) {
+        if (angular.isArray(evt)) evt = evt[0];
         return egConfirmDialog.open(
             egCore.strings.COPY_ALERT_MSG_DIALOG_TITLE, 
             evt.payload,  // payload == alert message text

commit 7081631586ae6ad3df5e62a5e151cfdf15130fc5
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Jan 30 15:47:53 2015 -0500

    LP#1402797 Change Thaw Date column label to Activation Date
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 2f2974d..08041ca 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -5439,7 +5439,7 @@ SELECT  usr,
 			<field reporter:label="Bib Record link" name="bib_rec" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Eligible Copies" name="eligible_copies" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Currently Frozen" name="frozen" reporter:datatype="bool"/>
-			<field reporter:label="Thaw Date (if frozen)" name="thaw_date" reporter:datatype="timestamp"/>
+			<field reporter:label="Activation Date" name="thaw_date" reporter:datatype="timestamp"/>
 			<field reporter:label="Shelf Time" name="shelf_time" reporter:datatype="timestamp"/>
 			<field reporter:label="Cancelation cause" name="cancel_cause" reporter:datatype="link" />
 			<field reporter:label="Cancelation note" name="cancel_note" reporter:datatype="text" />
@@ -5587,7 +5587,7 @@ SELECT  usr,
 			<field reporter:label="Bib Record link" name="bib_rec" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Eligible Copies" name="eligible_copies" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Currently Frozen" name="frozen" reporter:datatype="bool"/>
-			<field reporter:label="Thaw Date (if frozen)" name="thaw_date" reporter:datatype="timestamp"/>
+			<field reporter:label="Activation Date" name="thaw_date" reporter:datatype="timestamp"/>
 			<field reporter:label="Shelf Time" name="shelf_time" reporter:datatype="timestamp"/>
 			<field reporter:label="Cancelation cause" name="cancel_cause" reporter:datatype="link" />
 			<field reporter:label="Cancelation note" name="cancel_note" reporter:datatype="text" />
@@ -5670,7 +5670,7 @@ SELECT  usr,
 			<field reporter:label="Bib Record link" name="bib_rec" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Eligible Copies" name="eligible_copies" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Currently Frozen" name="frozen" reporter:datatype="bool"/>
-			<field reporter:label="Thaw Date (if frozen)" name="thaw_date" reporter:datatype="timestamp"/>
+			<field reporter:label="Activation Date" name="thaw_date" reporter:datatype="timestamp"/>
 			<field reporter:label="Shelf Time" name="shelf_time" reporter:datatype="timestamp"/>
 			<field reporter:label="Cancelation cause" name="cancel_cause" reporter:datatype="link" />
 			<field reporter:label="Cancelation note" name="cancel_note" reporter:datatype="text" />
@@ -5752,7 +5752,7 @@ SELECT  usr,
 			<field reporter:label="Hold Cancel Date/Time" name="cancel_time" reporter:datatype="timestamp"/>
 			<field reporter:label="Bib Record link" name="bib_rec" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Currently Frozen" name="frozen" reporter:datatype="bool"/>
-			<field reporter:label="Thaw Date (if frozen)" name="thaw_date" reporter:datatype="timestamp"/>
+			<field reporter:label="Activation Date" name="thaw_date" reporter:datatype="timestamp"/>
 			<field reporter:label="Shelf Time" name="shelf_time" reporter:datatype="timestamp"/>
 			<field reporter:label="Cancelation cause" name="cancel_cause" reporter:datatype="link" />
 			<field reporter:label="Cancelation note" name="cancel_note" reporter:datatype="text" />
@@ -5811,7 +5811,7 @@ SELECT  usr,
 			<field reporter:label="Hold Cancel Date/Time" name="cancel_time" reporter:datatype="timestamp"/>
 			<field reporter:label="Bib Record link" name="bib_rec" oils_persist:virtual="true" reporter:datatype="link"/>
 			<field reporter:label="Currently Frozen" name="frozen" reporter:datatype="bool"/>
-			<field reporter:label="Thaw Date (if frozen)" name="thaw_date" reporter:datatype="timestamp"/>
+			<field reporter:label="Activation Date" name="thaw_date" reporter:datatype="timestamp"/>
 			<field reporter:label="Shelf Time" name="shelf_time" reporter:datatype="timestamp"/>
 			<field reporter:label="Cancelation cause" name="cancel_cause" reporter:datatype="link" />
 			<field reporter:label="Cancelation note" name="cancel_note" reporter:datatype="text" />

commit d9cce1c07e2a46afbe4ea3b9ddeb233efb9a047c
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Jan 30 15:38:27 2015 -0500

    LP#1402797 Fixed typo stopping patron search expand/collapse stickiness
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
index e6b22c0..f2f21a3 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
@@ -920,7 +920,7 @@ function($scope,  $q,  $routeParams,  $timeout,  $window,  $location,  egCore,
         $event.preventDefault();
     }
 
-    egCore.hatch.getItem('eg.prefs.circ.patron.search.showExtras')
+    egCore.hatch.getItem('eg.circ.patron.search.show_extras')
     .then(function(val) {$scope.showExtras = val});
 
     // map form arguments into search params

commit b3f9abee16e07d87a78dbd3850477697030f03e6
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Jan 30 14:17:45 2015 -0500

    LP#1402797 Renew instead of in/out when the item is already checked out to the user
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/share/t_circ_exists_dialog.tt2 b/Open-ILS/src/templates/staff/circ/share/t_circ_exists_dialog.tt2
index 3a8b5d1..f0b37fd 100644
--- a/Open-ILS/src/templates/staff/circ/share/t_circ_exists_dialog.tt2
+++ b/Open-ILS/src/templates/staff/circ/share/t_circ_exists_dialog.tt2
@@ -21,8 +21,10 @@
         </div>
       </div>
       <div class="modal-footer">
-        <input type="submit" class="btn btn-primary" 
+        <input if="!sameUser" type="submit" class="btn btn-primary" 
             value="[% l('Normal Checkin then Checkout') %]"/>
+        <input ng-if="sameUser" type="submit" class="btn btn-primary" 
+            value="[% l('Renew') %]"/>
         <button class="btn btn-warning" 
             ng-click="cancel($event)">[% l('Cancel') %]</button>
       </div>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
index 40552ab..ec1bcca 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
@@ -851,7 +851,11 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
             }]
         }).result.then(
             function() {
-                
+                if (sameUser) {
+                    options.override = true;
+                    return service.renew(params, options);
+                }
+
                 return service.checkin(
                     {barcode : params.copy_barcode, noop : true}
                 ).then(function(checkin_resp) {

commit cde17e137d1084475fc1a5756348dc49bc47624b
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Jan 30 13:47:18 2015 -0500

    LP#1402797 Enter sumbits the prompt dialog
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/share/t_prompt_dialog.tt2 b/Open-ILS/src/templates/staff/share/t_prompt_dialog.tt2
index ce19832..9581bcc 100644
--- a/Open-ILS/src/templates/staff/share/t_prompt_dialog.tt2
+++ b/Open-ILS/src/templates/staff/share/t_prompt_dialog.tt2
@@ -9,7 +9,7 @@
   </div>
   <div class="modal-body">
     <div class="col-md-12">
-      <input type='text' ng-model="args.value" class="form-control" focus-me="focus"/>
+      <input ng-keyup="$event.keyCode == 13 ? ok() : null" type='text' ng-model="args.value" class="form-control" focus-me="focus"/>
     </div>
   </div>
   <div class="modal-footer">

commit bdb574f5e4dd7244bbc27aa6fee11dee240d79c8
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Jan 30 13:33:10 2015 -0500

    LP#1402797 Just hide the statusbar by default. It gets in the way.
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/statusbar.tt2 b/Open-ILS/src/templates/staff/statusbar.tt2
index 47675b6..1153223 100644
--- a/Open-ILS/src/templates/staff/statusbar.tt2
+++ b/Open-ILS/src/templates/staff/statusbar.tt2
@@ -3,7 +3,7 @@
 <div id="status-bar" 
   class="navbar navbar-default navbar-fixed-bottom" 
   ng-model="statusbar_hidden"
-  ng-hide="statusbar_hidden"
+  ng-hide="true"
   role="navigation">
 
   <!-- 

commit 72830baa57aa4e6a81b5b96a545fa95b661b3352
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Jan 30 13:05:26 2015 -0500

    LP#1402797 Be explicit about which direction users are moving between groups, and provide an informational message when none are selected but should be
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_move_to_group_dialog.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_move_to_group_dialog.tt2
index 96e9733..ee04de0 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_move_to_group_dialog.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_move_to_group_dialog.tt2
@@ -6,7 +6,8 @@
       [% l('Move user into this group?') %]
     </h4>
     <h4 ng-if="outbound" class="modal-title">
-      [% l("Move selected users to the following user's group?") %]
+      <span ng-show="selected.length">[% l("Move selected users to the following user's group?") %]</span>
+      <span ng-hide="selected.length">[% l("No users selected for move.") %]</span>
     </h4>
   </div>
   <div class="modal-body">
@@ -21,7 +22,7 @@
     </a>
   </div>
   <div class="modal-footer">
-    <button class="btn btn-primary" ng-click="ok()">[% l('Move User') %]</button>
+    <button ng-disabled="outbound && !selected.length" class="btn btn-primary" ng-click="ok()">[% l('Move User') %]</button>
     <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
   </div>
 </div>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
index d3c0add..e6b22c0 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js
@@ -1411,7 +1411,7 @@ function($scope,  $routeParams , $q , $window , $location , egCore ,
         $q.all(promises).then(function() {grid.refresh()});
     }
 
-    function showMoveToGroupConfirm(barcode, selected) {
+    function showMoveToGroupConfirm(barcode, selected, outbound) {
 
         // find the user
         egCore.pcrud.search('ac', {barcode : barcode})
@@ -1430,7 +1430,8 @@ function($scope,  $routeParams , $q , $window , $location , egCore ,
                                 '$scope','$modalInstance',
                         function($scope , $modalInstance) {
                             $scope.user = user;
-                            $scope.outbound = Boolean(selected);
+                            $scope.selected = selected;
+                            $scope.outbound = outbound;
                             $scope.ok = 
                                 function(count) { $modalInstance.close() }
                             $scope.cancel = 
@@ -1438,7 +1439,7 @@ function($scope,  $routeParams , $q , $window , $location , egCore ,
                         }
                     ]
                 }).result.then(function() {
-                    if (selected) {
+                    if (outbound) {
                         moveUsersToGroup(user, selected);
                     } else {
                         addUserToGroup(user);
@@ -1450,18 +1451,18 @@ function($scope,  $routeParams , $q , $window , $location , egCore ,
 
     // selected == move selected patrons to another patron's group
     // !selected == patron from a different group moves into our group
-    function moveToGroup(selected) {
+    function moveToGroup(selected, outbound) {
         egPromptDialog.open(
             egCore.strings.GROUP_ADD_USER, '',
             {ok : function(value) {
                 if (value) 
-                    showMoveToGroupConfirm(value, selected);
+                    showMoveToGroupConfirm(value, selected, outbound);
             }}
         );
     }
 
-    $scope.moveToGroup = function() { moveToGroup() };
-    $scope.moveToAnotherGroup = function(selected) { moveToGroup(selected) };
+    $scope.moveToGroup = function() { moveToGroup([], false) };
+    $scope.moveToAnotherGroup = function(selected) { moveToGroup(selected, true) };
 
     $scope.cloneUser = function(selected) {
         if (!selected.length) return;

commit 4ccbf980e1f2eadf9f99e015e7ac3d4765d046f2
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Jan 30 11:43:46 2015 -0500

    LP#1402797 Hold Shefl: Use max_chunk_size to pass updates in a timely fashion; Notify on the correct array to allow paging back to work
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
index 5061f24..144098e 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Holds.pm
@@ -1687,6 +1687,7 @@ sub print_hold_pull_list_stream {
     delete($$params{chunk_size}) unless (int($$params{chunk_size}));
     delete($$params{chunk_size}) if  ($$params{chunk_size} && $$params{chunk_size} > 50); # keep the size reasonable
     $$params{chunk_size} ||= 10;
+    $client->max_chunk_size($$params{chunk_size});
 
     $$params{org_id} = (defined $$params{org_id}) ? $$params{org_id}: $e->requestor->ws_ou;
     return $e->die_event unless $e->allowed('VIEW_HOLD', $$params{org_id });
@@ -2225,6 +2226,7 @@ sub print_expired_holds_stream {
     delete($$params{chunk_size}) unless (int($$params{chunk_size}));
     delete($$params{chunk_size}) if  ($$params{chunk_size} && $$params{chunk_size} > 50); # keep the size reasonable
     $$params{chunk_size} ||= 10;
+    $client->max_chunk_size($$params{chunk_size});
 
     $$params{org_id} = (defined $$params{org_id}) ? $$params{org_id}: $e->requestor->ws_ou;
 
@@ -3514,6 +3516,8 @@ sub clear_shelf_cache {
     return $e->die_event unless $e->checkauth and $e->allowed('VIEW_HOLD');
 
     $chunk_size ||= 25;
+    $client->max_chunk_size($chunk_size);
+
     my $hold_data = OpenSRF::Utils::Cache->new('global')->get_cache($cache_key);
 
     if (!$hold_data) {
@@ -3603,7 +3607,7 @@ __PACKAGE__->register_method(
 );
 
 sub clear_shelf_process {
-    my($self, $client, $auth, $org_id, $match_copy) = @_;
+    my($self, $client, $auth, $org_id, $match_copy, $chunk_size) = @_;
 
     my $e = new_editor(authtoken=>$auth);
     $e->checkauth or return $e->die_event;
@@ -3622,7 +3626,9 @@ sub clear_shelf_process {
 
     my @holds;
     my @canceled_holds; # newly canceled holds
-    my $chunk_size = 25; # chunked status updates
+    $chunk_size ||= 25; # chunked status updates
+    $client->max_chunk_size($chunk_size);
+
     my $counter = 0;
     for my $hold_id (@hold_ids) {
 
diff --git a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
index 21e9ffc..8ce83c4 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/holds/app.js
@@ -77,7 +77,7 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
 
         // see if we have the requested range cached
         if (holds[offset]) {
-            return provider.arrayNotifier(patronSvc.holds, offset, count);
+            return provider.arrayNotifier(holds, offset, count);
         }
 
         // see if we have the holds IDs for this range already loaded
@@ -173,7 +173,8 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
         egCore.net.request(
             'open-ils.circ',
             'open-ils.circ.hold.clear_shelf.process',
-            egCore.auth.token(), $scope.pickup_ou.id()
+            egCore.auth.token(), $scope.pickup_ou.id(),
+            null, 1
 
         // request responses from the clear shelf cache
         ).then(
@@ -184,7 +185,7 @@ function($scope , $q , $routeParams , $window , $location , egCore , egHolds , e
                 egCore.net.request(
                     'open-ils.circ',
                     'open-ils.circ.hold.clear_shelf.get_cache',
-                    egCore.auth.token(), resp.cache_key
+                    egCore.auth.token(), resp.cache_key, 1
                 ).then(null, null, handle_clear_cache_resp);
             }, 
 

commit afa3b5f9a08bb0fa8178bae4f0f3396f98d72d98
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Jan 27 18:40:18 2015 -0500

    LP#1402797 Respect ui.staff.require_initials.patron_standing_penalty OU setting
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/share/t_new_message_dialog.tt2 b/Open-ILS/src/templates/staff/circ/share/t_new_message_dialog.tt2
index a3dab2b..c6a8960 100644
--- a/Open-ILS/src/templates/staff/circ/share/t_new_message_dialog.tt2
+++ b/Open-ILS/src/templates/staff/circ/share/t_new_message_dialog.tt2
@@ -42,8 +42,8 @@
     <div class="modal-footer">
       <div class="row">
         <div class="col-md-2">
-          <input type="text" class="form-control" 
-            ng-model="args.initials" placeholder="[% l('Initials') %]" required/>
+          <input type="text" class="form-control" ng-hide="!require_initials" 
+            ng-model="args.initials" placeholder="[% l('Initials') %]" ng-required="require_initials"/>
         </div>
         <div class="col-md-10 pull-right">
           <input type="submit" class="btn btn-primary" value="[% l('OK') %]"/>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
index cd60333..40552ab 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
@@ -12,8 +12,17 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
     var service = {
         // auto-override these events after the first override
         auto_override_checkout_events : {},
+        require_initials : false
     };
 
+    egCore.startup.go().finally(function() {
+        egCore.org.settings([
+            'ui.staff.require_initials.patron_standing_penalty'
+        ]).then(function(set) {
+            service.require_initials = Boolean(set['ui.staff.require_initials.patron_standing_penalty']);
+        });
+    });
+
     service.reset = function() {
         service.auto_override_checkout_events = {};
     }
@@ -1444,6 +1453,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
             function($scope , $modalInstance , staffPenalties) {
                 $scope.focusNote = true;
                 $scope.penalties = staffPenalties;
+                $scope.require_initials = service.require_initials;
                 $scope.args = {penalty : 21}; // default to Note
                 $scope.setPenalty = function(id) {
                     args.penalty = id;
@@ -1460,7 +1470,8 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
                 var pen = new egCore.idl.ausp();
                 pen.usr(user_id);
                 pen.org_unit(egCore.auth.user().ws_ou());
-                pen.note(args.note + ' [' + args.initials + ']');
+                pen.note(args.note);
+                if (args.initials) pen.note(args.note + ' [' + args.initials + ']');
                 if (args.custom_penalty) {
                     pen.standing_penalty(args.custom_penalty);
                 } else {
@@ -1482,6 +1493,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
             function($scope , $modalInstance , staffPenalties) {
                 $scope.focusNote = true;
                 $scope.penalties = staffPenalties;
+                $scope.require_initials = service.require_initials;
                 $scope.args = {
                     penalty : usr_penalty.standing_penalty().id(),
                     note : usr_penalty.note()
@@ -1497,6 +1509,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
         }).result.then(
             function(args) {
                 usr_penalty.note(args.note);
+                if (args.initials) usr_penalty.note(args.note + ' [' + args.initials + ']');
                 usr_penalty.standing_penalty(args.penalty);
                 return egCore.pcrud.update(usr_penalty);
             }

commit ca7710651c2d89078c961804db76a464c8b33cb6
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Jan 27 12:33:13 2015 -0500

    LP#1402797 Require, and use, initials on penalty creation
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/share/t_new_message_dialog.tt2 b/Open-ILS/src/templates/staff/circ/share/t_new_message_dialog.tt2
index a656e38..a3dab2b 100644
--- a/Open-ILS/src/templates/staff/circ/share/t_new_message_dialog.tt2
+++ b/Open-ILS/src/templates/staff/circ/share/t_new_message_dialog.tt2
@@ -40,7 +40,15 @@
       </div>
     </div>
     <div class="modal-footer">
-      <input type="submit" class="btn btn-primary" value="[% l('OK') %]"/>
-      <button class="btn btn-warning" ng-click="cancel($event)">[% l('Cancel') %]</button>
+      <div class="row">
+        <div class="col-md-2">
+          <input type="text" class="form-control" 
+            ng-model="args.initials" placeholder="[% l('Initials') %]" required/>
+        </div>
+        <div class="col-md-10 pull-right">
+          <input type="submit" class="btn btn-primary" value="[% l('OK') %]"/>
+          <button class="btn btn-warning" ng-click="cancel($event)">[% l('Cancel') %]</button>
+        </div>
+      </div>
     </div>
 </form>
diff --git a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
index 27d8ed4..cd60333 100644
--- a/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
+++ b/Open-ILS/web/js/ui/default/staff/circ/services/circ.js
@@ -1460,7 +1460,7 @@ function($modal , $q , egCore , egAlertDialog , egConfirmDialog) {
                 var pen = new egCore.idl.ausp();
                 pen.usr(user_id);
                 pen.org_unit(egCore.auth.user().ws_ou());
-                pen.note(args.note);
+                pen.note(args.note + ' [' + args.initials + ']');
                 if (args.custom_penalty) {
                     pen.standing_penalty(args.custom_penalty);
                 } else {

commit b149ca10bf198aa3fd00546e3f43a9a8b25a9138
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Jan 26 22:30:03 2015 +0000

    LP#1402797 add "Delete Selected Records From Catalog" action to record buckets
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/bucket/record/index.tt2 b/Open-ILS/src/templates/staff/cat/bucket/record/index.tt2
index 2f21e72..2c45eff 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/record/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/index.tt2
@@ -9,6 +9,12 @@
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/bucket/record/app.js"></script>
+<script>
+  angular.module('egCoreMod').run(['egStrings', function(s) {
+    s.CONFIRM_DELETE_RECORD_BUCKET_ITEMS_FROM_CATALOG =
+      "[% l('Delete these records?') %]";
+  }]);
+</script>
 [% END %]
 
 <!-- using native Bootstrap taps because of limitations
diff --git a/Open-ILS/src/templates/staff/cat/bucket/record/t_records_not_deleted.tt2 b/Open-ILS/src/templates/staff/cat/bucket/record/t_records_not_deleted.tt2
new file mode 100644
index 0000000..fc8520f
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/t_records_not_deleted.tt2
@@ -0,0 +1,19 @@
+<div class="modal-dialog">
+  <div class="modal-content">
+    <div class="modal-header">
+      <button type="button" class="close" 
+        ng-click="cancel()" aria-hidden="true">×</button>
+      <h4 class="modal-title">[% l('Records That Could Not Be Deleted') %]</h4>
+    </div>
+    <div class="modal-body">
+      <ul>
+        <li ng-repeat="failure in failures">
+            {{failure.recordId}} / {{failure.desc}}
+        </li>
+      </ul>
+    </div>
+    <div class="modal-footer">
+      <button class="btn btn-primary" ng-click="ok()">[% l('OK') %]</button>
+    </div>
+  </div> <!-- modal-content -->
+</div> <!-- modal-dialog -->
diff --git a/Open-ILS/src/templates/staff/cat/bucket/record/t_view.tt2 b/Open-ILS/src/templates/staff/cat/bucket/record/t_view.tt2
index b6768c5..9ff16bf 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/record/t_view.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/t_view.tt2
@@ -16,6 +16,9 @@
   <eg-grid-action label="[% l('Remove Selected Records') %]" 
     handler="detachRecords"></eg-grid-action>
 
+  <eg-grid-action label="[% l('Delete Selected Records from Catalog') %]" 
+    handler="deleteRecordsFromCatalog"></eg-grid-action>
+
   <eg-grid-action label="[% l('Export Records') %]" 
     handler="openExportBucketDialog"></eg-grid-action>
 
diff --git a/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js b/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
index 3bcd714..c2df9cd 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
@@ -195,6 +195,24 @@ angular.module('egCatRecordBuckets',
         return deferred.promise;
     }
 
+    service.deleteRecordFromCatalog = function(recordId) {
+        var deferred = $q.defer();
+
+        egCore.net.request(
+            'open-ils.cat',
+            'open-ils.cat.biblio.record_entry.delete',
+            egCore.auth.token(), recordId
+        ).then(function(resp) { 
+            // rather than rejecting the promise in the
+            // case of a failure, we'll let the caller
+            // look for errors -- doing this because AngularJS
+            // does not have a native $q.allSettled() yet.
+            deferred.resolve(resp);
+        });
+        
+        return deferred.promise;
+    }
+
     // delete bucket by ID.
     // resolved w/ response on successful delete,
     // rejected otherwise.
@@ -486,9 +504,9 @@ function($scope,  $routeParams,  bucketSvc , egGridDataProvider) {
 
 .controller('ViewCtrl',
        ['$scope','$q','$routeParams','bucketSvc', 'egCore', '$window',
-        '$timeout',
+        '$timeout', 'egConfirmDialog', '$modal',
 function($scope,  $q , $routeParams,  bucketSvc, egCore, $window,
-        $timeout) {
+        $timeout, egConfirmDialog, $modal) {
 
     $scope.setTab('view');
     $scope.bucketId = $routeParams.id;
@@ -544,6 +562,42 @@ function($scope,  $q , $routeParams,  bucketSvc, egCore, $window,
         return $q.all(promises).then(drawBucket);
     }
 
+    $scope.deleteRecordsFromCatalog = function(records) {
+        egConfirmDialog.open(
+            egCore.strings.CONFIRM_DELETE_RECORD_BUCKET_ITEMS_FROM_CATALOG,
+            '',
+            {}
+        ).result.then(function() {
+            var promises = [];
+            angular.forEach(records, function(rec) {
+                promises.push(bucketSvc.deleteRecordFromCatalog(rec.id));
+            });
+            bucketSvc.bucketNeedsRefresh = true;
+            return $q.all(promises).then(function(results) {
+                var failures = results.filter(function(result) {
+                    return egCore.evt.parse(result);
+                }).map(function(result) {
+                    var evt = egCore.evt.parse(result);
+                    if (evt) {
+                        return { recordId: evt.payload, desc: evt.desc };
+                    }
+                });
+                if (failures.length) {
+                    $modal.open({
+                        templateUrl: './cat/bucket/record/t_records_not_deleted',
+                        controller :
+                            ['$scope', '$modalInstance', function($scope, $modalInstance) {
+                            $scope.failures = failures;
+                            $scope.ok = function() { $modalInstance.close() }
+                            $scope.cancel = function() { $modalInstance.dismiss() }
+                            }]
+                    });
+                }
+                drawBucket();
+            });
+        });
+    }
+
     // fetch the bucket;  on error show the not-allowed message
     if ($scope.bucketId) 
         drawBucket()['catch'](function() { $scope.forbidden = true });

commit 91e08b415b5d19aaac56c2ba0fea427654cbf346
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Jan 26 20:37:12 2015 +0000

    LP#1402797 catch and allow overriding of COPY_DELETE_WARNING and TITLE_LAST_COPY
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2
index 259beea..db0e6dc 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2
@@ -13,6 +13,10 @@
   angular.module('egCoreMod').run(['egStrings', function(s) {
     s.CONFIRM_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG =
       "[% l('Are you sure you want to delete selected items in bucket from catalog?') %]";
+    s.OVERRIDE_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG_TITLE =
+      "[% l('One or more items could not be deleted. Override?') %]";
+    s.OVERRIDE_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG_BODY =
+      "[% l('Reason(s) include: [_1]', '{{evt_desc}}') %]";
   }])
 </script>
 [% END %]
diff --git a/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js b/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
index 8f6e3f6..1834de7 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
@@ -475,7 +475,24 @@ function($scope,  $q , $routeParams,  bucketSvc, egCore,
                     'open-ils.cat.asset.copy.fleshed.batch.update',
                     egCore.auth.token(), fleshed_copies, true
                 ).then(function(resp) {
-                    // TODO deal with events that this method could return
+                    var evt = egCore.evt.parse(resp);
+                    if (evt) {
+                        egConfirmDialog.open(
+                            egCore.strings.OVERRIDE_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG_TITLE,
+                            egCore.strings.OVERRIDE_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG_BODY,
+                            {'evt_desc': evt.desc}
+                        ).result.then(function() {
+                            egCore.net.request(
+                                'open-ils.cat',
+                                'open-ils.cat.asset.copy.fleshed.batch.update.override',
+                                egCore.auth.token(), fleshed_copies, true,
+                                { events: ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING'] }
+                            ).then(function(resp) {
+                                bucketSvc.bucketNeedsRefresh = true;
+                                drawBucket();
+                            });
+                        });
+                    }
                     bucketSvc.bucketNeedsRefresh = true;
                     drawBucket();
                 });

commit ad7e1b91f8c433dd737da404d8ba1f24183ac32b
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Jan 26 19:54:41 2015 +0000

    LP#1402797 make it "delete selected" rather than "delete all"
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2
index 8a81f6d..259beea 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2
@@ -12,7 +12,7 @@
 <script>
   angular.module('egCoreMod').run(['egStrings', function(s) {
     s.CONFIRM_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG =
-      "[% l('Are you sure you want to delete all items in bucket from catalog?') %]";
+      "[% l('Are you sure you want to delete selected items in bucket from catalog?') %]";
   }])
 </script>
 [% END %]
diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/t_view.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/t_view.tt2
index e9446bd..07e5f23 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/copy/t_view.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/t_view.tt2
@@ -12,7 +12,7 @@
 
   <eg-grid-action label="[% l('Remove Selected Copies') %]" 
     handler="detachCopies"></eg-grid-action>
-  <eg-grid-action label="[% l('Delete All from Catalog') %]" 
+  <eg-grid-action label="[% l('Delete Selected Copies from Catalog') %]" 
     handler="deleteCopiesFromCatalog"></eg-grid-action>
 
   <eg-grid-field path="id" required hidden></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js b/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
index 3a9bf0e..8f6e3f6 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
@@ -449,19 +449,19 @@ function($scope,  $q , $routeParams,  bucketSvc, egCore,
         return $q.all(promises).then(drawBucket);
     }
 
-    $scope.deleteCopiesFromCatalog = function() {
+    $scope.deleteCopiesFromCatalog = function(copies) {
         egConfirmDialog.open(
             egCore.strings.CONFIRM_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG,
             '', {}
         ).result.then(function() {
             var fleshed_copies = [];
             var promises = [];
-            angular.forEach(bucketSvc.currentBucket.items(), function(i) {
+            angular.forEach(copies, function(i) {
                 promises.push(
                     egCore.net.request(
                         'open-ils.search',
                         'open-ils.search.asset.copy.fleshed2.retrieve',
-                        i.target_copy()
+                        i.id
                     ).then(function(copy) {
                         copy.ischanged(1);
                         copy.isdeleted(1);

commit 9a0705241adb554fd33788056b6395ced99b5144
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Jan 19 21:48:10 2015 -0500

    LP#1402797 Implement "set bottom view as default"
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
index 8953bb7..29a6eb6 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
@@ -31,6 +31,8 @@
             [% l('Mark as Title Hold Transfer Destination') %]</a></li>
         <li><a href ng-click="transfer_holds_to_marked()">
             [% l('Transfer All Title Holds') %]</a></li>
+        <li><a href ng-click="set_default_record_tab()">
+            [% l('Set bottom view as default') %]</a></li>
       </ul>
     </div>
   </div>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
index f41a3fe..181a9ff 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
@@ -148,6 +148,14 @@ function($scope , $routeParams , $location , $q , egCore , egHolds,
         } else {
             delete $scope.record_id;
         }
+
+        if ($scope.record_id) {
+            var default_tab = egCore.hatch.getLocalItem( 'eg.cat.default_record_tab' );
+            tab = $routeParams.record_tab || default_tab || 'catalog';
+        } else {
+            tab = $routeParams.record_tab || 'catalog';
+        }
+        $scope.set_record_tab(tab);
     }
 
     // xulG catalog handlers
@@ -281,7 +289,18 @@ function($scope , $routeParams , $location , $q , egCore , egHolds,
         }
     }
 
-    var tab = $routeParams.record_tab || 'catalog';
+    $scope.set_default_record_tab = function() {
+        egCore.hatch.setLocalItem(
+            'eg.cat.default_record_tab', $scope.record_tab);
+    }
+
+    var tab;
+    if ($scope.record_id) {
+        var default_tab = egCore.hatch.getLocalItem( 'eg.cat.default_record_tab' );
+        tab = $routeParams.record_tab || default_tab || 'catalog';
+    } else {
+        tab = $routeParams.record_tab || 'catalog';
+    }
     $scope.set_record_tab(tab);
 
 }])

commit 17b0f48cf1ec49049bdb6762448040c967e235bf
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Jan 19 17:22:20 2015 -0500

    LP#1402797 Use target="_self" to force a top-level page load
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
index aefa817..89eb58d 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
@@ -59,7 +59,7 @@
   <eg-grid-field label="[% l('Pickup Library') %]" path='hold.pickup_lib.shortname'></eg-grid-field>
 
   <eg-grid-field label="[% l('Title') %]" path='mvr.title'>
-    <a href="[% ctx.base_path %]/staff/cat/catalog/record/{{item.mvr.doc_id()}}">
+    <a target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{item.mvr.doc_id()}}">
       {{item.mvr.title()}}
     </a>
   </eg-grid-field>
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_items_out.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_items_out.tt2
index db55327..912ada5 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_items_out.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_items_out.tt2
@@ -81,7 +81,7 @@
   <eg-grid-field label="[% l('Renewals Remaining') %]" path='renewal_remaining'></eg-grid-field>
   <eg-grid-field label="[% l('Fines Stopped') %]" path='stop_fines'></eg-grid-field>
   <eg-grid-field label="[% l('Title') %]" name="title">
-    <a href="[% ctx.base_path %]/staff/cat/catalog/record/{{item.target_copy().call_number().record().id()}}">
+    <a target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{item.target_copy().call_number().record().id()}}">
       {{item.target_copy().call_number().record().simple_record().title()}}
     </a>
   </eg-grid-field>
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_xact_details.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_xact_details.tt2
index 2655eaf..059153b 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_xact_details.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_xact_details.tt2
@@ -7,7 +7,7 @@
   <div class="col-md-2">{{xact.summary().total_owed() | currency}}</div>
   <div class="col-md-2 strong-text">[% l('Title') %]</div>
   <div class="col-md-2">
-    <a ng-if="title_id" href="[% ctx.base_path %]/staff/cat/catalog/record/{{title_id}}">{{title}}</a>
+    <a ng-if="title_id" target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{title_id}}">{{title}}</a>
     <span ng-if="!title_id">{{title}}</span>
   </div>
 </div>

commit 06e505f2fee20e1b99c5087368a6f5c03022c1f2
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Jan 19 17:15:53 2015 -0500

    LP#1402797 Use the staff-wrapped catalog when jumping to a bib record
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/circ/patron/t_checkout.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_checkout.tt2
index f42bfe9..28bba44 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_checkout.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_checkout.tt2
@@ -100,7 +100,7 @@
     path='circ.renewal_remaining'></eg-grid-field>
 
   <eg-grid-field label="[% l('Title') %]" path="title">
-    <a target="_self" href="[% ctx.base_path %]/opac/record/{{record.doc_id()}}">
+    <a target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{record.doc_id()}}">
       {{item.title}}
     </a>
   </eg-grid-field>
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
index c9bdf36..aefa817 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_holds_list.tt2
@@ -59,7 +59,7 @@
   <eg-grid-field label="[% l('Pickup Library') %]" path='hold.pickup_lib.shortname'></eg-grid-field>
 
   <eg-grid-field label="[% l('Title') %]" path='mvr.title'>
-    <a href="[% ctx.base_path %]/opac/record/{{item.mvr.doc_id()}}">
+    <a href="[% ctx.base_path %]/staff/cat/catalog/record/{{item.mvr.doc_id()}}">
       {{item.mvr.title()}}
     </a>
   </eg-grid-field>
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_items_out.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_items_out.tt2
index ca3eef1..db55327 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_items_out.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_items_out.tt2
@@ -81,7 +81,7 @@
   <eg-grid-field label="[% l('Renewals Remaining') %]" path='renewal_remaining'></eg-grid-field>
   <eg-grid-field label="[% l('Fines Stopped') %]" path='stop_fines'></eg-grid-field>
   <eg-grid-field label="[% l('Title') %]" name="title">
-    <a href="[% ctx.base_path %]/opac/record/{{item.target_copy().call_number().record().id()}}">
+    <a href="[% ctx.base_path %]/staff/cat/catalog/record/{{item.target_copy().call_number().record().id()}}">
       {{item.target_copy().call_number().record().simple_record().title()}}
     </a>
   </eg-grid-field>
diff --git a/Open-ILS/src/templates/staff/circ/patron/t_xact_details.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_xact_details.tt2
index 7bc04e7..2655eaf 100644
--- a/Open-ILS/src/templates/staff/circ/patron/t_xact_details.tt2
+++ b/Open-ILS/src/templates/staff/circ/patron/t_xact_details.tt2
@@ -7,7 +7,7 @@
   <div class="col-md-2">{{xact.summary().total_owed() | currency}}</div>
   <div class="col-md-2 strong-text">[% l('Title') %]</div>
   <div class="col-md-2">
-    <a ng-if="title_id" href="[% ctx.base_path %]/opac/record/{{title_id}}">{{title}}</a>
+    <a ng-if="title_id" href="[% ctx.base_path %]/staff/cat/catalog/record/{{title_id}}">{{title}}</a>
     <span ng-if="!title_id">{{title}}</span>
   </div>
 </div>
diff --git a/Open-ILS/src/templates/staff/circ/renew/t_renew.tt2 b/Open-ILS/src/templates/staff/circ/renew/t_renew.tt2
index c2880c1..f8946d5 100644
--- a/Open-ILS/src/templates/staff/circ/renew/t_renew.tt2
+++ b/Open-ILS/src/templates/staff/circ/renew/t_renew.tt2
@@ -102,7 +102,7 @@
     path='circ.renewal_remaining'></eg-grid-field>
 
   <eg-grid-field label="[% l('Title') %]" path="title">
-    <a target="_self" href="[% ctx.base_path %]/opac/record/{{record.doc_id()}}">
+    <a target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{record.doc_id()}}">
       {{item.title}}
     </a>
   </eg-grid-field>

commit 8fe98678d6e34e7b415148bb6fd5494d01a9bf6d
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Jan 19 17:05:12 2015 -0500

    LP#1402797 For any grid with a persistKey, store the last selected row count as a localStorage value
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/services/grid.js b/Open-ILS/web/js/ui/default/staff/services/grid.js
index 1f89a9c..8b2b441 100644
--- a/Open-ILS/web/js/ui/default/staff/services/grid.js
+++ b/Open-ILS/web/js/ui/default/staff/services/grid.js
@@ -90,7 +90,6 @@ angular.module('egGridMod',
 
             grid.init = function() {
                 grid.offset = 0;
-                grid.limit = Number($scope.pageSize) || 25;
                 $scope.items = [];
                 $scope.showGridConf = false;
                 grid.totalCount = -1;
@@ -106,6 +105,14 @@ angular.module('egGridMod',
                 grid.persistKey = $scope.persistKey;
                 delete $scope.persistKey;
 
+                var stored_limit = 0;
+                if (grid.persistKey) {
+                    var stored_limit = Number(
+                        egCore.hatch.getLocalItem('eg.grid.' + grid.persistKey + '.limit')
+                    );
+                }
+                grid.limit = Number(stored_limit) || Number($scope.pageSize) || 25;
+
                 grid.indexField = $scope.idField;
                 delete $scope.idField;
 
@@ -217,6 +224,8 @@ angular.module('egGridMod',
                 }
 
                 controls.setLimit = function(limit) {
+                    if (grid.persistKey)
+                        egCore.hatch.setLocalItem('eg.grid.' + grid.persistKey + '.limit', limit);
                     grid.limit = limit;
                 }
                 controls.getLimit = function() {
@@ -389,8 +398,11 @@ angular.module('egGridMod',
             }
 
             $scope.limit = function(l) { 
-                if (angular.isNumber(l))
+                if (angular.isNumber(l)) {
+                    if (grid.persistKey)
+                        egCore.hatch.setLocalItem('eg.grid.' + grid.persistKey + '.limit', l);
                     grid.limit = l;
+                }
                 return grid.limit 
             }
 

commit 4cec7e69222e96c390fdb393be4a88793fd73605
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Jan 19 15:17:37 2015 -0500

    LP#1402797 Add record summary bar to the catalog display, and hide Actions for this Record until usable
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
index 0549d7d..8953bb7 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
@@ -1,12 +1,16 @@
 
-<div class="row pad-vert">
-  <div class="col-md-9">
-    <div class="alert alert-info alert-less-pad strong-text-2">
-      <span ng-if="record_tab == 'catalog'">[% l('Catalog') %]</span>
-      <span ng-if="record_tab == 'marc_html'">[% l('MARC HTML') %]</span>
-      <span ng-if="record_tab == 'holds'">[% l('Holds for Record') %]</span>
-    </div>
-  </div>
+<div class="row pad-vert col-md-12 alert alert-info alert-less-pad strong-text-2">
+  <span ng-if="record_tab == 'catalog'">[% l('Catalog') %]</span>
+  <span ng-if="record_tab == 'marc_html'">[% l('MARC HTML') %]</span>
+  <span ng-if="record_tab == 'holds'">[% l('Holds for Record') %]</span>
+</div>
+
+<div ng-show="record_id" class="row col-md-12">
+    <eg-record-summary record-id="record_id" record="summary_pane_record"></eg-record-summary>
+</div>
+
+<div ng-show="record_id" class="row col-md-12 pad-vert">
+  <div class="col-md-9"></div> <!-- padding -->
   <div class="col-md-3">
     <!-- actions for this record menu -->
     <div class="btn-group pull-right" dropdown>
@@ -32,7 +36,7 @@
   </div>
 </div>
 
-<div>
+<div class="row col-md-12">
   <!-- ng-show allows the catalog iframe to stay loaded (unlike ng-if) -->
   <div ng-show="record_tab == 'catalog'">
     <eg-embed-frame url="catalog_url" handlers="handlers" onchange="handle_page"></eg-embed-frame>

commit 0ad7e3945d90cf0ab0a919e82ae49e96d477467e
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Fri Jan 16 22:12:01 2015 +0000

    LP#1402797 implement "delete all from catalog" for copy buckets
    
    TODO: at present, this is pretty optimistic
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2
index 9b08f10..8a81f6d 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2
@@ -9,6 +9,12 @@
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/bucket/copy/app.js"></script>
+<script>
+  angular.module('egCoreMod').run(['egStrings', function(s) {
+    s.CONFIRM_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG =
+      "[% l('Are you sure you want to delete all items in bucket from catalog?') %]";
+  }])
+</script>
 [% END %]
 
 <!-- using native Bootstrap taps because of limitations
diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/t_view.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/t_view.tt2
index 336c1b7..e9446bd 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/copy/t_view.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/t_view.tt2
@@ -12,6 +12,8 @@
 
   <eg-grid-action label="[% l('Remove Selected Copies') %]" 
     handler="detachCopies"></eg-grid-action>
+  <eg-grid-action label="[% l('Delete All from Catalog') %]" 
+    handler="deleteCopiesFromCatalog"></eg-grid-action>
 
   <eg-grid-field path="id" required hidden></eg-grid-field>
   <eg-grid-field path="call_number.record.id" required hidden></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js b/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
index 061a23b..3a9bf0e 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
@@ -402,8 +402,10 @@ function($scope,  $routeParams,  bucketSvc , egGridDataProvider,   egCore) {
 }])
 
 .controller('ViewCtrl',
-       ['$scope','$q','$routeParams','bucketSvc',
-function($scope,  $q , $routeParams,  bucketSvc) {
+       ['$scope','$q','$routeParams','bucketSvc', 'egCore',
+        'egConfirmDialog',
+function($scope,  $q , $routeParams,  bucketSvc, egCore,
+         egConfirmDialog) {
 
     $scope.setTab('view');
     $scope.bucketId = $routeParams.id;
@@ -447,6 +449,40 @@ function($scope,  $q , $routeParams,  bucketSvc) {
         return $q.all(promises).then(drawBucket);
     }
 
+    $scope.deleteCopiesFromCatalog = function() {
+        egConfirmDialog.open(
+            egCore.strings.CONFIRM_DELETE_COPY_BUCKET_ITEMS_FROM_CATALOG,
+            '', {}
+        ).result.then(function() {
+            var fleshed_copies = [];
+            var promises = [];
+            angular.forEach(bucketSvc.currentBucket.items(), function(i) {
+                promises.push(
+                    egCore.net.request(
+                        'open-ils.search',
+                        'open-ils.search.asset.copy.fleshed2.retrieve',
+                        i.target_copy()
+                    ).then(function(copy) {
+                        copy.ischanged(1);
+                        copy.isdeleted(1);
+                        fleshed_copies.push(copy);
+                    })
+                );
+            });
+            $q.all(promises).then(function() {
+                egCore.net.request(
+                    'open-ils.cat',
+                    'open-ils.cat.asset.copy.fleshed.batch.update',
+                    egCore.auth.token(), fleshed_copies, true
+                ).then(function(resp) {
+                    // TODO deal with events that this method could return
+                    bucketSvc.bucketNeedsRefresh = true;
+                    drawBucket();
+                });
+            });
+        });
+    }
+
     // fetch the bucket;  on error show the not-allowed message
     if ($scope.bucketId) 
         drawBucket()['catch'](function() { $scope.forbidden = true });

commit 258b8e00578cd93fc7a4f9d5555f8167f96acd7c
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 15 22:12:49 2015 +0000

    LP#1402797 webstaff: add basic copy bucket management functionality
    
    This adds an interface for managing copy buckets, including
    adding and removing them, adding items to a pending list and to
    copy buckets by barcode, and removing items from a bucket.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2
new file mode 100644
index 0000000..9b08f10
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2
@@ -0,0 +1,57 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Copy Buckets"); 
+  ctx.page_app = "egCatCopyBuckets";
+  ctx.page_ctrl = "CopyBucketCtrl";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/bucket/copy/app.js"></script>
+[% END %]
+
+<!-- using native Bootstrap taps because of limitations
+with angular-ui tabsets. it always defaults to making the
+first tab active, so it can't be driven from the route
+https://github.com/angular-ui/bootstrap/issues/910 
+No JS is needed to drive the native tabs, since we're
+changing routes with each tab selection anyway.
+-->
+
+<ul class="nav nav-tabs">
+  <li ng-class="{active : tab == 'pending'}">
+    <a href="./cat/bucket/copy/pending/{{bucketSvc.currentBucket.id()}}">
+        [% l('Pending Copies') %]
+        <span ng-cloak>({{bucketSvc.pendingList.length}})</span>
+    </a>
+  </li>
+  <li ng-class="{active : tab == 'view'}">
+    <a href="./cat/bucket/copy/view/{{bucketSvc.currentBucket.id()}}">
+        [% l('Bucket View') %]
+        <span ng-cloak>({{bucketSvc.currentBucket.items().length}})</span>
+    </a>
+  </li>
+</ul>
+<div class="tab-content">
+  <div class="tab-pane active">
+
+    <!-- bucket info header -->
+    <div class="row">
+      <div class="col-md-6">
+        [% INCLUDE 'staff/cat/bucket/copy/t_bucket_info.tt2' %]
+      </div>
+    </div>
+
+    <!-- bucket not accessible warning -->
+    <div class="col-md-10 col-md-offset-1" ng-show="forbidden">
+      <div class="alert alert-warning">
+        [% l('The selected bucket "{{bucketId}}" is not visible to this login.') %]
+      </div>
+    </div>
+
+    <div ng-view></div>
+  </div>
+</div>
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_create.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_create.tt2
new file mode 100644
index 0000000..e6bb3fe
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_create.tt2
@@ -0,0 +1,35 @@
+<!-- edit bucket dialog -->
+
+<!-- use <form> so we get submit-on-enter for free -->
+<form class="form-validated" novalidate name="form" ng-submit="ok(args)">
+  <div>
+    <div class="modal-header">
+      <button type="button" class="close" 
+        ng-click="cancel()" aria-hidden="true">×</button>
+      <h4 class="modal-title">[% l('Create Bucket') %]</h4>
+    </div>
+    <div class="modal-body">
+      <div class="form-group">
+        <label for="edit-bucket-name">[% l('Name') %]</label>
+        <input type="text" class="form-control" focus-me='focusMe' required
+          id="edit-bucket-name" ng-model="args.name" placeholder="[% l('Name...') %]"/>
+      </div>
+      <div class="form-group">
+        <label for="edit-bucket-desc">[% l('Description') %]</label>
+        <input type="text" class="form-control" id="edit-bucket-desc"
+          ng-model="args.desc" placeholder="[% l('Description...') %]"/>
+      </div>
+       <div class="checkbox">
+        <label>
+          <input ng-model="args.pub" type="checkbox"/> 
+          [% l('Publicly Visible?') %]
+        </label>
+      </div>
+    </div>
+    <div class="modal-footer">
+      <input type="submit" ng-disabled="form.$invalid" 
+          class="btn btn-primary" value="[% l('Create Bucket') %]"/>
+      <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+    </div>
+  </div> <!-- modal-content -->
+</form>
diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_delete.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_delete.tt2
new file mode 100644
index 0000000..0ca9887
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_delete.tt2
@@ -0,0 +1,16 @@
+<div class="modal-dialog">
+  <div class="modal-content">
+    <div class="modal-header">
+      <button type="button" class="close" 
+        ng-click="cancel()" aria-hidden="true">×</button>
+      <h4 class="modal-title">[% l('Confirm Bucket Delete') %]</h4>
+    </div>
+    <div class="modal-body">
+      <p>[% l('Delete bucket {{bucket().name()}}?') %]</p>
+    </div>
+    <div class="modal-footer">
+      <button class="btn btn-primary" ng-click="ok()">[% l('Delete Bucket') %]</button>
+      <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+    </div>
+  </div> <!-- modal-content -->
+</div> <!-- modal-dialog -->
diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_edit.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_edit.tt2
new file mode 100644
index 0000000..288c577
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_edit.tt2
@@ -0,0 +1,34 @@
+<!-- edit bucket dialog -->
+<form class="form-validated" novalidate ng-submit="ok(args)" name="form">
+  <div>
+    <div class="modal-header">
+      <button type="button" class="close" 
+        ng-click="cancel()" aria-hidden="true">×</button>
+      <h4 class="modal-title">[% l('Edit Bucket') %]</h4>
+    </div>
+    <div class="modal-body">
+      <div class="form-group">
+        <label for="edit-bucket-name">[% l('Name') %]</label>
+        <input type="text" class="form-control" focus-me='focusMe' required
+          id="edit-bucket-name" ng-model="args.name" placeholder="[% l('Name...') %]"/>
+      </div>
+      <div class="form-group">
+        <label for="edit-bucket-desc">[% l('Description') %]</label>
+        <input type="text" class="form-control" id="edit-bucket-desc"
+          ng-model="args.desc" placeholder="[% l('Description...') %]"/>
+      </div>
+       <div class="checkbox">
+        <label>
+          <input ng-model="args.pub" type="checkbox"> 
+          [% l('Publicly Visible?') %]
+        </label>
+      </div>
+    </div>
+    <div class="modal-footer">
+      <input type="submit" class="btn btn-primary" 
+          ng-disabled="form.$invalid" value="[% l('Apply Changes') %]"/>
+      <button class="btn btn-warning" ng-click="cancel()"
+          ng-class="{disabled : actionPending}">[% l('Cancel') %]</button>
+    </div>
+  </div> <!-- modal-content -->
+</form>
diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_info.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_info.tt2
new file mode 100644
index 0000000..877fcf6
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_info.tt2
@@ -0,0 +1,16 @@
+
+<div ng-show="bucket()">
+  <strong>[% l('Bucket: {{bucket().name()}}') %]</strong> 
+  <span>
+    <ng-pluralize count="bucketSvc.currentBucket.items().length"
+      when="{'one': '[% l("1 item") %]', 'other': '[% l("{} items") %]'}">
+    </ng-pluralize>
+  </span> 
+  <span> / [% l('Created {{bucket().create_time() | date}}') %]</span>
+  <span ng-show="bucket().description()"> / {{bucket().description()}}</span>
+</div>
+
+<div ng-show="!bucket()">
+  <strong>[% l('No Bucket Selected') %]</strong>
+</div>
+
diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_selector.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_selector.tt2
new file mode 100644
index 0000000..37eef80
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/t_bucket_selector.tt2
@@ -0,0 +1,27 @@
+<div class="btn-group text-left" dropdown>
+  <button type="button" class="btn btn-default dropdown-toggle">
+    [% l('Buckets') %]<span class="caret"></span>
+  </button>
+  <ul class="dropdown-menu">
+    <li>
+      <a href='' ng-click="openCreateBucketDialog()">[% l('New Bucket') %]</a>
+    </li>
+    <li ng-class="{disabled : !bucket()}">
+      <a href='' ng-click="openEditBucketDialog()">[% l('Edit Bucket') %]</a>
+    </li>
+    <li ng-class="{disabled : !bucket()}">
+      <a href='' ng-click="openDeleteBucketDialog()">[% l('Delete Bucket') %]</a>
+    </li>
+    <li>
+      <a href='' ng-click="openSharedBucketDialog()">[% l('Load Shared Bucket') %]</a>
+    </li>
+    <li role="presentation" class="divider"></li>
+
+    <!-- list all of this user's buckets -->
+    <li ng-repeat="bkt in bucketSvc.allBuckets" 
+      ng-class="{disabled : bkt.id() == bucket().id()}">
+      <a href='' ng-click="loadBucket(bkt.id())">{{bkt.name()}}</a>
+    </li>
+  </ul>
+</div>
+
diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/t_grid_menu.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/t_grid_menu.tt2
new file mode 100644
index 0000000..a2e2bde
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/t_grid_menu.tt2
@@ -0,0 +1,20 @@
+
+<!-- global grid menu displayed on every Bucket page -->
+<eg-grid-menu-item label="[% l('New Bucket') %]" 
+  handler="openCreateBucketDialog"></eg-grid-menu-item>
+
+<eg-grid-menu-item label="[% l('Edit Bucket') %]" 
+  handler="openEditBucketDialog"></eg-grid-menu-item>
+
+<eg-grid-menu-item label="[% l('Delete Bucket') %]" 
+  handler="openDeleteBucketDialog"></eg-grid-menu-item>
+
+<eg-grid-menu-item label="[% l('Shared Bucket') %]" 
+  handler="openSharedBucketDialog"></eg-grid-menu-item>
+
+<eg-grid-menu-item divider="true"></eg-grid-menu-item>
+
+<eg-grid-menu-item ng-repeat="bkt in bucketSvc.allBuckets" 
+  label="{{bkt.name()}}" handler-data="bkt" 
+  handler="loadBucketFromMenu"></eg-grid-menu-item>
+
diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/t_load_shared.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/t_load_shared.tt2
new file mode 100644
index 0000000..9aab308
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/t_load_shared.tt2
@@ -0,0 +1,25 @@
+<!-- load bucket by id ("shared") -->
+<form class="form-validated" novalidate name="form" ng-submit="ok(args)">
+  <div>
+    <div class="modal-header">
+      <button type="button" class="close" 
+        ng-click="cancel()" aria-hidden="true">×</button>
+      <h4 class="modal-title">[% l('Load Shared Bucket Bucket by ID') %]</h4>
+    </div>
+    <div class="modal-body">
+      <div class="form-group">
+        <label for="load-bucket-id">[% l('Bucket ID') %]</label>
+        <!-- NOTE: type='number' / required -->
+        <input type="number" class="form-control" focus-me='focusMe' required
+          id="load-bucket-id" ng-model="args.id" placeholder="[% l('Bucket ID...') %]"/>
+      </div>
+    </div>
+    <div class="modal-footer">
+      <input type="submit" ng-disabled="form.$invalid" 
+          class="btn btn-primary" value="[% l('Load Bucket') %]"/>
+      <button class="btn btn-warning" 
+          ng-click="cancel()">[% l('Cancel') %]</button>
+    </div>
+  </div> <!-- modal-content -->
+</form>
+
diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/t_pending.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/t_pending.tt2
new file mode 100644
index 0000000..dcd0815
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/t_pending.tt2
@@ -0,0 +1,52 @@
+<div class="row">
+  <div class="col-md-6">
+    <form ng-submit="search()">
+      <div class="input-group">
+        <span class="input-group-addon">[% l('Scan Item') %]</span>
+        <input type="text" class="form-control" focus-me="focusMe"
+        ng-model="bucketSvc.barcodeString" placeholder="[% l('Barcode...') %]">
+      </div>
+    </form>
+  </div>
+</div>
+
+<br/>
+
+<eg-grid
+  ng-hide="forbidden"
+  features="-sort,-multisort,-display"
+  id-field="id"
+  idl-class="acp"
+  auto-fields="true"
+  grid-controls="gridControls"
+  items-provider="gridDataProvider"
+  menu-label="[% l('Buckets') %]"
+  persist-key="cat.bucket.copy.pending">
+
+  [% INCLUDE 'staff/cat/bucket/copy/t_grid_menu.tt2' %]
+
+  <!-- actions drop-down -->
+  <eg-grid-action label="[% l('Add To Bucket') %]" 
+    handler="addToBucket"></eg-grid-action>
+
+  <eg-grid-action label="[% l('Clear List') %]" 
+    handler="resetPendingList"></eg-grid-action>
+
+  <eg-grid-field path="id" required hidden></eg-grid-field>
+  <eg-grid-field path="call_number.record.id" required hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Barcode') %]"     path='barcode' visible>
+    <a target="_self" href="[% ctx.base_path %]/staff/cat/item/{{item['id']}}">
+      {{item['barcode']}}
+    </a>
+  </eg-grid-field>
+  <eg-grid-field label="[% l('Call Number') %]" path="call_number.label" visible></eg-grid-field>
+  <eg-grid-field label="[% l('Location') %]"    path="location.name" visible></eg-grid-field>
+
+  <eg-grid-field label="[% l('Title') %]"
+    path="call_number.record.simple_record.title" visible>
+    <a target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{item['call_number.record.id']}}">
+      {{item['call_number.record.simple_record.title']}}
+    </a>
+  </eg-grid-field>
+
+</eg-grid>
diff --git a/Open-ILS/src/templates/staff/cat/bucket/copy/t_view.tt2 b/Open-ILS/src/templates/staff/cat/bucket/copy/t_view.tt2
new file mode 100644
index 0000000..336c1b7
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/bucket/copy/t_view.tt2
@@ -0,0 +1,33 @@
+<eg-grid
+  ng-hide="forbidden"
+  features="-display"
+  id-field="id"
+  idl-class="acp"
+  auto-fields="true"
+  grid-controls="gridControls"
+  menu-label="[% l('Buckets') %]"
+  persist-key="cat.bucket.copy.view">
+
+  [% INCLUDE 'staff/cat/bucket/copy/t_grid_menu.tt2' %]
+
+  <eg-grid-action label="[% l('Remove Selected Copies') %]" 
+    handler="detachCopies"></eg-grid-action>
+
+  <eg-grid-field path="id" required hidden></eg-grid-field>
+  <eg-grid-field path="call_number.record.id" required hidden></eg-grid-field>
+  <eg-grid-field label="[% l('Barcode') %]"     path='barcode' visible>
+    <a target="_self" href="[% ctx.base_path %]/staff/cat/item/{{item['id']}}">
+      {{item['barcode']}}
+    </a>
+  </eg-grid-field>
+  <eg-grid-field label="[% l('Call Number') %]" path="call_number.label" visible></eg-grid-field>
+  <eg-grid-field label="[% l('Location') %]"    path="location.name" visible></eg-grid-field>
+
+  <eg-grid-field label="[% l('Title') %]"
+    path="call_number.record.simple_record.title" visible>
+    <a target="_self" href="[% ctx.base_path %]/staff/cat/catalog/record/{{item['call_number.record.id']}}">
+      {{item['call_number.record.simple_record.title']}}
+    </a>
+  </eg-grid-field>
+
+</eg-grid>
diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2
index e94b1ea..35988d4 100644
--- a/Open-ILS/src/templates/staff/navbar.tt2
+++ b/Open-ILS/src/templates/staff/navbar.tt2
@@ -169,6 +169,12 @@
               [% l('Record Buckets') %]
             </a>
           </li>
+          <li>
+            <a href="./cat/bucket/copy/view" target="_self">
+              <span class="glyphicon glyphicon-list-alt"></span>
+              [% l('Copy Buckets') %]
+            </a>
+          </li>
           <li class="divider"></li>
           <li>
             <a href="./cat/catalog/retrieve_by_id" target="_self">
diff --git a/Open-ILS/src/templates/staff/t_splash.tt2 b/Open-ILS/src/templates/staff/t_splash.tt2
index d259698..2884ba6 100644
--- a/Open-ILS/src/templates/staff/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/t_splash.tt2
@@ -39,6 +39,10 @@
             <img src="/xul/server/skin/media/images/portal/bucket.png"/>
             <a target="_self" href="./cat/bucket/record/">[% l('Record Buckets') %]</a>
           </div>
+          <div>
+            <img src="/xul/server/skin/media/images/portal/bucket.png"/>
+            <a target="_self" href="./cat/bucket/copy/">[% l('Copy Buckets') %]</a>
+          </div>
         </div>
       </div>
     </div>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js b/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
new file mode 100644
index 0000000..061a23b
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
@@ -0,0 +1,453 @@
+/**
+ * Copy Buckets
+ *
+ * Known Issues
+ *
+ * add-all actions only add visible/fetched items.
+ * remove all from bucket UI leaves busted pagination 
+ *   -- apply a refresh after item removal?
+ * problems with bucket view fetching by record ID instead of bucket item:
+ *   -- dupe bibs always sort to the bottom
+ *   -- dupe bibs result in more records displayed per page than requested
+ *   -- item 'pos' ordering is not honored on initial load.
+ */
+
+angular.module('egCatCopyBuckets', 
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod'])
+
+.config(function($routeProvider, $locationProvider, $compileProvider) {
+    $locationProvider.html5Mode(true);
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
+
+    var resolver = {delay : function(egStartup) {return egStartup.go()}};
+
+    $routeProvider.when('/cat/bucket/copy/pending/:id', {
+        templateUrl: './cat/bucket/copy/t_pending',
+        controller: 'PendingCtrl',
+        resolve : resolver
+    });
+
+    $routeProvider.when('/cat/bucket/copy/pending', {
+        templateUrl: './cat/bucket/copy/t_pending',
+        controller: 'PendingCtrl',
+        resolve : resolver
+    });
+
+    $routeProvider.when('/cat/bucket/copy/view/:id', {
+        templateUrl: './cat/bucket/copy/t_view',
+        controller: 'ViewCtrl',
+        resolve : resolver
+    });
+
+    $routeProvider.when('/cat/bucket/copy/view', {
+        templateUrl: './cat/bucket/copy/t_view',
+        controller: 'ViewCtrl',
+        resolve : resolver
+    });
+
+    // default page / bucket view
+    $routeProvider.otherwise({redirectTo : '/cat/bucket/copy/view'});
+})
+
+/**
+ * bucketSvc allows us to communicate between the pending
+ * and view controllers.  It also allows us to cache
+ * data for each so that data reloads are not needed on every 
+ * tab click (i.e. route persistence).
+ */
+.factory('bucketSvc', ['$q','egCore', function($q,  egCore) { 
+
+    var service = {
+        allBuckets : [], // un-fleshed user buckets
+        barcodeString : '', // last scanned barcode
+        barcodeRecords : [], // last scanned barcode results
+        currentBucket : null, // currently viewed bucket
+
+        // per-page list collections
+        pendingList : [],
+        viewList  : [],
+
+        // fetches all staff/copy buckets for the authenticated user
+        // this function may only be called after startup.
+        fetchUserBuckets : function(force) {
+            if (this.allBuckets.length && !force) return;
+            var self = this;
+            return egCore.net.request(
+                'open-ils.actor',
+                'open-ils.actor.container.retrieve_by_class.authoritative',
+                egCore.auth.token(), egCore.auth.user().id(), 
+                'copy', 'staff_client'
+            ).then(function(buckets) { self.allBuckets = buckets });
+        },
+
+        createBucket : function(name, desc) {
+            var deferred = $q.defer();
+            var bucket = new egCore.idl.ccb();
+            bucket.owner(egCore.auth.user().id());
+            bucket.name(name);
+            bucket.description(desc || '');
+            bucket.btype('staff_client');
+
+            egCore.net.request(
+                'open-ils.actor',
+                'open-ils.actor.container.create',
+                egCore.auth.token(), 'copy', bucket
+            ).then(function(resp) {
+                if (resp) {
+                    if (typeof resp == 'object') {
+                        console.error('bucket create error: ' + js2JSON(resp));
+                        deferred.reject();
+                    } else {
+                        deferred.resolve(resp);
+                    }
+                }
+            });
+
+            return deferred.promise;
+        },
+
+        // edit the current bucket.  since we edit the 
+        // local object, there's no need to re-fetch.
+        editBucket : function(args) {
+            var bucket = service.currentBucket;
+            bucket.name(args.name);
+            bucket.description(args.desc);
+            bucket.pub(args.pub);
+            return egCore.net.request(
+                'open-ils.actor',
+                'open-ils.actor.container.update',
+                egCore.auth.token(), 'copy', bucket
+            );
+        }
+    }
+
+    // returns 1 if full refresh is needed
+    // returns 2 if list refresh only is needed
+    service.bucketRefreshLevel = function(id) {
+        if (!service.currentBucket) return 1;
+        if (service.bucketNeedsRefresh) {
+            service.bucketNeedsRefresh = false;
+            service.currentBucket = null;
+            return 1;
+        }
+        if (service.currentBucket.id() != id) return 1;
+        return 2;
+    }
+
+    // returns a promise, resolved with bucket, rejected if bucket is
+    // not fetch-able
+    service.fetchBucket = function(id) {
+        var refresh = service.bucketRefreshLevel(id);
+        if (refresh == 2) return $q.when(service.currentBucket);
+
+        var deferred = $q.defer();
+
+        egCore.net.request(
+            'open-ils.actor',
+            'open-ils.actor.container.flesh.authoritative',
+            egCore.auth.token(), 'copy', id
+        ).then(function(bucket) {
+            var evt = egCore.evt.parse(bucket);
+            if (evt) {
+                console.debug(evt);
+                deferred.reject(evt);
+                return;
+            }
+            service.currentBucket = bucket;
+            deferred.resolve(bucket);
+        });
+
+        return deferred.promise;
+    }
+
+    // deletes a single container item from a bucket by container item ID.
+    // promise is rejected on failure
+    service.detachCopy = function(itemId) {
+        var deferred = $q.defer();
+        egCore.net.request(
+            'open-ils.actor',
+            'open-ils.actor.container.item.delete',
+            egCore.auth.token(), 'copy', itemId
+        ).then(function(resp) { 
+            var evt = egCore.evt.parse(resp);
+            if (evt) {
+                console.error(evt);
+                deferred.reject(evt);
+                return;
+            }
+            console.log('detached bucket item ' + itemId);
+            deferred.resolve(resp);
+        });
+
+        return deferred.promise;
+    }
+
+    // delete bucket by ID.
+    // resolved w/ response on successful delete,
+    // rejected otherwise.
+    service.deleteBucket = function(id) {
+        var deferred = $q.defer();
+        egCore.net.request(
+            'open-ils.actor',
+            'open-ils.actor.container.full_delete',
+            egCore.auth.token(), 'copy', id
+        ).then(function(resp) {
+            var evt = egCore.evt.parse(resp);
+            if (evt) {
+                console.error(evt);
+                deferred.reject(evt);
+                return;
+            }
+            deferred.resolve(resp);
+        });
+        return deferred.promise;
+    }
+
+    return service;
+}])
+
+/**
+ * Top-level controller.  
+ * Hosts functions needed by all controllers.
+ */
+.controller('CopyBucketCtrl',
+       ['$scope','$location','$q','$timeout','$modal',
+        '$window','egCore','bucketSvc',
+function($scope,  $location,  $q,  $timeout,  $modal,  
+         $window,  egCore,  bucketSvc) {
+
+    $scope.bucketSvc = bucketSvc;
+    $scope.bucket = function() { return bucketSvc.currentBucket }
+
+    // tabs: search, pending, view
+    $scope.setTab = function(tab) { 
+        $scope.tab = tab;
+
+        // for bucket selector; must be called after route resolve
+        bucketSvc.fetchUserBuckets(); 
+    };
+
+    $scope.loadBucketFromMenu = function(item, bucket) {
+        if (bucket) return $scope.loadBucket(bucket.id());
+    }
+
+    $scope.loadBucket = function(id) {
+        $location.path(
+            '/cat/bucket/copy/' + 
+                $scope.tab + '/' + encodeURIComponent(id));
+    }
+
+    $scope.addToBucket = function(recs) {
+        if (recs.length == 0) return;
+        bucketSvc.bucketNeedsRefresh = true;
+
+        angular.forEach(recs,
+            function(rec) {
+                var item = new egCore.idl.ccbi();
+                item.bucket(bucketSvc.currentBucket.id());
+                item.target_copy(rec.id);
+                egCore.net.request(
+                    'open-ils.actor',
+                    'open-ils.actor.container.item.create', 
+                    egCore.auth.token(), 'copy', item
+                ).then(function(resp) {
+
+                    // HACK: add the IDs of the added items so that the size
+                    // of the view list will grow (and update any UI looking at
+                    // the list size).  The data stored is inconsistent, but since
+                    // we are forcing a bucket refresh on the next rendering of 
+                    // the view pane, the list will be repaired.
+                    bucketSvc.currentBucket.items().push(resp);
+                });
+            }
+        );
+    }
+
+    $scope.openCreateBucketDialog = function() {
+        $modal.open({
+            templateUrl: './cat/bucket/copy/t_bucket_create',
+            controller: 
+                ['$scope', '$modalInstance', function($scope, $modalInstance) {
+                $scope.focusMe = true;
+                $scope.ok = function(args) { $modalInstance.close(args) }
+                $scope.cancel = function () { $modalInstance.dismiss() }
+            }]
+        }).result.then(function (args) {
+            if (!args || !args.name) return;
+            bucketSvc.createBucket(args.name, args.desc).then(
+                function(id) {
+                    if (!id) return;
+                    bucketSvc.viewList = [];
+                    bucketSvc.allBuckets = []; // reset
+                    bucketSvc.currentBucket = null;
+                    $location.path(
+                        '/cat/bucket/copy/' + $scope.tab + '/' + id);
+                }
+            );
+        });
+    }
+
+    $scope.openEditBucketDialog = function() {
+        $modal.open({
+            templateUrl: './cat/bucket/copy/t_bucket_edit',
+            controller: 
+                ['$scope', '$modalInstance', function($scope, $modalInstance) {
+                $scope.focusMe = true;
+                $scope.args = {
+                    name : bucketSvc.currentBucket.name(),
+                    desc : bucketSvc.currentBucket.description(),
+                    pub : bucketSvc.currentBucket.pub() == 't'
+                };
+                $scope.ok = function(args) { 
+                    if (!args) return;
+                    $scope.actionPending = true;
+                    args.pub = args.pub ? 't' : 'f';
+                    // close the dialog after edit has completed
+                    bucketSvc.editBucket(args).then(
+                        function() { $modalInstance.close() });
+                }
+                $scope.cancel = function () { $modalInstance.dismiss() }
+            }]
+        })
+    }
+
+
+    // opens the delete confirmation and deletes the current
+    // bucket if the user confirms.
+    $scope.openDeleteBucketDialog = function() {
+        $modal.open({
+            templateUrl: './cat/bucket/copy/t_bucket_delete',
+            controller : 
+                ['$scope', '$modalInstance', function($scope, $modalInstance) {
+                $scope.bucket = function() { return bucketSvc.currentBucket }
+                $scope.ok = function() { $modalInstance.close() }
+                $scope.cancel = function() { $modalInstance.dismiss() }
+            }]
+        }).result.then(function () {
+            bucketSvc.deleteBucket(bucketSvc.currentBucket.id())
+            .then(function() {
+                bucketSvc.allBuckets = [];
+                $location.path('/cat/bucket/copy/view');
+            });
+        });
+    }
+
+    // retrieves the requested bucket by ID
+    $scope.openSharedBucketDialog = function() {
+        $modal.open({
+            templateUrl: './cat/bucket/copy/t_load_shared',
+            controller :
+                ['$scope', '$modalInstance', function($scope, $modalInstance) {
+                $scope.focusMe = true;
+                $scope.ok = function(args) {
+                    if (args && args.id) {
+                        $modalInstance.close(args.id)
+                    }
+                }
+                $scope.cancel = function() { $modalInstance.dismiss() }
+            }]
+        }).result.then(function(id) {
+            // RecordBucketCtrl $scope is not inherited by the
+            // modal, so we need to call loadBucket from the
+            // promise resolver.
+            $scope.loadBucket(id);
+        });
+    }
+
+}])
+
+.controller('PendingCtrl',
+       ['$scope','$routeParams','bucketSvc','egGridDataProvider', 'egCore',
+function($scope,  $routeParams,  bucketSvc , egGridDataProvider,   egCore) {
+    $scope.setTab('pending');
+
+    var query;
+    $scope.gridControls = {
+        setQuery : function(q) {
+            if (bucketSvc.pendingList.length)
+                return {id : bucketSvc.pendingList};
+            else
+            return null;
+        }
+    }
+
+    $scope.search = function() {
+        bucketSvc.barcodeRecords = [];
+
+        egCore.pcrud.search(
+            'acp',
+            {barcode : bucketSvc.barcodeString, deleted : 'f'},
+            {}
+        ).then(null, null, function(copy) {
+            bucketSvc.pendingList.push(copy.id());
+            $scope.gridControls.setQuery({id : bucketSvc.pendingList});
+        });
+    }
+
+    $scope.resetPendingList = function() {
+        bucketSvc.pendingList = [];
+        $scope.gridControls.setQuery({});
+    }
+    
+    if ($routeParams.id && 
+        (!bucketSvc.currentBucket || 
+            bucketSvc.currentBucket.id() != $routeParams.id)) {
+        // user has accessed this page cold with a bucket ID.
+        // fetch the bucket for display, then set the totalCount
+        // (also for display), but avoid fully fetching the bucket,
+        // since it's premature, in this UI.
+        bucketSvc.fetchBucket($routeParams.id);
+    }
+    $scope.gridControls.setQuery();
+}])
+
+.controller('ViewCtrl',
+       ['$scope','$q','$routeParams','bucketSvc',
+function($scope,  $q , $routeParams,  bucketSvc) {
+
+    $scope.setTab('view');
+    $scope.bucketId = $routeParams.id;
+
+    var query;
+    $scope.gridControls = {
+        setQuery : function(q) {
+            if (q) query = q;
+            return query;
+        }
+    };
+
+    function drawBucket() {
+        return bucketSvc.fetchBucket($scope.bucketId).then(
+            function(bucket) {
+                var ids = bucket.items().map(
+                    function(i){return i.target_copy()}
+                );
+                if (ids.length) {
+                    $scope.gridControls.setQuery({id : ids});
+                } else {
+                    $scope.gridControls.setQuery({});
+                }
+            }
+        );
+    }
+
+    $scope.detachCopies = function(copies) {
+        var promises = [];
+        angular.forEach(copies, function(rec) {
+            var item = bucketSvc.currentBucket.items().filter(
+                function(i) {
+                    return (i.target_copy() == rec.id)
+                }
+            );
+            if (item.length)
+                promises.push(bucketSvc.detachCopy(item[0].id()));
+        });
+
+        bucketSvc.bucketNeedsRefresh = true;
+        return $q.all(promises).then(drawBucket);
+    }
+
+    // fetch the bucket;  on error show the not-allowed message
+    if ($scope.bucketId) 
+        drawBucket()['catch'](function() { $scope.forbidden = true });
+}])

commit 191c1426e8ac03cf00b02e995be51b32e313a665
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 15 21:52:00 2015 +0000

    LP#1402797 make clearing the record bucket pending list action functional
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/bucket/record/t_pending.tt2 b/Open-ILS/src/templates/staff/cat/bucket/record/t_pending.tt2
index eefc60a..6d3a525 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/record/t_pending.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/t_pending.tt2
@@ -15,6 +15,6 @@
     handler="addToBucket"></eg-grid-action>
 
   <eg-grid-action label="[% l('Clear List') %]" 
-    handler="clearPendingList"></eg-grid-action>
+    handler="resetPendingList"></eg-grid-action>
 
 </eg-grid>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js b/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
index 5b4ad21..3bcd714 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
@@ -469,6 +469,7 @@ function($scope,  $routeParams,  bucketSvc , egGridDataProvider) {
 
     $scope.resetPendingList = function() {
         bucketSvc.pendingList = [];
+        $scope.gridDataProvider.refresh();
     }
     
 

commit 46c3f0be689d3aad8c42b8bd68b6b3fd17d517d9
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 15 16:16:03 2015 +0000

    LP#1402797 implement Show All in Catalog action for record buckets
    
    Note that this will typically require the brower's pop-up
    blocker to be disabled for the webstaff site.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/bucket/record/t_view.tt2 b/Open-ILS/src/templates/staff/cat/bucket/record/t_view.tt2
index 39c866f..b6768c5 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/record/t_view.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/t_view.tt2
@@ -10,6 +10,9 @@
   [% INCLUDE 'staff/cat/bucket/record/t_grid_menu.tt2' %]
 
   <!-- actions drop-down -->
+  <eg-grid-action label="[% l('Show All in Catalog') %]"
+    handler="showAllRecords"></eg-grid-action>
+
   <eg-grid-action label="[% l('Remove Selected Records') %]" 
     handler="detachRecords"></eg-grid-action>
 
diff --git a/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js b/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
index 937218e..5b4ad21 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
@@ -484,8 +484,10 @@ function($scope,  $routeParams,  bucketSvc , egGridDataProvider) {
 }])
 
 .controller('ViewCtrl',
-       ['$scope','$q','$routeParams','bucketSvc',
-function($scope,  $q , $routeParams,  bucketSvc) {
+       ['$scope','$q','$routeParams','bucketSvc', 'egCore', '$window',
+        '$timeout',
+function($scope,  $q , $routeParams,  bucketSvc, egCore, $window,
+        $timeout) {
 
     $scope.setTab('view');
     $scope.bucketId = $routeParams.id;
@@ -513,6 +515,18 @@ function($scope,  $q , $routeParams,  bucketSvc) {
         );
     }
 
+    $scope.showAllRecords = function() {
+        // TODO: maybe show selected would be better?
+        // TODO: probably want to set a limit on the number of
+        //       new tabs one could choose to open at once
+        angular.forEach(bucketSvc.currentBucket.items(), function(rec) {
+            var url = egCore.env.basePath +
+                      'cat/catalog/record/' +
+                      rec.target_biblio_record_entry();
+            $timeout(function() { $window.open(url, '_blank') });
+        });
+    }
+
     $scope.detachRecords = function(records) {
         var promises = [];
         angular.forEach(records, function(rec) {

commit e7c26bb5e792d8004064f69288743abd9cc556d9
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 15 15:01:02 2015 +0000

    LP#1402797 use staff search for record bucket queries
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js b/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
index de59a45..937218e 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/bucket/record/app.js
@@ -432,7 +432,7 @@ function($scope,  $routeParams,  egCore , bucketSvc) {
 
         egCore.net.request(
             'open-ils.search',
-            'open-ils.search.biblio.multiclass.query', {   
+            'open-ils.search.biblio.multiclass.query.staff', {   
                 limit : 500 // meh
             }, bucketSvc.queryString, true
         ).then(function(resp) {

commit 4025380f9e8121364d6661d75a6c229272c431a2
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Jan 15 09:11:26 2015 -0500

    LP#1402797 Improve styling of retrieve-by pages
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_id.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_id.tt2
index 785b52d..9b0cfa1 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_id.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_id.tt2
@@ -1,4 +1,5 @@
 
+<div class="col-md-3">
 <form ng-submit="submitId(args)" role="form" class="form-inline">
   <div class="input-group">
 
@@ -13,8 +14,13 @@
   </div>
   <input class="btn btn-default" type="submit" value="[% l('Submit') %]"/>
 </form>
+</div>
 
 <br/>
+<br/>
+<br/>
+<br/>
+<br/>
 <div class="alert alert-warning" ng-show="recordNotFound">
   [% l('Bib Record Not Found: [_1]', '{{recordNotFound}}') %]
 </div>
diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_tcn.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_tcn.tt2
index e8a86a6..5b102b2 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_tcn.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_tcn.tt2
@@ -1,4 +1,5 @@
 
+<div class="col-md-3">
 <form ng-submit="submitTCN(args)" role="form" class="form-inline">
   <div class="input-group">
 
@@ -13,8 +14,13 @@
   </div>
   <input class="btn btn-default" type="submit" value="[% l('Submit') %]"/>
 </form>
+</div>
 
 <br/>
+<br/>
+<br/>
+<br/>
+<br/>
 <div class="alert alert-warning" ng-show="moreRecordsFound">
   [% l('More than one Bib Record found with TCN: [_1]', '{{recordNotFound}}') %]
 </div>

commit c24efb21fc8ffadba2154a90c05c6cffdc737a59
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Jan 14 17:20:23 2015 -0500

    LP#1402797 Implement retrieve record by id and tcn
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_id.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_id.tt2
new file mode 100644
index 0000000..785b52d
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_id.tt2
@@ -0,0 +1,22 @@
+
+<form ng-submit="submitId(args)" role="form" class="form-inline">
+  <div class="input-group">
+
+    <label class="input-group-addon" 
+      for="record-retreive-id" >[% l('Bib Record Id') %]</label>
+
+    <input select-me="selectMe" class="form-control"
+      ng-model="args.record_id" focus-me="focusMe"
+      placeholder="[% l('Bib Record Id') %]"
+      id="record-retreive-id" type="text"/> 
+
+  </div>
+  <input class="btn btn-default" type="submit" value="[% l('Submit') %]"/>
+</form>
+
+<br/>
+<div class="alert alert-warning" ng-show="recordNotFound">
+  [% l('Bib Record Not Found: [_1]', '{{recordNotFound}}') %]
+</div>
+
+
diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_tcn.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_tcn.tt2
new file mode 100644
index 0000000..e8a86a6
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_tcn.tt2
@@ -0,0 +1,25 @@
+
+<form ng-submit="submitTCN(args)" role="form" class="form-inline">
+  <div class="input-group">
+
+    <label class="input-group-addon" 
+      for="record-retreive-tcn" >[% l('Bib Record TCN') %]</label>
+
+    <input select-me="selectMe" class="form-control"
+      ng-model="args.record_tcn" focus-me="focusMe"
+      placeholder="[% l('Bib Record TCN') %]"
+      id="record-retreive-tcn" type="text"/> 
+
+  </div>
+  <input class="btn btn-default" type="submit" value="[% l('Submit') %]"/>
+</form>
+
+<br/>
+<div class="alert alert-warning" ng-show="moreRecordsFound">
+  [% l('More than one Bib Record found with TCN: [_1]', '{{recordNotFound}}') %]
+</div>
+<div class="alert alert-warning" ng-show="recordNotFound">
+  [% l('Bib Record Not Found: [_1]', '{{recordNotFound}}') %]
+</div>
+
+
diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2
index 018a93f..e94b1ea 100644
--- a/Open-ILS/src/templates/staff/navbar.tt2
+++ b/Open-ILS/src/templates/staff/navbar.tt2
@@ -169,6 +169,19 @@
               [% l('Record Buckets') %]
             </a>
           </li>
+          <li class="divider"></li>
+          <li>
+            <a href="./cat/catalog/retrieve_by_id" target="_self">
+              <span class="glyphicon glyphicon-file"></span>
+              [% l('Retrieve Bib Record by ID') %]
+            </a>
+          </li>
+          <li>
+            <a href="./cat/catalog/retrieve_by_tcn" target="_self">
+              <span class="glyphicon glyphicon-tag"></span>
+              [% l('Retrieve Bib Record by TCN') %]
+            </a>
+          </li>
           <li>
             <a href="" ng-click="retrieveLastRecord()" target="_self">
               <span class="glyphicon glyphicon-share-alt"></span>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
index 9e9ad7d..f41a3fe 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
@@ -22,6 +22,18 @@ angular.module('egCatalogApp', ['ui.bootstrap','ngRoute','egCoreMod','egGridMod'
         resolve : resolver
     });
 
+    $routeProvider.when('/cat/catalog/retrieve_by_id', {
+        templateUrl: './cat/catalog/t_retrieve_by_id',
+        controller: 'CatalogRecordRetrieve',
+        resolve : resolver
+    });
+
+    $routeProvider.when('/cat/catalog/retrieve_by_tcn', {
+        templateUrl: './cat/catalog/t_retrieve_by_tcn',
+        controller: 'CatalogRecordRetrieve',
+        resolve : resolver
+    });
+
     // create some catalog page-specific mappings
     $routeProvider.when('/cat/catalog/record/:record_id', {
         templateUrl: './cat/catalog/t_catalog',
@@ -42,6 +54,68 @@ angular.module('egCatalogApp', ['ui.bootstrap','ngRoute','egCoreMod','egGridMod'
 
 /**
  * */
+.controller('CatalogRecordRetrieve',
+       ['$scope','$routeParams','$location','$q','egCore',
+function($scope , $routeParams , $location , $q , egCore ) {
+
+    $scope.focusMe = true;
+
+    // jump to the patron checkout UI
+    function loadRecord(record_id) {
+        $location
+        .path('/cat/catalog/record/' + record_id);
+    }
+
+    $scope.submitId = function(args) {
+        $scope.recordNotFound = null;
+        if (!args.record_id) return;
+
+        // blur so next time it's set to true it will re-apply select()
+        $scope.selectMe = false;
+
+        return loadRecord(args.record_id);
+    }
+
+    $scope.submitTCN = function(args) {
+        $scope.recordNotFound = null;
+        $scope.moreRecordsFound = null;
+        if (!args.record_tcn) return;
+
+        // blur so next time it's set to true it will re-apply select()
+        $scope.selectMe = false;
+
+        // lookup TCN
+        egCore.net.request(
+            'open-ils.search',
+            'open-ils.search.biblio.tcn',
+            args.record_tcn)
+
+        .then(function(resp) { // get_barcodes
+
+            if (evt = egCore.evt.parse(resp)) {
+                alert(evt); // FIXME
+                return;
+            }
+
+            if (!resp.count) {
+                $scope.recordNotFound = args.record_tcn;
+                $scope.selectMe = true;
+                return;
+            }
+
+            if (resp.count > 1) {
+                $scope.moreRecordsFound = args.record_tcn;
+                $scope.selectMe = true;
+                return;
+            }
+
+            var record_id = resp.ids[0];
+            return loadRecord(record_id);
+        });
+    }
+
+}])
+
 .controller('CatalogCtrl',
        ['$scope','$routeParams','$location','$q','egCore','egHolds',
         'egGridDataProvider','egHoldGridActions',

commit 3b87bd33ce8eeae7ea75d37cfe695839e7778967
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Jan 14 15:27:15 2015 -0500

    LP#1402797 Use egCore.env.basePath to construct the URL for last record
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/services/navbar.js b/Open-ILS/web/js/ui/default/staff/services/navbar.js
index ba168cb..27c6c63 100644
--- a/Open-ILS/web/js/ui/default/staff/services/navbar.js
+++ b/Open-ILS/web/js/ui/default/staff/services/navbar.js
@@ -48,9 +48,8 @@ angular.module('egCoreMod')
                 $scope.retrieveLastRecord = function() {
                     var last_record = egCore.hatch.getLocalItem("eg.cat.last_record_retrieved");
                     if (last_record) {
-                        var reg = new RegExp($location.path());
                         $window.location.href =
-                            $window.location.href + 'cat/catalog/record/' + last_record;
+                            egCore.env.basePath + 'cat/catalog/record/' + last_record;
                     }
                 }
 

commit 601f011a113d14faaeac7d0f33201c61fc7348ae
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Jan 14 15:05:14 2015 -0500

    LP#1402797 Implement "Retrieve Last Bib Record" using localStorage record cache
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2
index fe0695a..018a93f 100644
--- a/Open-ILS/src/templates/staff/navbar.tt2
+++ b/Open-ILS/src/templates/staff/navbar.tt2
@@ -169,6 +169,12 @@
               [% l('Record Buckets') %]
             </a>
           </li>
+          <li>
+            <a href="" ng-click="retrieveLastRecord()" target="_self">
+              <span class="glyphicon glyphicon-share-alt"></span>
+              [% l('Retrieve Last Bib Record') %]
+            </a>
+          </li>
        </ul>
       </li>
 
diff --git a/Open-ILS/web/js/ui/default/staff/services/navbar.js b/Open-ILS/web/js/ui/default/staff/services/navbar.js
index 8806d65..ba168cb 100644
--- a/Open-ILS/web/js/ui/default/staff/services/navbar.js
+++ b/Open-ILS/web/js/ui/default/staff/services/navbar.js
@@ -45,6 +45,15 @@ angular.module('egCoreMod')
                     hotkeys.add(key, desc, function() { navTo(path) });
                 };
 
+                $scope.retrieveLastRecord = function() {
+                    var last_record = egCore.hatch.getLocalItem("eg.cat.last_record_retrieved");
+                    if (last_record) {
+                        var reg = new RegExp($location.path());
+                        $window.location.href =
+                            $window.location.href + 'cat/catalog/record/' + last_record;
+                    }
+                }
+
                 $scope.applyLocale = function(locale) {
                     // EGWeb.pm can change the locale for us w/ the right param
                     // Note: avoid using $location.search() to derive a new

commit 71d31a63ab15580192a52b29d2a35aadfa8235e2
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Jan 14 14:15:25 2015 -0500

    LP#1402797 Save the record we land on from within the staff client as the "last record retrieved"
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Bill Erickson <berickxx at gmail.com>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
index 9b466a9..9e9ad7d 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/catalog/app.js
@@ -51,6 +51,10 @@ function($scope , $routeParams , $location , $q , egCore , egHolds,
     // set record ID on page load if available...
     $scope.record_id = $routeParams.record_id;
 
+    // Set the "last bib" cookie, if we have that
+    if ($scope.record_id)
+        egCore.hatch.setLocalItem("eg.cat.last_record_retrieved", $scope.record_id);
+
     // also set it when the iframe changes to a new record
     $scope.handle_page = function(url) {
 
@@ -62,6 +66,7 @@ function($scope , $routeParams , $location , $q , egCore , egHolds,
         var match = url.match(/\/+opac\/+record\/+(\d+)/);
         if (match) {
             $scope.record_id = match[1];
+            egCore.hatch.setLocalItem("eg.cat.last_record_retrieved", $scope.record_id);
 
             // force the record_id to show up in the page.  
             // not sure why a $digest isn't occuring here.

-----------------------------------------------------------------------

Summary of changes:
 Open-ILS/examples/fm_IDL.xml                       |   10 +-
 .../perlmods/lib/OpenILS/Application/Circ/Holds.pm |   10 +-
 .../src/templates/opac/parts/record/copy_table.tt2 |    2 +-
 .../templates/staff/admin/workstation/t_splash.tt2 |    1 +
 Open-ILS/src/templates/staff/base.tt2              |    1 +
 Open-ILS/src/templates/staff/base_js.tt2           |    1 +
 .../src/templates/staff/cat/bucket/copy/index.tt2  |   67 +
 .../bucket/{record => copy}/t_bucket_create.tt2    |    0
 .../bucket/{record => copy}/t_bucket_delete.tt2    |    0
 .../cat/bucket/{record => copy}/t_bucket_edit.tt2  |    0
 .../cat/bucket/{record => copy}/t_bucket_info.tt2  |    0
 .../bucket/{record => copy}/t_bucket_selector.tt2  |    0
 .../cat/bucket/{record => copy}/t_grid_menu.tt2    |    0
 .../cat/bucket/{record => copy}/t_load_shared.tt2  |    0
 .../templates/staff/cat/bucket/copy/t_pending.tt2  |   52 +
 .../src/templates/staff/cat/bucket/copy/t_view.tt2 |   35 +
 .../templates/staff/cat/bucket/record/index.tt2    |    6 +
 .../staff/cat/bucket/record/t_pending.tt2          |    2 +-
 .../cat/bucket/record/t_records_not_deleted.tt2    |   19 +
 .../templates/staff/cat/bucket/record/t_view.tt2   |    6 +
 Open-ILS/src/templates/staff/cat/catalog/index.tt2 |    2 +
 .../src/templates/staff/cat/catalog/t_catalog.tt2  |   30 +-
 .../src/templates/staff/cat/catalog/t_holds.tt2    |    8 +-
 .../staff/cat/catalog/t_retrieve_by_id.tt2         |   28 +
 .../staff/cat/catalog/t_retrieve_by_tcn.tt2        |   31 +
 .../templates/staff/cat/item/t_circ_list_pane.tt2  |   10 +
 .../src/templates/staff/cat/share/t_marcedit.tt2   |   32 +
 .../src/templates/staff/circ/checkin/t_checkin.tt2 |   62 +-
 .../src/templates/staff/circ/holds/t_pull_list.tt2 |    6 +-
 .../src/templates/staff/circ/patron/t_checkout.tt2 |    2 +-
 .../templates/staff/circ/patron/t_credentials.tt2  |    6 +-
 .../src/templates/staff/circ/patron/t_group.tt2    |    9 +-
 .../templates/staff/circ/patron/t_holds_list.tt2   |   13 +-
 .../templates/staff/circ/patron/t_items_out.tt2    |    3 +-
 .../staff/circ/patron/t_move_to_group_dialog.tt2   |    5 +-
 .../src/templates/staff/circ/patron/t_search.tt2   |    4 +-
 .../staff/circ/patron/t_search_results.tt2         |    1 +
 .../src/templates/staff/circ/patron/t_summary.tt2  |    6 +-
 .../templates/staff/circ/patron/t_xact_details.tt2 |    2 +-
 .../src/templates/staff/circ/renew/t_renew.tt2     |    2 +-
 .../staff/circ/share/t_cancel_hold_dialog.tt2      |    2 +-
 .../staff/circ/share/t_circ_exists_dialog.tt2      |   20 +-
 .../staff/circ/share/t_event_override_dialog.tt2   |   12 +-
 .../staff/circ/share/t_hold_edit_pickup_lib.tt2    |    2 +-
 .../staff/circ/share/t_new_message_dialog.tt2      |   23 +-
 .../staff/circ/share/t_transit_dialog.tt2          |    2 +-
 Open-ILS/src/templates/staff/css/cat.css.tt2       |  140 ++
 Open-ILS/src/templates/staff/navbar.tt2            |   31 +
 Open-ILS/src/templates/staff/share/t_autogrid.tt2  |    4 +-
 .../src/templates/staff/share/t_prompt_dialog.tt2  |    2 +-
 Open-ILS/src/templates/staff/statusbar.tt2         |    2 +-
 Open-ILS/src/templates/staff/t_splash.tt2          |    4 +
 Open-ILS/web/css/skin/default/register.css         |    2 +-
 .../js/ui/default/staff/admin/workstation/app.js   |    2 +
 .../web/js/ui/default/staff/cat/bucket/copy/app.js |  506 +++++
 .../js/ui/default/staff/cat/bucket/record/app.js   |   75 +-
 .../web/js/ui/default/staff/cat/catalog/app.js     |  102 +-
 Open-ILS/web/js/ui/default/staff/cat/item/app.js   |   19 +-
 .../js/ui/default/staff/cat/services/marcedit.js   |  815 +++++++
 .../web/js/ui/default/staff/circ/checkin/app.js    |    4 +
 Open-ILS/web/js/ui/default/staff/circ/holds/app.js |    9 +-
 .../web/js/ui/default/staff/circ/patron/app.js     |   95 +-
 .../js/ui/default/staff/circ/patron/checkout.js    |    4 +
 .../web/js/ui/default/staff/circ/patron/holds.js   |    8 +
 Open-ILS/web/js/ui/default/staff/circ/renew/app.js |    3 +
 .../web/js/ui/default/staff/circ/services/circ.js  |  185 +-
 .../web/js/ui/default/staff/circ/services/holds.js |    9 +-
 Open-ILS/web/js/ui/default/staff/marcrecord.js     | 2367 ++++++++++++++++++++
 Open-ILS/web/js/ui/default/staff/services/grid.js  |   92 +-
 .../web/js/ui/default/staff/services/navbar.js     |    8 +
 Open-ILS/web/js/ui/default/staff/services/org.js   |   16 +
 Open-ILS/web/js/ui/default/staff/services/ui.js    |   32 +-
 72 files changed, 4818 insertions(+), 253 deletions(-)
 create mode 100644 Open-ILS/src/templates/staff/cat/bucket/copy/index.tt2
 copy Open-ILS/src/templates/staff/cat/bucket/{record => copy}/t_bucket_create.tt2 (100%)
 copy Open-ILS/src/templates/staff/cat/bucket/{record => copy}/t_bucket_delete.tt2 (100%)
 copy Open-ILS/src/templates/staff/cat/bucket/{record => copy}/t_bucket_edit.tt2 (100%)
 copy Open-ILS/src/templates/staff/cat/bucket/{record => copy}/t_bucket_info.tt2 (100%)
 copy Open-ILS/src/templates/staff/cat/bucket/{record => copy}/t_bucket_selector.tt2 (100%)
 copy Open-ILS/src/templates/staff/cat/bucket/{record => copy}/t_grid_menu.tt2 (100%)
 copy Open-ILS/src/templates/staff/cat/bucket/{record => copy}/t_load_shared.tt2 (100%)
 create mode 100644 Open-ILS/src/templates/staff/cat/bucket/copy/t_pending.tt2
 create mode 100644 Open-ILS/src/templates/staff/cat/bucket/copy/t_view.tt2
 create mode 100644 Open-ILS/src/templates/staff/cat/bucket/record/t_records_not_deleted.tt2
 create mode 100644 Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_id.tt2
 create mode 100644 Open-ILS/src/templates/staff/cat/catalog/t_retrieve_by_tcn.tt2
 create mode 100644 Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
 create mode 100644 Open-ILS/src/templates/staff/css/cat.css.tt2
 create mode 100644 Open-ILS/web/js/ui/default/staff/cat/bucket/copy/app.js
 create mode 100644 Open-ILS/web/js/ui/default/staff/cat/services/marcedit.js
 create mode 100644 Open-ILS/web/js/ui/default/staff/marcrecord.js


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list