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

Evergreen Git git at git.evergreen-ils.org
Tue Feb 2 15:12:05 EST 2016


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  fefe48aa724e7f41fe81962ce178a63cd2840c76 (commit)
       via  e6ece3bcce07891997504433fadb78f8715989a4 (commit)
       via  f0a5c2c4391b763fb2bd9366deda29d815ff99d7 (commit)
       via  f3e057a4920a3d4a5c6a9a1efdcc186256a006a6 (commit)
       via  af10ff328cdb790b3ddcdc4635a101652a32431b (commit)
       via  993854771a27ad2222f8c038b59f79ec0f6c83a4 (commit)
       via  dcde4a9367d35a1e29fc55c156ca42c058202413 (commit)
       via  c888137935e4eeeec1738ff4a45aa7bbcd5d9026 (commit)
       via  89096f521dc0fcce6c64f67dd466a39a05be2319 (commit)
       via  ae1d8ab62a85ded1b4b5ffcc7b7cce8bd6396d1e (commit)
       via  bca8bddc768579d8a778dcd10453c39d31619767 (commit)
       via  4b7ed0d8c6ce3524b5c5ff9c352f4f5d47e38d19 (commit)
       via  67e04cb1b3c00b5962ec8f4ecc4769d6d19288a2 (commit)
       via  47e2f657c661b0a30a384993f6ad0a2e96676cf9 (commit)
       via  15020e772154c91cee266f3654fb8b579716041d (commit)
       via  e813de67fe8ea6a3ef07a9ebcc281a89b0cac74c (commit)
       via  b9f9da96a44a154c8fd4f37ccea690afcb66d5e8 (commit)
       via  df37aed816d3e179331ad104d4138381684f7d60 (commit)
       via  13770ac8b9b1df28aa20e5d837ed9054b182b585 (commit)
       via  61d82bdd525f26323c07e756411e823e1d9fce32 (commit)
       via  91a25d96cc50f78d373a7430db44fa3b58b6c582 (commit)
       via  44489c666f1a7105ea7c22810133759e7c45f024 (commit)
       via  4ed811a38128a29512e3de0f9dd84857ebb17145 (commit)
       via  66f9a004fe916e8217fd2bac71ed35ab49a5b9ff (commit)
       via  8fa1d455162aac7b8434b89f840f0486bfb18bbb (commit)
       via  ec8fb38aa66b2f37bb20eaccedecc113c56ed318 (commit)
       via  5e4d6d9516edd050be602bbe7d54a2d396710b35 (commit)
       via  d6e8b7b7ac4659f80a74e7d21d97e7b506a0f914 (commit)
       via  68c13f4aa20982d9c71d8f4010f71d56a729edc4 (commit)
       via  43b89b3e46173fb559d7d69f307819c807329013 (commit)
       via  c76310d9512b64dab0a9717839804787f7235661 (commit)
       via  04e636dda48d2c86b93efa4b3fcad9fcaf0ab7d3 (commit)
       via  fd2d65d30cfb5d04e010fffa384ee612def47fb4 (commit)
       via  ba31a26fd198f0a343e9ce15a4585f1c7b941f26 (commit)
       via  a531dcf72fbb8654cb9813da76e36372d4f2531c (commit)
       via  a384012336285a0d512f53045e702dde653db012 (commit)
       via  37317d9522a5c147a26745817a8aab73dc93637c (commit)
       via  53dff6c013f25a840e75f3841afaa53c4f4b4219 (commit)
       via  4a27bdb57e442368c12b795bfdc7b2afda1663b0 (commit)
       via  f8699ee46f3b708850063a1fda93aebf7cb97a08 (commit)
       via  69dd244701035b23ab4c88983bd16bef341841be (commit)
       via  9974afdb64d0f70a65913822dba9368b5e4d7f9e (commit)
       via  377f3c4dedb93b045bfddfd8b79880379feefa56 (commit)
       via  ede7e78925a07dc800bc4e1dda10b2321d176f7d (commit)
       via  2844f4932032a1d6f13d62ff40afb5cacc2fae35 (commit)
       via  6edcb4c8ba45722c6c16115b586818f2500320d7 (commit)
       via  fabdc5aba3f13489bcd8192dfd7195de516702dc (commit)
       via  e41818e2bfe41f27b84738f1301c82e3b57ca50f (commit)
       via  ff8c9540828fbf847ef22dee9ff0c147d2f0187e (commit)
       via  521ad4eb04864b32a1280c91f5315f26f70c3228 (commit)
       via  45f8829636546402e25d979d1e5b408bb0f07ce3 (commit)
       via  ead8f829d65a461ec1c74365fe9748b8e5a0dca1 (commit)
       via  c4636c5059fb84c6e2a92563c71cf52c060206cc (commit)
       via  3a0c68e6a1a4697c6c5bd085d019729263829318 (commit)
       via  31aa4a56919511db52af7d89d14190f3a3d1c15f (commit)
       via  1e3c97b29562a2fff9a19cb1a816b5b7d5adbd8e (commit)
       via  9de17a732bdbffd99692a00d253172cc0d818223 (commit)
       via  93b4748131936d5ca5068180e38f1da4060ad413 (commit)
       via  bb4dece7faaca1edb826952cb7f8b74b32aba1a5 (commit)
       via  55e829e0e102bdab385258d60f5a197ec9d08bb3 (commit)
       via  2f4233da2a4bb7c553194b89315fe1ef69ec8644 (commit)
       via  954e4df49590eaa4087e2fc883e78227d71e3612 (commit)
       via  41b4fa2de10ff2c43e82c8678e6e6cdf2b2591e3 (commit)
       via  e9f8ea0aaf2df1d6c543eb91f8788589cdfc20ce (commit)
       via  e9e14cbf5c570495649862d1e2a7afdf92ff4c50 (commit)
       via  d19ea04d9ecee521483b02173effb03efc1ae583 (commit)
       via  3a9012302c0cf3a8427c8bd6c60da9dcab51dc37 (commit)
       via  281791d5894c86a2848f5d41d68b9a4a0739ef2a (commit)
       via  c8eee7679a0d28a04548b6b2c88db07b58a93cda (commit)
       via  f3cd370d4051129de26f385d66cbcc4eb0e8b985 (commit)
       via  bcc887e2eb91ed259573a37fddd55c8d492eec46 (commit)
       via  3b073cf53a39c519188c04075b61bf922254d2fa (commit)
       via  86e1c005095a8f4399d6be0cfcca0eab20c82ce0 (commit)
       via  d5fb03768e5475437f2b2ae2b541ea59b7e3d32c (commit)
       via  1923c1a7c37dee9d5b0b4bd12af481a88049fbf9 (commit)
       via  48146678b1595e560583fc2490087091bd1a31ad (commit)
       via  9f6f60339a80babe39b50be787afd978d4deccb9 (commit)
       via  a7d228849c70fec2135d31fbebfc798f16a64b5a (commit)
       via  b40e2b445ebd9242b12cc0cb50b17e54454a4ace (commit)
       via  8fab469efa6f4c67cf9110db353be530512b7b1c (commit)
       via  758589f30a0d9cfe8b810f4408b988db3a0713c7 (commit)
       via  eecff61c9c2b9bd3c760f41aab605e3b33c7bc98 (commit)
       via  1405801a19f39e1266f4d9a4d1dc16861c2936dc (commit)
       via  a307cec41c8f4146cc3218faeec11b9a042b564c (commit)
       via  68b7de5d25e21f6491ce611edf3afee030e92950 (commit)
       via  1abdc5e561a26136039117751f673d8e914c55de (commit)
       via  c687951fa292b93ce9dc08a320d429a025629282 (commit)
       via  affd9e16ffaf0365c553aa743bb21c1c2aeba5d9 (commit)
       via  64aec752d9196abcabed99757271ad7347e38cd4 (commit)
       via  5255a3d761243fec36e0c8216f00cf1f8881ca50 (commit)
       via  9b97dbb7fd2629a21ec4df824877d27e1105b470 (commit)
       via  1192a75905f4b35900ad0e2576e309f19dd9be87 (commit)
       via  81c3147fa65cad36edd0bbae42073c6d17f21130 (commit)
       via  b9a5367c9a34c3ccfc04c57c5a53cca95ca91a29 (commit)
       via  51752c5ce08db857d3d50c8904772cea185baccc (commit)
       via  097edb99ade07c520fd72b4e332bfaff5036de5d (commit)
       via  282e0d477322670e67b65792d15aaa2e9d3b1ad0 (commit)
       via  86b7e0e60726ab5f117d93990ced8d5e819e2dce (commit)
       via  19ce00f906a59d8acc0d9290d539927cf2499613 (commit)
       via  9fa3aa1491ae8feda6d33345a859ef83edeec1a8 (commit)
       via  6ed5e2e821bc2fc38f6b2432e84b8eaa8670c130 (commit)
       via  3d06623543fa676b67bde8e04c8b5e82e301bce9 (commit)
       via  686f51e7deac84404485278833333c9ee0b04ba2 (commit)
       via  6df934e21a61456dcc50d78b2a4fcf19edd16bac (commit)
       via  f905064514dfabcbf20cecacd2019b26848e57fd (commit)
       via  ff204cfb0d7ef49a036266116c45780a7c924ec8 (commit)
       via  be0e433ea8b201e3b593bcd9cba2df996b1d2e88 (commit)
       via  e901025f0714beaf9d58e54a27c615d4a15d993f (commit)
       via  8d9c75d587953cad9a6e20e8e7ff60dc04da6ecb (commit)
       via  11cf343871aaa09da7c6c890e6843bc034586f1e (commit)
       via  9ee99166172a666184191181bd7ca03018403fa4 (commit)
       via  090474e49b0a33b6449dcc4bac93271efc10f818 (commit)
       via  0732c79142e208579c6857a5772e37432a28372d (commit)
       via  322002bc8c4f1b50754d278b91367113362632a8 (commit)
       via  4f5e7f397e19b4f2daf509818bb765e8013eeb96 (commit)
       via  ae2c9d67c0c0a6962b4586658a7dbad4e7d8b062 (commit)
       via  17658549d1ef9b77d0a2a16d4d538138725f86d8 (commit)
       via  1a2d327fac0a1a79c409fab7e81d60c7c665fa54 (commit)
       via  bb210af3c8c71217a7e2f71504f37a071ba70831 (commit)
       via  b0e1e2c9630496d911257428fc9592066610fe5a (commit)
       via  e2cd5cbb7e0dabf20459e9d64db21f371e261c67 (commit)
       via  15bdb59b66ace10b1115a76558edc5de342522c0 (commit)
       via  58e4c9c00b09f2f803a085703b8f86c29e26b045 (commit)
       via  1f7f4731b8e1999a77e7d06cc78ccb0fa7cff615 (commit)
       via  5095b0783de9e4547f47bdfd96dd5761b2de6500 (commit)
       via  2acd0c2239dcf11fee2c611609f2d61fa8d09f42 (commit)
       via  567cbee9bc695d0fd86effb41500a8b0d2b81c3c (commit)
       via  301d6a4d6e55f11962254d8f03b4656b7c7a2c92 (commit)
       via  1b8c205b7d45b78a5eee0715ad89cb21857436dc (commit)
       via  4521fcbcf39b07f2cd8921111ee3512ce0b0b8b4 (commit)
       via  84c93d33f241c02f1f901baf3857e5828364c4b9 (commit)
       via  d0437cd1517425c8ceb9742a92c92e5fb3d4e439 (commit)
       via  68bcea20845e50ff15bb28ae13522c39c15c278f (commit)
       via  c5f8418b70d088bbd0f2e3ee08adbe9745a2b2c1 (commit)
       via  45a3db6fae09212d5860d270684aa9a7d83750b3 (commit)
       via  197ab7554b99d7db07bf5ce3c850bfb666d46201 (commit)
       via  9bcc91a67131a496ae5c6658eaf684bed1b7a10f (commit)
       via  8106d873c2d21bcf2ceb34f063804743186ede15 (commit)
       via  641c8ec0ce89b215f00458beeb0763e84bb81a43 (commit)
       via  8778d16cbd47dc864173fe5afa05da0b25cc9256 (commit)
       via  313a5fd02b887477662c0b4f2ac9208a4e350b77 (commit)
      from  6f8216512853b3d705bea46b3e362fc83d996bb1 (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 fefe48aa724e7f41fe81962ce178a63cd2840c76
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Feb 1 19:53:43 2016 -0500

    webstaff: fix more cases where new copies weren't being saved
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index ab5cecc..78c14b0 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -546,7 +546,9 @@ function(egCore , $q) {
                     while ($scope.copy_count > $scope.copies.length) {
                         var cp = itemSvc.generateNewCopy(
                             $scope.callNumber,
-                            $scope.callNumber.owning_lib()
+                            $scope.callNumber.owning_lib(),
+                            $scope.fast_add,
+                            true
                         );
                         $scope.copies.push( cp );
                         $scope.allcopies.push( cp );
@@ -665,7 +667,12 @@ function(egCore , $q) {
                             cn.owning_lib( $scope.owning_lib.id() );
                             cn.record( $scope.full_cn.record() );
 
-                            var cp = itemSvc.generateNewCopy(cn, $scope.owning_lib.id());
+                            var cp = itemSvc.generateNewCopy(
+                                cn,
+                                $scope.owning_lib.id(),
+                                $scope.fast_add,
+                                true
+                            );
 
                             $scope.struct[cn.id()] = [cp];
                             $scope.allcopies.push(cp);

commit e6ece3bcce07891997504433fadb78f8715989a4
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Feb 1 19:19:46 2016 -0500

    webstaff: work around race condition in vol/copy editor
    
    Fix (or workaround) issue whereby the volume/copy editor
    could fail to select a copy's current stat cats.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index ce8f29c..ab5cecc 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -1318,6 +1318,10 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                                 createStatcatUpdateWatcher(s.id());
                             });
                             $scope.in_item_select = false;
+                            // do a refresh here to work around a race
+                            // condition that can result in stat cats
+                            // not being selected.
+                            $scope.workingGridDataProvider.refresh();
                         });
                     }
                 }

commit f0a5c2c4391b763fb2bd9366deda29d815ff99d7
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Feb 1 11:43:09 2016 -0500

    webstaff: clean up console noise
    
    Ensure that if a default stat cat OU filter is not set,
    that errors aren't logged when creating a new volume/copy.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index cef2bf3..ce8f29c 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -751,7 +751,10 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                 $scope.batch.prefix = $scope.defaults.prefix;
                 $scope.batch.suffix = $scope.defaults.suffix;
                 $scope.working.statcat_filter = $scope.defaults.statcat_filter;
-                if (typeof $scope.defaults.statcat_filter == 'object') {
+                if (
+                        typeof $scope.defaults.statcat_filter == 'object' &&
+                        Object.keys($scope.defaults.statcat_filter).length > 0
+                   ) {
                     // want fieldmapper object here...
                     $scope.defaults.statcat_filter =
                          egCore.idl.Clone($scope.defaults.statcat_filter);
@@ -1559,7 +1562,10 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                         if (t) {
                             $scope.defaults = t;
                             $scope.working.statcat_filter = $scope.defaults.statcat_filter;
-                            if (typeof $scope.defaults.statcat_filter == 'object') {
+                            if (
+                                    typeof $scope.defaults.statcat_filter == 'object' &&
+                                    Object.keys($scope.defaults.statcat_filter).length > 0
+                                ) {
                                 // want fieldmapper object here...
                                 $scope.defaults.statcat_filter =
                                     egCore.idl.Clone($scope.defaults.statcat_filter);

commit f3e057a4920a3d4a5c6a9a1efdcc186256a006a6
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Fri Jan 29 18:38:53 2016 -0500

    webstaff: fix issue that prevent new copies from being added
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 26d4aae..cef2bf3 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -1135,7 +1135,8 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                                 var cp = new itemSvc.generateNewCopy(
                                     cn,
                                     proto.owner || egCore.auth.user().ws_ou(),
-                                    $scope.is_fast_add
+                                    $scope.is_fast_add,
+                                    true
                                 );
 
                                 if (proto.barcode) cp.barcode( proto.barcode );

commit af10ff328cdb790b3ddcdc4635a101652a32431b
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 28 18:33:34 2016 -0500

    webstaff: add copy ID column to holdings table and volcopy editor
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
index 6435c81..28bb55e 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
@@ -105,6 +105,7 @@
     <eg-grid-field label="[% l('Circulating Library') %]"    path="circ_lib.name"></eg-grid-field>
     <eg-grid-field label="[% l('Price') %]"                  path="price"></eg-grid-field>
     <eg-grid-field label="[% l('Circulation Modifier') %]"   path="circ_modifier"></eg-grid-field>
+    <eg-grid-field label="[% l('Copy ID') %]"                path="id"></eg-grid-field>
     <eg-grid-field label="[% l('Circulate As MARC Type') %]" path="circ_as_type"></eg-grid-field>
     <eg-grid-field label="[% l('Circulate') %]"              datatype="bool" path="circulate"></eg-grid-field>
     <eg-grid-field label="[% l('Holdable') %]"               datatype="bool" path="holdable"></eg-grid-field>
diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
index 8d3190f..9511963 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
@@ -114,6 +114,7 @@
                           <eg-grid-field label="[% l('Created') %]"     path="create_date" visible></eg-grid-field>
                           <eg-grid-field label="[% l('Activated') %]"   path="active_date" visible></eg-grid-field>
                           <eg-grid-field label="[% l('Call Number') %]" path="call_number.label" visible></eg-grid-field>
+                          <eg-grid-field label="[% l('Copy ID') %]"     path="id" hidden></eg-grid-field>
                         
                         </eg-grid>
         
@@ -156,7 +157,8 @@
                  <eg-grid-field label="[% l('Reference?') %]"  path="ref" visible></eg-grid-field>
                  <eg-grid-field label="[% l('Status') %]"      flesher="statusById" path="status.name" visible></eg-grid-field>
                  <eg-grid-field label="[% l('OPAC Visible') %]" path="opac_visible" visible></eg-grid-field>
-               
+                 <eg-grid-field label="[% l('Copy ID') %]"      path="id" hidden></eg-grid-field>
+
                </eg-grid>
             </div>
           </div>

commit 993854771a27ad2222f8c038b59f79ec0f6c83a4
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 28 18:21:57 2016 -0500

    webstaff: add circulate as MARC type column to holdings table
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
index a875631..6435c81 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
@@ -105,6 +105,7 @@
     <eg-grid-field label="[% l('Circulating Library') %]"    path="circ_lib.name"></eg-grid-field>
     <eg-grid-field label="[% l('Price') %]"                  path="price"></eg-grid-field>
     <eg-grid-field label="[% l('Circulation Modifier') %]"   path="circ_modifier"></eg-grid-field>
+    <eg-grid-field label="[% l('Circulate As MARC Type') %]" path="circ_as_type"></eg-grid-field>
     <eg-grid-field label="[% l('Circulate') %]"              datatype="bool" path="circulate"></eg-grid-field>
     <eg-grid-field label="[% l('Holdable') %]"               datatype="bool" path="holdable"></eg-grid-field>
     <eg-grid-field label="[% l('Reference') %]"              datatype="bool" path="ref"></eg-grid-field>

commit dcde4a9367d35a1e29fc55c156ca42c058202413
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 28 18:06:19 2016 -0500

    webstaff: add prefix, suffix, and parts columns to holdings table
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
index 9764b9d..a875631 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
@@ -99,6 +99,9 @@
     <eg-grid-field label="[% l('Barcode') %]"         path="barcode" visible></eg-grid-field>
     <eg-grid-field label="[% l('Status') %]"          path="status.name" flex="1" visible></eg-grid-field>
 
+    <eg-grid-field label="[% l('Prefix') %]"                 path="call_number.prefix.label"></eg-grid-field>
+    <eg-grid-field label="[% l('Suffix') %]"                 path="call_number.suffix.label"></eg-grid-field>
+    <eg-grid-field label="[% l('Parts') %]"                  path="monograph_parts"></eg-grid-field>
     <eg-grid-field label="[% l('Circulating Library') %]"    path="circ_lib.name"></eg-grid-field>
     <eg-grid-field label="[% l('Price') %]"                  path="price"></eg-grid-field>
     <eg-grid-field label="[% l('Circulation Modifier') %]"   path="circ_modifier"></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
index bde91e2..f33fa65 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
@@ -15,7 +15,7 @@ function(egCore , $q) {
     service.prototype.flesh = {   
         flesh : 2, 
         flesh_fields : {
-            acp : ['status','location','circ_lib'],
+            acp : ['status','location','circ_lib','parts'],
             acn : ['prefix','suffix','copies']
         }
     }
@@ -106,6 +106,14 @@ function(egCore , $q) {
                     }
                 );
 
+                // create virtual field for displaying active parts
+                angular.forEach(svc.copies, function (cp) {
+                    cp.monograph_parts = '';
+                    if (cp.parts && cp.parts.length > 0) {
+                        cp.monograph_parts = cp.parts.map(function(obj) { return obj.label; }).join();
+                    }
+                });
+
                 // create a label using just the unique part of the owner list
                 var index = 0;
                 var prev_owner_list;

commit c888137935e4eeeec1738ff4a45aa7bbcd5d9026
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 28 17:46:36 2016 -0500

    webstaff: ensure that volcopy editor saves copy part maps
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index d90c50e..26d4aae 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -344,6 +344,7 @@ function(egCore , $q) {
                     } else {
                         $scope.copy.parts([]);
                     }
+                    $scope.copy.ischanged(1);
                 }
                 $scope.$watch('part', $scope.updatePart);
 

commit 89096f521dc0fcce6c64f67dd466a39a05be2319
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 28 14:46:15 2016 -0500

    webstaff: add circulating library as optional column in holdings view
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
index af92ff1..9764b9d 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
@@ -99,6 +99,7 @@
     <eg-grid-field label="[% l('Barcode') %]"         path="barcode" visible></eg-grid-field>
     <eg-grid-field label="[% l('Status') %]"          path="status.name" flex="1" visible></eg-grid-field>
 
+    <eg-grid-field label="[% l('Circulating Library') %]"    path="circ_lib.name"></eg-grid-field>
     <eg-grid-field label="[% l('Price') %]"                  path="price"></eg-grid-field>
     <eg-grid-field label="[% l('Circulation Modifier') %]"   path="circ_modifier"></eg-grid-field>
     <eg-grid-field label="[% l('Circulate') %]"              datatype="bool" path="circulate"></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
index e80d712..bde91e2 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
@@ -15,7 +15,7 @@ function(egCore , $q) {
     service.prototype.flesh = {   
         flesh : 2, 
         flesh_fields : {
-            acp : ['status','location'],
+            acp : ['status','location','circ_lib'],
             acn : ['prefix','suffix','copies']
         }
     }

commit ae1d8ab62a85ded1b4b5ffcc7b7cce8bd6396d1e
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 28 14:32:56 2016 -0500

    webstaff: copy template fix
    
    This patches fixes applying the volume portion of a
    template when in the copy template tab of the volume/copy
    editor.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 760a56b..d90c50e 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -1602,6 +1602,8 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                             $scope.working[k] = angular.copy(v);
                         } else {
                             angular.forEach(v, function (sv,sk) {
+                                if (!(k in $scope.working))
+                                    $scope.working[k] = {};
                                 $scope.working[k][sk] = angular.copy(sv);
                             });
                         }

commit bca8bddc768579d8a778dcd10453c39d31619767
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 28 14:13:27 2016 -0500

    webstaff: fix updating stat cats in the volume/copy editor
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index f52e88b..760a56b 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -891,26 +891,25 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                         );
     
                         if (newval) {
-                            var e = new egCore.idl.ascecm();
+                            var e = new egCore.idl.asce();
                             e.isnew( 1 );
-                            e.owning_copy( cp.id() );
                             e.stat_cat( id );
-                            e.stat_cat_entry( newval );
+                            e.id(newval);
 
                             cp.stat_cat_entries(
-                                cp.stat_cat_entries().concat([ e ])
+                                cp.stat_cat_entries() ?
+                                    cp.stat_cat_entries().concat([ e ]) :
+                                    [ e ]
                             );
 
                         }
 
-                        cp.stat_cat_entries( // trim out ephemeral deleted ones
+                        // trim out all deleted ones; the API used to
+                        // do the update doesn't actually consult
+                        // isdeleted for stat cat entries
+                        cp.stat_cat_entries(
                             cp.stat_cat_entries().filter(function (e) {
-                                if (Boolean(e.isnew())) {
-                                    if (Boolean(e.isdeleted())) {
-                                        return false;
-                                    }
-                                }
-                                return true;
+                                return !Boolean(e.isdeleted());
                             })
                         );
    
@@ -1238,7 +1237,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                                 });
 
                                 if (right_sc.length > 0) {
-                                    value_hash[right_sc[0].stat_cat_entry()] = right_sc[0].stat_cat_entry();
+                                    value_hash[right_sc[0].id()] = right_sc[0].id();
                                 } else {
                                     none = true;
                                 }

commit 4b7ed0d8c6ce3524b5c5ff9c352f4f5d47e38d19
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 28 14:11:08 2016 -0500

    webstaff: expose ascecm to open-ils.pcrud
    
    Doing this allows the volume/copy editor to use
    pcrud to retrieve copies with stat_cat_entries fleshed.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 8cff802..75c7f12 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -5233,7 +5233,7 @@ SELECT  usr,
 		</permacrud>
 	</class>
 
-	<class id="ascecm" controller="open-ils.cstore" oils_obj:fieldmapper="asset::stat_cat_entry_copy_map" oils_persist:tablename="asset.stat_cat_entry_copy_map" reporter:label="Statistical Category Entry Copy Map">
+	<class id="ascecm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="asset::stat_cat_entry_copy_map" oils_persist:tablename="asset.stat_cat_entry_copy_map" reporter:label="Statistical Category Entry Copy Map">
 		<fields oils_persist:primary="id" oils_persist:sequence="asset.stat_cat_entry_copy_map_id_seq">
 			<field name="id" reporter:datatype="id" />
 			<field name="owning_copy" reporter:datatype="link"/>
@@ -5245,6 +5245,11 @@ SELECT  usr,
 			<link field="stat_cat_entry" reltype="has_a" key="id" map="" class="asce"/>
 			<link field="stat_cat" reltype="has_a" key="id" map="" class="asc"/>
 		</links>
+		<permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+			<actions>
+				<retrieve/>
+			</actions>
+		</permacrud>
 	</class>
 	<class id="citm" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::item_type_map" oils_persist:tablename="config.item_type_map" reporter:label="Item Type Map" oils_persist:field_safe="true">
 		<fields oils_persist:primary="code">

commit 67e04cb1b3c00b5962ec8f4ecc4769d6d19288a2
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 28 12:44:37 2016 -0500

    webstaff: fix selection of stat cats in copy editor and templates
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 94d3079..f52e88b 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -750,6 +750,13 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                 $scope.batch.prefix = $scope.defaults.prefix;
                 $scope.batch.suffix = $scope.defaults.suffix;
                 $scope.working.statcat_filter = $scope.defaults.statcat_filter;
+                if (typeof $scope.defaults.statcat_filter == 'object') {
+                    // want fieldmapper object here...
+                    $scope.defaults.statcat_filter =
+                         egCore.idl.Clone($scope.defaults.statcat_filter);
+                    // ... and ID here
+                    $scope.working.statcat_filter = $scope.defaults.statcat_filter.id();
+                }
                 if ($scope.defaults.always_volumes) $scope.show_vols = true;
                 if ($scope.defaults.barcode_checkdigit) itemSvc.barcode_checkdigit = true;
                 if ($scope.defaults.auto_gen_barcode) itemSvc.auto_gen_barcode = true;
@@ -758,6 +765,9 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
     }
     $scope.fetchDefaults();
 
+    $scope.$watch('defaults.statcat_filter', function() {
+        $scope.saveDefaults();
+    });
     $scope.$watch('defaults.auto_gen_barcode', function (n,o) {
         itemSvc.auto_gen_barcode = n
     });
@@ -1314,8 +1324,8 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
 
         $scope.statcat_visible = function (sc_owner) {
             var visible = typeof $scope.working.statcat_filter === 'undefined' || !$scope.working.statcat_filter;
-            angular.forEach(egCore.org.ancestors(sc_owner), function (anscestor_org) {
-                if ($scope.working.statcat_filter == anscestor_org.id())
+            angular.forEach(egCore.org.ancestors(sc_owner), function (ancestor_org) {
+                if ($scope.working.statcat_filter == ancestor_org.id())
                     visible = true;
             });
             return visible;
@@ -1548,6 +1558,13 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                         if (t) {
                             $scope.defaults = t;
                             $scope.working.statcat_filter = $scope.defaults.statcat_filter;
+                            if (typeof $scope.defaults.statcat_filter == 'object') {
+                                // want fieldmapper object here...
+                                $scope.defaults.statcat_filter =
+                                    egCore.idl.Clone($scope.defaults.statcat_filter);
+                                // ... and ID here
+                                $scope.working.statcat_filter = $scope.defaults.statcat_filter.id();
+                            }
                         }
                     });
                 }
@@ -1685,6 +1702,15 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                     statcat_filter: undefined
                 };
             
+                $scope.statcat_visible = function (sc_owner) {
+                    var visible = typeof $scope.working.statcat_filter === 'undefined' || !$scope.working.statcat_filter;
+                    angular.forEach(egCore.org.ancestors(sc_owner), function (ancestor_org) {
+                        if ($scope.working.statcat_filter == ancestor_org.id())
+                            visible = true;
+                    });
+                    return visible;
+                }
+
                 createStatcatUpdateWatcher = function (id) {
                     return $scope.$watch('working.statcats[' + id + ']', function () {
                         if ($scope.working.statcats) {

commit 47e2f657c661b0a30a384993f6ad0a2e96676cf9
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Wed Jan 27 17:36:37 2016 -0500

    webstaff: fix setting default copy status for new items
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 2052e79..94d3079 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -252,7 +252,7 @@ function(egCore , $q) {
 
     // create a new acp object with default values
     // (both hard-coded and coming from OU settings)
-    service.generateNewCopy = function(callNumber, owningLib, isNew) {
+    service.generateNewCopy = function(callNumber, owningLib, isFastAdd, isNew) {
         var cp = new egCore.idl.acp();
         cp.id( --service.new_cp_id );
         if (isNew) {
@@ -271,6 +271,19 @@ function(egCore , $q) {
         cp.opac_visible('t');
         cp.ref('f');
         cp.mint_condition('t');
+
+        var status_setting = isFastAdd ?
+            'cat.default_copy_status_fast' :
+            'cat.default_copy_status_normal';
+        egCore.org.settings(
+            [status_setting],
+            owningLib
+        ).then(function(set) {
+            var default_ccs = set[status_setting] || 
+                (isFastAdd ? 0 : 5); // 0 is Available, 5 is In Process
+            cp.status(default_ccs);
+        });
+
         return cp;
     }
 
@@ -1065,6 +1078,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                                     var cp = new itemSvc.generateNewCopy(
                                         cn,
                                         proto.owner || egCore.auth.user().ws_ou(),
+                                        $scope.is_fast_add,
                                         ((!$scope.only_vols) ? true : false)
                                     );
 
@@ -1110,7 +1124,8 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
 
                                 var cp = new itemSvc.generateNewCopy(
                                     cn,
-                                    proto.owner || egCore.auth.user().ws_ou()
+                                    proto.owner || egCore.auth.user().ws_ou(),
+                                    $scope.is_fast_add
                                 );
 
                                 if (proto.barcode) cp.barcode( proto.barcode );
@@ -1133,21 +1148,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
 
         }).then( function() {
             $scope.data = itemSvc;
-            if ($scope.add_vols_copies) {
-                var status_setting = $scope.is_fast_add ?
-                    'cat.default_copy_status_fast' :
-                    'cat.default_copy_status_normal';
-                egCore.org.settings([
-                    status_setting
-                ]).then(function(set) {
-                    $scope.default_ccs = set[status_setting] || 
-                        ($scope.is_fast_add ? 0 : 5); // 0 is Available, 5 is In Process
-                    angular.forEach($scope.data.copies, function (cp) {
-                        cp.status($scope.default_ccs);
-                    });
-                    $scope.workingGridDataProvider.refresh();
-                });
-            }
+            $scope.workingGridDataProvider.refresh();
         });
 
         $scope.focusNextFirst = function(prev_lib,prev_bc) {

commit 15020e772154c91cee266f3654fb8b579716041d
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Wed Jan 27 16:38:33 2016 -0500

    webstaff: consolidate code for generate new copies
    
    This patch does a bit of refactoring to get rid of
    some copy-and-paste code that creates new copies
    in the volume/copy editor.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 5ec7c28..2052e79 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -250,6 +250,30 @@ function(egCore , $q) {
         );
     }
 
+    // create a new acp object with default values
+    // (both hard-coded and coming from OU settings)
+    service.generateNewCopy = function(callNumber, owningLib, isNew) {
+        var cp = new egCore.idl.acp();
+        cp.id( --service.new_cp_id );
+        if (isNew) {
+            cp.isnew( true );
+        }
+        cp.circ_lib( owningLib );
+        cp.call_number( callNumber );
+        cp.deposit(0);
+        cp.price(0);
+        cp.deposit_amount(0);
+        cp.fine_level(2); // Normal
+        cp.loan_duration(2); // Normal
+        cp.location(1); // Stacks
+        cp.circulate('t');
+        cp.holdable('t');
+        cp.opac_visible('t');
+        cp.ref('f');
+        cp.mint_condition('t');
+        return cp;
+    }
+
     return service;
 }])
 
@@ -506,22 +530,10 @@ function(egCore , $q) {
 
                 $scope.changeCPCount = function () {
                     while ($scope.copy_count > $scope.copies.length) {
-                        var cp = new egCore.idl.acp();
-                        cp.id( --itemSvc.new_cp_id );
-                        cp.isnew( true );
-                        cp.circ_lib( $scope.callNumber.owning_lib() );
-                        cp.call_number( $scope.callNumber );
-                        cp.deposit(0);
-                        cp.price(0);
-                        cp.deposit_amount(0);
-                        cp.fine_level(2); // Normal
-                        cp.loan_duration(2); // Normal
-                        cp.location(1); // Stacks
-                        cp.circulate('t');
-                        cp.holdable('t');
-                        cp.opac_visible('t');
-                        cp.ref('f');
-                        cp.mint_condition('t');
+                        var cp = itemSvc.generateNewCopy(
+                            $scope.callNumber,
+                            $scope.callNumber.owning_lib()
+                        );
                         $scope.copies.push( cp );
                         $scope.allcopies.push( cp );
 
@@ -639,25 +651,7 @@ function(egCore , $q) {
                             cn.owning_lib( $scope.owning_lib.id() );
                             cn.record( $scope.full_cn.record() );
 
-                            var cp = new egCore.idl.acp();
-                            cp.call_number( cn );
-                            cp.id( --itemSvc.new_cp_id );
-                            cp.isnew( true );
-
-                            cp.deposit(0);
-                            cp.price(0);
-                            cp.deposit_amount(0);
-                            cp.fine_level(2); // Normal
-                            cp.loan_duration(2); // Normal
-                            cp.location(1); // Stacks
-                            cp.circulate('t');
-                            cp.holdable('t');
-                            cp.opac_visible('t');
-                            cp.ref('f');
-                            cp.mint_condition('t');
-
-                            cp.circ_lib( $scope.owning_lib.id() );
-                            cp.call_number( cn );
+                            var cp = itemSvc.generateNewCopy(cn, $scope.owning_lib.id());
 
                             $scope.struct[cn.id()] = [cp];
                             $scope.allcopies.push(cp);
@@ -1068,23 +1062,11 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                             if (proto.callnumber) {
                                 return egCore.pcrud.retrieve('acn', proto.callnumber)
                                 .then(function(cn) {
-                                    var cp = new egCore.idl.acp();
-                                    cp.call_number( cn );
-                                    cp.id( --itemSvc.new_cp_id );
-                                    if (!$scope.only_vols) cp.isnew( true );
-                                    cp.circ_lib( proto.owner || egCore.auth.user().ws_ou() );
-
-                                    cp.deposit(0);
-                                    cp.price(0);
-                                    cp.deposit_amount(0);
-                                    cp.fine_level(2); // Normal
-                                    cp.loan_duration(2); // Normal
-                                    cp.location(1); // Stacks
-                                    cp.circulate('t');
-                                    cp.holdable('t');
-                                    cp.opac_visible('t');
-                                    cp.ref('f');
-                                    cp.mint_condition('t');
+                                    var cp = new itemSvc.generateNewCopy(
+                                        cn,
+                                        proto.owner || egCore.auth.user().ws_ou(),
+                                        ((!$scope.only_vols) ? true : false)
+                                    );
 
                                     if (proto.barcode) cp.barcode( proto.barcode );
 
@@ -1126,24 +1108,11 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                                     }
                                 });
 
-                                var cp = new egCore.idl.acp();
-                                cp.call_number( cn );
-                                cp.id( --itemSvc.new_cp_id );
-                                cp.isnew( true );
-
-                                cp.deposit(0);
-                                cp.price(0);
-                                cp.deposit_amount(0);
-                                cp.fine_level(2); // Normal
-                                cp.loan_duration(2); // Normal
-                                cp.location(1); // Stacks
-                                cp.circulate('t');
-                                cp.holdable('t');
-                                cp.opac_visible('t');
-                                cp.ref('f');
-                                cp.mint_condition('t');
-
-                                cp.circ_lib( proto.owner || egCore.auth.user().ws_ou() );
+                                var cp = new itemSvc.generateNewCopy(
+                                    cn,
+                                    proto.owner || egCore.auth.user().ws_ou()
+                                );
+
                                 if (proto.barcode) cp.barcode( proto.barcode );
 
                                 itemSvc.addCopy(cp)

commit e813de67fe8ea6a3ef07a9ebcc281a89b0cac74c
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Jan 26 17:44:32 2016 -0500

    webstaff: fix console noise spawned by OU selector
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 f5ce418..4fe04a7 100644
--- a/Open-ILS/web/js/ui/default/staff/services/ui.js
+++ b/Open-ILS/web/js/ui/default/staff/services/ui.js
@@ -348,7 +348,7 @@ function($modal, $interpolate) {
             });
 
             $scope.getSelectedName = function() {
-                if ($scope.selected)
+                if ($scope.selected && $scope.selected.shortname)
                     return $scope.selected.shortname();
                 return $scope.label;
             }

commit b9f9da96a44a154c8fd4f37ccea690afcb66d5e8
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Jan 26 17:34:03 2016 -0500

    webstaff: tweak setting default classification scheme in volcopy editor
    
    With this patch, the volume/copy editor creation default for
    classification scheme now can have a value of "Use Library Setting".
    If this is in effect, the default classification scheme for new
    volumes is taken from the library setting.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_defaults.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_defaults.tt2
index dc67634..df0a1d7 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_defaults.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_defaults.tt2
@@ -27,7 +27,7 @@
                 <div class="col-xs-12">
                     <h4 class="pad-vert">[% l('Creation Defaults') %]</h4>
                     <select class="form-control" ng-change="saveDefaults()" ng-model="defaults.classification" ng-options="cl.id() as cl.name() for cl in classification_list">
-                        <option value="">Unset Default Classification Scheme</option>
+                        <option value="">[% ('Use Library Setting') %]</option>
                     </select>
                 </div>
             </div>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index f347be7..5ec7c28 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -661,6 +661,14 @@ function(egCore , $q) {
 
                             $scope.struct[cn.id()] = [cp];
                             $scope.allcopies.push(cp);
+                            if (!scope.defaults.classification) {
+                                egCore.org.settings(
+                                    ['cat.default_classification_scheme'],
+                                    cn.owning_lib()
+                                ).then(function (val) {
+                                    cn.label_class(val['cat.default_classification_scheme']);
+                                });
+                            }
                         }
                     } else if (n < o && n >= $scope.orig_cn_count) { // removing
                         var how_many = o - n;
@@ -1088,26 +1096,35 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                                 cn.isnew( true );
                                 cn.prefix( $scope.defaults.prefix || -1 );
                                 cn.suffix( $scope.defaults.suffix || -1 );
-                                cn.label_class( $scope.defaults.classification || 1 );
                                 cn.owning_lib( proto.owner || egCore.auth.user().ws_ou() );
                                 cn.record( $scope.record_id );
-                                if (proto.label) {
-                                     cn.label( proto.label );
-                                } else {
-                                    egCore.net.request(
-                                        'open-ils.cat',
-                                        'open-ils.cat.biblio.record.marc_cn.retrieve',
-                                        $scope.record_id,
-                                        $scope.defaults.classification || 1
-                                    ).then(function(cn_array) {
-                                        if (cn_array.length > 0) {
-                                            for (var field in cn_array[0]) {
-                                                cn.label( cn_array[0][field] );
-                                                break;
+                                egCore.org.settings(
+                                    ['cat.default_classification_scheme'],
+                                    cn.owning_lib()
+                                ).then(function (val) {
+                                    cn.label_class(
+                                        $scope.defaults.classification ||
+                                        val['cat.default_classification_scheme'] ||
+                                        1
+                                    );
+                                    if (proto.label) {
+                                        cn.label( proto.label );
+                                    } else {
+                                        egCore.net.request(
+                                            'open-ils.cat',
+                                            'open-ils.cat.biblio.record.marc_cn.retrieve',
+                                            $scope.record_id,
+                                            cn.label_class()
+                                        ).then(function(cn_array) {
+                                            if (cn_array.length > 0) {
+                                                for (var field in cn_array[0]) {
+                                                    cn.label( cn_array[0][field] );
+                                                    break;
+                                                }
                                             }
-                                        }
-                                    });
-                                } 
+                                        });
+                                    }
+                                });
 
                                 var cp = new egCore.idl.acp();
                                 cp.call_number( cn );

commit df37aed816d3e179331ad104d4138381684f7d60
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Jan 26 17:01:21 2016 -0500

    webstaff: add more volume transfer options
    
    The volume/copy editor now has the following new actions:
    
      * (Transfer) Volumes to Previously Marked Record
      * (Transfer) Volumes to Previously Marked Record and Library
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
index 6f50ceb..af92ff1 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
@@ -81,8 +81,12 @@
     <eg-grid-action handler="selectedHoldingsVolCopyDelete" group="[% l('Delete') %]" disabled="copies_not_shown"
       label="[% l('Volumes and Copies') %]"></eg-grid-action>
 
+    <eg-grid-action handler="transferVolumesToRecord" group="[% l('Transfer') %]"
+      label="[% l('Volumes to Previously Marked Record') %]"></eg-grid-action>
     <eg-grid-action handler="transferVolumes" group="[% l('Transfer') %]"
       label="[% l('Volumes to Previously Marked Library') %]"></eg-grid-action>
+    <eg-grid-action handler="transferVolumesToRecordAndLibrary" group="[% l('Transfer') %]"
+      label="[% l('Volumes to Previously Marked Record and Library') %]"></eg-grid-action>
     <eg-grid-action handler="changeItemOwningLib" group="[% l('Transfer') %]"
       label="[% l('Copies to Previously Marked Library') %]"></eg-grid-action>
     <eg-grid-action handler="transferItems" group="[% l('Transfer') %]"
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 2ab0074..bfa8ee6 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
@@ -1020,7 +1020,45 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
         );
     }
 
-    $scope.transferVolumes = function (){
+    $scope.transferVolumesToRecord = function (){
+        var target_record = egCore.hatch.getLocalItem('eg.cat.marked_volume_transfer_record');
+        if (!target_record) return;
+        if ($scope.record_id == target_record) return;
+        var items = $scope.holdingsGridControls.selectedItems();
+        if (!items.length) return;
+
+        var vols_to_move   = {};
+        angular.forEach(items, function(item) {
+            if (!(item.call_number.owning_lib in vols_to_move)) {
+                vols_to_move[item.call_number.owning_lib] = new Array;
+            }
+            vols_to_move[item.call_number.owning_lib].push(item.call_number.id);
+        });
+
+        var promises = [];        
+        angular.forEach(vols_to_move, function(vols, owning_lib) {
+            promises.push(egCore.net.request(
+                'open-ils.cat',
+                'open-ils.cat.asset.volume.batch.transfer.override',
+                egCore.auth.token(), {
+                    docid   : target_record,
+                    lib     : owning_lib,
+                    volumes : vols
+                }
+            ));
+        });
+        $q.all(promises).then(function(success) {
+            if (success) {
+                holdingsSvcInst.fetchAgain().then(function() {
+                    $scope.holdingsGridDataProvider.refresh();
+                });
+            } else {
+                alert('Could not transfer volumes!');
+            }
+        });
+    }
+
+    $scope.transferVolumes = function (new_record){
         var xfer_target = egCore.hatch.getLocalItem('eg.cat.volume_transfer_target');
 
         if (xfer_target) {
@@ -1028,7 +1066,7 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
                 'open-ils.cat',
                 'open-ils.cat.asset.volume.batch.transfer.override',
                 egCore.auth.token(), {
-                    docid   : $scope.record_id,
+                    docid   : (new_record ? new_record : $scope.record_id),
                     lib     : xfer_target,
                     volumes : gatherSelectedVolumeIds()
                 }
@@ -1045,6 +1083,12 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
         
     }
 
+    $scope.transferVolumesToRecordAndLibrary = function() {
+        var target_record = egCore.hatch.getLocalItem('eg.cat.marked_volume_transfer_record');
+        if (!target_record) return;
+        $scope.transferVolumes(target_record);
+    }
+
     // this "transfers" selected copies to a new owning library,
     // auto-creating volumes and deleting unused volumes as required.
     $scope.changeItemOwningLib = function() {

commit 13770ac8b9b1df28aa20e5d837ed9054b182b585
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Jan 26 16:13:27 2016 -0500

    webstaff: add a Transfer Copies to Previously Marked Library action
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
index 8f3c6cc..6f50ceb 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
@@ -40,7 +40,7 @@
     <eg-grid-action handler="attach_to_peer_bib"
       label="[% l('Link as Conjoined to Previously Marked Bib Record') %]"></eg-grid-action>
     <eg-grid-action handler="markLibAsVolTarget"
-      label="[% l('Choose Library for Volume Transfer Destination') %]"></eg-grid-action>
+      label="[% l('Choose Library for Volume/Copy Transfer Destination') %]"></eg-grid-action>
 
     <eg-grid-action handler="selectedHoldingsItemStatus" group="[% l('Show') %]"
       label="[% l('Item Status (list)') %]"></eg-grid-action>
@@ -83,6 +83,8 @@
 
     <eg-grid-action handler="transferVolumes" group="[% l('Transfer') %]"
       label="[% l('Volumes to Previously Marked Library') %]"></eg-grid-action>
+    <eg-grid-action handler="changeItemOwningLib" group="[% l('Transfer') %]"
+      label="[% l('Copies to Previously Marked Library') %]"></eg-grid-action>
     <eg-grid-action handler="transferItems" group="[% l('Transfer') %]"
       label="[% l('Items to Previously Marked Volume') %]"></eg-grid-action>
 
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 bb3abda..2ab0074 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
@@ -1045,6 +1045,59 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
         
     }
 
+    // this "transfers" selected copies to a new owning library,
+    // auto-creating volumes and deleting unused volumes as required.
+    $scope.changeItemOwningLib = function() {
+        var xfer_target = egCore.hatch.getLocalItem('eg.cat.volume_transfer_target');
+        var items = $scope.holdingsGridControls.selectedItems();
+        if (!xfer_target || !items.length) {
+            return;
+        }
+        var vols_to_move   = {};
+        var copies_to_move = {};
+        angular.forEach(items, function(item) {
+            if (item.call_number.owning_lib != xfer_target) {
+                if (item.call_number.id in vols_to_move) {
+                    copies_to_move[item.call_number.id].push(item.id);
+                } else {
+                    vols_to_move[item.call_number.id] = item.call_number;
+                    copies_to_move[item.call_number.id] = new Array;
+                    copies_to_move[item.call_number.id].push(item.id);
+                }
+            }
+        });
+    
+        var promises = [];
+        angular.forEach(vols_to_move, function(vol) {
+            promises.push(egCore.net.request(
+                'open-ils.cat',
+                'open-ils.cat.call_number.find_or_create',
+                egCore.auth.token(),
+                vol.label,
+                vol.record,
+                xfer_target,
+                vol.prefix.id,
+                vol.suffix.id,
+                vol.label_class
+            ).then(function(resp) {
+                var evt = egCore.evt.parse(resp);
+                if (evt) return;
+                return egCore.net.request(
+                    'open-ils.cat',
+                    'open-ils.cat.transfer_copies_to_volume',
+                    egCore.auth.token(),
+                    resp.acn_id,
+                    copies_to_move[vol.id]
+                );
+            }));
+        });
+        $q.all(promises).then(function() {
+            holdingsSvcInst.fetchAgain().then(function() {
+                $scope.holdingsGridDataProvider.refresh();
+            });
+        });
+    }
+
     $scope.transferItems = function (){
         var xfer_target = egCore.hatch.getLocalItem('eg.cat.item_transfer_target');
         var copy_ids = gatherSelectedHoldingsIds();

commit 61d82bdd525f26323c07e756411e823e1d9fce32
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Jan 26 14:06:57 2016 -0500

    webstaff: grab default call number from bib when creating volume
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 7b72add..f347be7 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -1091,7 +1091,23 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                                 cn.label_class( $scope.defaults.classification || 1 );
                                 cn.owning_lib( proto.owner || egCore.auth.user().ws_ou() );
                                 cn.record( $scope.record_id );
-                                if (proto.label) cn.label( proto.label );
+                                if (proto.label) {
+                                     cn.label( proto.label );
+                                } else {
+                                    egCore.net.request(
+                                        'open-ils.cat',
+                                        'open-ils.cat.biblio.record.marc_cn.retrieve',
+                                        $scope.record_id,
+                                        $scope.defaults.classification || 1
+                                    ).then(function(cn_array) {
+                                        if (cn_array.length > 0) {
+                                            for (var field in cn_array[0]) {
+                                                cn.label( cn_array[0][field] );
+                                                break;
+                                            }
+                                        }
+                                    });
+                                } 
 
                                 var cp = new egCore.idl.acp();
                                 cp.call_number( cn );

commit 91a25d96cc50f78d373a7430db44fa3b58b6c582
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Jan 26 13:06:14 2016 -0500

    webstaff: save copy templates now unconditional
    
    The save copy templates button in the volume/copy editor
    no longer requires that a working template be active; this
    way, users can save the set of templates after an import
    without having to select one of them first.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index cfd974a..7b72add 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -1620,6 +1620,10 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                         $scope.$parent.fetchTemplates();
 
                         $scope.dirty = false;
+                    } else {
+                        // save all templates, as we might do after an import
+                        egCore.hatch.setItem('cat.copy.templates', $scope.templates);
+                        $scope.$parent.fetchTemplates();
                     }
                 }
             

commit 44489c666f1a7105ea7c22810133759e7c45f024
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Jan 25 18:34:54 2016 -0500

    webstaff: implement import/export of copy templates
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
index 9753d53..09e87d2 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
@@ -19,8 +19,15 @@
                 </div>
                 <div class="col-md-8">
                     <div class="btn-group pull-right">
-                        <label class="btn btn-default" ng-click="importTemplates()">[% l('Import') %]</label>
-                        <label class="btn btn-default" ng-click="exportTemplates()">[% l('Export') %]</label>
+                        <span class="btn btn-default btn-file">
+                            [% l('Import') %]
+                            <input type="file" eg-file-reader container="imported_templates.data">
+                        </span>
+                        <label class="btn btn-default"
+                            eg-json-exporter container="templates"
+                            default-file-name="'[% l('exported_copy_templates.json') %]'">
+                            [% l('Export') %]
+                        </label>
                     </div>
                 </div>
             </div>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 9bdec0b..cfd974a 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -1624,9 +1624,24 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                 }
             
                 $scope.templates = {};
+                $scope.imported_templates = { data : '' };
                 $scope.template_name = '';
                 $scope.template_name_list = [];
-            
+
+                $scope.$watch('imported_templates.data', function(newVal, oldVal) {
+                    if (newVal && newVal != oldVal) {
+                        try {
+                            var newTemplates = JSON.parse(newVal);
+                            if (!Object.keys(newTemplates).length) return;
+                            $scope.templates = newTemplates;
+                            $scope.template_name_list = Object.keys(newTemplates);
+                            $scope.template_name = '';
+                        } catch (E) {
+                            console.log('tried to import an invalid copy template file');
+                        }
+                    }
+                });
+
                 $scope.tracker = function (x,f) { if (x) return x[f]() };
                 $scope.idTracker = function (x) { if (x) return $scope.tracker(x,'id') };
                 $scope.cant_have_vols = function (id) { return !egCore.org.CanHaveVolumes(id); };

commit 4ed811a38128a29512e3de0f9dd84857ebb17145
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Jan 25 18:30:48 2016 -0500

    webstaff: add egJsonExporter directive
    
    This directive is used to allow a piece of JSON
    to be saved to the user's filesystem.  For example:
    
    <span eg-json-exporter container="foo" default-file-name="example.json">Export</span>
    
    specifies a button that, when click, allows the user to save
    the contents of the scope variable foo.  The dialog that
    appears will use "example.json" as the default filename.
    
    This also adds a new dependency, the MIT-licensed
     angular-file-saver service written by Philipp Alferov:
    
    https://alferov.github.io/angular-file-saver/
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/base_js.tt2 b/Open-ILS/src/templates/staff/base_js.tt2
index 4601e9c..db4f85a 100644
--- a/Open-ILS/src/templates/staff/base_js.tt2
+++ b/Open-ILS/src/templates/staff/base_js.tt2
@@ -8,6 +8,7 @@
 <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>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/hotkeys.min.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/angular-file-saver.bundle.min.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/angular-location-update.min.js"></script>
 
 <!-- IDL / opensrf (network) -->
diff --git a/Open-ILS/web/js/ui/default/staff/Gruntfile.js b/Open-ILS/web/js/ui/default/staff/Gruntfile.js
index 087f0ff..2c0d289 100644
--- a/Open-ILS/web/js/ui/default/staff/Gruntfile.js
+++ b/Open-ILS/web/js/ui/default/staff/Gruntfile.js
@@ -22,6 +22,7 @@ module.exports = function(grunt) {
             'bower_components/angular-bootstrap/ui-bootstrap.min.js',
             'bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js',
             'bower_components/angular-hotkeys/build/hotkeys.min.js',
+            'bower_components/angular-file-saver/dist/angular-file-saver.bundle.min.js',
             'bower_components/angular-location-update/angular-location-update.min.js',
             'bower_components/jquery/dist/jquery.min.js',
           ]
diff --git a/Open-ILS/web/js/ui/default/staff/bower.json b/Open-ILS/web/js/ui/default/staff/bower.json
index db8e900..fd1c566 100644
--- a/Open-ILS/web/js/ui/default/staff/bower.json
+++ b/Open-ILS/web/js/ui/default/staff/bower.json
@@ -27,6 +27,7 @@
   },
   "dependencies": {
     "angular-hotkeys": "chieffancypants/angular-hotkeys#~1.3.0",
-    "angular-location-update": "./extern/angular-location-update/"
+    "angular-location-update": "./extern/angular-location-update/",
+    "angular-file-saver": "~1.0.2"
   }
 }
diff --git a/Open-ILS/web/js/ui/default/staff/services/core.js b/Open-ILS/web/js/ui/default/staff/services/core.js
index e0ef021..94c4b46 100644
--- a/Open-ILS/web/js/ui/default/staff/services/core.js
+++ b/Open-ILS/web/js/ui/default/staff/services/core.js
@@ -3,4 +3,4 @@
  * egCoreMod houses all of the services, etc. required by all pages
  * for basic functionality.
  */
-angular.module('egCoreMod', ['cfp.hotkeys']);
+angular.module('egCoreMod', ['cfp.hotkeys', 'ngFileSaver']);
diff --git a/Open-ILS/web/js/ui/default/staff/services/file.js b/Open-ILS/web/js/ui/default/staff/services/file.js
index 8dc0c2b..473f4fe 100644
--- a/Open-ILS/web/js/ui/default/staff/services/file.js
+++ b/Open-ILS/web/js/ui/default/staff/services/file.js
@@ -25,4 +25,20 @@ angular.module('egCoreMod')
             });
         }
     }
-}]);
+}])
+
+.directive('egJsonExporter', ['FileSaver', 'Blob', function(FileSaver, Blob) {
+    return {
+        scope: {
+            container: '=',
+            defaultFileName: '='
+        },
+        link: function (scope, element, attributes) {
+            element.bind('click', function (clickEvent) {
+                var data = new Blob([JSON.stringify(scope.container)], {type : 'application/json'});
+                FileSaver.saveAs(data, scope.defaultFileName);
+            });
+        }
+    }
+}])
+;

commit 66f9a004fe916e8217fd2bac71ed35ab49a5b9ff
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Jan 25 15:41:52 2016 -0500

    webstaff: style file input buttons more nicely
    
    Using technique devised by Cory LaViska per
    http://www.abeautifulsite.net/whipping-file-inputs-into-shape-with-bootstrap-3/
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/css/style.css.tt2 b/Open-ILS/src/templates/staff/css/style.css.tt2
index 80fb47a..0c21913 100644
--- a/Open-ILS/src/templates/staff/css/style.css.tt2
+++ b/Open-ILS/src/templates/staff/css/style.css.tt2
@@ -132,6 +132,29 @@ table.list tr.selected td { /* deprecated? */
   margin-left: 10px;
 }
 
+/* button styling by Cory LaViska from
+   http://www.abeautifulsite.net/whipping-file-inputs-into-shape-with-bootstrap-3/
+*/
+.btn-file {
+    position: relative;
+    overflow: hidden;
+}
+.btn-file input[type=file] {
+    position: absolute;
+    top: 0;
+    right: 0;
+    min-width: 100%;
+    min-height: 100%;
+    font-size: 100px;
+    text-align: right;
+    filter: alpha(opacity=0);
+    opacity: 0;
+    outline: none;
+    background: white;
+    cursor: inherit;
+    display: block;
+}
+
 .strong-text {
   font-weight: bold;
 }

commit 8fa1d455162aac7b8434b89f840f0486bfb18bbb
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 21 17:42:47 2016 -0500

    webstaff: fixed client-side sorting of names in add-to-bucket dialog
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_add_to_bucket.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_add_to_bucket.tt2
index cbcf35d..79f2914 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_add_to_bucket.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_add_to_bucket.tt2
@@ -10,7 +10,7 @@
     </div>
     <div class="col-md-4">
       <select id="select-bucket" class="form-control" ng-model="bucket_id"
-              ng-options="bucket.id() as bucket.name() for bucket in allBuckets | orderBy:bucket.name()">
+              ng-options="bucket.id() as bucket.name() for bucket in allBuckets | orderBy:'name()'">
       </select>
     </div>
     <div class="col-md-4">

commit ec8fb38aa66b2f37bb20eaccedecc113c56ed318
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 21 17:31:46 2016 -0500

    webstaff: alphabetize status and location dropdowns in copy-editor
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 5dbccfa..9bdec0b 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -126,7 +126,7 @@ function(egCore , $q) {
     service.get_locations = function(orgs) {
         return egCore.pcrud.search('acpl',
             {owning_lib : orgs},
-            null, {atomic : true}
+            {order_by : { acpl : 'name' }}, {atomic : true}
         );
     };
 
@@ -142,7 +142,7 @@ function(egCore , $q) {
         if (egCore.env.ccs)
             return $q.when(egCore.env.ccs.list);
 
-        return egCore.pcrud.retrieveAll('ccs', {}, {atomic : true}).then(
+        return egCore.pcrud.retrieveAll('ccs', {order_by : { ccs : 'name' }}, {atomic : true}).then(
             function(list) {
                 egCore.env.absorbList(list, 'ccs');
                 return list;

commit 5e4d6d9516edd050be602bbe7d54a2d396710b35
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 21 17:20:43 2016 -0500

    webstaff: display floating group correctly in item status
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/item/t_summary_pane.tt2 b/Open-ILS/src/templates/staff/cat/item/t_summary_pane.tt2
index bb7370a..7d1a676 100644
--- a/Open-ILS/src/templates/staff/cat/item/t_summary_pane.tt2
+++ b/Open-ILS/src/templates/staff/cat/item/t_summary_pane.tt2
@@ -140,7 +140,7 @@
 
   <div class="flex-row">
     <div class="flex-cell">[% l('Floating') %]</div>
-    <div class="flex-cell well">{{copy.floating()}}</div>
+    <div class="flex-cell well">{{copy.floating().name()}}</div>
 
     <div class="flex-cell">[% l('Circulate') %]</div>
     <div class="flex-cell well">{{copy.circulate()}}</div>
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 b3ba37b..837b5b3 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
@@ -59,7 +59,7 @@ function(egCore) {
     service.flesh = {   
         flesh : 3, 
         flesh_fields : {
-            acp : ['call_number','location','status','location'],
+            acp : ['call_number','location','status','location','floating'],
             acn : ['record','prefix','suffix'],
             bre : ['simple_record','creator','editor']
         },
@@ -302,7 +302,7 @@ function($scope , $q , $location , $routeParams , $timeout , $window , egCore ,
 
             // make boolean for auto-magic true/false display
             angular.forEach(
-                ['ref','opac_visible','holdable','floating','circulate'],
+                ['ref','opac_visible','holdable','circulate'],
                 function(field) { copy[field](Boolean(copy[field]() == 't')) }
             );
 

commit d6e8b7b7ac4659f80a74e7d21d97e7b506a0f914
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Jan 21 17:15:53 2016 -0500

    webstaff: add copy editor support for floating field
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
index 553cb25..9753d53 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
@@ -370,6 +370,23 @@
                     </button>
                 </div>
             </div>
+
+            <div class="row pad-vert"></div>
+
+            <div class="row bg-info">
+                <div class="col-md-6">
+                    <b>[% l('Floating') %]</b>
+                </div>
+            </div>
+
+            <div class="row">
+                <div class="col-md-6" ng-class="{'bg-success': working.floating !== undefined}">
+                    <select class="form-control"
+                        ng-disabled="!defaults.attributes.floating" ng-model="working.floating"
+                        ng-options="a.id() as a.name() for a in floating_list"
+                    ></select>
+                </div>
+            </div>
         </div>
 
         <div class="col-md-4">
diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_defaults.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_defaults.tt2
index d2c6f98..dc67634 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_defaults.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_defaults.tt2
@@ -295,6 +295,14 @@
                 </div>
             </div>
 
+            <div class="row">
+                <div class="col-xs-6">
+                    <label>
+                        <input type="checkbox" ng-change="saveDefaults()" ng-model="defaults.attributes.floating"/>
+                        [% l('Floating') %]
+                    </label>
+                </div>
+            </div>
         </div>
 
         <div class="col-md-4">
diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index dfe8df3..5dbccfa 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -190,6 +190,19 @@ function(egCore , $q) {
 
     };
 
+    service.get_floating_groups = function() {
+        if (egCore.env.cfg)
+            return $q.when(egCore.env.cfg.list);
+
+        return egCore.pcrud.retrieveAll('cfg', {}, {atomic : true}).then(
+            function(list) {
+                egCore.env.absorbList(list, 'cfg');
+                return list;
+            }
+        );
+
+    };
+
     service.bmp_parts = {};
     service.get_parts = function(rec) {
         if (service.bmp_parts[rec])
@@ -702,7 +715,8 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
             circ_as_type : true,
             location : true,
             holdable : true,
-            age_protect : true
+            age_protect : true,
+            floating : true
         }
     };
 
@@ -1353,6 +1367,12 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
         });
         createSimpleUpdateWatcher('age_protect');
 
+        $scope.floating_list = [];
+        itemSvc.get_floating_groups().then(function(list){
+            $scope.floating_list = list;
+        });
+        createSimpleUpdateWatcher('floating');
+
         createSimpleUpdateWatcher('circ_lib');
         createSimpleUpdateWatcher('circulate');
         createSimpleUpdateWatcher('holdable');
@@ -1515,7 +1535,8 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                         circ_as_type : true,
                         location : true,
                         holdable : true,
-                        age_protect : true
+                        age_protect : true,
+                        floating : true
                     }
                 };
 

commit 68c13f4aa20982d9c71d8f4010f71d56a729edc4
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Mon Jan 4 15:51:14 2016 -0500

    webstaff: rework Local Administration
    
    maintain links and labels as a data structure
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
index c55235f..8dcb28f 100644
--- a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
@@ -7,156 +7,47 @@
 
 <div class="container admin-splash-container">
 
+[%
+    interfaces = [
+     [ l('Address Alerts'), "./admin/local/actor/address_alert" ]
+    ,[ l('Age Overdue Circs to Lost'), "./admin/local/circ/age_to_lost" ]
+    ,[ l('Auto-Print Settings'), "./admin/local/config/auto_print" ]
+    ,[ l('Barcode Completion'), "./admin/local/config/barcode_completion" ]
+    ,[ l('Cash Reports'), "./admin/local/money/cash_reports" ]
+    ,[ l('Circ Limit Sets'), "./admin/local/config/circ_limit_set" ]
+    ,[ l('Circulation Policies'), "./admin/local/config/circ_matrix_matchpoint" ]
+    ,[ l('Closed Dates Editor'), "./admin/local/actor/closed_dates" ]
+    ,[ l('Copy Location Groups'), "./admin/local/asset/copy_location_group" ]
+    ,[ l('Copy Location Order'), "./admin/local/asset/copy_location_order" ]
+    ,[ l('Copy Locations Editor'), "./admin/local/asset/copy_locations" ]
+    ,[ l('Copy Template Editor'), "./admin/local/asset/copy_template" ]
+    ,[ l('Field Documentation'), "./admin/local/config/idl_field_doc" ]
+    ,[ l('Group Penalty Thresholds'), "./admin/local/permission/grp_penalty_threshold" ]
+    ,[ l('Hold Policies'), "./admin/local/config/hold_matrix_matchpoint" ]
+    ,[ l('Library Settings Editor'), "./admin/local/asset/org_unit_settings" ]
+    ,[ l('Non-Cataloged Types Editor'), "./admin/local/config/non_cat_types" ]
+    ,[ l('Notifications / Action Triggers'), "./admin/local/action_trigger/event_definition" ]
+    ,[ l('Patrons with Negative Balances'), "./admin/local/circ/neg_balance_users" ]
+    ,[ l('Search Filter Groups'), "./admin/local/actor/search_filter_group" ]
+    ,[ l('Standing Penalties'), "./admin/local/config/standing_penalty" ]
+    ,[ l('Statistical Categories Editor'), "./admin/local/asset/stat_cat_editor" ]
+   ];
+
+   USE table(interfaces, rows=9);
+%]
+
+[% FOREACH row = table.rows %]
   <div class="row new-entry">
+    [% FOREACH item = row %][% IF item.1 %]
     <div class="col-md-4">
       <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/actor/address_alert">
-        [% l('Address Alerts') %]
-      </a>
-    </div>
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/asset/copy_locations">
-        [% l('Copy Locations Editor') %]
-      </a>
-    </div>
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/circ/neg_balance_users">
-        [% l('Patrons with Negative Balances') %]
-      </a>
-    </div>
-  </div>
-  <div class="row new-entry">
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/circ/age_to_lost">
-        [% l('Age Overdue Circs to Lost') %]
-      </a>
-    </div>
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/asset/copy_template">
-        [% l('Copy Template Editor') %]
-      </a>
-    </div>
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/actor/search_filter_group">
-        [% l('Search Filter Groups') %]
-      </a>
-    </div>
-  </div>
-  <div class="row new-entry">
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/config/barcode_completion">
-        [% l('Barcode Completion') %]
-      </a>
-    </div>
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/config/auto_print">
-        [% l('Auto-Print Settings') %]
-      </a>
-    </div>
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/config/standing_penalty">
-        [% l('Standing Penalties') %]
-      </a>
-    </div>
-  </div>
-  <div class="row new-entry">
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/money/cash_reports">
-        [% l('Cash Reports') %]
-      </a>
-    </div>
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/config/idl_field_doc">
-        [% l('Field Documentation') %]
-      </a>
-    </div>
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/asset/stat_cat_editor">
-        [% l('Statistical Categories Editor') %]
-      </a>
-    </div>
-  </div>
-  <div class="row new-entry">
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/config/circ_limit_set">
-        [% l('Circ Limit Sets') %]
-      </a>
-    </div>
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/permission/grp_penalty_threshold">
-        [% l('Group Penalty Thresholds') %]
-      </a>
-    </div>
-  </div>
-  <div class="row new-entry">
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/config/circ_matrix_matchpoint">
-        [% l('Circulation Policies') %]
-      </a>
-    </div>
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/config/hold_matrix_matchpoint">
-        [% l('Hold Policies') %]
-      </a>
-    </div>
-  </div>
-  <div class="row new-entry">
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/actor/closed_dates">
-        [% l('Closed Dates Editor') %]
-      </a>
-    </div>
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/asset/org_unit_settings">
-        [% l('Library Settings Editor') %]
-      </a>
-    </div>
-  </div>
-  <div class="row new-entry">
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/asset/copy_location_group">
-        [% l('Copy Location Groups') %]
-      </a>
-    </div>
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/config/non_cat_types">
-        [% l('Non-Cataloged Types Editor') %]
-      </a>
-    </div>
-  </div>
-  <div class="row new-entry">
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/asset/copy_location_order">
-        [% l('Copy Location Order') %]
-      </a>
-    </div>
-    <div class="col-md-4">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/action_trigger/event_definition">
-        [% l('Notifications / Action Triggers') %]
+      <a target="_self" href="[% item.1 %]">
+        [% item.0 %]
       </a>
     </div>
+    [% END %][% END %]
   </div>
+[% END %]
 
 </div>
 

commit 43b89b3e46173fb559d7d69f307819c807329013
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 29 23:24:35 2015 +0000

    webstaff: tweak how element IDs in MARC editor are generated
    
    For new records (or records that for whatever reason lack
    a 901 field), use '0' as a dummy value for the record
    ID when constructing element IDs (e.g., r0f5, r0f10sf0value,
    etc.).  This fixes a problem where setCaret() couldn't
    successfully change focus after adding a field or subfield
    to a new record.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 a81f7d8..0a42054 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
@@ -331,7 +331,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         restrict: 'E',
         template: '<span>'+
                     '<span><label class="marcedit marcsfcodedelimiter"'+
-                        'for="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}s{{subfield[2]}}code" '+
+                        'for="r{{field.record.subfield(\'901\',\'c\')[1] || 0}}f{{field.position}}s{{subfield[2]}}code" '+
                         '>‡</label><eg-marc-edit-editable '+
                         'itype="sfc" '+
                         'select-on-focus="true" '+
@@ -343,7 +343,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                         'max="1" '+
                         'on-keydown="onKeydown" '+
                         'context-item-generator="sf_code_options" '+
-                        'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}s{{subfield[2]}}code" '+
+                        'id="r{{field.record.subfield(\'901\',\'c\')[1] || 0}}f{{field.position}}s{{subfield[2]}}code" '+
                     '/></span>'+
                     '<span><eg-marc-edit-editable '+
                         'itype="sfv" '+
@@ -353,7 +353,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                         'content="subfield[1]" '+
                         'on-keydown="onKeydown" '+
                         'context-item-generator="sf_val_options" '+
-                        'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}s{{subfield[2]}}value" '+
+                        'id="r{{field.record.subfield(\'901\',\'c\')[1] || 0}}f{{field.position}}s{{subfield[2]}}value" '+
                     '/></span>'+
                   '</span>',
         scope: { field: "=", subfield: "=", onKeydown: '=' },
@@ -386,7 +386,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                       'max="1" '+
                       'on-keydown="onKeydown" '+
                       'context-item-generator="ind_val_options" '+
-                      'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}i{{indNumber}}"'+
+                      'id="r{{field.record.subfield(\'901\',\'c\')[1] || 0}}f{{field.position}}i{{indNumber}}"'+
                       '/></span>',
         scope: { ind : '=', field: '=', onKeydown: '=', indNumber: '@' },
         replace: true,
@@ -415,7 +415,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                       'max="3" '+
                       'on-keydown="onKeydown" '+
                       'context-item-generator="tag_options" '+
-                      'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}tag"'+
+                      'id="r{{field.record.subfield(\'901\',\'c\')[1] || 0}}f{{field.position}}tag"'+
                       '/></span>',
         scope: { tag : '=', field: '=', onKeydown: '=', contextFunctions: '=' },
         replace: true,
@@ -530,7 +530,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                       'class="marcedit marcdata" '+
                       'content="field.data" '+
                       'on-keydown="onKeydown" '+
-                      'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}data"'+
+                      'id="r{{field.record.subfield(\'901\',\'c\')[1] || 0}}f{{field.position}}data"'+
                       '/></span>'+
                       // TODO: move to TT2 template
                       '<button class="btn btn-info btn-xs" '+
@@ -594,7 +594,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                       'itype="ldr" '+
                       'max="{{record.leader.length}}" '+
                       'content="record.leader" '+
-                      'id="r{{record.subfield(\'901\',\'c\')[1]}}leaderdata" '+
+                      'id="r{{record.subfield(\'901\',\'c\')[1] || 0}}leaderdata" '+
                       'on-keydown="onKeydown"'+
                       '/></span>'+
                   '</div>',

commit c76310d9512b64dab0a9717839804787f7235661
Author: Bill Erickson <berickxx at gmail.com>
Date:   Sun Oct 4 22:05:58 2015 -0400

    webstaff: users with negative balance
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/local/circ/neg_balance_users.tt2 b/Open-ILS/src/templates/staff/admin/local/circ/neg_balance_users.tt2
new file mode 100644
index 0000000..73d7ec8
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/local/circ/neg_balance_users.tt2
@@ -0,0 +1,55 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Users with Negative Balances"); 
+  ctx.page_app = "egAdminCirc";
+  ctx.page_ctrl = 'NegBalances';
+%]
+
+[% 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/admin/local/circ/neg_balance_users.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
+[% END %]
+
+<div class="container-fluid" style="text-align:center">
+  <div class="alert alert-info alert-less-pad strong-text-2">
+    [% l('Patrons with Negative Balances') %]
+  </div>
+</div>
+
+<div class="row">
+  <div class="col-md-4">
+    <div class="form-group">
+      <label>[% l('Patron Library') %]</label>
+      <eg-org-selector onchange="org_changed" 
+        selected="context_org"></eg-org-selector>
+    </div>
+  </div>
+</div>
+
+<eg-grid
+  id-field="usr_id"
+  features="-sort,-multisort"
+  items-provider="grid_provider"
+  grid-controls="grid_controls"
+  persist-key="admin.local.circ.neg_balance_users">
+
+  <eg-grid-action handler="get_user"
+    label="[% l('Retrieve Patron') %]"></eg-grid-action>
+
+  <eg-grid-field label="[% l('Barred') %]" path='usr.barred'></eg-grid-field>
+  <eg-grid-field label="[% l('Date of Birth') %]" path='usr.dob'></eg-grid-field>
+  <eg-grid-field label="[% l('Last Name') %]" path='usr.family_name'></eg-grid-field>
+  <eg-grid-field label="[% l('First Name') %]" path='usr.first_given_name'></eg-grid-field>
+  <eg-grid-field label="[% l('Middle Name') %]" path='usr.second_given_name'></eg-grid-field>
+  <eg-grid-field label="[% l('Balance Owed') %]" path='balance_owed'></eg-grid-field>
+  <eg-grid-field label="[% l('Last Billing Activity') %]" 
+    path='last_billing_activity' datatype='timestamp'></eg-grid-field>
+
+  <eg-grid-field path='usr.*' parent-idl-class="au" hidden></eg-grid-field>
+</eg-grid>
+
+   
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
index 8aa67de..c55235f 100644
--- a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
@@ -22,8 +22,8 @@
     </div>
     <div class="col-md-4">
       <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/actor/search_filter_group">
-        [% l('Search Filter Groups') %]
+      <a target="_self" href="./admin/local/circ/neg_balance_users">
+        [% l('Patrons with Negative Balances') %]
       </a>
     </div>
   </div>
@@ -42,8 +42,8 @@
     </div>
     <div class="col-md-4">
       <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/config/standing_penalty">
-        [% l('Standing Penalties') %]
+      <a target="_self" href="./admin/local/actor/search_filter_group">
+        [% l('Search Filter Groups') %]
       </a>
     </div>
   </div>
@@ -62,8 +62,8 @@
     </div>
     <div class="col-md-4">
       <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/local/asset/stat_cat_editor">
-        [% l('Statistical Categories Editor') %]
+      <a target="_self" href="./admin/local/config/standing_penalty">
+        [% l('Standing Penalties') %]
       </a>
     </div>
   </div>
@@ -80,6 +80,12 @@
         [% l('Field Documentation') %]
       </a>
     </div>
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/asset/stat_cat_editor">
+        [% l('Statistical Categories Editor') %]
+      </a>
+    </div>
   </div>
   <div class="row new-entry">
     <div class="col-md-4">
diff --git a/Open-ILS/web/js/ui/default/staff/admin/local/circ/neg_balance_users.js b/Open-ILS/web/js/ui/default/staff/admin/local/circ/neg_balance_users.js
new file mode 100644
index 0000000..43fa878
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/admin/local/circ/neg_balance_users.js
@@ -0,0 +1,46 @@
+
+angular.module('egAdminCirc',
+    ['ngRoute','ui.bootstrap','egCoreMod','egUiMod','egGridMod'])
+
+.controller('NegBalances',
+       ['$scope','$q','$timeout','$location','$window','egCore','egGridDataProvider',
+function($scope , $q , $timeout , $location , $window , egCore , egGridDataProvider) {
+
+    egCore.startup.go(); // standalone mode requires manual startup
+
+    $scope.grid_provider = egGridDataProvider.instance({});
+
+    // API does not currenlty support paging, so it's all or none.
+    $scope.grid_provider.get = function(offset, count) {
+        if (!$scope.context_org) return $q.when();
+
+        var deferred = $q.defer();
+
+        egCore.net.request(
+            'open-ils.actor',
+            'open-ils.actor.users.negative_balance',
+            egCore.auth.token(), $scope.context_org.id())
+        .then(deferred.resolve, null, deferred.notify);
+
+        return deferred.promise;
+    }
+
+    $scope.org_changed = function(org) {
+        $scope.context_org = org; // hmm, why necessary.
+        $scope.grid_provider.refresh();
+    }
+
+    // NOTE: Chrome only allows one tab/window to open per user
+    // action.  Only the first patron will be displayed.
+    $scope.get_user = function(selected) {
+        if (!selected.length) return;
+        angular.forEach(selected, function(data) {
+            $timeout(function() {
+                var url = $location.absUrl().replace(
+                    /admin\/local\/.*/,
+                    'circ/patron/' + data.usr.id() + '/checkout');
+                $window.open(url, '_blank')
+            });
+        });
+    }
+}])

commit 04e636dda48d2c86b93efa4b3fcad9fcaf0ab7d3
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Oct 26 12:13:45 2015 -0400

    webstaff: Wire up statcat filtering in item attribute editor
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
index 4d1ec8e..553cb25 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
@@ -385,7 +385,7 @@
 
             <div class="row pad-vert"></div>
 
-            <div class="row" ng-repeat="sc in statcats">
+            <div class="row" ng-repeat="sc in statcats" ng-show="statcat_visible(sc.owner().id())">
                 <div class="col-xs-12">
                     <div class="row bg-info">
                         <div class="col-xs-12">
diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index b70f81f..dfe8df3 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -1295,6 +1295,15 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
             $scope.workingGridDataProvider.refresh();
         });
 
+        $scope.statcat_visible = function (sc_owner) {
+            var visible = typeof $scope.working.statcat_filter === 'undefined' || !$scope.working.statcat_filter;
+            angular.forEach(egCore.org.ancestors(sc_owner), function (anscestor_org) {
+                if ($scope.working.statcat_filter == anscestor_org.id())
+                    visible = true;
+            });
+            return visible;
+        }
+
         $scope.suffix_list = [];
         itemSvc.get_suffixes(egCore.auth.user().ws_ou()).then(function(list){
             $scope.suffix_list = list;

commit fd2d65d30cfb5d04e010fffa384ee612def47fb4
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Oct 23 16:34:16 2015 -0400

    webstaff: open title from item status in a new tab
    
    Also, iindicate that will happen with an icon by the title link
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/item/t_list.tt2 b/Open-ILS/src/templates/staff/cat/item/t_list.tt2
index cbd2e76..f58ea81 100644
--- a/Open-ILS/src/templates/staff/cat/item/t_list.tt2
+++ b/Open-ILS/src/templates/staff/cat/item/t_list.tt2
@@ -13,8 +13,8 @@
 
   <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 target="_blank" href="[% ctx.base_path %]/staff/cat/catalog/record/{{item['call_number.record.id']}}">
+      {{item['call_number.record.simple_record.title']}} <span ng-show="item['call_number.record.id']" class="glyphicon glyphicon-new-window"/>
     </a>
   </eg-grid-field>
 </eg-grid>

commit ba31a26fd198f0a343e9ce15a4585f1c7b941f26
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Oct 23 16:25:19 2015 -0400

    webstaff: Move new Vol Transfer UI menu option to "misc" section to avoid confusion
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
index 85d212d..8f3c6cc 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
@@ -39,6 +39,8 @@
       label="[% l('Request Items') %]"></eg-grid-action>
     <eg-grid-action handler="attach_to_peer_bib"
       label="[% l('Link as Conjoined to Previously Marked Bib Record') %]"></eg-grid-action>
+    <eg-grid-action handler="markLibAsVolTarget"
+      label="[% l('Choose Library for Volume Transfer Destination') %]"></eg-grid-action>
 
     <eg-grid-action handler="selectedHoldingsItemStatus" group="[% l('Show') %]"
       label="[% l('Item Status (list)') %]"></eg-grid-action>
@@ -53,10 +55,8 @@
       label="[% l('Item as Damaged') %]"></eg-grid-action>
     <eg-grid-action handler="selectedHoldingsMissing" group="[% l('Mark') %]"
       label="[% l('Item as Missing') %]"></eg-grid-action>
-    <eg-grid-action handler="markLibAsVolTarget" group="[% l('Mark') %]"
-      label="[% l('Library as Volume Transfer Destination') %]"></eg-grid-action>
     <eg-grid-action handler="markLibFromSelectedAsVolTarget" group="[% l('Mark') %]"
-      label="[% l('Library from Selection as Volume Transfer Destination') %]"></eg-grid-action>
+      label="[% l('Library as Volume Transfer Destination') %]"></eg-grid-action>
     <eg-grid-action handler="markVolAsItemTarget" group="[% l('Mark') %]" disabled="vols_not_shown"
       label="[% l('Volume as Item Transfer Destination') %]"></eg-grid-action>
 

commit a531dcf72fbb8654cb9813da76e36372d4f2531c
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Oct 22 16:03:26 2015 -0400

    webstaff: Use the record id to fetch the summary record data in item status
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/item/t_view.tt2 b/Open-ILS/src/templates/staff/cat/item/t_view.tt2
index 82920b4..1859865 100644
--- a/Open-ILS/src/templates/staff/cat/item/t_view.tt2
+++ b/Open-ILS/src/templates/staff/cat/item/t_view.tt2
@@ -1,4 +1,4 @@
-<eg-record-summary record="summaryRecord"></eg-record-summary>
+<eg-record-summary record-id="recordId" record="summaryRecord"></eg-record-summary>
 
 <!-- tabbed copy data view -->
 
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 aafea2e..b3ba37b 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
@@ -242,6 +242,7 @@ function($scope , $q , $location , $routeParams , $timeout , $window , egCore ,
     var copyId = $routeParams.id;
     $scope.tab = $routeParams.tab || 'summary';
     $scope.context.page = 'detail';
+    $scope.summaryRecord = null;
 
     $scope.edit = false;
     if ($scope.tab == 'edit') {
@@ -252,7 +253,7 @@ function($scope , $q , $location , $routeParams , $timeout , $window , egCore ,
 
     // use the cached record info
     if (itemSvc.copy)
-        $scope.summaryRecord = itemSvc.copy.call_number().record();
+        $scope.recordId = itemSvc.copy.call_number().record().id();
 
     function loadCopy(barcode) {
         $scope.context.itemNotFound = false;
@@ -289,7 +290,6 @@ function($scope , $q , $location , $routeParams , $timeout , $window , egCore ,
 
             $scope.copy = copy;
             $scope.recordId = copy.call_number().record().id();
-            $scope.summaryRecord = itemSvc.copy.call_number().record();
             $scope.args.barcode = '';
 
             // locally flesh org units

commit a384012336285a0d512f53045e702dde653db012
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Oct 22 11:42:15 2015 -0400

    webstaff: for selected grids, allow display of 10000 items
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 9f9ec65..655e785 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
@@ -1,6 +1,6 @@
 <eg-grid
   ng-hide="forbidden"
-  features="-display"
+  features="allowAll,-display"
   id-field="id"
   idl-class="acp"
   auto-fields="true"
diff --git a/Open-ILS/src/templates/staff/share/t_autogrid.tt2 b/Open-ILS/src/templates/staff/share/t_autogrid.tt2
index 087b058..3ffd9f2 100644
--- a/Open-ILS/src/templates/staff/share/t_autogrid.tt2
+++ b/Open-ILS/src/templates/staff/share/t_autogrid.tt2
@@ -102,7 +102,7 @@
     </div>
 
     <div class="btn-group" dropdown is-open="gridRowCountIsOpen" ng-show="showPagination">
-      <button type="button" title="[% ('Select Row Count') %]"
+      <button type="button" title="[% l('Select Row Count') %]"
         class="btn btn-default dropdown-toggle">
         [% l('Rows [_1]', '{{limit()}}') %]
         <span class="caret"></span>
@@ -113,11 +113,14 @@
             {{t}}
           </a>
         </li>
+        <li ng-if="allowAll" >
+          <a href ng-click='offset(0);limit(10000);collect()'>[% l('All') %]</a>
+        </li>
       </ul>
     </div>
 
     <div class="btn-group" dropdown is-open="gridPageSelectIsOpen" ng-show="showPagination">
-      <button type="button" title="[% ('Select Page') %]"
+      <button type="button" title="[% l('Select Page') %]"
         class="btn btn-default dropdown-toggle">
         [% l('Page [_1]', '{{page()}}') %]
         <span class="caret"></span>
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 4e48f93..d02841e 100644
--- a/Open-ILS/web/js/ui/default/staff/services/grid.js
+++ b/Open-ILS/web/js/ui/default/staff/services/grid.js
@@ -38,10 +38,11 @@ angular.module('egGridMod',
             // comma-separated list of supported or disabled grid features
             // supported features:
             //  startSelected : init the grid with all rows selected by default
+            //  allowAll : add an "All" option to row count (really 10000)
             //  -menu : don't show any menu buttons (or use space for them)
             //  -picker : don't show the column picker
             //  -pagination : don't show any pagination elements, and set
-            //                the limit to 1000
+            //                the limit to 10000
             //  -actions : don't show the actions dropdown
             //  -index : don't show the row index column (can't use "index"
             //           as the idField in this case)
@@ -132,6 +133,7 @@ angular.module('egGridMod',
 
                 $scope.showIndex = (features.indexOf('-index') == -1);
 
+                $scope.allowAll = (features.indexOf('allowAll') > -1);
                 $scope.startSelected = $scope.selectAll = (features.indexOf('startSelected') > -1);
                 $scope.showActions = (features.indexOf('-actions') == -1);
                 $scope.showPagination = (features.indexOf('-pagination') == -1);

commit 37317d9522a5c147a26745817a8aab73dc93637c
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Oct 21 17:53:21 2015 -0400

    webstaff: enable copy edit link from opac view
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 c87c27e..c6ab1aa 100644
--- a/Open-ILS/src/templates/opac/parts/record/copy_table.tt2
+++ b/Open-ILS/src/templates/opac/parts/record/copy_table.tt2
@@ -112,12 +112,7 @@ END; # FOREACH bib
                     [% IF ctx.has_perm('UPDATE_COPY', copy_info.circ_lib) 
                         OR ctx.has_perm('UPDATE_COPY', copy_info.call_number_owning_lib) %]
                         <span> | </span>
-                        <!-- XXX: copy edit is not yet supported in browser client.
-                          Enable this link when available
-                        -->
-                        <!--
-                        <a href="[% ctx.base_path %]/staff/cat/item/[% copy_info.id %]/edit">[% l('edit') %]</a>
-                        -->
+                        <a target="_blank" href="[% ctx.base_path %]/staff/cat/item/[% copy_info.id %]/edit">[% l('edit') %]</a>
                     [% END %]
                   [% ELSE %]
                     <a onclick="xulG.new_tab(xulG.urls.XUL_COPY_STATUS, {}, {'from_item_details_new': true, 'barcodes': ['[%- copy_info.barcode | html | replace('\'', '\\\'') -%]']})"
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 22392ee..aafea2e 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
@@ -243,6 +243,13 @@ function($scope , $q , $location , $routeParams , $timeout , $window , egCore ,
     $scope.tab = $routeParams.tab || 'summary';
     $scope.context.page = 'detail';
 
+    $scope.edit = false;
+    if ($scope.tab == 'edit') {
+        $scope.tab = 'summary';
+        $scope.edit = true;
+    }
+
+
     // use the cached record info
     if (itemSvc.copy)
         $scope.summaryRecord = itemSvc.copy.call_number().record();
@@ -563,6 +570,28 @@ function($scope , $q , $location , $routeParams , $timeout , $window , egCore ,
                 $scope.triggered_events_url = url;
                 $scope.funcs = {};
         }
+
+        if ($scope.edit) {
+            egCore.net.request(
+                'open-ils.actor',
+                'open-ils.actor.anon_cache.set_value',
+                null, 'edit-these-copies', {
+                    record_id: $scope.recordId,
+                    copies: [copyId],
+                    hide_vols : true,
+                    hide_copies : false
+                }
+            ).then(function(key) {
+                if (key) {
+                    var url = egCore.env.basePath + 'cat/volcopy/' + key;
+                    $window.location.href = url;
+                } else {
+                    alert('Could not create anonymous cache key!');
+                }
+            });
+        }
+
+        return;
     }
 
     $scope.context.toggleDisplay = function() {

commit 53dff6c013f25a840e75f3841afaa53c4f4b4219
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Wed Oct 21 17:39:08 2015 +0000

    LP#1508477: bump up minimum version of angular-hotkeys
    
    Version 1.3.0 of angular-hotkeys is now required, as this
    is the version that introduces the ability the specify
    that hotkeys can be used even when inputs, selects,
    and textareas have focus.
    
    After applying the patch (and doing a bower update,
    grunt uglify, and install), one can test hotkeys in
    the web staff client by (for example):
    
    - starting from the home page and hitting F1 to
      get to patron search by barcode
    - hitting F5 to get the item status page
    
    Note that if the page embeds the OPAC or a Dojo
    interface using an egEmbedFrame, if any element
    of the iframe's content has focus, the hotkeys won't
    work -- that will be the subject of another patch.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/bower.json b/Open-ILS/web/js/ui/default/staff/bower.json
index d31ec5c..db8e900 100644
--- a/Open-ILS/web/js/ui/default/staff/bower.json
+++ b/Open-ILS/web/js/ui/default/staff/bower.json
@@ -26,7 +26,7 @@
     "angular-bootstrap": "~0.11.0"
   },
   "dependencies": {
-    "angular-hotkeys": "chieffancypants/angular-hotkeys#~1.2.0",
+    "angular-hotkeys": "chieffancypants/angular-hotkeys#~1.3.0",
     "angular-location-update": "./extern/angular-location-update/"
   }
 }

commit 4a27bdb57e442368c12b795bfdc7b2afda1663b0
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Oct 20 20:08:44 2015 +0000

    webstaff: add another option for choosing volume transfer library
    
    The actions menu on the holdings view now has two ways of
    specifying a library to transfer volumes to:
    
     * Library as Volume Transfer Destination
    
       Pops up a modal with a library selector.
    
     * Library from Selection as Volume Transfer Destination
    
       Uses the currently selected volume's owning
       library to specify the target library.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_choose_vol_target_lib.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_choose_vol_target_lib.tt2
new file mode 100644
index 0000000..74e02c0
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_choose_vol_target_lib.tt2
@@ -0,0 +1,25 @@
+<form ng-submit="ok(org)" role="form">
+    <div class="modal-header">
+      <button type="button" class="close" ng-click="cancel()" 
+        aria-hidden="true">×</button>
+      <h4 class="modal-title">[% l('Choose volume transfer target') %]</h4>
+    </div>
+    <div class="modal-body">
+      <div class="row">
+        <div class="col-md-6">
+            <b>[% l('Target library:') %]</b>
+        </div>
+        <div class="col-md-6">
+            <eg-org-selector selected="org" disable-test="cant_have_vols"></eg-org-selector>
+        </div>
+      </div>
+    </div>
+    <div class="modal-footer">
+      <div class="row">
+        <div class="col-md-12 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/src/templates/staff/cat/catalog/t_holdings.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
index e1eaa08..85d212d 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
@@ -55,6 +55,8 @@
       label="[% l('Item as Missing') %]"></eg-grid-action>
     <eg-grid-action handler="markLibAsVolTarget" group="[% l('Mark') %]"
       label="[% l('Library as Volume Transfer Destination') %]"></eg-grid-action>
+    <eg-grid-action handler="markLibFromSelectedAsVolTarget" group="[% l('Mark') %]"
+      label="[% l('Library from Selection as Volume Transfer Destination') %]"></eg-grid-action>
     <eg-grid-action handler="markVolAsItemTarget" group="[% l('Mark') %]" disabled="vols_not_shown"
       label="[% l('Volume as Item Transfer Destination') %]"></eg-grid-action>
 
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 36e8a8b..bb3abda 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
@@ -978,6 +978,31 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
     }
 
     $scope.markLibAsVolTarget = function() {
+        return $modal.open({
+            templateUrl: './cat/catalog/t_choose_vol_target_lib',
+            animation: true,
+            controller:
+                   ['$scope','$modalInstance',
+            function($scope , $modalInstance) {
+
+                var orgId = egCore.hatch.getLocalItem('eg.cat.volume_transfer_target') || 1;
+                $scope.org = egCore.org.get(orgId);
+                $scope.cant_have_vols = function (id) { return !egCore.org.CanHaveVolumes(id); };
+                $scope.ok = function(org) {
+                    egCore.hatch.setLocalItem(
+                        'eg.cat.volume_transfer_target',
+                        org.id()
+                    );
+                    $modalInstance.close();
+                }
+                $scope.cancel = function($event) {
+                    $modalInstance.dismiss();
+                    $event.preventDefault();
+                }
+            }]
+        });
+    }
+    $scope.markLibFromSelectedAsVolTarget = function() {
         egCore.hatch.setLocalItem(
             'eg.cat.volume_transfer_target',
             $scope.holdingsGridControls.selectedItems()[0].owner_id

commit f8699ee46f3b708850063a1fda93aebf7cb97a08
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Oct 20 19:39:10 2015 +0000

    webstaff: keep $scope.{pre,suf}fix as acnp/acns objects
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 6902a18..b70f81f 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -426,10 +426,14 @@ function(egCore , $q) {
                     if (angular.isObject(currentPrefix)) currentPrefix = currentPrefix.id();
                     itemSvc.get_prefixes($scope.callNumber.owning_lib()).then(function(list){
                         $scope.prefix_list = list;
-                        $scope.prefix = $scope.prefix_list.filter(function (p) {
+                        var newPrefixId = $scope.prefix_list.filter(function (p) {
                             return p.id() == currentPrefix;
                         })[0] || -1;
-                        if ($scope.prefix != currentPrefix) {
+                        if (newPrefixId.id) newPrefixId = newPrefixId.id();
+                        $scope.prefix = $scope.prefix_list.filter(function (p) {
+                            return p.id() == newPrefixId;
+                        })[0];
+                        if ($scope.newPrefixId != currentPrefix) {
                             $scope.callNumber.prefix($scope.prefix);
                         }
                     });
@@ -437,10 +441,14 @@ function(egCore , $q) {
                     if (angular.isObject(currentSuffix)) currentSuffix = currentSuffix.id();
                     itemSvc.get_suffixes($scope.callNumber.owning_lib()).then(function(list){
                         $scope.suffix_list = list;
-                        $scope.suffix = $scope.suffix_list.filter(function (s) {
+                        var newSuffixId = $scope.suffix_list.filter(function (s) {
                             return s.id() == currentSuffix;
                         })[0] || -1;
-                        if ($scope.suffix != currentSuffix) {
+                        if (newSuffixId.id) newSuffixId = newSuffixId.id();
+                        $scope.suffix = $scope.suffix_list.filter(function (s) {
+                            return s.id() == newSuffixId;
+                        })[0];
+                        if ($scope.newSuffixId != currentSuffix) {
                             $scope.callNumber.suffix($scope.suffix);
                         }
                     });

commit 69dd244701035b23ab4c88983bd16bef341841be
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Oct 20 19:12:44 2015 +0000

    webstaff: sort prefix/suffix selectors by sortkey
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 2a3d37a..6902a18 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -106,7 +106,7 @@ function(egCore , $q) {
     service.get_prefixes = function(org) {
         return egCore.pcrud.search('acnp',
             {owning_lib : egCore.org.fullPath(org, true)},
-            null, {atomic : true}
+            {order_by : { acnp : 'label_sortkey' }}, {atomic : true}
         );
 
     };
@@ -133,7 +133,7 @@ function(egCore , $q) {
     service.get_suffixes = function(org) {
         return egCore.pcrud.search('acns',
             {owning_lib : egCore.org.fullPath(org, true)},
-            null, {atomic : true}
+            {order_by : { acns : 'label_sortkey' }}, {atomic : true}
         );
 
     };

commit 9974afdb64d0f70a65913822dba9368b5e4d7f9e
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Oct 20 19:08:33 2015 +0000

    webstaff: update volume prefix/suffix selectors
    
    Ensuring that when the owning library changes that
    the selectors for the volume prefix and suffix
    refelect the options available to that OU.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 4684246..2a3d37a 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -420,6 +420,31 @@ function(egCore , $q) {
                         cp.call_number().ischanged(1);
                     });
                 }
+                $scope.$watch('callNumber.owning_lib()', function(oldLib, newLib) {
+                    if (oldLib == newLib) return;
+                    var currentPrefix = $scope.callNumber.prefix();
+                    if (angular.isObject(currentPrefix)) currentPrefix = currentPrefix.id();
+                    itemSvc.get_prefixes($scope.callNumber.owning_lib()).then(function(list){
+                        $scope.prefix_list = list;
+                        $scope.prefix = $scope.prefix_list.filter(function (p) {
+                            return p.id() == currentPrefix;
+                        })[0] || -1;
+                        if ($scope.prefix != currentPrefix) {
+                            $scope.callNumber.prefix($scope.prefix);
+                        }
+                    });
+                    var currentSuffix = $scope.callNumber.suffix();
+                    if (angular.isObject(currentSuffix)) currentSuffix = currentSuffix.id();
+                    itemSvc.get_suffixes($scope.callNumber.owning_lib()).then(function(list){
+                        $scope.suffix_list = list;
+                        $scope.suffix = $scope.suffix_list.filter(function (s) {
+                            return s.id() == currentSuffix;
+                        })[0] || -1;
+                        if ($scope.suffix != currentSuffix) {
+                            $scope.callNumber.suffix($scope.suffix);
+                        }
+                    });
+                });
 
                 $scope.classification_list = [];
                 itemSvc.get_classifications().then(function(list){
@@ -570,9 +595,11 @@ function(egCore , $q) {
                 $scope.orig_cn_count = $scope.cn_count;
 
                 $scope.owning_lib = egCore.org.get($scope.lib);
-                $scope.$watch('owning_lib', function (l) {
-                    angular.forEach( $scope.struct[$scope.first_cn], function (cp) {
-                        cp.call_number().owning_lib( $scope.owning_lib.id() );
+                $scope.$watch('owning_lib', function (oldLib, newLib) {
+                    if (oldLib == newLib) return;
+                    angular.forEach( Object.keys($scope.struct), function (cn) {
+                        $scope.struct[cn][0].call_number().owning_lib( $scope.owning_lib.id() );
+                        $scope.struct[cn][0].call_number().ischanged(1);
                     });
                 });
 

commit 377f3c4dedb93b045bfddfd8b79880379feefa56
Author: Bill Erickson <berickxx at gmail.com>
Date:   Sun Oct 4 21:35:25 2015 -0400

    webstaff: browser client: remove pending label from closed dates
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
index cdd2c37..8aa67de 100644
--- a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
@@ -113,7 +113,7 @@
     <div class="col-md-4">
       <span class="glyphicon glyphicon-pencil"></span>
       <a target="_self" href="./admin/local/actor/closed_dates">
-        [% l('Closed Dates Editor (pending)') %]
+        [% l('Closed Dates Editor') %]
       </a>
     </div>
     <div class="col-md-4">

commit ede7e78925a07dc800bc4e1dda10b2321d176f7d
Author: Bill Erickson <berickxx at gmail.com>
Date:   Sun Oct 4 21:34:01 2015 -0400

    webstaff: browser client: Remove closed dates editor XUL-y requirements
    
    Replace JSAN date functions with manual, inline date formatting
    functions.  Remove JSAN and XUL constants requirements.
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/xul/staff_client/server/admin/closed_dates.js b/Open-ILS/xul/staff_client/server/admin/closed_dates.js
index e5ee4a8..50d74c2 100644
--- a/Open-ILS/xul/staff_client/server/admin/closed_dates.js
+++ b/Open-ILS/xul/staff_client/server/admin/closed_dates.js
@@ -24,19 +24,6 @@ var myPerms = [
 
 function cdEditorInit() {
 
-    try {
-        if (typeof JSAN == 'undefined') { throw( "The JSAN library object is missing."); }
-        JSAN.errorLevel = "die"; // none, warn, or die
-        JSAN.addRepository('..');
-        JSAN.use('util.error'); g.error = new util.error();
-        JSAN.use('util.date');
-    } catch(E) {
-        var err_msg = "!! This software has encountered an error.  Please tell your friendly " +
-            "system administrator or software developer the following:\nadmin/closed_dates.xhtml\n" + E + '\n';
-        try { g.error.sdump('D_ERROR',err_msg); } catch(E) { dump(err_msg); }
-        alert(err_msg);
-    }
-
     /* set the various template rows */
     cdTbody = $('cd_tbody');
     cdRowTemplate                    = cdTbody.removeChild($('cd_row'));
@@ -176,11 +163,18 @@ function cdBuild(r) {
 }
 
 function cdDateToHours(date) {
-    return util.date.formatted_date(date, '%H:%M');
+    var date_obj = new Date(Date.parse(date));
+    var hrs = date_obj.getHours();
+    var mins = date_obj.getMinutes();
+    // wee, strftime
+    if (hrs < 10) hrs = '0' + hrs;
+    if (mins < 10) mins = '0' + mins;
+    return hrs + ':' + mins;
 }
 
 function cdDateToDate(date) {
-    return util.date.formatted_date(date, '%F');
+    var date_obj = new Date(Date.parse(date));
+    return date_obj.toISOString().replace(/T.*/,''); // == %F
 }
 
 
@@ -396,8 +390,8 @@ function cdGetOrgList(org) {
 function cdCreateOne( org, start, end, note, refresh ) {
     var date = new aoucd();
 
-    date.close_start(util.date.formatted_date(start, '%{iso8601}'));
-    date.close_end(util.date.formatted_date(end, '%{iso8601}'));
+    date.close_start(start.toISOString());
+    date.close_end(end.toISOString());
     date.org_unit(org);
     date.reason(note);
 
diff --git a/Open-ILS/xul/staff_client/server/admin/closed_dates.xhtml b/Open-ILS/xul/staff_client/server/admin/closed_dates.xhtml
index c4e1bf0..8eb074e 100644
--- a/Open-ILS/xul/staff_client/server/admin/closed_dates.xhtml
+++ b/Open-ILS/xul/staff_client/server/admin/closed_dates.xhtml
@@ -23,8 +23,6 @@
         <script type='text/javascript' src='/opac/common/js/org_utils.js'> </script>
         <script type='text/javascript' src='/opac/common/js/init.js'> </script>
         <script type='text/javascript' src='/opac/common/js/RemoteRequest.js'> </script>
-        <script type="text/javascript" src="/xul/server/main/JSAN.js"></script>
-        <script type="text/javascript" src="/xul/server/main/constants.js" />
         <script type='text/javascript' src='adminlib.js'> </script>
         <script type='text/javascript' src='closed_dates.js'> </script>
 

commit 2844f4932032a1d6f13d62ff40afb5cacc2fae35
Author: Bill Erickson <berickxx at gmail.com>
Date:   Tue Sep 29 21:57:40 2015 -0400

    webstaff: browser client: misc. new links
    
    field documentation
    group penalty threshold
    hold policies
    library settings edito
    noncat types
    action/trigger
    search filter group
    stat cats editor
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
index 63c1d35..cdd2c37 100644
--- a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
@@ -20,6 +20,12 @@
         [% l('Copy Locations Editor') %]
       </a>
     </div>
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/actor/search_filter_group">
+        [% l('Search Filter Groups') %]
+      </a>
+    </div>
   </div>
   <div class="row new-entry">
     <div class="col-md-4">
@@ -34,6 +40,12 @@
         [% l('Copy Template Editor') %]
       </a>
     </div>
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/config/standing_penalty">
+        [% l('Standing Penalties') %]
+      </a>
+    </div>
   </div>
   <div class="row new-entry">
     <div class="col-md-4">
@@ -48,6 +60,12 @@
         [% l('Auto-Print Settings') %]
       </a>
     </div>
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/asset/stat_cat_editor">
+        [% l('Statistical Categories Editor') %]
+      </a>
+    </div>
   </div>
   <div class="row new-entry">
     <div class="col-md-4">
@@ -56,6 +74,12 @@
         [% l('Cash Reports') %]
       </a>
     </div>
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/config/idl_field_doc">
+        [% l('Field Documentation') %]
+      </a>
+    </div>
   </div>
   <div class="row new-entry">
     <div class="col-md-4">
@@ -64,6 +88,12 @@
         [% l('Circ Limit Sets') %]
       </a>
     </div>
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/permission/grp_penalty_threshold">
+        [% l('Group Penalty Thresholds') %]
+      </a>
+    </div>
   </div>
   <div class="row new-entry">
     <div class="col-md-4">
@@ -72,6 +102,12 @@
         [% l('Circulation Policies') %]
       </a>
     </div>
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/config/hold_matrix_matchpoint">
+        [% l('Hold Policies') %]
+      </a>
+    </div>
   </div>
   <div class="row new-entry">
     <div class="col-md-4">
@@ -80,6 +116,12 @@
         [% l('Closed Dates Editor (pending)') %]
       </a>
     </div>
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/asset/org_unit_settings">
+        [% l('Library Settings Editor') %]
+      </a>
+    </div>
   </div>
   <div class="row new-entry">
     <div class="col-md-4">
@@ -88,6 +130,12 @@
         [% l('Copy Location Groups') %]
       </a>
     </div>
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/config/non_cat_types">
+        [% l('Non-Cataloged Types Editor') %]
+      </a>
+    </div>
   </div>
   <div class="row new-entry">
     <div class="col-md-4">
@@ -96,6 +144,12 @@
         [% l('Copy Location Order') %]
       </a>
     </div>
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/action_trigger/event_definition">
+        [% l('Notifications / Action Triggers') %]
+      </a>
+    </div>
   </div>
 
 </div>
diff --git a/Open-ILS/web/js/ui/default/staff/admin/local/app.js b/Open-ILS/web/js/ui/default/staff/admin/local/app.js
index 8a520dd..20b9647 100644
--- a/Open-ILS/web/js/ui/default/staff/admin/local/app.js
+++ b/Open-ILS/web/js/ui/default/staff/admin/local/app.js
@@ -32,6 +32,25 @@ angular.module('egLocalAdmin',
         resolve : resolver
     });
 
+    // non-conify routes come first
+    $routeProvider.when('/admin/local/asset/org_unit_settings', {
+        template: eframe_template,
+        controller: 'EmbedXHTMLCtl', // non-conify
+        resolve : resolver
+    });
+
+    $routeProvider.when('/admin/local/config/non_cat_types', {
+        template: eframe_template,
+        controller: 'EmbedXHTMLCtl', // non-conify
+        resolve : resolver
+    });
+
+    $routeProvider.when('/admin/local/asset/stat_cat_editor', {
+        template: eframe_template,
+        controller: 'EmbedXHTMLCtl', // non-conify
+        resolve : resolver
+    });
+
     // Conify page handler
     $routeProvider.when('/admin/local/:schema/:page', {
         template: eframe_template,

commit 6edcb4c8ba45722c6c16115b586818f2500320d7
Author: Bill Erickson <berickxx at gmail.com>
Date:   Sun Aug 9 18:59:00 2015 -0400

    webstaff: browser client local admin additions
    
    * copy locations editor
    * copy locations groups
    * copy locations order
    * copy template editor
    * auto-print settings (new UI)
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/local/config/auto_print.tt2 b/Open-ILS/src/templates/staff/admin/local/config/auto_print.tt2
new file mode 100644
index 0000000..a6b4725
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/local/config/auto_print.tt2
@@ -0,0 +1,86 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Auto-Print Settings"); 
+  ctx.page_app = "egAdminConfig";
+  ctx.page_ctrl = 'AutoPrintCtl';
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/local/config/auto_print.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
+[% END %]
+
+<div class="container-fluid" style="text-align:center">
+  <div class="alert alert-info alert-less-pad strong-text-2">
+    [% l('Auto-Print Settings') %]
+  </div>
+</div>
+
+<div id="auto-print-container">
+
+  <div class="strong-text-2 row">
+    [% l('Disable Automatic Print Attempt Type List') %]
+  </div>
+
+  <div class="row">
+[% | l %]Disable automatic print attempts from staff client interfaces
+for the receipt types in this list.  Possible values: "Checkout", "Bill
+Pay", "Hold Slip", "Transit Slip", and "Hold/Transit Slip".  This is
+different from the Auto-Print checkbox in the pertinent interfaces
+in that it disables automatic print attempts altogether, rather than
+encouraging silent printing by suppressing the print dialog.  The
+Auto-Print checkbox in these interfaces have no effect on the behavior
+for this setting.  In the case of the Hold, Transit, and Hold/Transit
+slips, this also suppresses the alert dialogs that precede the print
+dialog (the ones that offer Print and Do Not Print as options).[% END %]
+  </div>
+
+  <div class="row">
+    <div class="strong-text-2">[% l('New Setting') %]</div>
+    <div>
+      [% l('Disable auto-print attempts for these receipt/slip types') %]
+    </div>
+  </div>
+
+  <div class="row"></div><!-- add some padding -->
+
+  <div>
+    <div class="form-group">
+      <label class="col-md-2">[% l('Checkout Receipt') %]</label>
+      <input type="checkbox" ng-model="co_recpt"/>
+    </div>
+    <div class="form-group">
+      <label class="col-md-2">[% l('Bill Pay Receipt') %]</label>
+      <input type="checkbox" ng-model="bill_recpt"/>
+    </div>
+    <div class="form-group">
+      <label class="col-md-2">[% l('Hold Slip') %]</label>
+      <input type="checkbox" ng-model="hold_slip"/>
+    </div>
+    <div class="form-group">
+      <label class="col-md-2">[% l('Transit Slip') %]</label>
+      <input type="checkbox" ng-model="transit_slip"/>
+    </div>
+    <div class="form-group">
+      <label class="col-md-2">[% l('Hold/Transit Slip') %]</label>
+      <input type="checkbox" ng-model="hold_transit_slip"/>
+    </div>
+    <div class="form-group">
+      <label class="col-md-2">[% l('For This Library and Descendants') %]</label>
+      <eg-org-selector selected="context_org" 
+        onchange="show_org_values" disable-test="cant_use_org"></eg-org-selector>
+    </div>
+  </div>
+  </div>
+    <div class="form-group">
+      <button class="btn btn-default" 
+        ng-class="{disabled : in_flight}"
+        ng-click="update_auto_print()">
+        [% l('Update Settings') %]
+      </button>
+    </div>
+  </div>
+
+</div>
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
index 701286b..63c1d35 100644
--- a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
@@ -8,31 +8,49 @@
 <div class="container admin-splash-container">
 
   <div class="row new-entry">
-    <div class="col-md-3">
+    <div class="col-md-4">
       <span class="glyphicon glyphicon-pencil"></span>
       <a target="_self" href="./admin/local/actor/address_alert">
         [% l('Address Alerts') %]
       </a>
     </div>
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/asset/copy_locations">
+        [% l('Copy Locations Editor') %]
+      </a>
+    </div>
   </div>
   <div class="row new-entry">
-    <div class="col-md-3">
+    <div class="col-md-4">
       <span class="glyphicon glyphicon-pencil"></span>
       <a target="_self" href="./admin/local/circ/age_to_lost">
         [% l('Age Overdue Circs to Lost') %]
       </a>
     </div>
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/asset/copy_template">
+        [% l('Copy Template Editor') %]
+      </a>
+    </div>
   </div>
   <div class="row new-entry">
-    <div class="col-md-3">
+    <div class="col-md-4">
       <span class="glyphicon glyphicon-pencil"></span>
       <a target="_self" href="./admin/local/config/barcode_completion">
         [% l('Barcode Completion') %]
       </a>
     </div>
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/config/auto_print">
+        [% l('Auto-Print Settings') %]
+      </a>
+    </div>
   </div>
   <div class="row new-entry">
-    <div class="col-md-3">
+    <div class="col-md-4">
       <span class="glyphicon glyphicon-pencil"></span>
       <a target="_self" href="./admin/local/money/cash_reports">
         [% l('Cash Reports') %]
@@ -40,7 +58,7 @@
     </div>
   </div>
   <div class="row new-entry">
-    <div class="col-md-3">
+    <div class="col-md-4">
       <span class="glyphicon glyphicon-pencil"></span>
       <a target="_self" href="./admin/local/config/circ_limit_set">
         [% l('Circ Limit Sets') %]
@@ -48,7 +66,7 @@
     </div>
   </div>
   <div class="row new-entry">
-    <div class="col-md-3">
+    <div class="col-md-4">
       <span class="glyphicon glyphicon-pencil"></span>
       <a target="_self" href="./admin/local/config/circ_matrix_matchpoint">
         [% l('Circulation Policies') %]
@@ -56,10 +74,26 @@
     </div>
   </div>
   <div class="row new-entry">
-    <div class="col-md-3">
+    <div class="col-md-4">
       <span class="glyphicon glyphicon-pencil"></span>
       <a target="_self" href="./admin/local/actor/closed_dates">
-        [% l('Closed Dates Editor') %]
+        [% l('Closed Dates Editor (pending)') %]
+      </a>
+    </div>
+  </div>
+  <div class="row new-entry">
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/asset/copy_location_group">
+        [% l('Copy Location Groups') %]
+      </a>
+    </div>
+  </div>
+  <div class="row new-entry">
+    <div class="col-md-4">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/asset/copy_location_order">
+        [% l('Copy Location Order') %]
       </a>
     </div>
   </div>
diff --git a/Open-ILS/src/templates/staff/css/admin.css.tt2 b/Open-ILS/src/templates/staff/css/admin.css.tt2
index 1c96365..0de1df9 100644
--- a/Open-ILS/src/templates/staff/css/admin.css.tt2
+++ b/Open-ILS/src/templates/staff/css/admin.css.tt2
@@ -21,3 +21,10 @@
   border-top: 2px solid #F5F5F5;
 }
 
+#auto-print-container {
+  margin-top: 20px;
+}
+
+#auto-print-container .row {
+  margin-top: 20px;
+}
diff --git a/Open-ILS/web/js/ui/default/staff/admin/local/app.js b/Open-ILS/web/js/ui/default/staff/admin/local/app.js
index b3716f7..8a520dd 100644
--- a/Open-ILS/web/js/ui/default/staff/admin/local/app.js
+++ b/Open-ILS/web/js/ui/default/staff/admin/local/app.js
@@ -24,6 +24,13 @@ angular.module('egLocalAdmin',
         controller: 'EmbedXHTMLCtl', // non-conify
         resolve : resolver
     });
+    
+    // non-conify routes come first
+    $routeProvider.when('/admin/local/asset/copy_locations', {
+        template: eframe_template,
+        controller: 'EmbedXHTMLCtl', // non-conify
+        resolve : resolver
+    });
 
     // Conify page handler
     $routeProvider.when('/admin/local/:schema/:page', {
diff --git a/Open-ILS/web/js/ui/default/staff/admin/local/config/auto_print.js b/Open-ILS/web/js/ui/default/staff/admin/local/config/auto_print.js
new file mode 100644
index 0000000..4097efb
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/admin/local/config/auto_print.js
@@ -0,0 +1,75 @@
+
+angular.module('egAdminConfig',
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod','egUiMod'])
+
+.controller('AutoPrintCtl',
+       ['$scope','egCore',
+function($scope , egCore) {
+
+    $scope.allowed_orgs = [];
+    $scope.cant_use_org = function(org_id) {
+        return $scope.allowed_orgs.indexOf(org_id) == -1;
+    }
+
+    // The org setting stores the values as English words.
+    // Map those to scope-storable bools
+    var values_map = {
+        co_recpt : 'Checkout',
+        bill_recpt : 'Bill Pay',
+        hold_slip : 'Hold Slip',
+        transit_slip : 'Transit Slip',
+        hold_transit_slip : 'Hold/Transit Slip'
+    }
+
+    // fetch and display values for the currently selected org unit
+    $scope.show_org_values = function(org) {
+        egCore.org.settings(
+            ['circ.staff_client.do_not_auto_attempt_print'], org.id()
+        ).then(function(values) { 
+            list = values['circ.staff_client.do_not_auto_attempt_print'] || [];
+            angular.forEach(values_map, function(val, key) {
+                if (list.indexOf(val) > -1) {
+                    $scope[key] = true;
+                } else {
+                    $scope[key] = false;
+                }
+            });
+        });
+    }
+
+    function fetch_data() {
+        // TODO: The XUL app tested the ADMIN_ORG_UNIT_SETTING_TYPE perm
+        // to see wher the user could change the print settings.  There
+        // should be a separate, less powerful permission that allows 
+        // users to change this value.
+        egCore.perm.hasPermAt(['ADMIN_ORG_UNIT_SETTING_TYPE'], true)
+        .then(function(settings) { 
+            $scope.allowed_orgs = settings.ADMIN_ORG_UNIT_SETTING_TYPE;
+        });
+        $scope.show_org_values(egCore.org.get(egCore.auth.user().ws_ou()));
+    }
+
+    $scope.update_auto_print = function() {
+        $scope.in_flight = true;
+        var values = [];
+        angular.forEach(values_map, function(val, key) {
+            if ($scope[key]) { values.push(val) }
+        });
+
+        console.log('updating for ' + $scope.context_org.id());
+        egCore.net.request(
+            'open-ils.actor',
+            'open-ils.actor.org_unit.settings.update',
+            egCore.auth.token(),
+            $scope.context_org.id(),
+            {"circ.staff_client.do_not_auto_attempt_print": values}
+        ).then(function() {
+            $scope.in_flight = false; // re-enable the submit button
+        });
+    }
+
+    // This is a standalone with page w/ no startup resolver.
+    // Kick off startup locally.
+    egCore.startup.go().then(fetch_data);
+
+}])

commit fabdc5aba3f13489bcd8192dfd7195de516702dc
Author: Bill Erickson <berickxx at gmail.com>
Date:   Wed Jul 29 09:33:26 2015 -0400

    webstaff: browser: closed dates / research missing dump() func
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
index 9505bc3..701286b 100644
--- a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
+++ b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
@@ -55,6 +55,14 @@
       </a>
     </div>
   </div>
+  <div class="row new-entry">
+    <div class="col-md-3">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/actor/closed_dates">
+        [% l('Closed Dates Editor') %]
+      </a>
+    </div>
+  </div>
 
 </div>
 
diff --git a/Open-ILS/web/js/ui/default/staff/admin/local/app.js b/Open-ILS/web/js/ui/default/staff/admin/local/app.js
index e2ecfa8..b3716f7 100644
--- a/Open-ILS/web/js/ui/default/staff/admin/local/app.js
+++ b/Open-ILS/web/js/ui/default/staff/admin/local/app.js
@@ -14,7 +14,14 @@ angular.module('egLocalAdmin',
     // non-conify routes come first
     $routeProvider.when('/admin/local/money/cash_reports', {
         template: eframe_template,
-        controller: 'CashReportsCtl', // non-conify
+        controller: 'EmbedXHTMLCtl', // non-conify
+        resolve : resolver
+    });
+   
+    // non-conify routes come first
+    $routeProvider.when('/admin/local/actor/closed_dates', {
+        template: eframe_template,
+        controller: 'EmbedXHTMLCtl', // non-conify
         resolve : resolver
     });
 
@@ -52,15 +59,21 @@ function($scope , $routeParams , $location , egCore) {
 
 }])
 
-.controller('CashReportsCtl', 
-       ['$scope','$location','egCore',
-function($scope , $location , egCore) {
-    $scope.local_admin_url = $location.absUrl().replace(
-        /\/.*/, '/xul/server/admin/cash_reports.xhtml');
+.controller('EmbedXHTMLCtl', 
+       ['$scope','$location','egCore','$timeout',
+function($scope , $location , egCore , $timeout) {
 
-    // old-school XUL admin UI's only want CGI ses values.
-    $scope.local_admin_url += '?ses=' + egCore.auth.token();
+    $scope.funcs = {};
+
+    var xul_base = '/xul/server/admin/';
+    var page_parts = $location.path().split(/\//);
+    var url = xul_base + page_parts[page_parts.length - 1] + '.xhtml';
 
+    // old-school XUL admin UI's only want CGI ses values.
+    url += '?ses=' + egCore.auth.token();
+    
     console.log('Loading local admin URL: ' + $scope.local_admin_url);
+
+    $scope.local_admin_url = $location.absUrl().replace(/\/.*/, url);
 }])
 

commit e41818e2bfe41f27b84738f1301c82e3b57ca50f
Author: Bill Erickson <berickxx at gmail.com>
Date:   Sat Jul 25 14:11:00 2015 -0400

    webstaff: browser client local admin re-org 2
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/local/circ/age_to_lost.tt2 b/Open-ILS/src/templates/staff/admin/local/circ/age_to_lost.tt2
new file mode 100644
index 0000000..66bc9b4
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/local/circ/age_to_lost.tt2
@@ -0,0 +1,91 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Age Circs to Lost"); 
+  ctx.page_app = "egAdminCirc";
+  ctx.page_ctrl = 'AgeToLostCtl';
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/local/circ/age_to_lost.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
+[% END %]
+
+<div class="container-fluid" style="text-align:center">
+  <div class="alert alert-info alert-less-pad strong-text-2">
+    [% l('Age Circulations to Lost') %]
+  </div>
+</div>
+
+<span>
+[% | l %]
+Choose the user profile and circulation library for the overdue circulations
+you wish to age to a Lost status.  Note the descendants of these values 
+(sub-groups, sub-libraries) will also be affected.
+[% END %]
+</span>
+
+<div id='age-to-lost-container'>
+  <div xclass="form-inline">
+    <div class="form-group">
+      <label class="col-md-2">[% l('User Profile') %]</label>
+      <div class="btn-group" dropdown>
+        <button type="button" class="btn btn-default dropdown-toggle">
+          <span style="padding-right: 5px;">{{selected_profile.name()}}</span>
+          <span class="caret"></span>
+        </button>
+        <ul class="dropdown-menu">
+          <li ng-repeat="grp in profiles">
+            <a href
+              style="padding-left: {{pgt_depth(grp) * 10 + 5}}px"
+              ng-click="set_profile(grp)">{{grp.name()}}</a>
+          </li>
+        </ul>
+      </div>
+    </div>
+    <div class="form-group">
+      <label class="col-md-2">[% l('Circulation Library') %]</label>
+      <eg-org-selector selected="context_org"></eg-org-selector>
+    </div>
+    <div class="form-group">
+      <label class="col-md-2">[% l('Are you sure?') %]</label>
+      <input type="checkbox" ng-model="i_am_sure"/>
+    </div>
+  </div>
+    <div class="form-group">
+      <button class="btn btn-default" 
+        ng-class="{disabled : !i_am_sure}" ng-click="age_to_lost()">
+        [% l('Queue for Aging') %]</button>
+    </div>
+  </div>
+
+  <div class="row" ng-show="in_progress">
+    <div class="col-md-3"></div>
+    <div class="progress progress-striped active col-md-6">
+      <div class="progress-bar" role="progressbar" aria-valuenow="100" 
+        aria-valuemin="0" aria-valuemax="100" style="width: 100%">
+        <span class="sr-only">[% l('Processing...') %]</span>
+      </div>
+    </div>
+  </div>
+
+  <div class="row" ng-show="in_progress || all_done">
+    <div class="col-md-3"></div>
+    <div class="col-md-3">
+      [% l('Chunks Processed: [_1]', '{{chunks_processed}}') %]</div>
+    <div class="col-md-3">
+      [% l('Events Created: [_1]', '{{events_created}}') %]</div>
+    </div>
+  </div>
+
+  <div class="row" ng-show="all_done">
+    <div class="col-md-3"></div>
+    <div class="col-md-6">
+     <div class="alert alert-success" role="alert">
+      [% l('Processing Complete') %]</div>
+    </div>
+  </div>
+
+</div>
+   
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/admin/local/index.tt2 b/Open-ILS/src/templates/staff/admin/local/index.tt2
new file mode 100644
index 0000000..02d604f
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/local/index.tt2
@@ -0,0 +1,16 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Local Administration"); 
+  ctx.page_app = "egLocalAdmin";
+  #ctx.page_ctrl = "LocalAdminCtl";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/local/app.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
+[% END %]
+
+<div ng-view></div>
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/admin/local/t_splash.tt2 b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
new file mode 100644
index 0000000..9505bc3
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/local/t_splash.tt2
@@ -0,0 +1,60 @@
+
+<div class="container-fluid" style="text-align:center">
+  <div class="alert alert-info alert-less-pad strong-text-2">
+    <span>[% l('Local Administration') %]</span>
+  </div>
+</div>
+
+<div class="container admin-splash-container">
+
+  <div class="row new-entry">
+    <div class="col-md-3">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/actor/address_alert">
+        [% l('Address Alerts') %]
+      </a>
+    </div>
+  </div>
+  <div class="row new-entry">
+    <div class="col-md-3">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/circ/age_to_lost">
+        [% l('Age Overdue Circs to Lost') %]
+      </a>
+    </div>
+  </div>
+  <div class="row new-entry">
+    <div class="col-md-3">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/config/barcode_completion">
+        [% l('Barcode Completion') %]
+      </a>
+    </div>
+  </div>
+  <div class="row new-entry">
+    <div class="col-md-3">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/money/cash_reports">
+        [% l('Cash Reports') %]
+      </a>
+    </div>
+  </div>
+  <div class="row new-entry">
+    <div class="col-md-3">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/config/circ_limit_set">
+        [% l('Circ Limit Sets') %]
+      </a>
+    </div>
+  </div>
+  <div class="row new-entry">
+    <div class="col-md-3">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/local/config/circ_matrix_matchpoint">
+        [% l('Circulation Policies') %]
+      </a>
+    </div>
+  </div>
+
+</div>
+
diff --git a/Open-ILS/web/js/ui/default/staff/admin/local/app.js b/Open-ILS/web/js/ui/default/staff/admin/local/app.js
new file mode 100644
index 0000000..e2ecfa8
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/admin/local/app.js
@@ -0,0 +1,66 @@
+angular.module('egLocalAdmin',
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod','egUiMod'])
+
+.config(['$routeProvider','$locationProvider','$compileProvider', 
+ function($routeProvider , $locationProvider , $compileProvider) {
+
+    $locationProvider.html5Mode(true);
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); 
+    var resolver = {delay : function(egStartup) {return egStartup.go()}};
+
+    var eframe_template = 
+        '<eg-embed-frame url="local_admin_url" handlers="funcs"></eg-embed-frame>';
+
+    // non-conify routes come first
+    $routeProvider.when('/admin/local/money/cash_reports', {
+        template: eframe_template,
+        controller: 'CashReportsCtl', // non-conify
+        resolve : resolver
+    });
+
+    // Conify page handler
+    $routeProvider.when('/admin/local/:schema/:page', {
+        template: eframe_template,
+        controller: 'EmbedConifyCtl',
+        resolve : resolver
+    });
+
+    // default page 
+    $routeProvider.otherwise({
+        templateUrl : './admin/local/t_splash',
+        resolve : resolver
+    });
+}])
+
+.controller('EmbedConifyCtl', 
+       ['$scope','$routeParams','$location','egCore',
+function($scope , $routeParams , $location , egCore) {
+
+    $scope.funcs = {
+        ses : egCore.auth.token(),
+    }
+
+    var conify_path = '/eg/conify/global/' + 
+        $routeParams.schema + '/' + $routeParams.page;
+
+    // embed URL must include protocol/domain or it will be loaded via
+    // push-state, resulting in an infinitely nested pages.
+    $scope.local_admin_url = 
+        $location.absUrl().replace(/\/eg\/staff.*/, conify_path);
+
+    console.log('Loading local admin URL: ' + $scope.local_admin_url);
+
+}])
+
+.controller('CashReportsCtl', 
+       ['$scope','$location','egCore',
+function($scope , $location , egCore) {
+    $scope.local_admin_url = $location.absUrl().replace(
+        /\/.*/, '/xul/server/admin/cash_reports.xhtml');
+
+    // old-school XUL admin UI's only want CGI ses values.
+    $scope.local_admin_url += '?ses=' + egCore.auth.token();
+
+    console.log('Loading local admin URL: ' + $scope.local_admin_url);
+}])
+
diff --git a/Open-ILS/web/js/ui/default/staff/admin/local/circ/age_to_lost.js b/Open-ILS/web/js/ui/default/staff/admin/local/circ/age_to_lost.js
new file mode 100644
index 0000000..5d68c76
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/admin/local/circ/age_to_lost.js
@@ -0,0 +1,67 @@
+
+angular.module('egAdminCirc',
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod','egUiMod'])
+
+.controller('AgeToLostCtl',
+       ['$scope','egCore',
+function($scope , egCore) {
+    $scope.i_am_sure = false;
+    $scope.chunks_processed = 0;
+    $scope.events_created = 0;
+
+    function fetch_data() {
+
+        // fetch groups for the profile selector
+        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;
+                $scope.selected_profile = tree;
+            }
+        );
+
+        // determine the tree depth of the profile group
+        $scope.pgt_depth = function(grp) {
+            var d = 0;
+            while (grp = egCore.env.pgt.map[grp.parent()]) d++;
+            return d;
+        }
+    }
+
+    // This is a standalone with page w/ no startup resolve.
+    // Run (well, attach to) startup locally then kick off the needed
+    // network calls.
+    egCore.startup.go().then(fetch_data);
+
+    $scope.set_profile = function(g) {$scope.selected_profile = g}
+
+    $scope.age_to_lost = function() {
+        $scope.in_progress = true;
+        $scope.i_am_sure = false; // reset
+        $scope.all_done = false;
+
+        egCore.net.request(
+            'open-ils.circ',
+            'open-ils.circ.circulation.age_to_lost',
+            egCore.auth.token(), {
+                user_profile : $scope.selected_profile.id(),
+                circ_lib : $scope.context_org.id()
+            }
+        ).then(
+            function() {
+                $scope.in_progress = false;
+                $scope.all_done = true;
+            },
+            null, // on-error
+            function(response) {
+                if (!response) return;
+                if (response.progress)
+                    $scope.chunks_processed = response.progress;
+                if (response.created)
+                    $scope.events_created = response.created;
+            }
+        );
+    }
+}])

commit ff8c9540828fbf847ef22dee9ff0c147d2f0187e
Author: Bill Erickson <berickxx at gmail.com>
Date:   Sat Jul 25 14:10:37 2015 -0400

    webstaff: browser client local admin re-org; adding new links
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/actor/index.tt2 b/Open-ILS/src/templates/staff/admin/actor/index.tt2
deleted file mode 100644
index 5a3ba07..0000000
--- a/Open-ILS/src/templates/staff/admin/actor/index.tt2
+++ /dev/null
@@ -1,14 +0,0 @@
-[%
-  WRAPPER "staff/base.tt2";
-  ctx.page_title = l("Evergreen Configuration"); 
-  ctx.page_app = "egAdminActor";
-%]
-
-[% BLOCK APP_JS %]
-<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
-<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/actor/app.js"></script>
-[% END %]
-
-<div ng-view></div>
-
-[% END %]
diff --git a/Open-ILS/src/templates/staff/admin/actor/t_address_alert.tt2 b/Open-ILS/src/templates/staff/admin/actor/t_address_alert.tt2
deleted file mode 100644
index c84c9e2..0000000
--- a/Open-ILS/src/templates/staff/admin/actor/t_address_alert.tt2
+++ /dev/null
@@ -1 +0,0 @@
-<eg-embed-frame url="address_alert_url" handlers="funcs"></eg-embed-frame>
diff --git a/Open-ILS/src/templates/staff/admin/circ/index.tt2 b/Open-ILS/src/templates/staff/admin/circ/index.tt2
deleted file mode 100644
index 50c52b8..0000000
--- a/Open-ILS/src/templates/staff/admin/circ/index.tt2
+++ /dev/null
@@ -1,15 +0,0 @@
-[%
-  WRAPPER "staff/base.tt2";
-  ctx.page_title = l("Evergreen Configuration"); 
-  ctx.page_app = "egAdminCirc";
-%]
-
-[% BLOCK APP_JS %]
-<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
-<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/circ/app.js"></script>
-<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
-[% END %]
-
-<div ng-view></div>
-
-[% END %]
diff --git a/Open-ILS/src/templates/staff/admin/circ/t_age_to_lost.tt2 b/Open-ILS/src/templates/staff/admin/circ/t_age_to_lost.tt2
deleted file mode 100644
index c1e28dd..0000000
--- a/Open-ILS/src/templates/staff/admin/circ/t_age_to_lost.tt2
+++ /dev/null
@@ -1,80 +0,0 @@
-<div class="container-fluid" style="text-align:center">
-  <div class="alert alert-info alert-less-pad strong-text-2">
-    [% l('Age Circulations to Lost') %]
-  </div>
-</div>
-
-<span>
-[% | l %]
-Choose the user profile and circulation library for the overdue circulations
-you wish to age to a Lost status.  Note the descendants of these values 
-(sub-groups, sub-libraries) will also be affected.
-[% END %]
-</span>
-
-<div id='age-to-lost-container'>
-  <div xclass="form-inline">
-    <div class="form-group">
-      <label class="col-md-2">[% l('User Profile') %]</label>
-      <div class="btn-group" dropdown>
-        <button type="button" class="btn btn-default dropdown-toggle">
-          <span style="padding-right: 5px;">{{selected_profile.name()}}</span>
-          <span class="caret"></span>
-        </button>
-        <ul class="dropdown-menu">
-          <li ng-repeat="grp in profiles">
-            <a href
-              style="padding-left: {{pgt_depth(grp) * 10 + 5}}px"
-              ng-click="set_profile(grp)">{{grp.name()}}</a>
-          </li>
-        </ul>
-      </div>
-    </div>
-    <div class="form-group">
-      <label class="col-md-2">[% l('Circulation Library') %]</label>
-      <eg-org-selector selected="context_org"></eg-org-selector>
-    </div>
-    <div class="form-group">
-      <label class="col-md-2">[% l('Are you sure?') %]</label>
-      <input type="checkbox" ng-model="i_am_sure"/>
-    </div>
-  </div>
-    <div class="form-group">
-      <button class="btn btn-default" 
-        ng-class="{disabled : !i_am_sure}" ng-click="age_to_lost()">
-        [% l('Queue for Aging') %]</button>
-    </div>
-  </div>
-
-  <div class="row" ng-show="in_progress">
-    <div class="col-md-3"></div>
-    <div class="progress progress-striped active col-md-6">
-      <div class="progress-bar" role="progressbar" aria-valuenow="100" 
-        aria-valuemin="0" aria-valuemax="100" style="width: 100%">
-        <span class="sr-only">[% l('Processing...') %]</span>
-      </div>
-    </div>
-  </div>
-
-  <div class="row" ng-show="in_progress || all_done">
-    <div class="col-md-3"></div>
-    <div class="col-md-3">
-      [% l('Chunks Processed: [_1]', '{{chunks_processed}}') %]</div>
-    <div class="col-md-3">
-      [% l('Events Created: [_1]', '{{events_created}}') %]</div>
-    </div>
-  </div>
-
-  <div class="row" ng-show="all_done">
-    <div class="col-md-3"></div>
-    <div class="col-md-6">
-     <div class="alert alert-success" role="alert">
-      [% l('Processing Complete') %]</div>
-    </div>
-  </div>
-
-</div>
-   
-
-
-
diff --git a/Open-ILS/src/templates/staff/admin/local_admin.tt2 b/Open-ILS/src/templates/staff/admin/local_admin.tt2
deleted file mode 100644
index ed0590e..0000000
--- a/Open-ILS/src/templates/staff/admin/local_admin.tt2
+++ /dev/null
@@ -1,43 +0,0 @@
-[%
-  WRAPPER "staff/base.tt2";
-  ctx.page_title = l("Local Administration"); 
-  ctx.page_app = "egLocalAdmin";
-%]
-
-[% BLOCK APP_JS %]
-<script>
-  /* just enough JS for the the navbar to display */
-  angular.module('egLocalAdmin', ['ngRoute', 'ui.bootstrap', 'egCoreMod']);
-</script>
-<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
-[% END %]
-
-<div class="container-fluid" style="text-align:center">
-  <div class="alert alert-info alert-less-pad strong-text-2">
-    <span>[% l('Local Administration') %]</span>
-  </div>
-</div>
-
-<div class="container admin-splash-container">
-
-  <div class="row new-entry">
-    <div class="col-md-3">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/actor/address_alert">
-        [% l('Address Alerts') %]
-      </a>
-    </div>
-  </div>
-  <div class="row new-entry">
-    <div class="col-md-3">
-      <span class="glyphicon glyphicon-pencil"></span>
-      <a target="_self" href="./admin/circ/age_to_lost">
-        [% l('Age Overdue Circs to Lost') %]
-      </a>
-    </div>
-  </div>
-
-</div>
-
-
-[% END %]
diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2
index afe2c9c..28d8fd9 100644
--- a/Open-ILS/src/templates/staff/navbar.tt2
+++ b/Open-ILS/src/templates/staff/navbar.tt2
@@ -282,7 +282,7 @@
             </a>
           </li>
           <li>
-            <a href="./admin/local_admin" target="_self">
+            <a href="./admin/local/index" target="_self">
               <span class="glyphicon glyphicon-picture"></span>
               [% l('Local Administration') %]
             </a>
diff --git a/Open-ILS/web/js/ui/default/staff/admin/actor/app.js b/Open-ILS/web/js/ui/default/staff/admin/actor/app.js
index 94ac8e5..8fc04d1 100644
--- a/Open-ILS/web/js/ui/default/staff/admin/actor/app.js
+++ b/Open-ILS/web/js/ui/default/staff/admin/actor/app.js
@@ -34,8 +34,6 @@ angular.module('egAdminActor',
        ['$scope','$routeParams','$window','$location','egCore',
 function($scope , $routeParams , $window , $location , egCore) {
 
-    console.log('addr alert');
-    
     $scope.funcs = {
         ses : egCore.auth.token(),
     }
diff --git a/Open-ILS/web/js/ui/default/staff/admin/circ/app.js b/Open-ILS/web/js/ui/default/staff/admin/circ/app.js
deleted file mode 100644
index 24d3aad..0000000
--- a/Open-ILS/web/js/ui/default/staff/admin/circ/app.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * App to drive the base page. 
- * Login Form
- * Splash Page
- */
-
-angular.module('egAdminCirc',
-    ['ngRoute', 'ui.bootstrap', 'egCoreMod','egUiMod'])
-
-.config(['$routeProvider','$locationProvider','$compileProvider', 
- function($routeProvider , $locationProvider , $compileProvider) {
-
-    $locationProvider.html5Mode(true);
-    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); 
-    var resolver = {delay : function(egStartup) {return egStartup.go()}};
-
-    $routeProvider.when('/admin/circ/age_to_lost', {
-        templateUrl: './admin/circ/t_age_to_lost',
-        controller: 'AgeToLostCtl',
-        resolve : resolver
-    });
-
-    // default page 
-    /*
-    $routeProvider.otherwise({
-        templateUrl : 'user-perms-template',
-        controller: 'UserPermsCtrl',
-        resolve : resolver
-    });
-    */
-}])
-
-.controller('AgeToLostCtl',
-       ['$scope','egCore',
-function($scope , egCore) {
-    $scope.i_am_sure = false;
-    $scope.chunks_processed = 0;
-    $scope.events_created = 0;
-
-    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;
-            $scope.selected_profile = tree;
-        }
-    );
-
-    $scope.set_profile = function(g) {$scope.selected_profile = g}
-
-    // determine the tree depth of the profile group
-    $scope.pgt_depth = function(grp) {
-        var d = 0;
-        while (grp = egCore.env.pgt.map[grp.parent()]) d++;
-        return d;
-    }
-
-    $scope.age_to_lost = function() {
-        $scope.in_progress = true;
-        $scope.i_am_sure = false; // reset
-        $scope.all_done = false;
-
-        egCore.net.request(
-            'open-ils.circ',
-            'open-ils.circ.circulation.age_to_lost',
-            egCore.auth.token(), {
-                user_profile : $scope.selected_profile.id(),
-                circ_lib : $scope.context_org.id()
-            }
-        ).then(
-            function() {
-                $scope.in_progress = false;
-                $scope.all_done = true;
-            },
-            null, // on-error
-            function(response) {
-                if (!response) return;
-                if (response.progress)
-                    $scope.chunks_processed = response.progress;
-                if (response.created)
-                    $scope.events_created = response.created;
-            }
-        );
-    }
-}])
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 d52382c..f5ce418 100644
--- a/Open-ILS/web/js/ui/default/staff/services/ui.js
+++ b/Open-ILS/web/js/ui/default/staff/services/ui.js
@@ -314,8 +314,8 @@ function($modal, $interpolate) {
            + '</ul>'
           + '</div>',
 
-        controller : ['$scope','$timeout','egOrg','egAuth','egCore',
-              function($scope , $timeout , egOrg , egAuth , egCore) {
+        controller : ['$scope','$timeout','egOrg','egAuth','egCore','egStartup',
+              function($scope , $timeout , egOrg , egAuth , egCore , egStartup) {
 
             if ($scope.alldisabled) {
                 $scope.disable_button = $scope.alldisabled == 'true' ? true : false;
@@ -329,12 +329,22 @@ function($modal, $interpolate) {
 
             // avoid linking the full fleshed tree to the scope by 
             // tossing in a flattened list.
-            $scope.orgList = egOrg.list().map(function(org) {
-                return {
-                    id : org.id(),
-                    shortname : org.shortname(), 
-                    depth : org.ou_type().depth()
-                }
+            // --
+            // Run-time code referencing post-start data should be run
+            // from within a startup block, otherwise accessing this
+            // module before startup completes will lead to failure.
+            egStartup.go().then(function() {
+
+                $scope.orgList = egOrg.list().map(function(org) {
+                    return {
+                        id : org.id(),
+                        shortname : org.shortname(), 
+                        depth : org.ou_type().depth()
+                    }
+                });
+
+                if (!$scope.selected)
+                    $scope.selected = egOrg.get(egAuth.user().ws_ou());
             });
 
             $scope.getSelectedName = function() {

commit 521ad4eb04864b32a1280c91f5315f26f70c3228
Author: Bill Erickson <berickxx at gmail.com>
Date:   Thu Jul 23 22:35:47 2015 -0400

    webstaff: browser client: starting local admin landing page
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/local_admin.tt2 b/Open-ILS/src/templates/staff/admin/local_admin.tt2
new file mode 100644
index 0000000..ed0590e
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/local_admin.tt2
@@ -0,0 +1,43 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Local Administration"); 
+  ctx.page_app = "egLocalAdmin";
+%]
+
+[% BLOCK APP_JS %]
+<script>
+  /* just enough JS for the the navbar to display */
+  angular.module('egLocalAdmin', ['ngRoute', 'ui.bootstrap', 'egCoreMod']);
+</script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
+[% END %]
+
+<div class="container-fluid" style="text-align:center">
+  <div class="alert alert-info alert-less-pad strong-text-2">
+    <span>[% l('Local Administration') %]</span>
+  </div>
+</div>
+
+<div class="container admin-splash-container">
+
+  <div class="row new-entry">
+    <div class="col-md-3">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/actor/address_alert">
+        [% l('Address Alerts') %]
+      </a>
+    </div>
+  </div>
+  <div class="row new-entry">
+    <div class="col-md-3">
+      <span class="glyphicon glyphicon-pencil"></span>
+      <a target="_self" href="./admin/circ/age_to_lost">
+        [% l('Age Overdue Circs to Lost') %]
+      </a>
+    </div>
+  </div>
+
+</div>
+
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/css/admin.css.tt2 b/Open-ILS/src/templates/staff/css/admin.css.tt2
index f418546..1c96365 100644
--- a/Open-ILS/src/templates/staff/css/admin.css.tt2
+++ b/Open-ILS/src/templates/staff/css/admin.css.tt2
@@ -10,3 +10,14 @@
 #age-to-lost-container .form-group {
   padding-right: 10px;
 }
+  
+.admin-splash-container .row {
+  margin-top: 5px;
+}
+
+.admin-splash-container .new-entry {
+  margin-top: 10px;
+  padding-top: 10px;
+  border-top: 2px solid #F5F5F5;
+}
+
diff --git a/Open-ILS/src/templates/staff/navbar.tt2 b/Open-ILS/src/templates/staff/navbar.tt2
index 0c8748b..afe2c9c 100644
--- a/Open-ILS/src/templates/staff/navbar.tt2
+++ b/Open-ILS/src/templates/staff/navbar.tt2
@@ -281,6 +281,12 @@
               [% l('User Permission Editor') %]
             </a>
           </li>
+          <li>
+            <a href="./admin/local_admin" target="_self">
+              <span class="glyphicon glyphicon-picture"></span>
+              [% l('Local Administration') %]
+            </a>
+          </li>
         </ul> <!-- admin dropdown -->
       </li>
     </ul> <!-- end left side entries -->

commit 45f8829636546402e25d979d1e5b408bb0f07ce3
Author: Bill Erickson <berickxx at gmail.com>
Date:   Sun Jul 19 21:38:07 2015 -0400

    webstaff: browser client: age to lost UI
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/circ/index.tt2 b/Open-ILS/src/templates/staff/admin/circ/index.tt2
new file mode 100644
index 0000000..50c52b8
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/circ/index.tt2
@@ -0,0 +1,15 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Evergreen Configuration"); 
+  ctx.page_app = "egAdminCirc";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/circ/app.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/admin.css" />
+[% END %]
+
+<div ng-view></div>
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/admin/circ/t_age_to_lost.tt2 b/Open-ILS/src/templates/staff/admin/circ/t_age_to_lost.tt2
new file mode 100644
index 0000000..c1e28dd
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/circ/t_age_to_lost.tt2
@@ -0,0 +1,80 @@
+<div class="container-fluid" style="text-align:center">
+  <div class="alert alert-info alert-less-pad strong-text-2">
+    [% l('Age Circulations to Lost') %]
+  </div>
+</div>
+
+<span>
+[% | l %]
+Choose the user profile and circulation library for the overdue circulations
+you wish to age to a Lost status.  Note the descendants of these values 
+(sub-groups, sub-libraries) will also be affected.
+[% END %]
+</span>
+
+<div id='age-to-lost-container'>
+  <div xclass="form-inline">
+    <div class="form-group">
+      <label class="col-md-2">[% l('User Profile') %]</label>
+      <div class="btn-group" dropdown>
+        <button type="button" class="btn btn-default dropdown-toggle">
+          <span style="padding-right: 5px;">{{selected_profile.name()}}</span>
+          <span class="caret"></span>
+        </button>
+        <ul class="dropdown-menu">
+          <li ng-repeat="grp in profiles">
+            <a href
+              style="padding-left: {{pgt_depth(grp) * 10 + 5}}px"
+              ng-click="set_profile(grp)">{{grp.name()}}</a>
+          </li>
+        </ul>
+      </div>
+    </div>
+    <div class="form-group">
+      <label class="col-md-2">[% l('Circulation Library') %]</label>
+      <eg-org-selector selected="context_org"></eg-org-selector>
+    </div>
+    <div class="form-group">
+      <label class="col-md-2">[% l('Are you sure?') %]</label>
+      <input type="checkbox" ng-model="i_am_sure"/>
+    </div>
+  </div>
+    <div class="form-group">
+      <button class="btn btn-default" 
+        ng-class="{disabled : !i_am_sure}" ng-click="age_to_lost()">
+        [% l('Queue for Aging') %]</button>
+    </div>
+  </div>
+
+  <div class="row" ng-show="in_progress">
+    <div class="col-md-3"></div>
+    <div class="progress progress-striped active col-md-6">
+      <div class="progress-bar" role="progressbar" aria-valuenow="100" 
+        aria-valuemin="0" aria-valuemax="100" style="width: 100%">
+        <span class="sr-only">[% l('Processing...') %]</span>
+      </div>
+    </div>
+  </div>
+
+  <div class="row" ng-show="in_progress || all_done">
+    <div class="col-md-3"></div>
+    <div class="col-md-3">
+      [% l('Chunks Processed: [_1]', '{{chunks_processed}}') %]</div>
+    <div class="col-md-3">
+      [% l('Events Created: [_1]', '{{events_created}}') %]</div>
+    </div>
+  </div>
+
+  <div class="row" ng-show="all_done">
+    <div class="col-md-3"></div>
+    <div class="col-md-6">
+     <div class="alert alert-success" role="alert">
+      [% l('Processing Complete') %]</div>
+    </div>
+  </div>
+
+</div>
+   
+
+
+
diff --git a/Open-ILS/src/templates/staff/css/admin.css.tt2 b/Open-ILS/src/templates/staff/css/admin.css.tt2
new file mode 100644
index 0000000..f418546
--- /dev/null
+++ b/Open-ILS/src/templates/staff/css/admin.css.tt2
@@ -0,0 +1,12 @@
+
+#age-to-lost-container {
+  margin-top: 20px;
+}
+
+#age-to-lost-container .row {
+  margin-top: 20px;
+}
+
+#age-to-lost-container .form-group {
+  padding-right: 10px;
+}
diff --git a/Open-ILS/web/js/ui/default/staff/admin/circ/app.js b/Open-ILS/web/js/ui/default/staff/admin/circ/app.js
new file mode 100644
index 0000000..24d3aad
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/admin/circ/app.js
@@ -0,0 +1,86 @@
+/**
+ * App to drive the base page. 
+ * Login Form
+ * Splash Page
+ */
+
+angular.module('egAdminCirc',
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod','egUiMod'])
+
+.config(['$routeProvider','$locationProvider','$compileProvider', 
+ function($routeProvider , $locationProvider , $compileProvider) {
+
+    $locationProvider.html5Mode(true);
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); 
+    var resolver = {delay : function(egStartup) {return egStartup.go()}};
+
+    $routeProvider.when('/admin/circ/age_to_lost', {
+        templateUrl: './admin/circ/t_age_to_lost',
+        controller: 'AgeToLostCtl',
+        resolve : resolver
+    });
+
+    // default page 
+    /*
+    $routeProvider.otherwise({
+        templateUrl : 'user-perms-template',
+        controller: 'UserPermsCtrl',
+        resolve : resolver
+    });
+    */
+}])
+
+.controller('AgeToLostCtl',
+       ['$scope','egCore',
+function($scope , egCore) {
+    $scope.i_am_sure = false;
+    $scope.chunks_processed = 0;
+    $scope.events_created = 0;
+
+    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;
+            $scope.selected_profile = tree;
+        }
+    );
+
+    $scope.set_profile = function(g) {$scope.selected_profile = g}
+
+    // determine the tree depth of the profile group
+    $scope.pgt_depth = function(grp) {
+        var d = 0;
+        while (grp = egCore.env.pgt.map[grp.parent()]) d++;
+        return d;
+    }
+
+    $scope.age_to_lost = function() {
+        $scope.in_progress = true;
+        $scope.i_am_sure = false; // reset
+        $scope.all_done = false;
+
+        egCore.net.request(
+            'open-ils.circ',
+            'open-ils.circ.circulation.age_to_lost',
+            egCore.auth.token(), {
+                user_profile : $scope.selected_profile.id(),
+                circ_lib : $scope.context_org.id()
+            }
+        ).then(
+            function() {
+                $scope.in_progress = false;
+                $scope.all_done = true;
+            },
+            null, // on-error
+            function(response) {
+                if (!response) return;
+                if (response.progress)
+                    $scope.chunks_processed = response.progress;
+                if (response.created)
+                    $scope.events_created = response.created;
+            }
+        );
+    }
+}])

commit ead8f829d65a461ec1c74365fe9748b8e5a0dca1
Author: Bill Erickson <berickxx at gmail.com>
Date:   Tue Jul 14 22:04:39 2015 -0400

    webstaff: Browser client: integrate address alert HTML UI
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/admin/actor/index.tt2 b/Open-ILS/src/templates/staff/admin/actor/index.tt2
new file mode 100644
index 0000000..5a3ba07
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/actor/index.tt2
@@ -0,0 +1,14 @@
+[%
+  WRAPPER "staff/base.tt2";
+  ctx.page_title = l("Evergreen Configuration"); 
+  ctx.page_app = "egAdminActor";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/admin/actor/app.js"></script>
+[% END %]
+
+<div ng-view></div>
+
+[% END %]
diff --git a/Open-ILS/src/templates/staff/admin/actor/t_address_alert.tt2 b/Open-ILS/src/templates/staff/admin/actor/t_address_alert.tt2
new file mode 100644
index 0000000..c84c9e2
--- /dev/null
+++ b/Open-ILS/src/templates/staff/admin/actor/t_address_alert.tt2
@@ -0,0 +1 @@
+<eg-embed-frame url="address_alert_url" handlers="funcs"></eg-embed-frame>
diff --git a/Open-ILS/web/js/ui/default/staff/admin/actor/app.js b/Open-ILS/web/js/ui/default/staff/admin/actor/app.js
new file mode 100644
index 0000000..94ac8e5
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/admin/actor/app.js
@@ -0,0 +1,50 @@
+/**
+ * App to drive the base page. 
+ * Login Form
+ * Splash Page
+ */
+
+angular.module('egAdminActor',
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod','egUiMod'])
+
+.config(['$routeProvider','$locationProvider','$compileProvider', 
+ function($routeProvider , $locationProvider , $compileProvider) {
+
+    $locationProvider.html5Mode(true);
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); 
+    var resolver = {delay : function(egStartup) {return egStartup.go()}};
+
+    $routeProvider.when('/admin/actor/address_alert', {
+        templateUrl: './admin/actor/t_address_alert',
+        controller: 'AddressAlertCtl',
+        resolve : resolver
+    });
+
+    // default page 
+    /*
+    $routeProvider.otherwise({
+        templateUrl : 'user-perms-template',
+        controller: 'UserPermsCtrl',
+        resolve : resolver
+    });
+    */
+}])
+
+.controller('AddressAlertCtl',
+       ['$scope','$routeParams','$window','$location','egCore',
+function($scope , $routeParams , $window , $location , egCore) {
+
+    console.log('addr alert');
+    
+    $scope.funcs = {
+        ses : egCore.auth.token(),
+    }
+
+    // have to use the full URL, not just the path, to ensure
+    // the embeded page is not a nested version of this page (ad infinitum)
+    $scope.address_alert_url = $location.absUrl().replace(
+        /\/eg\/staff.*/, '/eg/conify/global/actor/address_alert');
+
+    console.log($scope.address_alert_url);
+
+}])

commit c4636c5059fb84c6e2a92563c71cf52c060206cc
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Oct 19 09:46:42 2015 -0400

    webstaff: Typo in method name for volume transfer
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 04b75d8..36e8a8b 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
@@ -1001,7 +1001,7 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
         if (xfer_target) {
             egCore.net.request(
                 'open-ils.cat',
-                'open-ils.open-ils.cat.asset.volume.batch.transfer.override',
+                'open-ils.cat.asset.volume.batch.transfer.override',
                 egCore.auth.token(), {
                     docid   : $scope.record_id,
                     lib     : xfer_target,

commit 3a0c68e6a1a4697c6c5bd085d019729263829318
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Oct 19 09:45:50 2015 -0400

    webstaff: Label field "Owning Library" instead of "Library" in call number area
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
index d5b4725..8d3190f 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
@@ -35,7 +35,7 @@
             </div>
         </div>
         <div class="row pad-vert">
-            <div class="col-xs-1"><b>[% l('Library') %]</b></div>
+            <div class="col-xs-1"><b>[% l('Owning Library') %]</b></div>
             <div class="col-xs-1"><b>[% l('Volumes') %]</b></div>
             <div class="col-xs-10">
                 <div class="row">

commit 31aa4a56919511db52af7d89d14190f3a3d1c15f
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Fri Oct 16 22:21:14 2015 +0000

    webstaff: teach egOrgSelector how to be sticky
    
    In order to make the org unit selector in the holdings view
    to be "sticky", the egOrgSelector directive now accepts an
    optional stickySetting attribute specifying the name of a
    preference key for storing the last selected org unit.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
index 5a79b1f..e1eaa08 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
@@ -4,7 +4,7 @@
     <div class="col-md-3">
       <div class="input-group">
         <span class="input-group-addon">[% l('Show holdings at or below') %]</span>
-        <eg-org-selector selected="holdings_ou" onchange="holdings_ou_changed"></eg-org-selector>
+        <eg-org-selector selected="holdings_ou" onchange="holdings_ou_changed" sticky-setting="cat.holdings_view_ou"></eg-org-selector>
       </div>
     </div>
   </div>
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 fefe0c4..d52382c 100644
--- a/Open-ILS/web/js/ui/default/staff/services/ui.js
+++ b/Open-ILS/web/js/ui/default/staff/services/ui.js
@@ -290,7 +290,11 @@ function($modal, $interpolate) {
             onchange : '=',
 
             // optional primary drop-down button label
-            label : '@'
+            label : '@',
+
+            // optional name of settings key for persisting
+            // the last selected org unit
+            stickySetting : '@'
         },
 
         // any reason to move this into a TT2 template?
@@ -310,8 +314,8 @@ function($modal, $interpolate) {
            + '</ul>'
           + '</div>',
 
-        controller : ['$scope','$timeout','egOrg','egAuth',
-              function($scope , $timeout , egOrg , egAuth) {
+        controller : ['$scope','$timeout','egOrg','egAuth','egCore',
+              function($scope , $timeout , egOrg , egAuth , egCore) {
 
             if ($scope.alldisabled) {
                 $scope.disable_button = $scope.alldisabled == 'true' ? true : false;
@@ -321,6 +325,7 @@ function($modal, $interpolate) {
 
             $scope.egOrg = egOrg; // for use in the link function
             $scope.egAuth = egAuth; // for use in the link function
+            $scope.hatch = egCore.hatch // for use in the link function
 
             // avoid linking the full fleshed tree to the scope by 
             // tossing in a flattened list.
@@ -340,6 +345,9 @@ function($modal, $interpolate) {
 
             $scope.orgChanged = function(org) {
                 $scope.selected = egOrg.get(org.id);
+                if ($scope.stickySetting) {
+                    egCore.hatch.setLocalItem($scope.stickySetting, org.id);
+                }
                 if ($scope.onchange) $scope.onchange($scope.selected);
             }
 
@@ -357,6 +365,13 @@ function($modal, $interpolate) {
                 }
             );
 
+            if (scope.stickySetting) {
+                var orgId = scope.hatch.getLocalItem(scope.stickySetting);
+                if (orgId) {
+                    scope.selected = scope.egOrg.get(orgId);
+                }
+            }
+
             if (!scope.selected && !scope.nodefault)
                 scope.selected = scope.egOrg.get(scope.egAuth.user().ws_ou());
         }

commit 1e3c97b29562a2fff9a19cb1a816b5b7d5adbd8e
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Fri Oct 16 18:02:39 2015 +0000

    webstaff: when FF input gains focus, select contents
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 f2294aa..a81f7d8 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
@@ -203,6 +203,11 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                 $scope.item_container = [];
                 $scope.in_handler = false;
                 $scope.ready = false;
+                $element.find('input').bind('focus', function (e) { e.target.select() });
+                $element.find('input').bind('mouseup', function(e) {
+                    e.preventDefault()
+                    return false;
+                });
 
                 $scope.$watch('content', function (newVal, oldVal) {
                     var input = $($element).find('input');

commit 9de17a732bdbffd99692a00d253172cc0d818223
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Oct 15 15:36:56 2015 -0400

    webstaff: fix org unit selectors in vol/copy editor
    
    Restrict owning and circ lib selection to only those orgs
    that have can_have_vols true for their org type
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
index 10c51ec..4d1ec8e 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
@@ -132,7 +132,7 @@
                         selected="working.circ_lib"
                         noDefault
                         label="[% l('(Unset)') %]"
-                        disableTest="cant_have_vols"
+                        disable-test="cant_have_vols"
                     ></eg-org-selector>
                 </div>
                 <div class="col-md-6" ng-class="{'bg-success': working.ref !== undefined}">
diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_defaults.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_defaults.tt2
index c11aa23..d2c6f98 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_defaults.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_defaults.tt2
@@ -275,7 +275,7 @@
                         <eg-org-selector
                             selected="defaults.statcat_filter"
                             noDefault label="[% l('Default Filter Library') %]"
-                            disableTest="cant_have_vols"></eg-org-selector>
+                        ></eg-org-selector>
                     </label>
                 </div>
             </div>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 3ca00b7..4684246 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -509,7 +509,7 @@ function(egCore , $q) {
         replace: true,
         template:
             '<div class="row">'+
-                '<div class="col-xs-1"><eg-org-selector alldisabled="{{record == 0}}" selected="owning_lib" disableTest="cant_have_vols"></eg-org-selector></div>'+
+                '<div class="col-xs-1"><eg-org-selector alldisabled="{{record == 0}}" selected="owning_lib" disable-test="cant_have_vols"></eg-org-selector></div>'+
                 '<div class="col-xs-1"><input ng-disabled="record == 0" class="form-control" type="number" min="{{orig_cn_count}}" ng-model="cn_count" ng-change="changeCNCount()"/></div>'+
                 '<div class="col-xs-10">'+
                     '<eg-vol-row only-vols="onlyVols" record="{{record}}"'+

commit 93b4748131936d5ca5068180e38f1da4060ad413
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Oct 14 12:03:28 2015 -0400

    webstaff: Force refresh from server on copy delete
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 50a63d9..04b75d8 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
@@ -889,7 +889,9 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
                 'open-ils.cat.asset.volume.fleshed.batch.update.override',
                 egCore.auth.token(), cnList, 1, flags
             ).then(function(update_count) {
-                $scope.holdingsGridDataProvider.refresh();
+                holdingsSvcInst.fetchAgain().then(function() {
+                    $scope.holdingsGridDataProvider.refresh();
+                });
             });
         });
     }

commit bb4dece7faaca1edb826952cb7f8b74b32aba1a5
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Oct 13 16:00:48 2015 -0400

    webstaff: be explicit about fast-add availability
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 bf83b8a..39ec701 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
@@ -104,7 +104,7 @@
       <eg-embed-frame save-space="300" frame="opac_iframe" url="catalog_url" handlers="handlers" onchange="handle_page"></eg-embed-frame>
     </div>
     <div ng-show="record_tab == 'marc_edit'">
-      <eg-marc-edit-record on-save="refresh_record_callback" dirty-flag="stop_unload" record-id="record_id"/>
+      <eg-marc-edit-record fast-add="true" on-save="refresh_record_callback" dirty-flag="stop_unload" record-id="record_id"/>
     </div>
     <!-- ng-if the remaining tabs so they can be instantiated on demand -->
     <div ng-if="record_tab == 'marc_html'">
diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_new_bib.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_new_bib.tt2
index fdc9757..c5ffa77 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_new_bib.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_new_bib.tt2
@@ -9,5 +9,5 @@
     </div>
 </div>
 <div ng-show="have_template" class="row col-md-12">
-  <eg-marc-edit-record dirty-flag="stop_unload" record_id="new_bib_id" marc-xml="marc_template" record-type="bre" />
+  <eg-marc-edit-record fast-add="true" dirty-flag="stop_unload" record_id="new_bib_id" marc-xml="marc_template" record-type="bre" />
 </div>
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 4fe77c0..5b61974 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
@@ -1,5 +1,5 @@
 <div>
-  <div ng-show="bre && !(brandNewRecord || embedded)" class="row pad-vert marcfastitemadd">
+  <div ng-show="bre && fastAdd" class="row pad-vert marcfastitemadd">
     <div class="col-md-2">
       <label><input type="checkbox" ng-model="enable_fast_add"/> [% l('Add Item') %]</label>
     </div>
diff --git a/Open-ILS/src/templates/staff/cat/z3950/t_marc_edit.tt2 b/Open-ILS/src/templates/staff/cat/z3950/t_marc_edit.tt2
index 9e0fe21..8e55ebf 100644
--- a/Open-ILS/src/templates/staff/cat/z3950/t_marc_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/z3950/t_marc_edit.tt2
@@ -7,7 +7,7 @@
   <div class="modal-body">
     <eg-marc-edit-record dirty-flag="dirty_flag" record-id="record_id" marc-xml="marc_xml"
                          record-type="bre" save-label="{{save_label}}"
-                         on-save="import_record_callback"
+                         on-save="import_record_callback" fast-add="true"
     />
   </div>
   <div class="modal-footer">
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 0086d41..f2294aa 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
@@ -617,6 +617,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
             // used just to munge some MARCXML client-side, rather
             // than to (immediately) update the database
             inPlaceMode : '@',
+            fastAdd : '@',
             flatOnly : '@',
             embedded : '@',
             recordType : '@',

commit 55e829e0e102bdab385258d60f5a197ec9d08bb3
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Oct 13 14:06:03 2015 -0400

    webstaff: use activateItem to open the vol/copy editor
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 9418804..50a63d9 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
@@ -508,7 +508,11 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
     // ------------------------------------------------------------------
     // Holdings
 
-    $scope.holdingsGridControls = {};
+    $scope.holdingsGridControls = {
+        activateItem : function (item) {
+            $scope.selectedHoldingsVolCopyEdit();
+        }
+    };
     $scope.holdingsGridDataProvider = egGridDataProvider.instance({
         get : function(offset, count) {
             return this.arrayNotifier(holdingsSvcInst.copies, offset, count);

commit 2f4233da2a4bb7c553194b89315fe1ef69ec8644
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Oct 13 12:24:55 2015 -0400

    webstaff: typo preventing a setting from working
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index a990e73..3ca00b7 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -686,7 +686,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                 $scope.batch.prefix = $scope.defaults.prefix;
                 $scope.batch.suffix = $scope.defaults.suffix;
                 $scope.working.statcat_filter = $scope.defaults.statcat_filter;
-                if ($scope.defaults.always_vols) $scope.show_vols = true;
+                if ($scope.defaults.always_volumes) $scope.show_vols = true;
                 if ($scope.defaults.barcode_checkdigit) itemSvc.barcode_checkdigit = true;
                 if ($scope.defaults.auto_gen_barcode) itemSvc.auto_gen_barcode = true;
             }
@@ -979,7 +979,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
         ).then(function (data) {
 
             if (data) {
-                if (data.hide_vols && !$scope.defaults.always_vols) $scope.show_vols = false;
+                if (data.hide_vols && !$scope.defaults.always_volumes) $scope.show_vols = false;
                 if (data.hide_copies) {
                     $scope.show_copies = false;
                     $scope.only_vols = true;

commit 954e4df49590eaa4087e2fc883e78227d71e3612
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Oct 13 12:24:29 2015 -0400

    webstaff: inform on record save failure, and mark the page clean on successful save
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 412c02e..0086d41 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
@@ -1083,6 +1083,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                         $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;
+                        $scope.dirtyFlag = false;
                         $scope.flat_text_marc = $scope.record.toBreaker();
 
                         if ($scope.record_type == 'bre') {
@@ -1275,7 +1276,9 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     if ($scope.recordId) {
                         return egCore.pcrud.update(
                             $scope.Record()
-                        ).then(function() {
+                        ).then(function() { // success
+                            $scope.save_stack_depth = $scope.record_undo_stack.length;
+                            $scope.dirtyFlag = false;
                             if ($scope.enable_fast_add) {
                                 egCore.net.request(
                                     'open-ils.actor',
@@ -1299,6 +1302,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                                     }
                                 });
                             }
+                        }, function() { // failure
+                            alert('Could not save the record!');
                         }).then(loadRecord).then(processOnSaveCallbacks);
                     } else {
                         $scope.Record().creator(egCore.auth.user().id());

commit 41b4fa2de10ff2c43e82c8678e6e6cdf2b2591e3
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Oct 13 11:41:12 2015 -0400

    webstaff: make sure copy_notes() contains an array before pushing to it
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 031a43b..a990e73 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -1415,6 +1415,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
 
                     if (note.initials) note.value += ' [' + note.initials + ']';
                     angular.forEach(copy_list, function (cp) {
+                        if (!angular.isArray(cp.notes())) cp.notes([]);
                         var n = new egCore.idl.acpn();
                         n.isnew(1);
                         n.creator(note.creator);

commit e9f8ea0aaf2df1d6c543eb91f8788589cdfc20ce
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Oct 13 11:30:48 2015 -0400

    webstaff: improve styling of aligned grid columns to avoid cell overlap
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/share/t_autogrid.tt2 b/Open-ILS/src/templates/staff/share/t_autogrid.tt2
index 0f12dd6..087b058 100644
--- a/Open-ILS/src/templates/staff/share/t_autogrid.tt2
+++ b/Open-ILS/src/templates/staff/share/t_autogrid.tt2
@@ -300,18 +300,18 @@
           ng-click="handleRowClick($event, item)"
           ng-dblclick="gridControls.activateItem(item)"
           ng-repeat="col in columns"
-          style="text-align:{{col.align}}; padding-left:5px; padding-right:5px; flex:{{col.flex}}"
+          style="text-align:{{col.align}}; flex:{{col.flex}}"
           ng-show="col.visible">
 
           <!-- if the cell comes with its own template,
                translate that content into HTML and insert it here -->
-          <span ng-if="col.template" 
+          <span ng-if="col.template" style="padding-left:5px; padding-right:10px;"
             ng-bind-html="translateCellTemplate(col, item)">
           </span>
 
           <!-- otherwise, simply display the item value, which may 
                pass through datatype-specific filtering. -->
-          <span ng-if="!col.template">
+          <span ng-if="!col.template" style="padding-left:5px; padding-right:10px;">
             {{itemFieldValue(item, col) | egGridValueFilter:col}}
           </span>
       </div>

commit e9e14cbf5c570495649862d1e2a7afdf92ff4c50
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Oct 12 21:12:08 2015 +0000

    webstaff: highlight currently active position in phys char wizard
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/share/t_physchar_wizard.tt2 b/Open-ILS/src/templates/staff/cat/share/t_physchar_wizard.tt2
index 2b86979..630e00a 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_physchar_wizard.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_physchar_wizard.tt2
@@ -1,7 +1,7 @@
 <div>
   <div class="row">
     <div class="col-md-4">[% l('007 Value') %]</div>
-    <div class="col-md-4">{{field.data}}</div>
+    <div class="col-md-4"><pre ng-bind-html="highlightedFieldData()"></pre></div>
   </div>
   <div class="row"><div class="col-md-12 pad-vert"><hr/></div></div>
   <div class="row">
diff --git a/Open-ILS/src/templates/staff/css/cat.css.tt2 b/Open-ILS/src/templates/staff/css/cat.css.tt2
index a94f56e..3b87e42 100644
--- a/Open-ILS/src/templates/staff/css/cat.css.tt2
+++ b/Open-ILS/src/templates/staff/css/cat.css.tt2
@@ -90,6 +90,11 @@ input.marcedit:focus {
     min-width: 1em;
 }
 
+.active-physchar {
+    font-weight: bold;
+    color: red;
+}
+
 .tooltip {
     /* width: 10em; */
 }
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 026767f..412c02e 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
@@ -1625,7 +1625,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
     }
 })
 
-.directive("egPhyscharWizard", function () {
+.directive("egPhyscharWizard", ['$sce', function ($sce) {
     return {
         restrict: 'E',
         replace: true,
@@ -1641,6 +1641,12 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                 // step==0 means we are currently selecting the type
                 $scope.step = 0;
 
+                // position and offset of the "subfields" we're
+                // currently editing; this is maintained as a convenience
+                // for the highlighting of the currently active position
+                $scope.offset = 0;
+                $scope.len = 1;
+
                 if (!$scope.field.data) 
                     $scope.field.data = '';
 
@@ -1661,6 +1667,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     var promise;
 
                     if ($scope.step == 0) {
+                        $scope.offset = 0;
+                        $scope.len    = 1;
                         promise = egTagTable.getPhysCharTypeMap();
                     } else {
                         promise = current_subfield().then(
@@ -1697,6 +1705,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                         var after = value.substr(slot[0] + slot[1]);
                         $scope.field.data = 
                             before + new_val.substr(0, slot[1]) + after;
+                        $scope.offset = slot[0];
+                        $scope.len    = slot[1];
                     });
                 }
 
@@ -1745,6 +1755,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                             return (opt.ptype_key() == current_ptype())})[0];
                     } else {
                         get_step_slot().then(function(slot) {
+                            $scope.offset = slot[0];
+                            $scope.len    = slot[1];
                             var val = String.prototype.substr.apply(                      
                                 $scope.field.data, slot);
                             if (val) {
@@ -1757,11 +1769,30 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                         })
                     }
                 }
+
+                $scope.highlightedFieldData = function() {
+                    if (
+                            $scope.len && $scope.field.data &&
+                            $scope.field.data.length > 0 &&
+                            $scope.field.data.length >= $scope.offset
+                        ) {
+                        return $sce.trustAsHtml(
+                            $scope.field.data.substring(0, $scope.offset) + 
+                            '<span class="active-physchar">' +
+                            $scope.field.data.substr($scope.offset, $scope.len) +
+                            '</span>' +
+                            $scope.field.data.substr($scope.offset + $scope.len)
+                        );
+                    } else {
+                        return $scope.field.data;
+                    }
+                };
+
                 set_values_for_step();
             }
         ]
     }
-})
+}])
 
 
 .directive("egMarcEditAuthorityBrowser", function () {

commit d19ea04d9ecee521483b02173effb03efc1ae583
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Oct 12 16:10:04 2015 -0400

    webstaff: allow batch copy attribute editing across bib records
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
index 519765e..d5b4725 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
@@ -11,7 +11,7 @@
     </div>
 
     <div class="container-fluid pad-vert" ng-show="show_vols">
-        <div class="row bg-info">
+        <div ng-show="record_id" class="row bg-info">
             <div class="col-xs-2"><h4 class="center-block">[% l('Batch Apply') %]</h4></div>
             <div class="col-xs-10">
                 <div class="row">
@@ -64,7 +64,7 @@
             focus-next="focusNextFirst"
             ng-repeat="(lib,callnumbers) in data.tree | orderBy:lib track by lib"
             ng-init="ind = $index"
-            record="record.id()"
+            record="{{record_id}}"
             only-vols="only_vols"
             lib="{{lib}}"
             allcopies="data.copies"
diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
index 2fb627e..1cfa4e0 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
@@ -7,7 +7,7 @@ eg-navbar {
 }
 </style>
 
-<eg-record-summary ng-if="!embedded"
+<eg-record-summary ng-if="!embedded && record_id"
      record-id="record_id" record="summaryRecord"></eg-record-summary>
 
 <!-- tabbed copy data view -->
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 ebc4bb9..da8d00d 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,31 +449,27 @@ function($scope,  $q , $routeParams , $timeout , $window , $modal , bucketSvc ,
     }
 
     $scope.spawnHoldingsEdit = function (copies) {
-        var rec_hash = {};
+        var cp_list = []
         angular.forEach($scope.gridControls.selectedItems(), function (i) {
-            var rec = i['call_number.record.id'];
-            if (!rec_hash[rec]) rec_hash[rec] = [];
-            rec_hash[rec].push(i.id);
+            cp_list.push(i.id);
         })
 
-        angular.forEach(rec_hash, function(cp_list,r) {
-            egCore.net.request(
-                'open-ils.actor',
-                'open-ils.actor.anon_cache.set_value',
-                null, 'edit-these-copies', {
-                    record_id: r,
-                    copies: cp_list,
-                    hide_vols : true,
-                    hide_copies : false
-                }
-            ).then(function(key) {
-                if (key) {
-                    var url = egCore.env.basePath + 'cat/volcopy/' + key;
-                    $timeout(function() { $window.open(url, '_blank') });
-                } else {
-                    alert('Could not create anonymous cache key!');
-                }
-            });
+        egCore.net.request(
+            'open-ils.actor',
+            'open-ils.actor.anon_cache.set_value',
+            null, 'edit-these-copies', {
+                record_id: 0, // false-y value for record_id disables record summary
+                copies: cp_list,
+                hide_vols : true,
+                hide_copies : false
+            }
+        ).then(function(key) {
+            if (key) {
+                var url = egCore.env.basePath + 'cat/volcopy/' + key;
+                $timeout(function() { $window.open(url, '_blank') });
+            } else {
+                alert('Could not create anonymous cache key!');
+            }
         });
     }
 
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/record.js b/Open-ILS/web/js/ui/default/staff/cat/services/record.js
index 853a046..590dea3 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/record.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/record.js
@@ -165,7 +165,9 @@ angular.module('egCoreMod')
                     });
                     $scope.bib_cn = null;
                     $scope.bib_cn_tooltip = '';
-                    var label_class = egCore.env.aous['cat.default_classification_scheme'] || 1;
+                    var label_class = 1;
+                    if (egCore.env.aous) 
+                        label_class = egCore.env.aous['cat.default_classification_scheme'] || 1;
                     egCore.net.request(
                         'open-ils.cat',
                         'open-ils.cat.biblio.record.marc_cn.retrieve',
diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index d58ac9f..031a43b 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -252,10 +252,10 @@ function(egCore , $q) {
                     ' type="text" ng-model="barcode" ng-change="updateBarcode()"/>'+
                 '</div>'+
                 '<div class="col-xs-3"><input class="form-control" type="number" ng-model="copy_number" ng-change="updateCopyNo()"/></div>'+
-                '<div class="col-xs-4"><eg-basic-combo-box list="parts" selected="part"></eg-basic-combo-box></div>'+
+                '<div class="col-xs-4"><eg-basic-combo-box eg-disabled="record == 0" list="parts" selected="part"></eg-basic-combo-box></div>'+
             '</div>',
 
-        scope: { focusNext: "=", copy: "=", callNumber: "=", index: "@" },
+        scope: { focusNext: "=", copy: "=", callNumber: "=", index: "@", record: "@" },
         controller : ['$scope','itemSvc','egCore',
             function ( $scope , itemSvc , egCore ) {
                 $scope.new_part_id = 0;
@@ -328,22 +328,22 @@ function(egCore , $q) {
         template:
             '<div class="row">'+
                 '<div class="col-xs-2">'+
-                    '<select class="form-control" ng-model="classification" ng-change="updateClassification()" ng-options="cl.name() for cl in classification_list"/>'+
+                    '<select ng-disabled="record == 0" class="form-control" ng-model="classification" ng-change="updateClassification()" ng-options="cl.name() for cl in classification_list"/>'+
                 '</div>'+
                 '<div class="col-xs-1">'+
-                    '<select class="form-control" ng-model="prefix" ng-change="updatePrefix()" ng-options="p.label() for p in prefix_list"/>'+
+                    '<select ng-disabled="record == 0" class="form-control" ng-model="prefix" ng-change="updatePrefix()" ng-options="p.label() for p in prefix_list"/>'+
                 '</div>'+
-                '<div class="col-xs-2"><input class="form-control" type="text" ng-change="updateLabel()" ng-model="label"/></div>'+
+                '<div class="col-xs-2"><input ng-disabled="record == 0" class="form-control" type="text" ng-change="updateLabel()" ng-model="label"/></div>'+
                 '<div class="col-xs-1">'+
-                    '<select class="form-control" ng-model="suffix" ng-change="updateSuffix()" ng-options="s.label() for s in suffix_list"/>'+
+                    '<select ng-disabled="record == 0" class="form-control" ng-model="suffix" ng-change="updateSuffix()" ng-options="s.label() for s in suffix_list"/>'+
                 '</div>'+
-                '<div ng-hide="onlyVols" class="col-xs-1"><input class="form-control" type="number" ng-model="copy_count" min="{{orig_copy_count}}" ng-change="changeCPCount()"></div>'+
+                '<div ng-hide="onlyVols" class="col-xs-1"><input ng-disabled="record == 0" class="form-control" type="number" ng-model="copy_count" min="{{orig_copy_count}}" ng-change="changeCPCount()"></div>'+
                 '<div ng-hide="onlyVols" class="col-xs-5">'+
-                    '<eg-vol-copy-edit ng-repeat="cp in copies track by idTracker(cp)" focus-next="focusNextBarcode" copy="cp" call-number="callNumber"></eg-vol-copy-edit>'+
+                    '<eg-vol-copy-edit record="{{record}}" ng-repeat="cp in copies track by idTracker(cp)" focus-next="focusNextBarcode" copy="cp" call-number="callNumber"></eg-vol-copy-edit>'+
                 '</div>'+
             '</div>',
 
-        scope: {focusNext: "=", allcopies: "=", copies: "=", onlyVols: "=" },
+        scope: {focusNext: "=", allcopies: "=", copies: "=", onlyVols: "=", record: "@" },
         controller : ['$scope','itemSvc','egCore',
             function ( $scope , itemSvc , egCore ) {
                 $scope.callNumber =  $scope.copies[0].call_number();
@@ -509,10 +509,10 @@ function(egCore , $q) {
         replace: true,
         template:
             '<div class="row">'+
-                '<div class="col-xs-1"><eg-org-selector selected="owning_lib" disableTest="cant_have_vols"></eg-org-selector></div>'+
-                '<div class="col-xs-1"><input class="form-control" type="number" min="{{orig_cn_count}}" ng-model="cn_count" ng-change="changeCNCount()"/></div>'+
+                '<div class="col-xs-1"><eg-org-selector alldisabled="{{record == 0}}" selected="owning_lib" disableTest="cant_have_vols"></eg-org-selector></div>'+
+                '<div class="col-xs-1"><input ng-disabled="record == 0" class="form-control" type="number" min="{{orig_cn_count}}" ng-model="cn_count" ng-change="changeCNCount()"/></div>'+
                 '<div class="col-xs-10">'+
-                    '<eg-vol-row only-vols="onlyVols"'+
+                    '<eg-vol-row only-vols="onlyVols" record="{{record}}"'+
                         'ng-repeat="(cn,copies) in struct | orderBy:cn track by cn" '+
                         'focus-next="focusNextFirst" copies="copies" allcopies="allcopies">'+
                     '</eg-vol-row>'+
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 af64ef4..fefe0c4 100644
--- a/Open-ILS/web/js/ui/default/staff/services/ui.js
+++ b/Open-ILS/web/js/ui/default/staff/services/ui.js
@@ -216,11 +216,12 @@ function($modal, $interpolate) {
         replace: true,
         scope: {
             list: "=", // list of strings
-            selected: "="
+            selected: "=",
+            egDisabled: "="
         },
         template:
             '<div class="input-group">'+
-                '<input type="text" class="form-control" ng-model="selected" ng-change="makeOpen()">'+
+                '<input type="text" ng-disabled="egDisabled" class="form-control" ng-model="selected" ng-change="makeOpen()">'+
                 '<div class="input-group-btn" dropdown ng-class="{open:isopen}">'+
                     '<button type="button" ng-click="showAll()" class="btn btn-default dropdown-toggle"><span class="caret"></span></button>'+
                     '<ul class="dropdown-menu dropdown-menu-right">'+

commit 3a9012302c0cf3a8427c8bd6c60da9dcab51dc37
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Oct 12 15:08:05 2015 +0000

    webstaff: make webstaff grid menus scrollable
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/share/t_autogrid.tt2 b/Open-ILS/src/templates/staff/share/t_autogrid.tt2
index e60b5bc..0f12dd6 100644
--- a/Open-ILS/src/templates/staff/share/t_autogrid.tt2
+++ b/Open-ILS/src/templates/staff/share/t_autogrid.tt2
@@ -13,7 +13,7 @@
     <button type="button" class="btn btn-default dropdown-toggle eg-grid-menu-item">
       {{menuLabel}}<span class="caret"></span>
     </button>
-    <ul class="dropdown-menu">
+    <ul class="dropdown-menu scrollable-menu">
       <li ng-repeat="item in menuItems | filter : { standalone : 'false' }" ng-class="{divider: item.divider}">
         <a ng-if="!item.divider" href ng-disabled="item.disabled"
           ng-click="item.handler()">{{item.label}}</a>

commit 281791d5894c86a2848f5d41d68b9a4a0739ef2a
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Oct 9 15:51:11 2015 -0400

    webstaff: Mark new copy notes as new, so they will save.
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index cd1678c..d58ac9f 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -1416,6 +1416,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                     if (note.initials) note.value += ' [' + note.initials + ']';
                     angular.forEach(copy_list, function (cp) {
                         var n = new egCore.idl.acpn();
+                        n.isnew(1);
                         n.creator(note.creator);
                         n.pub(note.pub);
                         n.title(note.title);

commit c8eee7679a0d28a04548b6b2c88db07b58a93cda
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Oct 9 13:33:09 2015 -0400

    webstaff: Typo fix (Catagories)
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
index 9a0b48e..10c51ec 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2
@@ -83,7 +83,7 @@
             <b>[% l('Status') %]</b>
         </div>
         <div class="col-md-4">
-            <b>[% l('Statistical Catagories') %]</b>
+            <b>[% l('Statistical Categories') %]</b>
         </div>
     </div>
 

commit f3cd370d4051129de26f385d66cbcc4eb0e8b985
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Oct 9 12:07:29 2015 -0400

    webstaff: A little spacing tweak to avoid overlapping UI elements in modals
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 67a36cb..4fe77c0 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
@@ -24,13 +24,13 @@
         [% l('Stack subfields') %]
       </label>
     </div>
-    <div class="col-md-2">
+    <div class="col-md-3">
       <div class="input-group">
         <span class="input-group-addon"><b>[% l('Record Type') %]</b></span>
         <span class="input-group-addon">{{calculated_record_type}}</span>
       </div>
     </div>
-    <div ng-if="bre" class="col-md-3">
+    <div ng-if="bre" class="col-md-2">
       <eg-marc-edit-bibsource/>
     </div>
     <div class="col-md-3">

commit bcc887e2eb91ed259573a37fddd55c8d492eec46
Author: Mike Rylander <mrylander at gmail.com>
Date:   Fri Oct 9 11:47:52 2015 -0400

    webstaff: Move the "Help" and "Stack Subfields" UI elements around a bit, to save space
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 25862c5..67a36cb 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
@@ -12,13 +12,19 @@
   </div>
 
   <div class="pad-vert row marctypesource">
-    <div class="col-md-2" ng-show="!flatOnly">
+    <div class="col-md-2 form-group" ng-show="!flatOnly">
       <label>
         <input type="checkbox" ng-model="flatEditor" ng-change="refreshVisual()"/>
         [% l('Flat Text Editor') %]
       </label>
     </div>
-    <div class="col-md-3">
+    <div class="col-md-2 form-group" ng-show="!flatOnly">
+      <label>
+        <input type="checkbox" ng-model="stackSubfields.enabled" />
+        [% l('Stack subfields') %]
+      </label>
+    </div>
+    <div class="col-md-2">
       <div class="input-group">
         <span class="input-group-addon"><b>[% l('Record Type') %]</b></span>
         <span class="input-group-addon">{{calculated_record_type}}</span>
@@ -39,6 +45,9 @@
           <button ng-hide="brandNewRecord || embedded || Record().deleted()" class="btn btn-default" ng-click="deleteRecord()">[% l('Delete') %]</button>
           <button ng-if="!brandNewRecord && Record().deleted()" class="btn btn-default" ng-click="undeleteRecord()">[% l('Undelete') %]</button>
         </span>
+        <span class="btn-group">
+          <button class="btn btn-default" ng-click="showHelp = !showHelp">[% l('Help') %]</button>
+        </span>
       </div>
     </div>
   </div>
@@ -141,30 +150,33 @@
       </div>
     </div>
     <div class="marcrecord pad-vert">
-      <div class="form-group">
-        <label>
-          <input type="checkbox" ng-model="stackSubfields.enabled" />
-          [% l('Stack subfields') %]
-        </label>
-        <button class="btn btn-default btn-sm" ng-click="showHelp = !showHelp">[% l('Help') %]</button>
-        <!-- TODO: when we update to angular-ui >= 0.13.0 and get popover-template, switching to
-                   that rather than using ui.bootstrap.collapse might be good -->
-      </div>
       <div collapse="!showHelp">
-        <ul>
-           <li>[% l('Undo: CTRL-z') %]</li>
-           <li>[% l('Redo: CTRL-y') %]</li>
-           <li>[% l('Add Row: CTRL+Enter') %]</li>
-           <li>[% l('Insert Row: CTRL+Shift+Enter') %]</li>
-           <li>[% l('Copy Current Row Above: CTRL+Up') %]</li>
-           <li>[% l('Copy Current Row Below: CTRL+Down') %]</li>
-           <li>[% l('Add Subfield: CTRL+D or CTRL+I') %]</li>
-           <li>[% l('Remove Row: CTRL+Del') %]</li>
-           <li>[% l('Remove Subfield: Shift+Del') %]</li>
-           <li>[% l('Create/Replace 006: Shift+F6') %]</li>
-           <li>[% l('Create/Replace 007: Shift+F7') %]</li>
-           <li>[% l('Create/Replace 008: Shift+F8') %]</li>
-        </ul>
+        <div class="row">
+          <div class="col-md-4">
+            <ul>
+               <li>[% l('Undo: CTRL-z') %]</li>
+               <li>[% l('Redo: CTRL-y') %]</li>
+               <li>[% l('Add Row: CTRL+Enter') %]</li>
+               <li>[% l('Insert Row: CTRL+Shift+Enter') %]</li>
+            </ul>
+          </div>
+          <div class="col-md-4">
+            <ul>
+               <li>[% l('Copy Current Row Above: CTRL+Up') %]</li>
+               <li>[% l('Copy Current Row Below: CTRL+Down') %]</li>
+               <li>[% l('Add Subfield: CTRL+D or CTRL+I') %]</li>
+               <li>[% l('Remove Row: CTRL+Del') %]</li>
+            </ul>
+          </div>
+          <div class="col-md-4">
+            <ul>
+               <li>[% l('Remove Subfield: Shift+Del') %]</li>
+               <li>[% l('Create/Replace 006: Shift+F6') %]</li>
+               <li>[% l('Create/Replace 007: Shift+F7') %]</li>
+               <li>[% l('Create/Replace 008: Shift+F8') %]</li>
+            </ul>
+          </div>
+        </div>
       </div>
       <div>
         <eg-marc-edit-leader record="record" on-keydown="onKeydown"/>

commit 3b073cf53a39c519188c04075b61bf922254d2fa
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Fri Oct 9 14:54:19 2015 +0000

    webstaff: CSS tweak - add padding belong MARC editor help button
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 f1ed6c4..25862c5 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
@@ -141,7 +141,7 @@
       </div>
     </div>
     <div class="marcrecord pad-vert">
-      <div class="input-group">
+      <div class="form-group">
         <label>
           <input type="checkbox" ng-model="stackSubfields.enabled" />
           [% l('Stack subfields') %]

commit 86e1c005095a8f4399d6be0cfcca0eab20c82ce0
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Fri Oct 9 14:50:17 2015 +0000

    webstaff: disable non-OPAC-view tabs when doing catalog search
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 05b25cd..bf83b8a 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
@@ -63,32 +63,32 @@
     </a>
   </li>
   <li ng-class="{disabled : !record_id, active : record_tab == 'marc_edit'}">
-    <a ng-click="set_record_tab('marc_edit')" >
+    <a ng-click="record_id && set_record_tab('marc_edit')" >
         [% l('MARC Edit') %]
     </a>
   </li>
   <li ng-class="{disabled : !record_id, active : record_tab == 'marc_html'}">
-    <a ng-click="set_record_tab('marc_html')" >
+    <a ng-click="record_id && set_record_tab('marc_html')" >
         [% l('MARC View') %]
     </a>
   </li>
   <li ng-class="{disabled : !record_id, active : record_tab == 'holds'}">
-    <a ng-click="set_record_tab('holds')" >
+    <a ng-click="record_id && set_record_tab('holds')" >
         [% l('View Holds') %]
     </a>
   </li>
   <li ng-class="{disabled : !record_id, active : record_tab == 'monoparts'}">
-    <a ng-click="set_record_tab('monoparts')" >
+    <a ng-click="record_id && set_record_tab('monoparts')" >
         [% l('Monograph Parts') %]
     </a>
   </li>
   <li ng-class="{disabled : !record_id, active : record_tab == 'holdings'}">
-    <a ng-click="set_record_tab('holdings')" >
+    <a ng-click="record_id && set_record_tab('holdings')" >
         [% l('Holdings View') %]
     </a>
   </li>
   <li ng-class="{disabled : !record_id, active : record_tab == 'conjoined'}">
-    <a ng-click="set_record_tab('conjoined')" >
+    <a ng-click="record_id && set_record_tab('conjoined')" >
         [% l('Conjoined Items') %]
     </a>
   </li>

commit d5fb03768e5475437f2b2ae2b541ea59b7e3d32c
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 8 21:28:53 2015 +0000

    webstaff: improve MARC record deletion
    
    The MARC editor now asks the user to confirm
    whether to delete the record, and in the case of
    deleting bibs, now uses open-ils.cat.biblio.record_entry.delete
    so as to catch things like volumes still attached
    to the bib and cancelling holds in the bib record.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/share/marcedit_strings.tt2 b/Open-ILS/src/templates/staff/cat/share/marcedit_strings.tt2
index 81b3190..0f6fa33 100644
--- a/Open-ILS/src/templates/staff/cat/share/marcedit_strings.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/marcedit_strings.tt2
@@ -8,5 +8,9 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
     s.ADD_007                   = "[% l('Add 007') %]";
     s.ADD_REPLACE_008           = "[% l('Add/Replace 008') %]";
     s.DELETE_FIELD              = "[% l('Delete field') %]";
+    s.CONFIRM_DELETE_RECORD     = "[% l('Delete Record') %]";
+    s.CONFIRM_DELETE_BRE_MSG    = "[% l('Are you sure you want to delete title record [_1] from the catalog?', '{{id}}') %]";
+    s.CONFIRM_DELETE_ARE_MSG    = "[% l('Are you sure you want to delete authority record [_1] from the catalog?', '{{id}}') %]";
+    s.ALERT_DELETE_FAILED       = "[% l('Could not delete record [_1]: [_2]', '{{id}}', '{{desc}}') %]";
 }]);
 </script>
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 5fb5202..026767f 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
@@ -645,8 +645,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
             });
 
         },
-        controller : ['$timeout','$scope','$q','$window','egCore', 'egTagTable',
-            function ( $timeout , $scope , $q,  $window , egCore ,  egTagTable ) {
+        controller : ['$timeout','$scope','$q','$window','egCore', 'egTagTable','egConfirmDialog','egAlertDialog',
+            function ( $timeout , $scope , $q,  $window , egCore ,  egTagTable , egConfirmDialog , egAlertDialog ) {
 
 
                 $scope.onSaveCallback = $scope.onSave;
@@ -1176,8 +1176,34 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                 };
 
                 $scope.deleteRecord = function () {
-                    $scope.Record().deleted(true);
-                    return $scope.saveRecord();
+                    egConfirmDialog.open(
+                        egCore.strings.CONFIRM_DELETE_RECORD,
+                        (($scope.record_type == 'bre') ?
+                            egCore.strings.CONFIRM_DELETE_BRE_MSG :
+                            egCore.strings.CONFIRM_DELETE_ARE_MSG),
+                        { id : $scope.recordId }
+                    ).result.then(function() {
+                        if ($scope.record_type == 'bre') {
+                            egCore.net.request(
+                                'open-ils.cat',
+                                'open-ils.cat.biblio.record_entry.delete',
+                                egCore.auth.token(), $scope.recordId
+                            ).then(function(resp) {
+                                var evt = egCore.evt.parse(resp);
+                                if (evt) {
+                                    return egAlertDialog.open(
+                                        egCore.strings.ALERT_DELETE_FAILED,
+                                        { id : $scope.recordId, desc : evt.desc }
+                                    );
+                                } else {
+                                    loadRecord().then(processOnSaveCallbacks);
+                                }
+                            });
+                        } else {
+                            $scope.Record().deleted(true);
+                            return $scope.saveRecord();
+                        }
+                    });
                 };
 
                 $scope.undeleteRecord = function () {

commit 1923c1a7c37dee9d5b0b4bd12af481a88049fbf9
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 8 19:23:36 2015 +0000

    webstaff: add keyboard shortcut legend to MARC editor
    
    Note that once we upgrade to angularjs-ui >= 0.13.0, using
    a popover rather than a collapse might be better.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 92dc60b..f1ed6c4 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
@@ -141,10 +141,31 @@
       </div>
     </div>
     <div class="marcrecord pad-vert">
-      <label>
-        <input type="checkbox" ng-model="stackSubfields.enabled" />
-        [% l('Stack subfields') %]
-      </label>
+      <div class="input-group">
+        <label>
+          <input type="checkbox" ng-model="stackSubfields.enabled" />
+          [% l('Stack subfields') %]
+        </label>
+        <button class="btn btn-default btn-sm" ng-click="showHelp = !showHelp">[% l('Help') %]</button>
+        <!-- TODO: when we update to angular-ui >= 0.13.0 and get popover-template, switching to
+                   that rather than using ui.bootstrap.collapse might be good -->
+      </div>
+      <div collapse="!showHelp">
+        <ul>
+           <li>[% l('Undo: CTRL-z') %]</li>
+           <li>[% l('Redo: CTRL-y') %]</li>
+           <li>[% l('Add Row: CTRL+Enter') %]</li>
+           <li>[% l('Insert Row: CTRL+Shift+Enter') %]</li>
+           <li>[% l('Copy Current Row Above: CTRL+Up') %]</li>
+           <li>[% l('Copy Current Row Below: CTRL+Down') %]</li>
+           <li>[% l('Add Subfield: CTRL+D or CTRL+I') %]</li>
+           <li>[% l('Remove Row: CTRL+Del') %]</li>
+           <li>[% l('Remove Subfield: Shift+Del') %]</li>
+           <li>[% l('Create/Replace 006: Shift+F6') %]</li>
+           <li>[% l('Create/Replace 007: Shift+F7') %]</li>
+           <li>[% l('Create/Replace 008: Shift+F8') %]</li>
+        </ul>
+      </div>
       <div>
         <eg-marc-edit-leader record="record" on-keydown="onKeydown"/>
       </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 c295207..5fb5202 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
@@ -672,6 +672,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                 $scope.controlfields = [];
                 $scope.datafields = [];
                 $scope.controlSet = egTagTable.getAuthorityControlSet();
+                $scope.showHelp = false;
                 $scope.stackSubfields = { enabled : false };
                 egCore.hatch.getItem('cat.marcedit.stack_subfields').then(function(val) {
                     $scope.stackSubfields.enabled = val;

commit 48146678b1595e560583fc2490087091bd1a31ad
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 8 16:43:42 2015 +0000

    webstaff: tweak layout of the merge interface
    
    The lead record is now displayed using the same sort
    of tabset as the subordinate records. While, of course,
    there can be only one lead record at a time, doing it
    this way gives us two things:
    
    [1] Display of the lead bib's ID
    [2] Better vertical alignment of the MARC records
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2 b/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
index 3e9fa6e..76e6409 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
@@ -9,14 +9,18 @@
           <div class="col-xs-6">
             <h4>[% l('Lead record') %]</h4>
             <div ng-if="lead_id">
-               <button class="btn btn-default btn-sm" ng-class="{disabled : editing_inplace}" ng-click="edit_lead_inplace()">[% l('Edit') %]</button>
-               <button class="btn btn-default btn-sm" ng-class="{disabled : editing_inplace}" ng-click="edit_lead()">[% l('Edit using full editor') %]</button>
-               <eg-marc-edit-record dirty-flag="dirty_flag" record-id="lead_id"
-                         record-type="bre" flat-only="true" embedded="true" 
-                         ng-if="editing_inplace" on-save="post_edit_inplace">
-               </eg-marc-edit-record>
-               <eg-record-breaker record-id="lead_id" ng-if="!editing_inplace"></eg-record-breaker>
-               <eg-volume-list record-id="lead_id" edit-copies="true" edit-volumes="true"></eg-volume-list>
+               <tabset>
+                 <tab heading="[% l('Bib [_1]', '{{lead_id}}') %]">
+                   <button class="btn btn-default btn-sm" ng-class="{disabled : editing_inplace}" ng-click="edit_lead_inplace()">[% l('Edit') %]</button>
+                   <button class="btn btn-default btn-sm" ng-class="{disabled : editing_inplace}" ng-click="edit_lead()">[% l('Edit using full editor') %]</button>
+                   <eg-marc-edit-record dirty-flag="dirty_flag" record-id="lead_id"
+                             record-type="bre" flat-only="true" embedded="true" 
+                             ng-if="editing_inplace" on-save="post_edit_inplace">
+                   </eg-marc-edit-record>
+                   <eg-record-breaker record-id="lead_id" ng-if="!editing_inplace"></eg-record-breaker>
+                   <eg-volume-list record-id="lead_id" edit-copies="true" edit-volumes="true"></eg-volume-list>
+                 </tab>
+               </tabset>
             </div>
             <div ng-if="!lead_id">
                 [% l('Please select a lead record from the right...') %]
@@ -29,8 +33,7 @@
                 <button class="btn btn-default btn-sm" ng-click="use_as_lead(rec)">[% l('Use as lead record') %]</button>
                 <button class="btn btn-default btn-sm" ng-click="drop(rec)">[% l('Remove from consideration') %]</button>
                 <eg-record-breaker record-id="rec.id"></eg-record-breaker>
-                <eg-volume-list record-id="rec.id"></eg-volume-list>
-               <eg-volume-list record-id="rec.id" edit-copies="true" edit-volumes="true"></eg-volume-list>
+                <eg-volume-list record-id="rec.id" edit-copies="true" edit-volumes="true"></eg-volume-list>
               </tab>
             </tabset>
           </div>

commit 9f6f60339a80babe39b50be787afd978d4deccb9
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 8 16:16:01 2015 +0000

    webstaff: automatically advance focus in MARC editor
    
    Upon completing a tag, indicator, or subfield code
    input, focus will now automatically advance to the next
    input, allowing users to enter records with less need
    to use the tab button.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 64a185a..c295207 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
@@ -65,7 +65,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
             contextItemGenerator: '=',
             max: '@',
             itype: '@',
-            selectOnFocus: '='
+            selectOnFocus: '=',
+            advanceFocusAfterInput: '='
         },
         controller : ['$scope',
             function ( $scope ) {
@@ -148,6 +149,35 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                 element.bind('focus', function () { element.select() });
             }
 
+            function findCaretTarget(id, itype) {
+                var tgt = null;
+                if (itype == 'tag') {
+                    tgt = id.replace(/tag$/, 'i1');
+                } else if (itype == 'ind') {
+                    if (id.match(/i1$/)) {
+                        tgt = id.replace(/i1$/, 'i2');
+                    } else if (id.match(/i2$/)) {
+                        tgt = id.replace(/i2$/, 's0code');
+                    }
+                } else if (itype == 'sfc') {
+                    tgt = id.replace(/code$/, 'value');
+                }
+                return tgt;
+            }
+            if (Boolean(scope.advanceFocusAfterInput)) {
+                element.bind('input', function (e) {
+                    if (scope.content.length == scope.max) {
+                        var tgt = findCaretTarget(e.currentTarget.id, scope.itype);
+                        if (tgt) {
+                            var element = $('#' + tgt).get(0);
+                            if (element) {
+                                element.focus();
+                            }
+                        }
+                    }
+                });
+            }
+
             element.bind('change', function (e) { element.size = scope.max || parseInt(scope.content.length * 1.1) });
 
             element.bind('contextmenu', {scope : scope}, scope.showContext);
@@ -300,6 +330,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                         '>‡</label><eg-marc-edit-editable '+
                         'itype="sfc" '+
                         'select-on-focus="true" '+
+                        'advance-focus-after-input="true" '+
                         'class="marcedit marcsf marcsfcode" '+
                         'field="field" '+
                         'subfield="subfield" '+
@@ -344,6 +375,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                       'itype="ind" '+
                       'class="marcedit marcind" '+
                       'select-on-focus="true" '+
+                      'advance-focus-after-input="true" '+
                       'field="field" '+
                       'content="ind" '+
                       'max="1" '+
@@ -372,6 +404,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                       'itype="tag" '+
                       'class="marcedit marctag" '+
                       'select-on-focus="true" '+
+                      'advance-focus-after-input="true" '+
                       'field="field" '+
                       'content="tag" '+
                       'max="3" '+

commit a7d228849c70fec2135d31fbebfc798f16a64b5a
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Oct 8 11:14:10 2015 -0400

    webstaff: Always accept double-dagger as a SF delimiter
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/marcrecord.js b/Open-ILS/web/js/ui/default/staff/marcrecord.js
index 95318ba..fa4db5b 100644
--- a/Open-ILS/web/js/ui/default/staff/marcrecord.js
+++ b/Open-ILS/web/js/ui/default/staff/marcrecord.js
@@ -302,13 +302,17 @@ var MARC21 = {
                         );
                     }
                 } else {
-                    if (current_line.substring(4,5) == me.delimiter) // add delimiters if they were left out
+                    if (current_line.substring(4,5) == me.delimiter) // add indicators 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);
+                    var local_delimiter = me.delimiter;
+                    if (data.indexOf('\u2021') > -1)
+                        local_delimiter = '\u2021';
+
+                    var sf_list = data.split(local_delimiter);
                     sf_list.shift();
 
                     me.fields.push(
@@ -319,7 +323,7 @@ var MARC21 = {
                                 ind2      : df_ind2(current_line),
                                 subfields : sf_list.map( function (sf, i) {
                                                 var sf_data = sf.substring(1);
-                                                if (me.delimiter == '$') sf_data = sf_data.replace(/\{dollar\}/g, '$');
+                                                if (local_delimiter == '$') sf_data = sf_data.replace(/\{dollar\}/g, '$');
                                                 return [ sf.substring(0,1), sf_data, i ];
                                             })
                         })

commit b40e2b445ebd9242b12cc0cb50b17e54454a4ace
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 8 14:46:33 2015 +0000

    webstaff: another fix to focus-on-new-MARC-field
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 65640da..64a185a 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
@@ -664,7 +664,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     var element = $(e.target);
 
                     var index_field = e.data.scope.field.position;
-                    var new_field_index = index_field + 1;
+                    var new_field_index = index_field;
 
                     var new_field = new MARC21.Field({
                         tag : '999',
@@ -681,6 +681,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                             e.data.scope.field,
                             new_field
                         );
+                        new_field_index++;
                     }
 
                     $scope.current_event_target = 'r' + $scope.recordId +

commit 8fab469efa6f4c67cf9110db353be530512b7b1c
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 8 14:43:29 2015 +0000

    webstaff: fix setting focus when inserting a new MARC field
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 734b63d..65640da 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
@@ -664,7 +664,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     var element = $(e.target);
 
                     var index_field = e.data.scope.field.position;
-                    var new_field = index_field + 1;
+                    var new_field_index = index_field + 1;
 
                     var new_field = new MARC21.Field({
                         tag : '999',
@@ -684,7 +684,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     }
 
                     $scope.current_event_target = 'r' + $scope.recordId +
-                                                  'f' + new_field + 'tag';
+                                                  'f' + new_field_index + 'tag';
 
                     $scope.current_event_target_cursor_pos = 0;
                     $scope.current_event_target_cursor_pos_end = 3;

commit 758589f30a0d9cfe8b810f4408b988db3a0713c7
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 8 14:04:30 2015 +0000

    webstaff: make context menus in MARC editor scrollable
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 ce6f4c8..734b63d 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
@@ -108,7 +108,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 
                         $scope.contextMenuEvent = event;
                         var tmpl = 
-                            '<ul class="dropdown-menu" role="menu" style="z-index: 2000;">'+
+                            '<ul class="dropdown-menu scrollable-menu" role="menu" style="z-index: 2000;">'+
                                 '<eg-context-menu-item context-menu-event="contextMenuEvent" ng-repeat="item in item_list" item="item" content="content"/>'+
                             '</ul>';
             
@@ -254,7 +254,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                         $('body').trigger('click');
 
                         var tmpl = 
-                            '<ul class="dropdown-menu" role="menu" style="z-index: 2000;">'+
+                            '<ul class="dropdown-menu scrollable-menu" role="menu" style="z-index: 2000;">'+
                                 '<eg-context-menu-item ng-repeat="item in item_container" item="item" content="content"/>'+
                             '</ul>';
             

commit eecff61c9c2b9bd3c760f41aab605e3b33c7bc98
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 8 14:04:13 2015 +0000

    webstaff: make egGrid actions menu scrollable
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/share/t_autogrid.tt2 b/Open-ILS/src/templates/staff/share/t_autogrid.tt2
index aaf14aa..e60b5bc 100644
--- a/Open-ILS/src/templates/staff/share/t_autogrid.tt2
+++ b/Open-ILS/src/templates/staff/share/t_autogrid.tt2
@@ -89,7 +89,7 @@
       <button type="button" class="btn btn-default dropdown-toggle">
         [% l('Actions') %] <span class="caret"></span>                       
       </button>                                                              
-      <ul class="dropdown-menu pull-right grid-action-dropdown">                                  
+      <ul class="dropdown-menu pull-right grid-action-dropdown scrollable-menu">                                  
         <li ng-repeat-start="group in actionGroups">
           <span style="padding-left: 1em;" ng-if="group.label"><strong><u>{{group.label}}</u></strong></span>
         </li>

commit 1405801a19f39e1266f4d9a4d1dc16861c2936dc
Author: Bill Erickson <berickxx at gmail.com>
Date:   Mon Jun 15 22:29:25 2015 -0400

    LP#1464767 Browser client scrollable selectors
    
    Adds a new scrollable-menu CSS class.  It's (provisionally?) limited to
    larger screens, w/ the assumption that smaller screens handle scrolling
    for you.
    
    New class is applied to org unit selects only so far.
    
    Signed-off-by: Bill Erickson <berickxx at gmail.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>
    
    Conflicts:
    	Open-ILS/src/templates/staff/css/style.css.tt2
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/css/style.css.tt2 b/Open-ILS/src/templates/staff/css/style.css.tt2
index 0a0ba3b..80fb47a 100644
--- a/Open-ILS/src/templates/staff/css/style.css.tt2
+++ b/Open-ILS/src/templates/staff/css/style.css.tt2
@@ -420,6 +420,15 @@ table.list tr.selected td { /* deprecated? */
   }
 }
 
+ at media all and (min-width: 800px) {
+    /* scrollable menus for full-size screens */
+    .scrollable-menu {
+        height: auto;
+        max-height: 200px;
+        overflow-x: hidden;
+    }
+}
+
 [%# 
 vim: ft=css 
 %]
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 9cd0abc..af64ef4 100644
--- a/Open-ILS/web/js/ui/default/staff/services/ui.js
+++ b/Open-ILS/web/js/ui/default/staff/services/ui.js
@@ -299,7 +299,7 @@ function($modal, $interpolate) {
              + '<span style="padding-right: 5px;">{{getSelectedName()}}</span>'
              + '<span class="caret"></span>'
            + '</button>'
-           + '<ul class="dropdown-menu">'
+           + '<ul class="dropdown-menu scrollable-menu">'
              + '<li ng-repeat="org in orgList" ng-hide="hiddenTest(org.id)">'
                + '<a href ng-click="orgChanged(org)" a-disabled="disableTest(org.id)" '
                  + 'style="padding-left: {{org.depth * 10 + 5}}px">'

commit a307cec41c8f4146cc3218faeec11b9a042b564c
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 8 02:09:41 2015 +0000

    webstaff: implement subfield stacking in MARC editor
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 808438f..92dc60b 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
@@ -141,6 +141,10 @@
       </div>
     </div>
     <div class="marcrecord pad-vert">
+      <label>
+        <input type="checkbox" ng-model="stackSubfields.enabled" />
+        [% l('Stack subfields') %]
+      </label>
       <div>
         <eg-marc-edit-leader record="record" on-keydown="onKeydown"/>
       </div>
diff --git a/Open-ILS/src/templates/staff/css/cat.css.tt2 b/Open-ILS/src/templates/staff/css/cat.css.tt2
index 7a5e12d..a94f56e 100644
--- a/Open-ILS/src/templates/staff/css/cat.css.tt2
+++ b/Open-ILS/src/templates/staff/css/cat.css.tt2
@@ -28,6 +28,11 @@
     margin: 0px;
 }
 
+.marcedit_stacked_subfield {
+    display: block;
+    margin-left: 3em;
+}
+
 input.marcedit:focus {
     background-color: lightcyan;
 }
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 ae88380..ce6f4c8 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
@@ -418,9 +418,10 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     '<span><eg-marc-edit-tag context-functions="contextFunctions" 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-class="{ \'unvalidatedheading\' : field.heading_checked && !field.heading_valid}" ng-repeat="subfield in field.subfields" subfield="subfield" field="field" on-keydown="onKeydown"/></span>'+
+                    '<span><eg-marc-edit-subfield ng-class="{ \'unvalidatedheading\' : field.heading_checked && !field.heading_valid, \'marcedit_stacked_subfield\' : stackSubfields.enabled }" ng-repeat="subfield in field.subfields" subfield="subfield" field="field" on-keydown="onKeydown"/></span>'+
                     // FIXME: template should probably be moved to file to improve
                     // translatibility
+                    '<span  ng-class="{ \'marcedit_stacked_subfield\' : stackSubfields.enabled }">' +
                     '<button class="btn btn-info btn-xs" '+
                     'aria-label="Manage authority record links" '+
                     'ng-show="isAuthorityControlled(field)"'+
@@ -430,11 +431,13 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     '</button>'+
                     '<span ng-show="field.heading_checked && field.heading_valid" class="glyphicon glyphicon-ok-sign"></span>'+
                     '<span ng-show="field.heading_checked && !field.heading_valid" class="glyphicon glyphicon-question-sign"></span>'+
+                    '</span>'+
                   '</div>',
         scope: { field: "=", onKeydown: '=', contextFunctions: '=' },
         replace: true,
         controller : ['$scope','$modal',
             function ( $scope,  $modal ) {
+                $scope.stackSubfields = $scope.$parent.$parent.stackSubfields;
                 $scope.isAuthorityControlled = function () {
                     return ($scope.$parent.$parent.record_type == 'bre') &&
                            $scope.$parent.$parent.controlSet.bibFieldByTag($scope.field.tag);
@@ -636,6 +639,13 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                 $scope.controlfields = [];
                 $scope.datafields = [];
                 $scope.controlSet = egTagTable.getAuthorityControlSet();
+                $scope.stackSubfields = { enabled : false };
+                egCore.hatch.getItem('cat.marcedit.stack_subfields').then(function(val) {
+                    $scope.stackSubfields.enabled = val;
+                });
+                $scope.$watch('stackSubfields.enabled', function (newVal, oldVal) {
+                    if (newVal != oldVal) egCore.hatch.setItem('cat.marcedit.stack_subfields', newVal);
+                });
 
                 egTagTable.loadTagTable({ marcRecordType : $scope.record_type });
 

commit 68b7de5d25e21f6491ce611edf3afee030e92950
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 8 01:10:35 2015 +0000

    webstaff: add session-stickiness for MARC template selector
    
    The new bib function now remembers the last MARC template
    that was used for the lifetime of the browser tab.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 63d01df..9418804 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
@@ -190,9 +190,12 @@ function($scope , $routeParams , $location , $window , $q , egCore) {
         });
         $scope.template_list.sort();
     });
-    egCore.hatch.getItem('cat.default_bib_marc_template').then(function(template) {
-        $scope.template_name = template;
-    });
+    $scope.template_name = egCore.hatch.getSessionItem('eg.cat.last_bib_marc_template');
+    if (!$scope.template_name) {
+        egCore.hatch.getItem('cat.default_bib_marc_template').then(function(template) {
+            $scope.template_name = template;
+        });
+    }
 
     $scope.loadTemplate = function() {
         if ($scope.template_name) {
@@ -203,6 +206,7 @@ function($scope , $routeParams , $location , $window , $q , egCore) {
             ).then(function(template) {
                 $scope.marc_template = template;
                 $scope.have_template = true;
+                egCore.hatch.setSessionItem('eg.cat.last_bib_marc_template', $scope.template_name);
             });
         }
     }

commit 1abdc5e561a26136039117751f673d8e914c55de
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 8 01:05:48 2015 +0000

    webstaff: start teaching egHatch about sessionStorage
    
    This patch adds getSessionItem(), setSessionItem(), and
    removeSessionItem(), which are all wrappers around
    $window.sessionStorage. This is done to support settings
    whose values are sticky for the duration of a session,
    which at present is the lifetime of the browser tab.
    
    An alternative would be using session cookies; some user
    testing is needed to see which lifetime makes the most
    sense.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/services/hatch.js b/Open-ILS/web/js/ui/default/staff/services/hatch.js
index 9e41d91..0b8935b 100644
--- a/Open-ILS/web/js/ui/default/staff/services/hatch.js
+++ b/Open-ILS/web/js/ui/default/staff/services/hatch.js
@@ -301,6 +301,12 @@ angular.module('egCoreMod')
         return JSON.parse(val);
     }
 
+    service.getSessionItem = function(key) {
+        var val = $window.sessionStorage.getItem(key);
+        if (val == null) return;
+        return JSON.parse(val);
+    }
+
     service.setItem = function(key, value) {
         var str = JSON.stringify(value);
         return service.setRemoteItem(key, str)['catch'](
@@ -333,6 +339,12 @@ angular.module('egCoreMod')
         $window.localStorage.setItem(key, jsonified);
     }
 
+    service.setSessionItem = function(key, value, jsonified) {
+        if (jsonified === undefined ) 
+            jsonified = JSON.stringify(value);
+        $window.sessionStorage.setItem(key, jsonified);
+    }
+
     // appends the value to the existing item stored at key.
     // If not item is found at key, this behaves just like setItem()
     service.appendItem = function(key, value) {
@@ -391,6 +403,10 @@ angular.module('egCoreMod')
         $window.localStorage.removeItem(key);
     }
 
+    service.removeSessionItem = function(key) {
+        $window.sessionStorage.removeItem(key);
+    }
+
     // if set, prefix limits the return set to keys starting with 'prefix'
     service.getKeys = function(prefix) {
         return service.getRemoteKeys(prefix)['catch'](

commit c687951fa292b93ce9dc08a320d429a025629282
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Wed Oct 7 21:01:51 2015 +0000

    webstaff: improve how egVolumeList opens the vol/copy editor
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
index 2201812..e80d712 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
@@ -304,7 +304,7 @@ function(egCore , $q) {
                     return cp_id_list;
                 }
 
-                $scope.edit_volumes = function (copies_too) {
+                var spawn_volume_editor = function (copies_too) {
                     egCore.net.request(
                         'open-ils.actor',
                         'open-ils.actor.anon_cache.set_value',
@@ -312,13 +312,14 @@ function(egCore , $q) {
                             record_id: $scope.recordId,
                             copies: gatherHoldingsIds(),
                             hide_vols : false,
-                            hide_copies : (copies_too) ? false : true
+                            hide_copies : ((copies_too) ? false : true)
                         }
                     ).then(function(key) {
                         if (key) {
                             $modal.open({
                                 templateUrl: './cat/share/t_embedded_volcopy',
                                 size: 'lg',
+                                windowClass: 'eg-wide-modal',
                                 controller:
                                     ['$scope', '$modalInstance', function($scope, $modalInstance) {
                                     $scope.volcopy_url = 
@@ -332,8 +333,11 @@ function(egCore , $q) {
                         }
                     });
                 }
+                $scope.edit_volumes = function() {
+                    spawn_volume_editor(false);
+                }
                 $scope.edit_copies = function() {
-                    $scope.edit_volumes(true);
+                    spawn_volume_editor(true);
                 }
 
                 function load_holdings() {

commit affd9e16ffaf0365c553aa743bb21c1c2aeba5d9
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Wed Oct 7 20:38:24 2015 +0000

    webstaff: teach volcopy editor how to set default item status
    
    It now consults the cat.default_copy_status_fast and
    cat.default_copy_status_normal library settings as appropriate.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 b32f80e..ae88380 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
@@ -1214,6 +1214,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                                         raw: [{
                                             label : $scope.fast_item_callnumber,
                                             barcode : $scope.fast_item_barcode,
+                                            fast_add : true
                                         }],
                                         hide_vols : false,
                                         hide_copies : false
diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 0f2efdc..cd1678c 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -970,6 +970,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
 
         $scope.workingGridControls = {};
         $scope.add_vols_copies = false;
+        $scope.is_fast_add = false;
 
         egNet.request(
             'open-ils.actor',
@@ -996,6 +997,8 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                      *      owner      : $org, // optional, defaults to ws_ou
                      *      label      : $cn_label, // optional, to supply a label on a new cn
                      *      barcode    : $cp_barcode // optional, to supply a barcode on a new cp
+                     *      fast_add   : boolean // optional, to specify whether this came
+                     *                              in as a fast add
                      * },...]
                      * 
                      * All can be left out and a completely empty vol/copy combo will be vivicated.
@@ -1004,6 +1007,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                     angular.forEach(
                         data.raw,
                         function (proto) {
+                            if (proto.fast_add) $scope.is_fast_add = true;
                             if (proto.callnumber) {
                                 return egCore.pcrud.retrieve('acn', proto.callnumber)
                                 .then(function(cn) {
@@ -1079,12 +1083,16 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
         }).then( function() {
             $scope.data = itemSvc;
             if ($scope.add_vols_copies) {
+                var status_setting = $scope.is_fast_add ?
+                    'cat.default_copy_status_fast' :
+                    'cat.default_copy_status_normal';
                 egCore.org.settings([
-                    'cat.default_copy_status_fast'
+                    status_setting
                 ]).then(function(set) {
-                    $scope.fast_ccs = set['cat.default_copy_status_fast'] || 0;
+                    $scope.default_ccs = set[status_setting] || 
+                        ($scope.is_fast_add ? 0 : 5); // 0 is Available, 5 is In Process
                     angular.forEach($scope.data.copies, function (cp) {
-                        cp.status($scope.fast_ccs);
+                        cp.status($scope.default_ccs);
                     });
                     $scope.workingGridDataProvider.refresh();
                 });

commit 64aec752d9196abcabed99757271ad7347e38cd4
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Wed Oct 7 20:01:18 2015 +0000

    webstaff: add ability to edit copies to the record merge interface
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2 b/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
index 53821f8..3e9fa6e 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
@@ -16,7 +16,7 @@
                          ng-if="editing_inplace" on-save="post_edit_inplace">
                </eg-marc-edit-record>
                <eg-record-breaker record-id="lead_id" ng-if="!editing_inplace"></eg-record-breaker>
-               <eg-volume-list record-id="lead_id"></eg-volume-list>
+               <eg-volume-list record-id="lead_id" edit-copies="true" edit-volumes="true"></eg-volume-list>
             </div>
             <div ng-if="!lead_id">
                 [% l('Please select a lead record from the right...') %]
@@ -30,6 +30,7 @@
                 <button class="btn btn-default btn-sm" ng-click="drop(rec)">[% l('Remove from consideration') %]</button>
                 <eg-record-breaker record-id="rec.id"></eg-record-breaker>
                 <eg-volume-list record-id="rec.id"></eg-volume-list>
+               <eg-volume-list record-id="rec.id" edit-copies="true" edit-volumes="true"></eg-volume-list>
               </tab>
             </tabset>
           </div>

commit 5255a3d761243fec36e0c8216f00cf1f8881ca50
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Wed Oct 7 20:00:08 2015 +0000

    webstaff: teach egVolumeList more tricks
    
    The directive now accepts two new attributes:
    
    * editVolumes - controls display of the 'Edit volumes' button
    * editCopies - if true, adds a 'Edit volumes and copies button'
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/share/t_volume_list.tt2 b/Open-ILS/src/templates/staff/cat/share/t_volume_list.tt2
index 56f1c07..1c029e0 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_volume_list.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_volume_list.tt2
@@ -5,7 +5,8 @@
     grid-controls="holdingsGridControls"
     persist-key="cat.record_overlay.holdings">
 
-    <eg-grid-menu-item handler="edit_volumes" label="[% l('Edit volumes') %]" />
+    <eg-grid-menu-item handler="edit_volumes" label="[% l('Edit volumes') %]" ng-if="editVolumes"></eg-grid-menu-item>
+    <eg-grid-menu-item handler="edit_copies" label="[% l('Edit volumes and copies') %]" ng-if="editCopies"></eg-grid-menu-item>
 
     <eg-grid-field label="[% l('Owning Library') %]" path="owner_label" flex="4" align="right" visible></eg-grid-field>
     <eg-grid-field label="[% l('Call Number') %]"    path="call_number.label" visible></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
index a0806c5..2201812 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
@@ -278,7 +278,9 @@ function(egCore , $q) {
     return {
         restrict:   'AE',
         scope: {
-            recordId : '='
+            recordId : '=',
+            editVolumes : '@',
+            editCopies  : '@'
         },
         templateUrl: './cat/share/t_volume_list',
         controller:
@@ -302,7 +304,7 @@ function(egCore , $q) {
                     return cp_id_list;
                 }
 
-                $scope.edit_volumes = function () {
+                $scope.edit_volumes = function (copies_too) {
                     egCore.net.request(
                         'open-ils.actor',
                         'open-ils.actor.anon_cache.set_value',
@@ -310,7 +312,7 @@ function(egCore , $q) {
                             record_id: $scope.recordId,
                             copies: gatherHoldingsIds(),
                             hide_vols : false,
-                            hide_copies : true
+                            hide_copies : (copies_too) ? false : true
                         }
                     ).then(function(key) {
                         if (key) {
@@ -330,6 +332,9 @@ function(egCore , $q) {
                         }
                     });
                 }
+                $scope.edit_copies = function() {
+                    $scope.edit_volumes(true);
+                }
 
                 function load_holdings() {
                     holdingsSvcInst.fetch({

commit 9b97dbb7fd2629a21ec4df824877d27e1105b470
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Wed Oct 7 19:27:02 2015 +0000

    webstaff: require SSL for /eg/staff/
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/examples/apache/eg_vhost.conf.in b/Open-ILS/examples/apache/eg_vhost.conf.in
index e64c2d7..dbc834c 100644
--- a/Open-ILS/examples/apache/eg_vhost.conf.in
+++ b/Open-ILS/examples/apache/eg_vhost.conf.in
@@ -814,6 +814,7 @@ RewriteRule ^/openurl$ ${openurl:%1} [NE,PT]
 </Location>
 
 <LocationMatch /eg/staff/>
+    SSLRequireSSL
     Options -MultiViews
     PerlSetVar OILSWebStopAtIndex "true"
 
diff --git a/Open-ILS/examples/apache_24/eg_vhost.conf.in b/Open-ILS/examples/apache_24/eg_vhost.conf.in
index 94d5c34..f2026aa 100644
--- a/Open-ILS/examples/apache_24/eg_vhost.conf.in
+++ b/Open-ILS/examples/apache_24/eg_vhost.conf.in
@@ -820,6 +820,7 @@ RewriteRule ^/openurl$ ${openurl:%1} [NE,PT]
 </Location>
 
 <LocationMatch /eg/staff/>
+    SSLRequireSSL
     Options -MultiViews
     PerlSetVar OILSWebStopAtIndex "true"
 

commit 1192a75905f4b35900ad0e2576e309f19dd9be87
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Oct 7 15:08:56 2015 -0400

    webstaff: force deletion of copies on "delete copies and volumes"
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 a933876..63d01df 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
@@ -868,6 +868,9 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
 
         if (cnList.length == 0) return;
 
+        var flags = {};
+        if (vols && copies) flags.force_delete_copies = 1;
+
         egConfirmDialog.open(
             egCore.strings.CONFIRM_DELETE_COPIES_VOLUMES,
             egCore.strings.CONFIRM_DELETE_COPIES_VOLUMES_MESSAGE,
@@ -876,7 +879,7 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
             egCore.net.request(
                 'open-ils.cat',
                 'open-ils.cat.asset.volume.fleshed.batch.update.override',
-                egCore.auth.token(), cnList, 1, {}
+                egCore.auth.token(), cnList, 1, flags
             ).then(function(update_count) {
                 $scope.holdingsGridDataProvider.refresh();
             });

commit 81c3147fa65cad36edd0bbae42073c6d17f21130
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Oct 7 14:58:59 2015 -0400

    webstaff: Protect against loops before cloning copy/volume data
    
    Also, set proper defaults on newly added copies
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index d76847f..0f2efdc 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -463,10 +463,22 @@ function(egCore , $q) {
                         var cp = new egCore.idl.acp();
                         cp.id( --itemSvc.new_cp_id );
                         cp.isnew( true );
-                        cp.circ_lib( $scope.lib );
+                        cp.circ_lib( $scope.callNumber.owning_lib() );
                         cp.call_number( $scope.callNumber );
+                        cp.deposit(0);
+                        cp.price(0);
+                        cp.deposit_amount(0);
+                        cp.fine_level(2); // Normal
+                        cp.loan_duration(2); // Normal
+                        cp.location(1); // Stacks
+                        cp.circulate('t');
+                        cp.holdable('t');
+                        cp.opac_visible('t');
+                        cp.ref('f');
+                        cp.mint_condition('t');
                         $scope.copies.push( cp );
                         $scope.allcopies.push( cp );
+
                     }
 
                     if ($scope.copy_count >= $scope.orig_copy_count) {
@@ -1305,15 +1317,20 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
         $scope.saveCompletedCopies = function (and_exit) {
             var cnHash = {};
             var perCnCopies = {};
-            angular.forEach( egCore.idl.Clone($scope.completed_copies), function (cp) {
+            angular.forEach( $scope.completed_copies, function (cp) {
+                var cn = cp.call_number();
+                var cn_cps = cp.call_number().copies();
+                cp.call_number().copies([]);
                 var cn_id = cp.call_number().id();
+                cp.call_number(cn_id); // prevent loops in JSON-ification
                 if (!cnHash[cn_id]) {
-                    cnHash[cn_id] = cp.call_number();
-                    perCnCopies[cn_id] = [cp];
+                    cnHash[cn_id] = egCore.idl.Clone(cn);
+                    perCnCopies[cn_id] = [egCore.idl.Clone(cp)];
                 } else {
-                    perCnCopies[cn_id].push(cp);
+                    perCnCopies[cn_id].push(egCore.idl.Clone(cp));
                 }
-                cp.call_number(cn_id); // prevent loops in JSON-ification
+                cp.call_number(cn); // put the data back
+                cp.call_number().copies(cn_cps);
                 if (typeof cnHash[cn_id].prefix() == 'object')
                     cnHash[cn_id].prefix(cnHash[cn_id].prefix().id()); // un-object-ize some fields
                 if (typeof cnHash[cn_id].suffix() == 'object')

commit b9a5367c9a34c3ccfc04c57c5a53cca95ca91a29
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Wed Oct 7 18:36:13 2015 +0000

    webstaff: fetch both staff_client and vandelay_queue record buckets
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 7ace380..9056b86 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
@@ -89,7 +89,7 @@ angular.module('egCatRecordBuckets',
                 'open-ils.actor',
                 'open-ils.actor.container.retrieve_by_class.authoritative',
                 egCore.auth.token(), egCore.auth.user().id(), 
-                'biblio', 'staff_client'
+                'biblio', ['staff_client', 'vandelay_queue']
             ).then(function(buckets) { self.allBuckets = buckets });
         },
 

commit 51752c5ce08db857d3d50c8904772cea185baccc
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Wed Oct 7 15:38:46 2015 +0000

    webstaff: mark some egEmbedFrame bindings as optional
    
    This gets rid of some noise in the browser console.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/services/eframe.js b/Open-ILS/web/js/ui/default/staff/services/eframe.js
index e312ba6..6aa377c 100644
--- a/Open-ILS/web/js/ui/default/staff/services/eframe.js
+++ b/Open-ILS/web/js/ui/default/staff/services/eframe.js
@@ -13,11 +13,11 @@ angular.module('egCoreMod')
 
             // optional hash of functions which augment or override 
             // the stock xulG functions defined below.
-            handlers : '=',
-            frame : '=',
+            handlers : '=?',
+            frame : '=?',
 
             // called after onload of each new iframe page
-            onchange : '=',
+            onchange : '=?',
             saveSpace : '@',
         },
 

commit 097edb99ade07c520fd72b4e332bfaff5036de5d
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Wed Oct 7 15:22:34 2015 +0000

    webstaff: fetch MVR to help build record summary
    
    Although this adds another OpenSRF request, using the
    MVR rather than fleshing simple_record() gives us
    two benefits:
    
    * getting access to the edition
    * fields like title and author are now displayed in
      their proper case, rather than the normalized lowercase
      supplied by simple_record().
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/share/t_record_summary.tt2 b/Open-ILS/src/templates/staff/cat/share/t_record_summary.tt2
index 29f0bb8..045bd93 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_record_summary.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_record_summary.tt2
@@ -36,16 +36,16 @@
     <div class="flex-cell flex-2">
       <a target="_self" 
         href="[% ctx.base_path %]/staff/cat/catalog/record/{{record.id()}}">
-        {{record.simple_record().title()}}
+        {{mvr.title()}}
       </a>
     </div>
 
     <div class="flex-cell strong-text">[% l('Author:') %]</div>
-    <div class="flex-cell flex-2">{{record.simple_record().author()}}</div>
+    <div class="flex-cell flex-2">{{mvr.author()}}</div>
 
     <div class="flex-cell strong-text">[% l('Pub Date:') %]</div>
     <div class="flex-cell">
-      {{record.simple_record().pubdate()}}
+      {{mvr.pubdate()}}
     </div>
 
     <div class="flex-cell strong-text">[% l('Database ID:') %]</div>
@@ -59,12 +59,12 @@
     <div class="flex-cell flex-2">
       <a target="_self" 
         href="[% ctx.base_path %]/staff/cat/catalog/record/{{record.id()}}">
-        {{record.simple_record().title()}}
+        {{mvr.title()}}
       </a>
     </div>
 
     <div class="flex-cell strong-text">[% l('Edition:') %]</div>
-    <div class="flex-cell"><!-- FIXME: no edition field on simple record --></div>
+    <div class="flex-cell">{{mvr.edition()}}</div>
 
     <div class="flex-cell strong-text">[% l('TCN:') %]</div>
     <div class="flex-cell">{{record.tcn_value()}}</div>
@@ -75,11 +75,11 @@
 
   <div class="flex-row">
     <div class="flex-cell strong-text">[% l('Author:') %]</div>
-    <div class="flex-cell flex-2">{{record.simple_record().author()}}</div>
+    <div class="flex-cell flex-2">{{mvr.author()}}</div>
 
     <div class="flex-cell strong-text">[% l('Pub Date:') %]</div>
     <div class="flex-cell">
-      {{record.simple_record().pubdate()}}
+      {{mvr.pubdate()}}
     </div>
 
     <div class="flex-cell strong-text">[% l('Database ID:') %]</div>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/record.js b/Open-ILS/web/js/ui/default/staff/cat/services/record.js
index 9e1cecd..853a046 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/record.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/record.js
@@ -150,12 +150,19 @@ angular.module('egCoreMod')
                     egCore.pcrud.retrieve('bre', $scope.recordId, {
                         flesh : 1,
                         flesh_fields : {
-                            bre : ['simple_record','creator','editor']
+                            bre : ['creator','editor']
                         }
                     }).then(function(rec) {
                         rec.owner(egCore.org.get(rec.owner()));
                         $scope.record = rec;
                     });
+                    egCore.net.request(
+                        'open-ils.search',
+                        'open-ils.search.biblio.record.mods_slim.retrieve.authoritative',
+                        $scope.recordId
+                    ).then(function(mvr) {
+                        $scope.mvr = mvr;
+                    });
                     $scope.bib_cn = null;
                     $scope.bib_cn_tooltip = '';
                     var label_class = egCore.env.aous['cat.default_classification_scheme'] || 1;

commit 282e0d477322670e67b65792d15aaa2e9d3b1ad0
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Wed Oct 7 14:59:09 2015 +0000

    webstaff: teach record summary how to display the bib call number
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/share/t_record_summary.tt2 b/Open-ILS/src/templates/staff/cat/share/t_record_summary.tt2
index 6f9f030..29f0bb8 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_record_summary.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_record_summary.tt2
@@ -91,7 +91,7 @@
 
   <div class="flex-row">
     <div class="flex-cell strong-text">[% l('Bib Call #:') %]</div>
-    <div class="flex-cell flex-2"><!-- FIXME: no bib call no on simple rec --></div>
+    <div class="flex-cell flex-2"><span tooltip-html-unsafe="{{bib_cn_tooltip}}">{{bib_cn}}<span></div>
 
     <div class="flex-cell strong-text"></div>
     <div class="flex-cell"></div>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/record.js b/Open-ILS/web/js/ui/default/staff/cat/services/record.js
index c54bc95..9e1cecd 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/record.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/record.js
@@ -143,8 +143,8 @@ angular.module('egCoreMod')
         },
         templateUrl : './cat/share/t_record_summary',
         controller : 
-                   ['$scope','egCore',
-            function($scope , egCore) {
+                   ['$scope','egCore','$sce',
+            function($scope , egCore , $sce) {
 
                 function loadRecord() {
                     egCore.pcrud.retrieve('bre', $scope.recordId, {
@@ -156,6 +156,29 @@ angular.module('egCoreMod')
                         rec.owner(egCore.org.get(rec.owner()));
                         $scope.record = rec;
                     });
+                    $scope.bib_cn = null;
+                    $scope.bib_cn_tooltip = '';
+                    var label_class = egCore.env.aous['cat.default_classification_scheme'] || 1;
+                    egCore.net.request(
+                        'open-ils.cat',
+                        'open-ils.cat.biblio.record.marc_cn.retrieve',
+                        $scope.recordId,
+                        label_class
+                    ).then(function(cn_array) {
+                        var tooltip = '';
+                        if (cn_array.length > 0) {
+                            for (var field in cn_array[0]) {
+                                $scope.bib_cn = cn_array[0][field];
+                            }
+                            for (var i in cn_array) {
+                                for (var field in cn_array[i]) {
+                                    tooltip += 
+                                        field + ' : ' + cn_array[i][field] + '<br>';
+                                }
+                            }
+                            $scope.bib_cn_tooltip = $sce.trustAsHtml(tooltip);
+                        }
+                    });
                 }
 
                 $scope.$watch('recordId', 

commit 86b7e0e60726ab5f117d93990ced8d5e819e2dce
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Oct 6 18:26:30 2015 +0000

    webstaff: teach side-by-side editing to the record merge interface
    
    It is now possible to edit the lead record in place (in
    flat editor mode).
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2 b/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
index 7efdcbc..53821f8 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
@@ -9,8 +9,13 @@
           <div class="col-xs-6">
             <h4>[% l('Lead record') %]</h4>
             <div ng-if="lead_id">
-               <button class="btn btn-default btn-sm" ng-click="edit_lead()">[% l('Edit') %]</button>
-               <eg-record-breaker record-id="lead_id"></eg-record-breaker>
+               <button class="btn btn-default btn-sm" ng-class="{disabled : editing_inplace}" ng-click="edit_lead_inplace()">[% l('Edit') %]</button>
+               <button class="btn btn-default btn-sm" ng-class="{disabled : editing_inplace}" ng-click="edit_lead()">[% l('Edit using full editor') %]</button>
+               <eg-marc-edit-record dirty-flag="dirty_flag" record-id="lead_id"
+                         record-type="bre" flat-only="true" embedded="true" 
+                         ng-if="editing_inplace" on-save="post_edit_inplace">
+               </eg-marc-edit-record>
+               <eg-record-breaker record-id="lead_id" ng-if="!editing_inplace"></eg-record-breaker>
                <eg-volume-list record-id="lead_id"></eg-volume-list>
             </div>
             <div ng-if="!lead_id">
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 de620f0..7ace380 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
@@ -550,6 +550,7 @@ function($scope,  $q , $routeParams,  bucketSvc,  egCore,  $window,
                 ['$scope', '$modalInstance', function($scope, $modalInstance) {
                 $scope.records = [];
                 $scope.lead_id = 0;
+                $scope.editing_inplace = false;
                 angular.forEach(records, function(rec) {
                     $scope.records.push({ id : rec.id });
                 });
@@ -574,6 +575,12 @@ function($scope,  $q , $routeParams,  bucketSvc,  egCore,  $window,
                         }
                     });
                 }
+                $scope.post_edit_inplace = function() {
+                    $scope.editing_inplace = false;
+                }
+                $scope.edit_lead_inplace = function() {
+                    $scope.editing_inplace = true;
+                }
                 $scope.edit_lead = function() {
                     var lead_id = $scope.lead_id;
                     $modal.open({
@@ -588,7 +595,7 @@ function($scope,  $q , $routeParams,  bucketSvc,  egCore,  $window,
                             $scope.cancel = function () { $modalInstance.dismiss() }
                         }]
                     }).result.then(function() {
-                        // TODO: need a way to force a refresh of the egRecordHtml, as
+                        // TODO: need a way to force a refresh of the egRecordBreaker, as
                         // the record ID does not change
                     });
                 };

commit 19ce00f906a59d8acc0d9290d539927cf2499613
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Oct 6 18:24:16 2015 +0000

    webstaff: teach MARC editor some more tricks
    
    [1] Now accepts a flat-only attribute specifying that
        the editor should be restricted to flat mode.
    [2] Now accepts an embedded attribute specifying that
        various stuff that isn't useful when the editor
        is embedded in another control should be ignored.
    [3] The width of the flat editor's textarea is now
        bounded by its container.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 f415950..808438f 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
@@ -1,5 +1,5 @@
 <div>
-  <div ng-show="bre" class="row pad-vert marcfastitemadd" ng-hide="brandNewRecord">
+  <div ng-show="bre && !(brandNewRecord || embedded)" class="row pad-vert marcfastitemadd">
     <div class="col-md-2">
       <label><input type="checkbox" ng-model="enable_fast_add"/> [% l('Add Item') %]</label>
     </div>
@@ -12,7 +12,7 @@
   </div>
 
   <div class="pad-vert row marctypesource">
-    <div class="col-md-2">
+    <div class="col-md-2" ng-show="!flatOnly">
       <label>
         <input type="checkbox" ng-model="flatEditor" ng-change="refreshVisual()"/>
         [% l('Flat Text Editor') %]
@@ -30,13 +30,13 @@
     <div class="col-md-3">
       <div class="btn-group">
         <span class="btn-group">
-          <button class="btn btn-default" ng-show="record_type == 'bre'" ng-click="validateHeadings()">[% l('Validate') %]</button>
+          <button class="btn btn-default" ng-show="record_type == 'bre' && !flatOnly" ng-click="validateHeadings()">[% l('Validate') %]</button>
         </span>
         <span class="btn-group">
           <button class="btn btn-primary" ng-click="saveRecord()">{{ saveLabel || "[% l('Save') %]"}}</button>
         </span>
         <span class="btn-group">
-          <button ng-hide="brandNewRecord || Record().deleted()" class="btn btn-default" ng-click="deleteRecord()">[% l('Delete') %]</button>
+          <button ng-hide="brandNewRecord || embedded || Record().deleted()" class="btn btn-default" ng-click="deleteRecord()">[% l('Delete') %]</button>
           <button ng-if="!brandNewRecord && Record().deleted()" class="btn btn-default" ng-click="undeleteRecord()">[% l('Undelete') %]</button>
         </span>
       </div>
@@ -44,7 +44,7 @@
   </div>
 
   <div ng-show="flatEditor">
-    <textarea cols="120" rows="40" ng-model="flat_text_marc" ng-blur="saveFlatTextMARC()"></textarea>
+    <textarea class="marcflateditor" cols="120" rows="40" ng-model="flat_text_marc" ng-blur="saveFlatTextMARC()"></textarea>
   </div>
   <div ng-show="!flatEditor">
     <div class="row pad-vert">
diff --git a/Open-ILS/src/templates/staff/css/cat.css.tt2 b/Open-ILS/src/templates/staff/css/cat.css.tt2
index 4faae17..7a5e12d 100644
--- a/Open-ILS/src/templates/staff/css/cat.css.tt2
+++ b/Open-ILS/src/templates/staff/css/cat.css.tt2
@@ -12,6 +12,10 @@
     //background-color: #f5f5f5;
 }
 
+.marcflateditor {
+    max-width: 100%;
+}
+
 .marcfastitemadd, .marctypesource {
     border-bottom: solid thin gray;
 }
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 cb978f3..b32f80e 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
@@ -581,6 +581,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
             // used just to munge some MARCXML client-side, rather
             // than to (immediately) update the database
             inPlaceMode : '@',
+            flatOnly : '@',
+            embedded : '@',
             recordType : '@',
             maxUndo : '@',
             saveLabel : '@'
@@ -620,7 +622,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                 $scope.enable_fast_add = false;
                 $scope.fast_item_callnumber = '';
                 $scope.fast_item_barcode = '';
-                $scope.flatEditor = false;
+                $scope.flatEditor = $scope.flatOnly ? true : false;
                 $scope.brandNewRecord = false;
                 $scope.bib_source = null;
                 $scope.record_type = $scope.recordType || 'bre';

commit 9fa3aa1491ae8feda6d33345a859ef83edeec1a8
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Oct 6 17:30:10 2015 +0000

    webstaff: display MARC records in merge interface in breaker format
    
    Doing this will ease copy-and-paste between records.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2 b/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
index ddcff75..7efdcbc 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
@@ -10,7 +10,7 @@
             <h4>[% l('Lead record') %]</h4>
             <div ng-if="lead_id">
                <button class="btn btn-default btn-sm" ng-click="edit_lead()">[% l('Edit') %]</button>
-               <eg-record-html record-id="lead_id"></eg-record-html>
+               <eg-record-breaker record-id="lead_id"></eg-record-breaker>
                <eg-volume-list record-id="lead_id"></eg-volume-list>
             </div>
             <div ng-if="!lead_id">
@@ -23,7 +23,7 @@
               <tab heading="[% l('Bib [_1]', '{{rec.id}}') %]" ng-repeat="rec in records">
                 <button class="btn btn-default btn-sm" ng-click="use_as_lead(rec)">[% l('Use as lead record') %]</button>
                 <button class="btn btn-default btn-sm" ng-click="drop(rec)">[% l('Remove from consideration') %]</button>
-                <eg-record-html record-id="rec.id"></eg-record-html>
+                <eg-record-breaker record-id="rec.id"></eg-record-breaker>
                 <eg-volume-list record-id="rec.id"></eg-volume-list>
               </tab>
             </tabset>

commit 6ed5e2e821bc2fc38f6b2432e84b8eaa8670c130
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Oct 6 17:28:58 2015 +0000

    webstaff: add egRecordBreaker directive
    
    This directive takes a blob of MARCXML or a bib
    record ID and renders the record in "breaker"
    format.
    
    Example usage:
    
      <eg-record-breaker record-id="1234"></eg-record-breaker>
      <eg-record-breaker marc-xml="xml"></eg-record-breaker>
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/record.js b/Open-ILS/web/js/ui/default/staff/cat/services/record.js
index 4adfe6c..c54bc95 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/record.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/record.js
@@ -75,6 +75,60 @@ angular.module('egCoreMod')
     }
 })
 
+.directive('egRecordBreaker', function() {
+    return {
+        restrict : 'AE',
+        template : '<pre>{{breaker}}</pre>',
+        scope : {
+            recordId : '=',
+            marcXml  : '@',
+        },
+        link : function(scope, element, attrs) {
+            scope.element = angular.element(element);
+
+            // kill refs to destroyed DOM elements
+            element.bind("$destroy", function() {
+                delete scope.element;
+            });
+        },
+        controller : 
+                   ['$scope','egCore',
+            function($scope , egCore) {
+
+                function loadRecordBreaker() {
+                    var xml;
+                    if ($scope.marcXml) {
+                        $scope.breaker = new MARC21.Record({ marcxml : $scope.marcXml }).toBreaker();
+                    } else {
+                        egCore.pcrud.retrieve('bre', $scope.recordId)
+                        .then(function(rec) {
+                            $scope.breaker = new MARC21.Record({ marcxml : rec.marc() }).toBreaker();
+                        });
+                    }
+                }
+
+                $scope.$watch('recordId', 
+                    function(newVal, oldVal) {
+                        if (newVal && newVal !== oldVal) {
+                            loadRecordBreaker();
+                        }
+                    }
+                );
+                $scope.$watch('marcXml', 
+                    function(newVal, oldVal) {
+                        if (newVal && newVal !== oldVal) {
+                            loadRecordBreaker();
+                        }
+                    }
+                );
+
+                if ($scope.recordId || $scope.marcXml) 
+                    loadRecordBreaker();
+            }
+        ]
+    }
+})
+
 /*
  * A record='foo' attribute is required as a storage location of the 
  * retrieved record

commit 3d06623543fa676b67bde8e04c8b5e82e301bce9
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Oct 6 13:56:45 2015 -0400

    webstaff: Allow editing of empty volumes
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 bdcacfc..a933876 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
@@ -796,6 +796,18 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
         return cp_list;
     }
 
+    function gatherSelectedEmptyVolumeIds () {
+        var cn_id_list = [];
+        angular.forEach(
+            $scope.holdingsGridControls.selectedItems(),
+            function (item) {
+                if (item.copy_count == 0)
+                    cn_id_list.push(item.call_number.id)
+            }
+        );
+        return cn_id_list;
+    }
+
     function gatherSelectedVolumeIds () {
         var cn_id_list = [];
         angular.forEach(
@@ -919,6 +931,9 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
             null, 'edit-these-copies', {
                 record_id: $scope.record_id,
                 copies: gatherSelectedHoldingsIds(),
+                raw: gatherSelectedEmptyVolumeIds().map(
+                    function(v){ return { callnumber : v } }
+                ),
                 hide_vols : hide_vols,
                 hide_copies : hide_copies
             }
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
index 0bebe6c..a0806c5 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
@@ -259,6 +259,7 @@ function(egCore , $q) {
                     svc.copies = svc.copies.concat(flat);
                 } else if (empty) {
                     svc.copies.push({
+                        id_list    : [],
                         owner_id   : owner_id,
                         owner_list : owner_name_list,
                         call_number: egCore.idl.toHash(cn),
diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 013b744..d76847f 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -974,11 +974,8 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
 
                 $scope.record_id = data.record_id;
 
-                if (data.copies && data.copies.length)
-                    return itemSvc.fetchIds(data.copies);
-
-                if (data.raw && data.raw.length) {
-                    $scope.dirty = true;
+                function fetchRaw () {
+                    if (!$scope.only_vols) $scope.dirty = true;
                     $scope.add_vols_copies = true;
 
                     /* data.raw data structure looks like this:
@@ -1001,7 +998,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                                     var cp = new egCore.idl.acp();
                                     cp.call_number( cn );
                                     cp.id( --itemSvc.new_cp_id );
-                                    cp.isnew( true );
+                                    if (!$scope.only_vols) cp.isnew( true );
                                     cp.circ_lib( proto.owner || egCore.auth.user().ws_ou() );
 
                                     cp.deposit(0);
@@ -1059,6 +1056,12 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
 
                     return itemSvc.copies;
                 }
+
+                if (data.copies && data.copies.length)
+                    return itemSvc.fetchIds(data.copies).then(fetchRaw);
+
+                return fetchRaw();
+
             }
 
         }).then( function() {

commit 686f51e7deac84404485278833333c9ee0b04ba2
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Oct 6 16:24:00 2015 +0000

    webstaff: fix saving copy price in vol/copy editor
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 9f7faaa..013b744 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -1291,6 +1291,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
         createSimpleUpdateWatcher('holdable');
         createSimpleUpdateWatcher('fine_level');
         createSimpleUpdateWatcher('loan_duration');
+        createSimpleUpdateWatcher('price');
         createSimpleUpdateWatcher('cost');
         createSimpleUpdateWatcher('deposit');
         createSimpleUpdateWatcher('deposit_amount');

commit 6df934e21a61456dcc50d78b2a4fcf19edd16bac
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Oct 6 14:12:43 2015 +0000

    webstaff: unbreak tabs in volume/copy editor
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
index f37726e..2fb627e 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
@@ -12,7 +12,7 @@ eg-navbar {
 
 <!-- tabbed copy data view -->
 
-<ul class="nav nav-tabs" ng-if="!embedded">
+<ul class="nav nav-tabs" ng-show="!embedded">
   <li ng-class="{active : tab == 'edit'}">
     <a ng-click="tab = 'edit'" >[% l('Edit') %]</a>
   </li>

commit f905064514dfabcbf20cecacd2019b26848e57fd
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Oct 5 22:02:44 2015 +0000

    webstaff: teach the merge record dialog how to edit volumes
    
    This adds an "edit volumes" button under each record in the
    merge records interface that opens a modal embedding the
    volume editor.
    
    The embedding is done via an iframe in lieu of splitting the
    relevant bits of the volume/copy editor into an independent
    service.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 82d087b..24b5ac9 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/record/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/index.tt2
@@ -8,6 +8,7 @@
 [% 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/services/eframe.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/marcrecord.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/tagtable.js"></script>
diff --git a/Open-ILS/src/templates/staff/cat/share/t_embedded_volcopy.tt2 b/Open-ILS/src/templates/staff/cat/share/t_embedded_volcopy.tt2
new file mode 100644
index 0000000..4c704d7
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/share/t_embedded_volcopy.tt2
@@ -0,0 +1,14 @@
+<div>
+  <div class="modal-header">
+    <button type="button" class="close"
+      ng-click="cancel()" aria-hidden="true">×</button>
+    <h4 class="modal-title">[% l('Edit volumes') %]</h4>
+  </div>
+  <div class="modal-body">
+    <eg-embed-frame save-space="500" url="volcopy_url"></eg-embed-frame>
+  </div>
+  <div class="modal-footer">
+    <input type="submit" ng-click="ok()"
+        class="btn btn-primary" value="[% l('Done') %]"/>
+  </div>
+</div>
diff --git a/Open-ILS/src/templates/staff/cat/share/t_volume_list.tt2 b/Open-ILS/src/templates/staff/cat/share/t_volume_list.tt2
index db77586..56f1c07 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_volume_list.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_volume_list.tt2
@@ -1,10 +1,12 @@
 <eg-grid
     id-field="index"
-    features="-menu,-index,-picker,-pagination,-action,-display,-sort,-multisort,-multiselect"
+    features="-index,-picker,-pagination,-action,-display,-sort,-multisort,-multiselect"
     items-provider="holdingsGridDataProvider"
     grid-controls="holdingsGridControls"
     persist-key="cat.record_overlay.holdings">
 
+    <eg-grid-menu-item handler="edit_volumes" label="[% l('Edit volumes') %]" />
+
     <eg-grid-field label="[% l('Owning Library') %]" path="owner_label" flex="4" align="right" visible></eg-grid-field>
     <eg-grid-field label="[% l('Call Number') %]"    path="call_number.label" visible></eg-grid-field>
     <eg-grid-field label="[% l('# Copies') %]"       path="copy_count" visible></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
index 74ba520..0bebe6c 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
@@ -281,8 +281,8 @@ function(egCore , $q) {
         },
         templateUrl: './cat/share/t_volume_list',
         controller:
-                   ['$scope','holdingsSvc','egCore','egGridDataProvider',
-            function($scope , holdingsSvc , egCore , egGridDataProvider) {
+                   ['$scope','holdingsSvc','egCore','egGridDataProvider','$modal',
+            function($scope , holdingsSvc , egCore , egGridDataProvider,  $modal) {
                 var holdingsSvcInst = new holdingsSvc();
 
                 $scope.holdingsGridControls = {};
@@ -291,6 +291,45 @@ function(egCore , $q) {
                         return this.arrayNotifier(holdingsSvcInst.copies, offset, count);
                     }
                 });
+
+                function gatherHoldingsIds () {
+                    var cp_id_list = [];
+                    angular.forEach(
+                        $scope.holdingsGridControls.allItems(),
+                        function (item) { cp_id_list = cp_id_list.concat(item.id_list) }
+                    );
+                    return cp_id_list;
+                }
+
+                $scope.edit_volumes = function () {
+                    egCore.net.request(
+                        'open-ils.actor',
+                        'open-ils.actor.anon_cache.set_value',
+                        null, 'edit-these-copies', {
+                            record_id: $scope.recordId,
+                            copies: gatherHoldingsIds(),
+                            hide_vols : false,
+                            hide_copies : true
+                        }
+                    ).then(function(key) {
+                        if (key) {
+                            $modal.open({
+                                templateUrl: './cat/share/t_embedded_volcopy',
+                                size: 'lg',
+                                controller:
+                                    ['$scope', '$modalInstance', function($scope, $modalInstance) {
+                                    $scope.volcopy_url = 
+                                        egCore.env.basePath + 'cat/volcopy/' + key + '/embedded';
+                                    $scope.ok = function(args) { $modalInstance.close(args) }
+                                    $scope.cancel = function () { $modalInstance.dismiss() }
+                                }]
+                            }).result.then(function() {
+                                load_holdings();
+                            });
+                        }
+                    });
+                }
+
                 function load_holdings() {
                     holdingsSvcInst.fetch({
                         rid   : $scope.recordId,

commit ff204cfb0d7ef49a036266116c45780a7c924ec8
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Oct 5 22:00:57 2015 +0000

    webstaff: teach volcopy editor how to be embeddable
    
    The route /eg/staff/cat/volcopy/$key/embedded will
    load the volume/copy editor in an embedded mode; specifically,
    
    [1] the navbar and record summary will not be displayed
    [2] "Save & Exit" becomes "Save changes"
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
index cb40f24..519765e 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
@@ -52,7 +52,10 @@
                         </div>
                     </div>
                     <div class="col-xs-2" ng-show="only_vols">
-                        <button class="btn btn-default center-block" ng-click="workingToComplete() && saveAndExit()" type="button">[% l('Save & Exit') %]</button>
+                        <button class="btn btn-default center-block" ng-click="workingToComplete() && saveAndExit()" type="button">
+                            <span ng-if="embedded">[% l('Save changes') %]</span>
+                            <span ng-if="!embedded">[% l('Save & Exit') %]</span>
+                        </button>
                     </div>
                 </div>
             </div>
diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
index 0379fad..f37726e 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_view.tt2
@@ -1,8 +1,18 @@
-<eg-record-summary record-id="record_id" record="summaryRecord"></eg-record-summary>
+<style ng-if="embedded">
+eg-navbar {
+    display: none;
+}
+#top-content-container {
+    padding-top: 0px;
+}
+</style>
+
+<eg-record-summary ng-if="!embedded"
+     record-id="record_id" record="summaryRecord"></eg-record-summary>
 
 <!-- tabbed copy data view -->
 
-<ul class="nav nav-tabs">
+<ul class="nav nav-tabs" ng-if="!embedded">
   <li ng-class="{active : tab == 'edit'}">
     <a ng-click="tab = 'edit'" >[% l('Edit') %]</a>
   </li>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 990062b..9f7faaa 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -25,6 +25,11 @@ angular.module('egVolCopy',
         resolve : resolver
     });
 
+    $routeProvider.when('/cat/volcopy/:dataKey/:mode', {
+        templateUrl: './cat/volcopy/t_view',
+        controller: 'EditCtrl',
+        resolve : resolver
+    });
 })
 
 .factory('itemSvc', 
@@ -654,6 +659,8 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
         }
     };
 
+    $scope.embedded = ($routeParams.mode && $routeParams.mode == 'embedded') ? true : false;
+
     $scope.saveDefaults = function () {
         egCore.hatch.setItem('cat.copy.defaults', $scope.defaults);
     }

commit be0e433ea8b201e3b593bcd9cba2df996b1d2e88
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Oct 5 20:25:06 2015 +0000

    webstaff: make record merge interface display volumes for all OUs
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
index 9721425..74ba520 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
@@ -294,7 +294,7 @@ function(egCore , $q) {
                 function load_holdings() {
                     holdingsSvcInst.fetch({
                         rid   : $scope.recordId,
-                        org   : egCore.org.get(egCore.auth.user().ws_ou()), // TOOD: use root OU?
+                        org   : egCore.org.root(),
                         copy  : false,
                         vol   : true,
                         empty : true

commit e901025f0714beaf9d58e54a27c615d4a15d993f
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Oct 5 20:24:43 2015 +0000

    webstaff: add unit test for egOrg.root()
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/test/unit/egOrg.js b/Open-ILS/web/js/ui/default/staff/test/unit/egOrg.js
index f919d66..253d48b 100644
--- a/Open-ILS/web/js/ui/default/staff/test/unit/egOrg.js
+++ b/Open-ILS/web/js/ui/default/staff/test/unit/egOrg.js
@@ -32,6 +32,11 @@ describe('egOrg', function(){
         mkTree(egIDL, egEnv);
         expect(egOrg.fullPath(4, true)).toEqual([4, 2, 1]);
     }));
+
+    it('should provide root', inject(function(egIDL, egEnv, egOrg) {
+        mkTree(egIDL, egEnv);
+        expect(egOrg.root().id()).toEqual(1);
+    }));
 });
 
 

commit 8d9c75d587953cad9a6e20e8e7ff60dc04da6ecb
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Oct 5 20:21:23 2015 +0000

    webstaff: add egOrg.root() method
    
    Also, start adding some documentation for egOrg.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 dcf1d52..e9fcc2d 100644
--- a/Open-ILS/web/js/ui/default/staff/services/org.js
+++ b/Open-ILS/web/js/ui/default/staff/services/org.js
@@ -1,7 +1,19 @@
 /**
  * Core Service - egOrg
  *
- * TODO: more docs
+ * This provides access to the organizational unit tree and
+ * caches it in browser session storage.
+ *
+ * Methods include:
+ *   get()  - retrieve OU based on ID or aou object
+ *   list() - retrieve flattened list of OUs
+ *   tree() - retrieve OU as tree
+ *   root() - get aou object representing root of the OU tree
+ *   ancestors() - get ancestors of supplied OU
+ *   descendants() - get descendants of supplied OU
+ * 
+ * TODO more to document
+ * 
  */
 angular.module('egCoreMod')
 
@@ -29,6 +41,11 @@ function($q,  egEnv,  egAuth,  egNet) {
         return egEnv.aou.tree;
     }
 
+    // get the root OU
+    service.root = function() {
+        return egEnv.aou.list[0];
+    }
+
     // list of org_unit objects or IDs for ancestors + me
     service.ancestors = function(node_or_id, as_id) {
         var node = service.get(node_or_id);

commit 11cf343871aaa09da7c6c890e6843bc034586f1e
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Oct 5 19:01:30 2015 +0000

    webstaff: go to lead record after merge from bucket
    
    After a successful record merge initiated from a record
    bucket, go to the lead record rather than refreshing
    the bucket display.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 0b9075e..de620f0 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
@@ -603,7 +603,8 @@ function($scope,  $q , $routeParams,  bucketSvc,  egCore,  $window,
                 args.lead_id,
                 args.records.map(function(val) { return val.id; })
             ).then(function() {
-                drawBucket();
+                $window.location.href =
+                    egCore.env.basePath + 'cat/catalog/record/' + args.lead_id;
             });
         });
     }

commit 9ee99166172a666184191181bd7ca03018403fa4
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Fri Oct 2 20:53:29 2015 +0000

    webstaff: teach the record merge modal how to display volumes
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 4f3c1ae..82d087b 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/record/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/index.tt2
@@ -13,6 +13,7 @@
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/tagtable.js"></script>
 [% INCLUDE 'staff/cat/share/marcedit_strings.tt2' %]
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/marcedit.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/holdings.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/holds.js"></script>
 [% INCLUDE 'staff/circ/share/hold_strings.tt2' %]
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/bucket/record/app.js"></script>
diff --git a/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2 b/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
index fb5e4fa..ddcff75 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
@@ -11,6 +11,7 @@
             <div ng-if="lead_id">
                <button class="btn btn-default btn-sm" ng-click="edit_lead()">[% l('Edit') %]</button>
                <eg-record-html record-id="lead_id"></eg-record-html>
+               <eg-volume-list record-id="lead_id"></eg-volume-list>
             </div>
             <div ng-if="!lead_id">
                 [% l('Please select a lead record from the right...') %]
@@ -23,6 +24,7 @@
                 <button class="btn btn-default btn-sm" ng-click="use_as_lead(rec)">[% l('Use as lead record') %]</button>
                 <button class="btn btn-default btn-sm" ng-click="drop(rec)">[% l('Remove from consideration') %]</button>
                 <eg-record-html record-id="rec.id"></eg-record-html>
+                <eg-volume-list record-id="rec.id"></eg-volume-list>
               </tab>
             </tabset>
           </div>
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 313e9ab..0b9075e 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
@@ -13,7 +13,7 @@
  */
 
 angular.module('egCatRecordBuckets', 
-    ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod', 'egMarcMod'])
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod', 'egMarcMod', 'egHoldingsMod'])
 
 .config(function($routeProvider, $locationProvider, $compileProvider) {
     $locationProvider.html5Mode(true);

commit 090474e49b0a33b6449dcc4bac93271efc10f818
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Fri Oct 2 20:50:03 2015 +0000

    webstaff: more changes to holdingsSvc
    
    - convert holdingsSvc from a singleton to a provider
      of holdings service instance objects
    - adjust the catalog app accordingly
    - add a new egVolumeList directive that displays
      a list of volumes attached to a bib record, including
      owning library, call number, and copies
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/share/t_volume_list.tt2 b/Open-ILS/src/templates/staff/cat/share/t_volume_list.tt2
new file mode 100644
index 0000000..db77586
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/share/t_volume_list.tt2
@@ -0,0 +1,12 @@
+<eg-grid
+    id-field="index"
+    features="-menu,-index,-picker,-pagination,-action,-display,-sort,-multisort,-multiselect"
+    items-provider="holdingsGridDataProvider"
+    grid-controls="holdingsGridControls"
+    persist-key="cat.record_overlay.holdings">
+
+    <eg-grid-field label="[% l('Owning Library') %]" path="owner_label" flex="4" align="right" visible></eg-grid-field>
+    <eg-grid-field label="[% l('Call Number') %]"    path="call_number.label" visible></eg-grid-field>
+    <eg-grid-field label="[% l('# Copies') %]"       path="copy_count" visible></eg-grid-field>
+
+</eg-grid>
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 ae1303e..bdcacfc 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
@@ -231,6 +231,8 @@ function($scope , $routeParams , $location , $window , $q , egCore) {
 function($scope , $routeParams , $location , $window , $q , egCore , egHolds , egCirc,  egConfirmDialog,
          egGridDataProvider , egHoldGridActions , $timeout , $modal , holdingsSvc , egUser , conjoinedSvc) {
 
+    var holdingsSvcInst = new holdingsSvc();
+
     // set record ID on page load if available...
     $scope.record_id = $routeParams.record_id;
     $scope.summary_pane_record;
@@ -505,7 +507,7 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
     $scope.holdingsGridControls = {};
     $scope.holdingsGridDataProvider = egGridDataProvider.instance({
         get : function(offset, count) {
-            return this.arrayNotifier(holdingsSvc.copies, offset, count);
+            return this.arrayNotifier(holdingsSvcInst.copies, offset, count);
         }
     });
 
@@ -706,7 +708,7 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
     $scope.holdings_record_id_changed = function(id) {
         if ($scope.record_id != id) $scope.record_id = id;
         console.log('record id changed to ' + id + ', loading new holdings');
-        holdingsSvc.fetch({
+        holdingsSvcInst.fetch({
             rid : $scope.record_id,
             org : $scope.holdings_ou,
             copy: $scope.holdings_show_copies,
@@ -721,7 +723,7 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
     $scope.holdings_ou = egCore.org.get(egCore.auth.user().ws_ou());
     $scope.holdings_ou_changed = function(org) {
         $scope.holdings_ou = org;
-        holdingsSvc.fetch({
+        holdingsSvcInst.fetch({
             rid : $scope.record_id,
             org : $scope.holdings_ou,
             copy: $scope.holdings_show_copies,
@@ -735,7 +737,7 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
     $scope.holdings_cb_changed = function(cb,newVal,norefresh) {
         $scope[cb] = newVal;
         egCore.hatch.setItem('cat.' + cb, newVal);
-        if (!norefresh) holdingsSvc.fetch({
+        if (!norefresh) holdingsSvcInst.fetch({
             rid : $scope.record_id,
             org : $scope.holdings_ou,
             copy: $scope.holdings_show_copies,
@@ -979,7 +981,7 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
                 }
             ).then(function(success) {
                 if (success) {
-                    holdingsSvc.fetchAgain().then(function() {
+                    holdingsSvcInst.fetchAgain().then(function() {
                         $scope.holdingsGridDataProvider.refresh();
                     });
                 } else {
@@ -1017,13 +1019,13 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
                                 copy_ids,
                                 { events: ['TITLE_LAST_COPY', 'COPY_DELETE_WARNING'] }
                             ).then(function(resp) {
-                                holdingsSvc.fetchAgain().then(function() {
+                                holdingsSvcInst.fetchAgain().then(function() {
                                     $scope.holdingsGridDataProvider.refresh();
                                 });
                             });
                         });
                     } else {
-                        holdingsSvc.fetchAgain().then(function() {
+                        holdingsSvcInst.fetchAgain().then(function() {
                             $scope.holdingsGridDataProvider.refresh();
                         });
                     }
@@ -1058,7 +1060,7 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
 
     $scope.selectedHoldingsDamaged = function () {
         egCirc.mark_damaged(gatherSelectedHoldingsIds()).then(function() {
-            holdingsSvc.fetchAgain().then(function() {
+            holdingsSvcInst.fetchAgain().then(function() {
                 $scope.holdingsGridDataProvider.refresh();
             });
         });
@@ -1066,7 +1068,7 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
 
     $scope.selectedHoldingsMissing = function () {
         egCirc.mark_missing(gatherSelectedHoldingsIds()).then(function() {
-            holdingsSvc.fetchAgain().then(function() {
+            holdingsSvcInst.fetchAgain().then(function() {
                 $scope.holdingsGridDataProvider.refresh();
             });
         });
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
index 8da1443..9721425 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
@@ -1,18 +1,18 @@
-angular.module('egHoldingsMod', ['egCoreMod'])
+angular.module('egHoldingsMod', ['egCoreMod','egGridMod'])
 
 .factory('holdingsSvc', 
        ['egCore','$q',
 function(egCore , $q) {
 
-    var service = {
-        ongoing : false,
-        copies : [], // record search results
-        index : 0, // search grid index
-        org : null,
-        rid : null
+    var service = function() {
+        this.ongoing = false;
+        this.copies = []; // record search results
+        this.index = 0; // search grid index
+        this.org = null;
+        this.rid = null;
     };
 
-    service.flesh = {   
+    service.prototype.flesh = {   
         flesh : 2, 
         flesh_fields : {
             acp : ['status','location'],
@@ -20,19 +20,20 @@ function(egCore , $q) {
         }
     }
 
-    service.fetchAgain = function() {
-        return service.fetch({
-            rid: service.rid,
-            org: service.org,
-            copy: service.copy,
-            vol: service.vol,
-            empty: service.empty
+    service.prototype.fetchAgain = function() {
+        return this.fetch({
+            rid: this.rid,
+            org: this.org,
+            copy: this.copy,
+            vol: this.vol,
+            empty: this.empty
         })
-    }
+    };
 
     // resolved with the last received copy
-    service.fetch = function(opts) {
-        if (service.ongoing) {
+    service.prototype.fetch = function(opts) {
+        var svc = this;
+        if (svc.ongoing) {
             console.log('Skipping fetch, ongoing = true');
             return $q.when();
         }
@@ -46,16 +47,16 @@ function(egCore , $q) {
         if (!rid) return $q.when();
         if (!org) return $q.when();
 
-        service.ongoing = true;
+        svc.ongoing = true;
 
-        service.rid = rid;
-        service.org = org;
-        service.copy = opts.copy;
-        service.vol = opts.vol;
-        service.empty = opts.empty;
+        svc.rid = rid;
+        svc.org = org;
+        svc.copy = opts.copy;
+        svc.vol = opts.vol;
+        svc.empty = opts.empty;
 
-        service.copies = [];
-        service.index = 0;
+        svc.copies = [];
+        svc.index = 0;
 
         var org_list = egCore.org.descendants(org.id(), true);
         console.log('Holdings fetch with: rid='+rid+' org='+org_list+' copy='+copy+' vol='+vol+' empty='+empty);
@@ -63,10 +64,10 @@ function(egCore , $q) {
         return egCore.pcrud.search(
             'acn',
             {record : rid, owning_lib : org_list, deleted : 'f'},
-            service.flesh
+            svc.flesh
         ).then(
             function() { // finished
-                service.copies = service.copies.sort(
+                svc.copies = svc.copies.sort(
                     function (a, b) {
                         function compare_array (x, y, i) {
                             if (x[i] && y[i]) { // both have values
@@ -108,7 +109,7 @@ function(egCore , $q) {
                 // create a label using just the unique part of the owner list
                 var index = 0;
                 var prev_owner_list;
-                angular.forEach(service.copies, function (cp) {
+                angular.forEach(svc.copies, function (cp) {
                     if (!prev_owner_list) {
                         cp.owner_label = cp.owner_list.join(' ... ');
                     } else {
@@ -124,7 +125,7 @@ function(egCore , $q) {
                     prev_owner_list = cp.owner_list.slice();
                 });
 
-                var new_list = service.copies;
+                var new_list = svc.copies;
                 if (!copy || !vol) { // collapse copy rows, supply a count instead
 
                     index = 0;
@@ -219,8 +220,8 @@ function(egCore , $q) {
                     }
                 }
 
-                service.copies = new_list;
-                service.ongoing = false;
+                svc.copies = new_list;
+                svc.ongoing = false;
             },
 
             null, // error
@@ -255,9 +256,9 @@ function(egCore , $q) {
                         flat.push(flat_cp);
                     });
 
-                    service.copies = service.copies.concat(flat);
+                    svc.copies = svc.copies.concat(flat);
                 } else if (empty) {
-                    service.copies.push({
+                    svc.copies.push({
                         owner_id   : owner_id,
                         owner_list : owner_name_list,
                         call_number: egCore.idl.toHash(cn),
@@ -268,7 +269,48 @@ function(egCore , $q) {
                 return cn;
             }
         );
-    }
+    };
 
     return service;
-}]);
+}])
+.directive("egVolumeList", function () {
+    return {
+        restrict:   'AE',
+        scope: {
+            recordId : '='
+        },
+        templateUrl: './cat/share/t_volume_list',
+        controller:
+                   ['$scope','holdingsSvc','egCore','egGridDataProvider',
+            function($scope , holdingsSvc , egCore , egGridDataProvider) {
+                var holdingsSvcInst = new holdingsSvc();
+
+                $scope.holdingsGridControls = {};
+                $scope.holdingsGridDataProvider = egGridDataProvider.instance({
+                    get : function(offset, count) {
+                        return this.arrayNotifier(holdingsSvcInst.copies, offset, count);
+                    }
+                });
+                function load_holdings() {
+                    holdingsSvcInst.fetch({
+                        rid   : $scope.recordId,
+                        org   : egCore.org.get(egCore.auth.user().ws_ou()), // TOOD: use root OU?
+                        copy  : false,
+                        vol   : true,
+                        empty : true
+                    }).then(function() {
+                        $scope.holdingsGridDataProvider.refresh();
+                    });
+                };
+                $scope.$watch('recordId',
+                    function(newVal, oldVal) {
+                        if (newVal && newVal !== oldVal) {
+                            load_holdings();
+                        }
+                    }
+                );
+                load_holdings();
+            }]
+    }
+})
+;

commit 0732c79142e208579c6857a5772e37432a28372d
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Fri Oct 2 19:09:50 2015 +0000

    webstaff: break holdingsSvc out of catalog/app.js
    
    This will allow using its logic for constructing
    volume/copy lists in other places.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/index.tt2 b/Open-ILS/src/templates/staff/cat/catalog/index.tt2
index c46a026..d8a1a53 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/index.tt2
@@ -16,6 +16,7 @@
 [% INCLUDE 'staff/circ/share/circ_strings.tt2' %]
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/circ/services/holds.js"></script>
 [% INCLUDE 'staff/circ/share/hold_strings.tt2' %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/holdings.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/catalog/app.js"></script>
 <script>
   angular.module('egCoreMod').run(['egStrings', function(s) {
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 ce8acac..ae1303e 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','ngLocationUpdate','egCoreMod','egGridMod', 'egMarcMod', 'egUserMod'])
+angular.module('egCatalogApp', ['ui.bootstrap','ngRoute','ngLocationUpdate','egCoreMod','egGridMod', 'egMarcMod', 'egUserMod', 'egHoldingsMod'])
 
 .config(function($routeProvider, $locationProvider, $compileProvider) {
     $locationProvider.html5Mode(true);
@@ -1332,279 +1332,6 @@ function($scope , $location , $routeParams) {
     }
 })
 
-.factory('holdingsSvc', 
-       ['egCore','$q',
-function(egCore , $q) {
-
-    var service = {
-        ongoing : false,
-        copies : [], // record search results
-        index : 0, // search grid index
-        org : null,
-        rid : null
-    };
-
-    service.flesh = {   
-        flesh : 2, 
-        flesh_fields : {
-            acp : ['status','location'],
-            acn : ['prefix','suffix','copies']
-        }
-    }
-
-    service.fetchAgain = function() {
-        return service.fetch({
-            rid: service.rid,
-            org: service.org,
-            copy: service.copy,
-            vol: service.vol,
-            empty: service.empty
-        })
-    }
-
-    // resolved with the last received copy
-    service.fetch = function(opts) {
-        if (service.ongoing) {
-            console.log('Skipping fetch, ongoing = true');
-            return $q.when();
-        }
-
-        var rid = opts.rid;
-        var org = opts.org;
-        var copy = opts.copy;
-        var vol = opts.vol;
-        var empty = opts.empty;
-
-        if (!rid) return $q.when();
-        if (!org) return $q.when();
-
-        service.ongoing = true;
-
-        service.rid = rid;
-        service.org = org;
-        service.copy = opts.copy;
-        service.vol = opts.vol;
-        service.empty = opts.empty;
-
-        service.copies = [];
-        service.index = 0;
-
-        var org_list = egCore.org.descendants(org.id(), true);
-        console.log('Holdings fetch with: rid='+rid+' org='+org_list+' copy='+copy+' vol='+vol+' empty='+empty);
-
-        return egCore.pcrud.search(
-            'acn',
-            {record : rid, owning_lib : org_list, deleted : 'f'},
-            service.flesh
-        ).then(
-            function() { // finished
-                service.copies = service.copies.sort(
-                    function (a, b) {
-                        function compare_array (x, y, i) {
-                            if (x[i] && y[i]) { // both have values
-                                if (x[i] == y[i]) { // need to look deeper
-                                    return compare_array(x, y, ++i);
-                                }
-
-                                if (x[i] < y[i]) { // x is first
-                                    return -1;
-                                } else if (x[i] > y[i]) { // y is first
-                                    return 1;
-                                }
-
-                            } else { // no orgs to compare ...
-                                if (x[i]) return -1;
-                                if (y[i]) return 1;
-                            }
-                            return 0;
-                        }
-
-                        var owner_order = compare_array(a.owner_list, b.owner_list, 0);
-                        if (!owner_order) {
-                            // now compare on CN label
-                            if (a.call_number.label < b.call_number.label) return -1;
-                            if (a.call_number.label > b.call_number.label) return 1;
-
-                            // try copy number
-                            if (a.copy_number < b.copy_number) return -1;
-                            if (a.copy_number > b.copy_number) return 1;
-
-                            // finally, barcode
-                            if (a.barcode < b.barcode) return -1;
-                            if (a.barcode > b.barcode) return 1;
-                        }
-                        return owner_order;
-                    }
-                );
-
-                // create a label using just the unique part of the owner list
-                var index = 0;
-                var prev_owner_list;
-                angular.forEach(service.copies, function (cp) {
-                    if (!prev_owner_list) {
-                        cp.owner_label = cp.owner_list.join(' ... ');
-                    } else {
-                        var current_owner_list = cp.owner_list.slice();
-                        while (current_owner_list[1] && prev_owner_list[1] && current_owner_list[0] == prev_owner_list[0]) {
-                            current_owner_list.shift();
-                            prev_owner_list.shift();
-                        }
-                        cp.owner_label = current_owner_list.join(' ... ');
-                    }
-
-                    cp.index = index++;
-                    prev_owner_list = cp.owner_list.slice();
-                });
-
-                var new_list = service.copies;
-                if (!copy || !vol) { // collapse copy rows, supply a count instead
-
-                    index = 0;
-                    var cp_list = [];
-                    var prev_key;
-                    var current_blob = { copy_count : 0 };
-                    angular.forEach(new_list, function (cp) {
-                        if (!prev_key) {
-                            prev_key = cp.owner_list.join('') + cp.call_number.label;
-                            if (cp.barcode) current_blob.copy_count = 1;
-                            current_blob.index = index++;
-                            current_blob.id_list = cp.id_list;
-                            if (cp.raw) current_blob.raw = cp.raw;
-                            current_blob.call_number = cp.call_number;
-                            current_blob.owner_list = cp.owner_list;
-                            current_blob.owner_label = cp.owner_label;
-                            current_blob.owner_id = cp.owner_id;
-                        } else {
-                            var current_key = cp.owner_list.join('') + cp.call_number.label;
-                            if (prev_key == current_key) { // collapse into current_blob
-                                current_blob.copy_count++;
-                                current_blob.id_list = current_blob.id_list.concat(cp.id_list);
-                                current_blob.raw = current_blob.raw.concat(cp.raw);
-                            } else {
-                                current_blob.barcode = current_blob.copy_count;
-                                cp_list.push(current_blob);
-                                prev_key = current_key;
-                                current_blob = { copy_count : 0 };
-                                if (cp.barcode) current_blob.copy_count = 1;
-                                current_blob.index = index++;
-                                current_blob.id_list = cp.id_list;
-                                if (cp.raw) current_blob.raw = cp.raw;
-                                current_blob.owner_label = cp.owner_label;
-                                current_blob.owner_id = cp.owner_id;
-                                current_blob.call_number = cp.call_number;
-                                current_blob.owner_list = cp.owner_list;
-                            }
-                        }
-                    });
-
-                    current_blob.barcode = current_blob.copy_count;
-                    cp_list.push(current_blob);
-                    new_list = cp_list;
-
-                    if (!vol) { // do the same for vol rows
-
-                        index = 0;
-                        var cn_list = [];
-                        prev_key = '';
-                        current_blob = { copy_count : 0 };
-                        angular.forEach(cp_list, function (cp) {
-                            if (!prev_key) {
-                                prev_key = cp.owner_list.join('');
-                                current_blob.index = index++;
-                                current_blob.id_list = cp.id_list;
-                                if (cp.raw) current_blob.raw = cp.raw;
-                                current_blob.cn_count = 1;
-                                current_blob.copy_count = cp.copy_count;
-                                current_blob.owner_list = cp.owner_list;
-                                current_blob.owner_label = cp.owner_label;
-                                current_blob.owner_id = cp.owner_id;
-                            } else {
-                                var current_key = cp.owner_list.join('');
-                                if (prev_key == current_key) { // collapse into current_blob
-                                    current_blob.cn_count++;
-                                    current_blob.copy_count += cp.copy_count;
-                                    current_blob.id_list = current_blob.id_list.concat(cp.id_list);
-                                    if (cp.raw) current_blob.raw = current_blob.raw.concat(cp.raw);
-                                } else {
-                                    current_blob.barcode = current_blob.copy_count;
-                                    current_blob.call_number = { label : current_blob.cn_count };
-                                    cn_list.push(current_blob);
-                                    prev_key = current_key;
-                                    current_blob = { copy_count : 0 };
-                                    current_blob.index = index++;
-                                    current_blob.id_list = cp.id_list;
-                                    if (cp.raw) current_blob.raw = cp.raw;
-                                    current_blob.owner_label = cp.owner_label;
-                                    current_blob.owner_id = cp.owner_id;
-                                    current_blob.cn_count = 1;
-                                    current_blob.copy_count = cp.copy_count;
-                                    current_blob.owner_list = cp.owner_list;
-                                }
-                            }
-                        });
-    
-                        current_blob.barcode = current_blob.copy_count;
-                        current_blob.call_number = { label : current_blob.cn_count };
-                        cn_list.push(current_blob);
-                        new_list = cn_list;
-    
-                    }
-                }
-
-                service.copies = new_list;
-                service.ongoing = false;
-            },
-
-            null, // error
-
-            // notify reads the stream of copies, one at a time.
-            function(cn) {
-
-                var copies = cn.copies().filter(function(cp){ return cp.deleted() == 'f' });
-                cn.copies([]);
-
-                angular.forEach(copies, function (cp) {
-                    cp.call_number(cn);
-                });
-
-                var owner_id = cn.owning_lib();
-                var owner = egCore.org.get(owner_id);
-
-                var owner_name_list = [];
-                while (owner.parent_ou()) { // we're going to skip the top of the tree...
-                    owner_name_list.unshift(owner.name());
-                    owner = egCore.org.get(owner.parent_ou());
-                }
-
-                if (copies[0]) {
-                    var flat = [];
-                    angular.forEach(copies, function (cp) {
-                        var flat_cp = egCore.idl.toHash(cp);
-                        flat_cp.owner_id = owner_id;
-                        flat_cp.owner_list = owner_name_list;
-                        flat_cp.id_list = [flat_cp.id];
-                        flat_cp.raw = [cp];
-                        flat.push(flat_cp);
-                    });
-
-                    service.copies = service.copies.concat(flat);
-                } else if (empty) {
-                    service.copies.push({
-                        owner_id   : owner_id,
-                        owner_list : owner_name_list,
-                        call_number: egCore.idl.toHash(cn),
-                        raw_call_number: cn
-                    });
-                }
-
-                return cn;
-            }
-        );
-    }
-
-    return service;
-}])
-
 .factory('conjoinedSvc', 
        ['egCore','$q',
 function(egCore , $q) {
diff --git a/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
new file mode 100644
index 0000000..8da1443
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
@@ -0,0 +1,274 @@
+angular.module('egHoldingsMod', ['egCoreMod'])
+
+.factory('holdingsSvc', 
+       ['egCore','$q',
+function(egCore , $q) {
+
+    var service = {
+        ongoing : false,
+        copies : [], // record search results
+        index : 0, // search grid index
+        org : null,
+        rid : null
+    };
+
+    service.flesh = {   
+        flesh : 2, 
+        flesh_fields : {
+            acp : ['status','location'],
+            acn : ['prefix','suffix','copies']
+        }
+    }
+
+    service.fetchAgain = function() {
+        return service.fetch({
+            rid: service.rid,
+            org: service.org,
+            copy: service.copy,
+            vol: service.vol,
+            empty: service.empty
+        })
+    }
+
+    // resolved with the last received copy
+    service.fetch = function(opts) {
+        if (service.ongoing) {
+            console.log('Skipping fetch, ongoing = true');
+            return $q.when();
+        }
+
+        var rid = opts.rid;
+        var org = opts.org;
+        var copy = opts.copy;
+        var vol = opts.vol;
+        var empty = opts.empty;
+
+        if (!rid) return $q.when();
+        if (!org) return $q.when();
+
+        service.ongoing = true;
+
+        service.rid = rid;
+        service.org = org;
+        service.copy = opts.copy;
+        service.vol = opts.vol;
+        service.empty = opts.empty;
+
+        service.copies = [];
+        service.index = 0;
+
+        var org_list = egCore.org.descendants(org.id(), true);
+        console.log('Holdings fetch with: rid='+rid+' org='+org_list+' copy='+copy+' vol='+vol+' empty='+empty);
+
+        return egCore.pcrud.search(
+            'acn',
+            {record : rid, owning_lib : org_list, deleted : 'f'},
+            service.flesh
+        ).then(
+            function() { // finished
+                service.copies = service.copies.sort(
+                    function (a, b) {
+                        function compare_array (x, y, i) {
+                            if (x[i] && y[i]) { // both have values
+                                if (x[i] == y[i]) { // need to look deeper
+                                    return compare_array(x, y, ++i);
+                                }
+
+                                if (x[i] < y[i]) { // x is first
+                                    return -1;
+                                } else if (x[i] > y[i]) { // y is first
+                                    return 1;
+                                }
+
+                            } else { // no orgs to compare ...
+                                if (x[i]) return -1;
+                                if (y[i]) return 1;
+                            }
+                            return 0;
+                        }
+
+                        var owner_order = compare_array(a.owner_list, b.owner_list, 0);
+                        if (!owner_order) {
+                            // now compare on CN label
+                            if (a.call_number.label < b.call_number.label) return -1;
+                            if (a.call_number.label > b.call_number.label) return 1;
+
+                            // try copy number
+                            if (a.copy_number < b.copy_number) return -1;
+                            if (a.copy_number > b.copy_number) return 1;
+
+                            // finally, barcode
+                            if (a.barcode < b.barcode) return -1;
+                            if (a.barcode > b.barcode) return 1;
+                        }
+                        return owner_order;
+                    }
+                );
+
+                // create a label using just the unique part of the owner list
+                var index = 0;
+                var prev_owner_list;
+                angular.forEach(service.copies, function (cp) {
+                    if (!prev_owner_list) {
+                        cp.owner_label = cp.owner_list.join(' ... ');
+                    } else {
+                        var current_owner_list = cp.owner_list.slice();
+                        while (current_owner_list[1] && prev_owner_list[1] && current_owner_list[0] == prev_owner_list[0]) {
+                            current_owner_list.shift();
+                            prev_owner_list.shift();
+                        }
+                        cp.owner_label = current_owner_list.join(' ... ');
+                    }
+
+                    cp.index = index++;
+                    prev_owner_list = cp.owner_list.slice();
+                });
+
+                var new_list = service.copies;
+                if (!copy || !vol) { // collapse copy rows, supply a count instead
+
+                    index = 0;
+                    var cp_list = [];
+                    var prev_key;
+                    var current_blob = { copy_count : 0 };
+                    angular.forEach(new_list, function (cp) {
+                        if (!prev_key) {
+                            prev_key = cp.owner_list.join('') + cp.call_number.label;
+                            if (cp.barcode) current_blob.copy_count = 1;
+                            current_blob.index = index++;
+                            current_blob.id_list = cp.id_list;
+                            if (cp.raw) current_blob.raw = cp.raw;
+                            current_blob.call_number = cp.call_number;
+                            current_blob.owner_list = cp.owner_list;
+                            current_blob.owner_label = cp.owner_label;
+                            current_blob.owner_id = cp.owner_id;
+                        } else {
+                            var current_key = cp.owner_list.join('') + cp.call_number.label;
+                            if (prev_key == current_key) { // collapse into current_blob
+                                current_blob.copy_count++;
+                                current_blob.id_list = current_blob.id_list.concat(cp.id_list);
+                                current_blob.raw = current_blob.raw.concat(cp.raw);
+                            } else {
+                                current_blob.barcode = current_blob.copy_count;
+                                cp_list.push(current_blob);
+                                prev_key = current_key;
+                                current_blob = { copy_count : 0 };
+                                if (cp.barcode) current_blob.copy_count = 1;
+                                current_blob.index = index++;
+                                current_blob.id_list = cp.id_list;
+                                if (cp.raw) current_blob.raw = cp.raw;
+                                current_blob.owner_label = cp.owner_label;
+                                current_blob.owner_id = cp.owner_id;
+                                current_blob.call_number = cp.call_number;
+                                current_blob.owner_list = cp.owner_list;
+                            }
+                        }
+                    });
+
+                    current_blob.barcode = current_blob.copy_count;
+                    cp_list.push(current_blob);
+                    new_list = cp_list;
+
+                    if (!vol) { // do the same for vol rows
+
+                        index = 0;
+                        var cn_list = [];
+                        prev_key = '';
+                        current_blob = { copy_count : 0 };
+                        angular.forEach(cp_list, function (cp) {
+                            if (!prev_key) {
+                                prev_key = cp.owner_list.join('');
+                                current_blob.index = index++;
+                                current_blob.id_list = cp.id_list;
+                                if (cp.raw) current_blob.raw = cp.raw;
+                                current_blob.cn_count = 1;
+                                current_blob.copy_count = cp.copy_count;
+                                current_blob.owner_list = cp.owner_list;
+                                current_blob.owner_label = cp.owner_label;
+                                current_blob.owner_id = cp.owner_id;
+                            } else {
+                                var current_key = cp.owner_list.join('');
+                                if (prev_key == current_key) { // collapse into current_blob
+                                    current_blob.cn_count++;
+                                    current_blob.copy_count += cp.copy_count;
+                                    current_blob.id_list = current_blob.id_list.concat(cp.id_list);
+                                    if (cp.raw) current_blob.raw = current_blob.raw.concat(cp.raw);
+                                } else {
+                                    current_blob.barcode = current_blob.copy_count;
+                                    current_blob.call_number = { label : current_blob.cn_count };
+                                    cn_list.push(current_blob);
+                                    prev_key = current_key;
+                                    current_blob = { copy_count : 0 };
+                                    current_blob.index = index++;
+                                    current_blob.id_list = cp.id_list;
+                                    if (cp.raw) current_blob.raw = cp.raw;
+                                    current_blob.owner_label = cp.owner_label;
+                                    current_blob.owner_id = cp.owner_id;
+                                    current_blob.cn_count = 1;
+                                    current_blob.copy_count = cp.copy_count;
+                                    current_blob.owner_list = cp.owner_list;
+                                }
+                            }
+                        });
+    
+                        current_blob.barcode = current_blob.copy_count;
+                        current_blob.call_number = { label : current_blob.cn_count };
+                        cn_list.push(current_blob);
+                        new_list = cn_list;
+    
+                    }
+                }
+
+                service.copies = new_list;
+                service.ongoing = false;
+            },
+
+            null, // error
+
+            // notify reads the stream of copies, one at a time.
+            function(cn) {
+
+                var copies = cn.copies().filter(function(cp){ return cp.deleted() == 'f' });
+                cn.copies([]);
+
+                angular.forEach(copies, function (cp) {
+                    cp.call_number(cn);
+                });
+
+                var owner_id = cn.owning_lib();
+                var owner = egCore.org.get(owner_id);
+
+                var owner_name_list = [];
+                while (owner.parent_ou()) { // we're going to skip the top of the tree...
+                    owner_name_list.unshift(owner.name());
+                    owner = egCore.org.get(owner.parent_ou());
+                }
+
+                if (copies[0]) {
+                    var flat = [];
+                    angular.forEach(copies, function (cp) {
+                        var flat_cp = egCore.idl.toHash(cp);
+                        flat_cp.owner_id = owner_id;
+                        flat_cp.owner_list = owner_name_list;
+                        flat_cp.id_list = [flat_cp.id];
+                        flat_cp.raw = [cp];
+                        flat.push(flat_cp);
+                    });
+
+                    service.copies = service.copies.concat(flat);
+                } else if (empty) {
+                    service.copies.push({
+                        owner_id   : owner_id,
+                        owner_list : owner_name_list,
+                        call_number: egCore.idl.toHash(cn),
+                        raw_call_number: cn
+                    });
+                }
+
+                return cn;
+            }
+        );
+    }
+
+    return service;
+}]);

commit 322002bc8c4f1b50754d278b91367113362632a8
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Fri Oct 2 14:19:52 2015 +0000

    webstaff: make overlay record modal wider
    
    This patch defines a CSS class, eg-wide-modal, that can be
    set as the windowClass of 'lg' Boostrap modals and increase
    their width from a fixed width to 95% of the window.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/css/style.css.tt2 b/Open-ILS/src/templates/staff/css/style.css.tt2
index c214bc8..0a0ba3b 100644
--- a/Open-ILS/src/templates/staff/css/style.css.tt2
+++ b/Open-ILS/src/templates/staff/css/style.css.tt2
@@ -413,6 +413,12 @@ table.list tr.selected td { /* deprecated? */
   }
 }
 
+/* optional class to make 'lg' Bootstrap modals even wider */
+ at media (min-width: 768px) {
+  .eg-wide-modal .modal-lg {
+    width: 95%;
+  }
+}
 
 [%# 
 vim: ft=css 
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 3ac7a5c..313e9ab 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
@@ -545,6 +545,7 @@ function($scope,  $q , $routeParams,  bucketSvc,  egCore,  $window,
         $modal.open({
             templateUrl: './cat/bucket/record/t_merge_records',
             size: 'lg',
+            windowClass: 'eg-wide-modal',
             controller:
                 ['$scope', '$modalInstance', function($scope, $modalInstance) {
                 $scope.records = [];

commit 4f5e7f397e19b4f2daf509818bb765e8013eeb96
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 1 21:56:12 2015 +0000

    webstaff: use tabset rather than accordion for bucket record merge modal
    
    This stylistic change will pave the way towards
    better side-by-side record management and editing in this
    interface.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2 b/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
index e25220a..fb5e4fa 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/t_merge_records.tt2
@@ -18,16 +18,13 @@
           </div>
           <div class="col-xs-6">
             <h4>[% l('Records to merge into lead') %]</h4>
-            <accordion>
-              <accordion-group ng-repeat="rec in records">
-                <accordion-heading>
-                    [% l('Record [_1]', '{{rec.id}}') %] <i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': status.open, 'glyphicon-chevron-right': !status.open}"></i>
-                </accordion-heading>
+            <tabset>
+              <tab heading="[% l('Bib [_1]', '{{rec.id}}') %]" ng-repeat="rec in records">
                 <button class="btn btn-default btn-sm" ng-click="use_as_lead(rec)">[% l('Use as lead record') %]</button>
                 <button class="btn btn-default btn-sm" ng-click="drop(rec)">[% l('Remove from consideration') %]</button>
                 <eg-record-html record-id="rec.id"></eg-record-html>
-              </accordion-group>
-            </accordian>
+              </tab>
+            </tabset>
           </div>
       </div>
   </div>

commit ae2c9d67c0c0a6962b4586658a7dbad4e7d8b062
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 1 20:31:08 2015 +0000

    webstaff: move some "mark for" buttons to record summary
    
    This patch moves the "Mark for..." overlay, volume transfer,
    and conjoined items buttons from the MARC editor to
    the record summary (next to the "Add To Bucket" button).
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 8437ee8..05b25cd 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_catalog.tt2
@@ -25,6 +25,34 @@
     <button type="button" class="btn btn-default" ng-click="add_to_record_bucket()">
         [% l('Add To Bucket') %]
     </button>
+    <div class="btn-group" dropdown dropdown-append-to-body>
+        <button id="mark-for-button" type="button" class="btn btn-default" dropdown-toggle>
+            [% l('Mark for:') %] <span class="caret"></span>
+        </button>
+        <ul class="dropdown-menu" role="menu" aria-labelledby="mark-for-button">
+            <li role="menuitem">
+                <a ng-click="markOverlay()">
+                    [% l('Overlay Target') %]
+                    <span class="target-record-aside" ng-if="current_overlay_target">[% l('(Currently [_1])', '{{current_overlay_target}}') %]</span>
+                </a>
+            </li>
+            <li role="menuitem">
+                <a ng-click="markVolTransfer()">
+                    [% l('Volume Transfer') %]
+                    <span class="target-record-aside" ng-if="current_voltransfer_target">[% l('(Currently [_1])', '{{current_voltransfer_target}}') %]</span>
+                </a>
+            </li>
+            <li role="menuitem">
+                    <a ng-click="markConjoined()">
+                        [% l('Conjoined Items') %]
+                    <span class="target-record-aside" ng-if="current_conjoined_target">[% l('(Currently [_1])', '{{current_conjoined_target}}') %]</span>
+                </a>
+            </li>
+            <li role="menuitem">
+                <a ng-click="clearRecordMarks()">[% l('Reset Record Marks') %]</a>
+            </li>
+        </ul>
+    </div>
   </div>
 </div>
 
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 5ee22e4..f415950 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
@@ -1,19 +1,5 @@
 <div>
   <div ng-show="bre" class="row pad-vert marcfastitemadd" ng-hide="brandNewRecord">
-    <div class="col-md-5">
-      <label>[% l('Mark for:') %]</label>
-      <div class="btn-group">
-        <span class="btn-group">
-          <button class="btn btn-default" ng-click="markOverlay()">[% l('Overlay Target') %]</button>
-        </span>
-        <span class="btn-group">
-          <button class="btn btn-default" ng-click="markVolTransfer()">[% l('Volume Transfer') %]</button>
-        </span>
-        <span class="btn-group">
-          <button class="btn btn-default" ng-click="markConjoined()">[% l('Conjoined Items') %]</button>
-        </span>
-      </div>
-    </div>
     <div class="col-md-2">
       <label><input type="checkbox" ng-model="enable_fast_add"/> [% l('Add Item') %]</label>
     </div>
diff --git a/Open-ILS/src/templates/staff/css/cat.css.tt2 b/Open-ILS/src/templates/staff/css/cat.css.tt2
index ede0007..4faae17 100644
--- a/Open-ILS/src/templates/staff/css/cat.css.tt2
+++ b/Open-ILS/src/templates/staff/css/cat.css.tt2
@@ -3,6 +3,11 @@
     color: red;
 }
 
+.target-record-aside {
+    font-size: 80%;
+    font-style: italic;
+}
+
 .marcrecord {
     //background-color: #f5f5f5;
 }
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 4b2bf9d..ce8acac 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
@@ -311,6 +311,34 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
         });
     }
 
+    $scope.current_overlay_target     = egCore.hatch.getLocalItem('eg.cat.marked_overlay_record');
+    $scope.current_voltransfer_target = egCore.hatch.getLocalItem('eg.cat.marked_volume_transfer_record');
+    $scope.current_conjoined_target   = egCore.hatch.getLocalItem('eg.cat.marked_conjoined_record');
+
+    $scope.markConjoined = function () {
+        $scope.current_conjoined_target = $scope.record_id;
+        egCore.hatch.setLocalItem('eg.cat.marked_conjoined_record',$scope.record_id);
+    };
+
+    $scope.markVolTransfer = function () {
+        $scope.current_voltransfer_target = $scope.record_id;
+        egCore.hatch.setLocalItem('eg.cat.marked_volume_transfer_record',$scope.record_id);
+    };
+
+    $scope.markOverlay = function () {
+        $scope.current_overlay_target = $scope.record_id;
+        egCore.hatch.setLocalItem('eg.cat.marked_overlay_record',$scope.record_id);
+    };
+
+    $scope.clearRecordMarks = function () {
+        $scope.current_overlay_target     = null;
+        $scope.current_voltransfer_target = null;
+        $scope.current_conjoined_target   = null;
+        egCore.hatch.removeLocalItem('eg.cat.marked_volume_transfer_record');
+        egCore.hatch.removeLocalItem('eg.cat.marked_conjoined_record');
+        egCore.hatch.removeLocalItem('eg.cat.marked_overlay_record');
+    }
+
     $scope.stop_unload = false;
     $scope.$watch('stop_unload',
         function(newVal, oldVal) {
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 3ec54d6..cb978f3 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
@@ -1265,18 +1265,6 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     alert($scope.record.toBreaker());
                 };
 
-                $scope.markConjoined = function () {
-                    egCore.hatch.setLocalItem('eg.cat.marked_conjoined_record',$scope.recordId);
-                };
-
-                $scope.markVolTransfer = function () {
-                    egCore.hatch.setLocalItem('eg.cat.marked_volume_transfer_record',$scope.recordId);
-                };
-
-                $scope.markOverlay = function () {
-                    egCore.hatch.setLocalItem('eg.cat.marked_overlay_record',$scope.recordId);
-                };
-
                 $scope.$watch('recordId',
                     function(newVal, oldVal) {
                         if (newVal && newVal !== oldVal) {

commit 17658549d1ef9b77d0a2a16d4d538138725f86d8
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Oct 1 19:24:57 2015 +0000

    webstaff: add drop-down for item type Z39.50 search field
    
    It should be noted that this works *only* for OCLC, as
    not all targets support use attribute 1001, and the list
    of values for that use attribute are target-defined.
    
    A more general solution might be creating a new
    config.z3950_attr_value_list table.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/z3950/t_search_fields.tt2 b/Open-ILS/src/templates/staff/cat/z3950/t_search_fields.tt2
index 377695e..753f998 100644
--- a/Open-ILS/src/templates/staff/cat/z3950/t_search_fields.tt2
+++ b/Open-ILS/src/templates/staff/cat/z3950/t_search_fields.tt2
@@ -2,7 +2,20 @@
     <div ng-repeat="(code, search_field) in fields" class="z3950-search-field-list form-group">
         <label for="z3950-field-{{code}}" class="col-xs-6 control-label">{{search_field.label}}</label>
         <div class="col-xs-6">
-            <input type="text" class="form=control" id="z3950-field-{{code}}" ng-model="search_field.query">
+            <input type="text" class="form=control" id="z3950-field-{{code}}" ng-model="search_field.query" ng-if="code != 'item_type'">
+            <select ng-if="code == 'item_type'" ng-model="search_field.query">
+                <option value="">[% l('All Formats') %]</option>
+                <option value='art'>[% l('Papers or Articles') %]</option>
+                <option value='bks'>[% l('Books') %]</option>
+                <option value='com'>[% l('Computer files') %]</option>
+                <option value='map'>[% l('Maps') %]</option>
+                <option value='mix'>[% l('Mixed material') %]</option>
+                <option value='rec'>[% l('Sound recordings') %]</option>
+                <option value='sco'>[% l('Musical scores') %]</option>
+                <option value='ser'>[% l('Serials') %]</option>
+                <option value='url'>[% l('Internet Resources') %]</option>
+                <option value='vis'>[% l('Visual materials') %]</option>
+            </select>
         </div>
     </div>
 </div>

commit 1a2d327fac0a1a79c409fab7e81d60c7c665fa54
Author: Mike Rylander <mrylander at gmail.com>
Date:   Wed Sep 30 15:45:17 2015 -0400

    webstaff: Use record, not owning_lib, for the part record field. Doh.
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 2c6be77..990062b 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -282,7 +282,7 @@ function(egCore , $q) {
                             part.id( --$scope.new_part_id );
                             part.isnew( true );
                             part.label( $scope.part );
-                            part.record( $scope.callNumber.owning_lib() );
+                            part.record( $scope.callNumber.record() );
                             $scope.copy.parts([part]);
                             $scope.copy.ischanged(1);
                         }

commit bb210af3c8c71217a7e2f71504f37a071ba70831
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Sep 29 21:47:24 2015 +0000

    webstaff: tweak labels of remove from bucket actions
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 6a66ed6..9f9ec65 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
@@ -14,7 +14,7 @@
     handler="requestItems"></eg-grid-action>
   <eg-grid-action label="[% l('Edit Selected Copies') %]" 
     handler="spawnHoldingsEdit"></eg-grid-action>
-  <eg-grid-action label="[% l('Remove Selected Copies') %]" 
+  <eg-grid-action label="[% l('Remove Selected Copies from Bucket') %]" 
     handler="detachCopies"></eg-grid-action>
   <eg-grid-action label="[% l('Transfer Selected Copies to Marked Volume') %]" 
     handler="transferCopies"></eg-grid-action>
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 421333b..b35a83b 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
@@ -13,7 +13,7 @@
   <eg-grid-action label="[% l('Show Selected Records in Catalog') %]"
     handler="showRecords"></eg-grid-action>
 
-  <eg-grid-action label="[% l('Remove Selected Records') %]" 
+  <eg-grid-action label="[% l('Remove Selected Records from Bucket') %]" 
     handler="detachRecords"></eg-grid-action>
 
   <eg-grid-action label="[% l('Delete Selected Records from Catalog') %]" 

commit b0e1e2c9630496d911257428fc9592066610fe5a
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Sep 29 21:45:51 2015 +0000

    webstaff: record buckets: implement show selected in catalog action
    
    This replaces the show all in catalog action.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 c1b2085..421333b 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,8 +10,8 @@
   [% 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('Show Selected Records in Catalog') %]"
+    handler="showRecords"></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 f07bea7..3ac7a5c 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
@@ -607,14 +607,13 @@ function($scope,  $q , $routeParams,  bucketSvc,  egCore,  $window,
         });
     }
 
-    $scope.showAllRecords = function() {
-        // TODO: maybe show selected would be better?
+    $scope.showRecords = function(records) {
         // 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) {
+        angular.forEach(records, function(rec) {
             var url = egCore.env.basePath +
                       'cat/catalog/record/' +
-                      rec.target_biblio_record_entry();
+                      rec.id;
             $timeout(function() { $window.open(url, '_blank') });
         });
     }

commit e2cd5cbb7e0dabf20459e9d64db21f371e261c67
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Sep 29 14:14:04 2015 -0400

    webstaff: Add a few important fields for the holdings view grid
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2 b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
index 84783e8..5a79b1f 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/t_holdings.tt2
@@ -90,6 +90,12 @@
     <eg-grid-field label="[% l('Copy #') %]"          path="copy_number" flex="1" visible></eg-grid-field>
     <eg-grid-field label="[% l('Barcode') %]"         path="barcode" visible></eg-grid-field>
     <eg-grid-field label="[% l('Status') %]"          path="status.name" flex="1" visible></eg-grid-field>
+
+    <eg-grid-field label="[% l('Price') %]"                  path="price"></eg-grid-field>
+    <eg-grid-field label="[% l('Circulation Modifier') %]"   path="circ_modifier"></eg-grid-field>
+    <eg-grid-field label="[% l('Circulate') %]"              datatype="bool" path="circulate"></eg-grid-field>
+    <eg-grid-field label="[% l('Holdable') %]"               datatype="bool" path="holdable"></eg-grid-field>
+    <eg-grid-field label="[% l('Reference') %]"              datatype="bool" path="ref"></eg-grid-field>
   
   </eg-grid>
 </div>

commit 15bdb59b66ace10b1115a76558edc5de342522c0
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Sep 29 14:01:42 2015 -0400

    webstaff: Make the last applied template sticky
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 273f589..2c6be77 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -844,10 +844,13 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                     $scope.template_name_list = Object.keys(t);
                 }
             });
+            egCore.hatch.getItem('cat.copy.last_template').then(function(t) {
+                if (t) $scope.template_name = t;
+            });
         }
         $scope.fetchTemplates();
 
-         $scope.applyTemplate = function (n) {
+        $scope.applyTemplate = function (n) {
             angular.forEach($scope.templates[n], function (v,k) {
                 if (k == 'circ_lib') {
                     $scope.working[k] = egCore.org.get(v);
@@ -867,7 +870,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                     });
                 }
             });
-            $scope.template_name = '';
+            egCore.hatch.setItem('cat.copy.last_template', n);
         }
 
         $scope.copytab = 'working';

commit 58e4c9c00b09f2f803a085703b8f86c29e26b045
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Sep 29 13:45:02 2015 -0400

    webstaff: Improve combo box functionality
    
    Clicking on down arrow shows all options (possibly following
    filtered options)
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 5264d68..9cd0abc 100644
--- a/Open-ILS/web/js/ui/default/staff/services/ui.js
+++ b/Open-ILS/web/js/ui/default/staff/services/ui.js
@@ -222,23 +222,31 @@ function($modal, $interpolate) {
             '<div class="input-group">'+
                 '<input type="text" class="form-control" ng-model="selected" ng-change="makeOpen()">'+
                 '<div class="input-group-btn" dropdown ng-class="{open:isopen}">'+
-                    '<button type="button" class="btn btn-default dropdown-toggle"><span class="caret"></span></button>'+
+                    '<button type="button" ng-click="showAll()" class="btn btn-default dropdown-toggle"><span class="caret"></span></button>'+
                     '<ul class="dropdown-menu dropdown-menu-right">'+
                         '<li ng-repeat="item in list|filter:selected"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
+                        '<li ng-if="all" class="divider"><span></span></li>'+
+                        '<li ng-if="all" ng-repeat="item in list"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
                     '</ul>'+
                 '</div>'+
             '</div>',
         controller: ['$scope','$filter',
             function( $scope , $filter) {
 
-                $scope.always = true;
+                $scope.all = false;
                 $scope.isopen = false;
 
+                $scope.showAll = function () {
+                    if ($scope.selected.length > 0)
+                        $scope.all = true;
+                }
+
                 $scope.makeOpen = function () {
-                    return $scope.isopen = $filter('filter')(
+                    $scope.isopen = $filter('filter')(
                         $scope.list,
                         $scope.selected
                     ).length > 0 && $scope.selected.length > 0;
+                    $scope.all = false;
                 }
 
                 $scope.changeValue = function (newVal) {

commit 1f7f4731b8e1999a77e7d06cc78ccb0fa7cff615
Author: Mike Rylander <mrylander at gmail.com>
Date:   Tue Sep 29 12:28:16 2015 -0400

    webstaff: Normalize the type of values going into in-memory objects
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index cead130..273f589 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -323,14 +323,14 @@ function(egCore , $q) {
         template:
             '<div class="row">'+
                 '<div class="col-xs-2">'+
-                    '<select class="form-control" ng-model="classification" ng-options="cl.name() for cl in classification_list track by idTracker(cl)"/>'+
+                    '<select class="form-control" ng-model="classification" ng-change="updateClassification()" ng-options="cl.name() for cl in classification_list"/>'+
                 '</div>'+
                 '<div class="col-xs-1">'+
-                    '<select class="form-control" ng-model="prefix" ng-change="updatePrefix()" ng-options="p.label() for p in prefix_list track by idTracker(p)"/>'+
+                    '<select class="form-control" ng-model="prefix" ng-change="updatePrefix()" ng-options="p.label() for p in prefix_list"/>'+
                 '</div>'+
                 '<div class="col-xs-2"><input class="form-control" type="text" ng-change="updateLabel()" ng-model="label"/></div>'+
                 '<div class="col-xs-1">'+
-                    '<select class="form-control" ng-model="suffix" ng-change="updateSuffix()" ng-options="s.label() for s in suffix_list track by idTracker(s)"/>'+
+                    '<select class="form-control" ng-model="suffix" ng-change="updateSuffix()" ng-options="s.label() for s in suffix_list"/>'+
                 '</div>'+
                 '<div ng-hide="onlyVols" class="col-xs-1"><input class="form-control" type="number" ng-model="copy_count" min="{{orig_copy_count}}" ng-change="changeCPCount()"></div>'+
                 '<div ng-hide="onlyVols" class="col-xs-5">'+
@@ -384,6 +384,7 @@ function(egCore , $q) {
                 itemSvc.get_suffixes($scope.callNumber.owning_lib()).then(function(list){
                     $scope.suffix_list = list;
                     $scope.$watch('callNumber.suffix()', function (v) {
+                        if (angular.isObject(v)) v = v.id();
                         $scope.suffix = $scope.suffix_list.filter( function (s) {
                             return s.id() == v;
                         })[0];
@@ -392,7 +393,7 @@ function(egCore , $q) {
                 });
                 $scope.updateSuffix = function () {
                     angular.forEach($scope.copies, function(cp) {
-                        cp.call_number().suffix($scope.suffix.id());
+                        cp.call_number().suffix($scope.suffix);
                         cp.call_number().ischanged(1);
                     });
                 }
@@ -401,6 +402,7 @@ function(egCore , $q) {
                 itemSvc.get_prefixes($scope.callNumber.owning_lib()).then(function(list){
                     $scope.prefix_list = list;
                     $scope.$watch('callNumber.prefix()', function (v) {
+                        if (angular.isObject(v)) v = v.id();
                         $scope.prefix = $scope.prefix_list.filter(function (p) {
                             return p.id() == v;
                         })[0];
@@ -409,7 +411,7 @@ function(egCore , $q) {
                 });
                 $scope.updatePrefix = function () {
                     angular.forEach($scope.copies, function(cp) {
-                        cp.call_number().prefix($scope.prefix.id());
+                        cp.call_number().prefix($scope.prefix);
                         cp.call_number().ischanged(1);
                     });
                 }
@@ -418,6 +420,7 @@ function(egCore , $q) {
                 itemSvc.get_classifications().then(function(list){
                     $scope.classification_list = list;
                     $scope.$watch('callNumber.label_class()', function (v) {
+                        if (angular.isObject(v)) v = v.id();
                         $scope.classification = $scope.classification_list.filter(function (c) {
                             return c.id() == v;
                         })[0];
@@ -426,7 +429,7 @@ function(egCore , $q) {
                 });
                 $scope.updateClassification = function () {
                     angular.forEach($scope.copies, function(cp) {
-                        cp.call_number().label_class($scope.classification.id());
+                        cp.call_number().label_class($scope.classification);
                         cp.call_number().ischanged(1);
                     });
                 }
@@ -883,18 +886,29 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                 angular.forEach($scope.data.tree, function(cn_hash) {
                     angular.forEach(cn_hash, function(copies) {
                         angular.forEach(copies, function(cp) {
-                            if (typeof $scope.batch.classification != 'undefined' && $scope.batch.classification != '')
-                                cp.call_number().label_class($scope.batch.classification);
+                            if (typeof $scope.batch.classification != 'undefined' && $scope.batch.classification != '') {
+                                var label_class = $scope.classification_list.filter(function(p){ return p.id() == $scope.batch.classification })[0];
+                                cp.call_number().label_class(label_class);
+                                cp.call_number().ischanged(1);
                                 $scope.dirty = true;
-                            if (typeof $scope.batch.prefix != 'undefined' && $scope.batch.prefix != '')
-                                cp.call_number().prefix($scope.batch.prefix);
+                            }
+                            if (typeof $scope.batch.prefix != 'undefined' && $scope.batch.prefix != '') {
+                                var prefix = $scope.prefix_list.filter(function(p){ return p.id() == $scope.batch.prefix })[0];
+                                cp.call_number().prefix(prefix);
+                                cp.call_number().ischanged(1);
                                 $scope.dirty = true;
-                            if (typeof $scope.batch.label != 'undefined' && $scope.batch.label != '')
+                            }
+                            if (typeof $scope.batch.label != 'undefined' && $scope.batch.label != '') {
                                 cp.call_number().label($scope.batch.label);
+                                cp.call_number().ischanged(1);
                                 $scope.dirty = true;
-                            if (typeof $scope.batch.suffix != 'undefined' && $scope.batch.suffix != '')
-                                cp.call_number().suffix($scope.batch.suffix);
+                            }
+                            if (typeof $scope.batch.suffix != 'undefined' && $scope.batch.suffix != '') {
+                                var suffix = $scope.suffix_list.filter(function(p){ return p.id() == $scope.batch.suffix })[0];
+                                cp.call_number().suffix(suffix);
+                                cp.call_number().ischanged(1);
                                 $scope.dirty = true;
+                            }
                         });
                     });
                 });

commit 5095b0783de9e4547f47bdfd96dd5761b2de6500
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Tue Sep 29 13:48:26 2015 +0000

    webstaff: replace acn prefix()/suffix() with IDs only when needed
    
    Fixes an issue that could break saving volumes.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 273c0df..cead130 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -1286,8 +1286,10 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                     perCnCopies[cn_id].push(cp);
                 }
                 cp.call_number(cn_id); // prevent loops in JSON-ification
-                cnHash[cn_id].prefix(cnHash[cn_id].prefix().id()); // un-object-ize some fields
-                cnHash[cn_id].suffix(cnHash[cn_id].suffix().id()); // un-object-ize some fields
+                if (typeof cnHash[cn_id].prefix() == 'object')
+                    cnHash[cn_id].prefix(cnHash[cn_id].prefix().id()); // un-object-ize some fields
+                if (typeof cnHash[cn_id].suffix() == 'object')
+                    cnHash[cn_id].suffix(cnHash[cn_id].suffix().id()); // un-object-ize some fields
             });
 
             angular.forEach(perCnCopies, function (v, k) {

commit 2acd0c2239dcf11fee2c611609f2d61fa8d09f42
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Sep 28 21:36:58 2015 +0000

    webstaff: adjust URL when navigating catalog search results
    
    Navigating from record to record after performing a catalog
    search now sets the URL in the browser to /eg/staff/cat/catalog/record/{record_id}.
    Clicking on the "Back To Results" button will set the URL
    back to /eg/staff/cat/catalog/index.
    
    This allows staff to copy and paste record URIs more conveniently.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 80423a1..4b2bf9d 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', 'egMarcMod', 'egUserMod'])
+angular.module('egCatalogApp', ['ui.bootstrap','ngRoute','ngLocationUpdate','egCoreMod','egGridMod', 'egMarcMod', 'egUserMod'])
 
 .config(function($routeProvider, $locationProvider, $compileProvider) {
     $locationProvider.html5Mode(true);
@@ -248,6 +248,9 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
             if (force_opac_tab) $scope.record_tab = 'catalog';
             $scope.in_opac_call = true;
             $scope.opac_iframe.dom.contentWindow[opac_frame_function]();
+            if (opac_frame_function == 'rdetailBackToResults') {
+                $location.update_path('/cat/catalog/index');
+            }
         }
     }
 
@@ -357,6 +360,7 @@ function($scope , $routeParams , $location , $window , $q , egCore , egHolds , e
                 $scope.conjoinedGridDataProvider.refresh();
             });
             init_parts_url();
+            $location.update_path('/cat/catalog/record/' + $scope.record_id);
         } else {
             delete $scope.record_id;
             $scope.from_route = false;

commit 567cbee9bc695d0fd86effb41500a8b0d2b81c3c
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Mon Sep 28 21:20:46 2015 +0000

    webstaff: add angular-location-update dependency
    
    This module adds a $location.update_path() routine to
    allow changing the URL without forcing a controller reload.
    
    This module is copyright (c) 2015 anglibs and released under
    the MIT license and is taken from
    https://github.com/anglibs/angular-location-update.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/base_js.tt2 b/Open-ILS/src/templates/staff/base_js.tt2
index 97a2ce0..4601e9c 100644
--- a/Open-ILS/src/templates/staff/base_js.tt2
+++ b/Open-ILS/src/templates/staff/base_js.tt2
@@ -8,6 +8,7 @@
 <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>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/hotkeys.min.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/build/js/angular-location-update.min.js"></script>
 
 <!-- IDL / opensrf (network) -->
 <script src="[% ctx.media_prefix %]/js/dojo/opensrf/JSON_v1.js"></script>
diff --git a/Open-ILS/web/js/ui/default/staff/Gruntfile.js b/Open-ILS/web/js/ui/default/staff/Gruntfile.js
index 8dcd3c1..087f0ff 100644
--- a/Open-ILS/web/js/ui/default/staff/Gruntfile.js
+++ b/Open-ILS/web/js/ui/default/staff/Gruntfile.js
@@ -22,6 +22,7 @@ module.exports = function(grunt) {
             'bower_components/angular-bootstrap/ui-bootstrap.min.js',
             'bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js',
             'bower_components/angular-hotkeys/build/hotkeys.min.js',
+            'bower_components/angular-location-update/angular-location-update.min.js',
             'bower_components/jquery/dist/jquery.min.js',
           ]
         }]
diff --git a/Open-ILS/web/js/ui/default/staff/bower.json b/Open-ILS/web/js/ui/default/staff/bower.json
index dc44126..d31ec5c 100644
--- a/Open-ILS/web/js/ui/default/staff/bower.json
+++ b/Open-ILS/web/js/ui/default/staff/bower.json
@@ -26,6 +26,7 @@
     "angular-bootstrap": "~0.11.0"
   },
   "dependencies": {
-    "angular-hotkeys": "chieffancypants/angular-hotkeys#~1.2.0"
+    "angular-hotkeys": "chieffancypants/angular-hotkeys#~1.2.0",
+    "angular-location-update": "./extern/angular-location-update/"
   }
 }
diff --git a/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/.bower.json b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/.bower.json
new file mode 100644
index 0000000..c31aaa7
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/.bower.json
@@ -0,0 +1,31 @@
+{
+  "name": "angular-location-update",
+  "homepage": "https://github.com/anglibs/angular-location-update",
+  "authors": [
+    "garmoshka-mo <dan at dub.ink>"
+  ],
+  "main": "angular-location-update.js",
+  "description": "Updates location path without reloading of controller",
+  "ignore": [
+    "**/.*",
+    "*.yml",
+    "*.xml",
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests"
+  ],
+  "dependencies": {
+    "angular": "*"
+  },
+  "_release": "103e945c53",
+  "_resolution": {
+    "type": "branch",
+    "branch": "master",
+    "commit": "103e945c535f9c0e3479c4faf77930404c823f21"
+  },
+  "_source": "git://github.com/anglibs/angular-location-update.git",
+  "_target": "*",
+  "_originalSource": "angular-location-update",
+  "_direct": true
+}
\ No newline at end of file
diff --git a/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/LICENSE b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/LICENSE
new file mode 100644
index 0000000..037a52f
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 anglibs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/README.md b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/README.md
new file mode 100644
index 0000000..1996b3a
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/README.md
@@ -0,0 +1,52 @@
+# angular-location-update
+
+Updates location path without reloading of controller
+
+## Install
+
+1 `bower install angular-location-update --save` or [download](http://anglibs.github.io/angular-location-update/angular-location-update.min.js) or include hosted from github.io
+````
+    <script src="//anglibs.github.io/angular-location-update/angular-location-update.min.js"></script>
+````
+
+2 Add module to your app:
+````
+  angular.module('your_app', ['ngLocationUpdate']);
+````
+
+## Usage
+
+````
+$location.update_path('/notes/1');
+$location.update_path('/notes/1/wysiwyg', true);
+````
+Parameters:
+ 1. New path
+ 1. Keep old path in browser history (By default it will be **replaced** by new one)
+
+## When it's needed?
+
+For example you have route `/notes/new` which shows form for new note.
+
+In modern web app you may have no "Save" button - note created and saved to database once user made any change.
+Then you would like to change route to `/notes/1` showing to user, that here is URL of his new document.
+Also if he will refresh page or go back and forward using browser buttons - he will see what he expects.
+
+## FYI
+
+Did you know, that you can easily change your URLs  
+
+from `http://mysite.com/#/notes/1` to `http://mysite.com/notes/1`
+
+For this: 
+ 1. Config app: `angular.module('your_app').config(function($locationProvider) { $locationProvider.html5Mode(true); });`
+ 2. Add in your HTML `<base href="/">`
+
+More info: https://docs.angularjs.org/guide/$location 
+
+## Credits
+
+Solution invented by guys in these threads:
+ 1. https://github.com/angular/angular.js/issues/1699
+ 1. https://github.com/angular-ui/ui-router/issues/427
+ 1. http://stackoverflow.com/questions/14974271/can-you-change-a-path-without-reloading-the-controller-in-angularjs
diff --git a/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/angular-location-update.js b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/angular-location-update.js
new file mode 100644
index 0000000..454f3cc
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/angular-location-update.js
@@ -0,0 +1,22 @@
+!function(angular, undefined) { 'use strict';
+
+  angular.module('ngLocationUpdate', [])
+      .run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
+        // todo: would be proper to change this to decorators of $location and $route
+        $location.update_path = function (path, keep_previous_path_in_history) {
+          if ($location.path() == path) return;
+
+          var routeToKeep = $route.current;
+          $rootScope.$on('$locationChangeSuccess', function () {
+            if (routeToKeep) {
+              $route.current = routeToKeep;
+              routeToKeep = null;
+            }
+          });
+
+          $location.path(path);
+          if (!keep_previous_path_in_history) $location.replace();
+        };
+      }]);
+
+}(window.angular);
diff --git a/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/angular-location-update.min.js b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/angular-location-update.min.js
new file mode 100644
index 0000000..1639d3f
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/angular-location-update.min.js
@@ -0,0 +1 @@
+!function(n){"use strict";n.module("ngLocationUpdate",[]).run(["$route","$rootScope","$location",function(n,t,o){o.update_path=function(c,u){if(o.path()!=c){var a=n.current;t.$on("$locationChangeSuccess",function(){a&&(n.current=a,a=null)}),o.path(c),u||o.replace()}}}])}(window.angular);
\ No newline at end of file
diff --git a/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/bower.json b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/bower.json
new file mode 100644
index 0000000..5592a6d
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/bower.json
@@ -0,0 +1,22 @@
+{
+  "name": "angular-location-update",
+  "version": "0.0.2",
+  "homepage": "https://github.com/anglibs/angular-location-update",
+  "authors": [
+    "garmoshka-mo <dan at dub.ink>"
+  ],
+  "main": "angular-location-update.js",
+  "description": "Updates location path without reloading of controller",
+  "ignore": [
+    "**/.*",
+    "*.yml",
+    "*.xml",
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests"
+  ],
+  "dependencies": {
+    "angular": "*"
+  }
+}
diff --git a/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/package.json b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/package.json
new file mode 100644
index 0000000..2dbebf5
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/staff/extern/angular-location-update/package.json
@@ -0,0 +1,25 @@
+{
+  "name": "angular-location-update",
+  "version": "0.0.2",
+  "main": "angular-location-update.js",
+  "description": "Updates location path without reloading of controller",
+  "homepage": "https://github.com/anglibs/angular-location-update",
+  "bugs": "https://github.com/anglibs/angular-location-update/issues",
+  "author": {
+    "name": "garmoshka-mo",
+    "email": "dan at dub.ink"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git at github.com:anglibs/angular-location-update.git"
+  },
+  "licenses": [
+    {
+      "type": "MIT"
+    }
+  ],
+  "dependencies": {
+  },
+  "devDependencies": {
+  }
+}

commit 301d6a4d6e55f11962254d8f03b4656b7c7a2c92
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Sep 28 12:41:13 2015 -0400

    webstaff: fix issue loading egGrid config
    
    Grid saveConfig can generate bad data (not sure how)
    but we will avoid duplicates in loadConfig
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 66a2bf3..4e48f93 100644
--- a/Open-ILS/web/js/ui/default/staff/services/grid.js
+++ b/Open-ILS/web/js/ui/default/staff/services/grid.js
@@ -450,7 +450,12 @@ angular.module('egGridMod',
                         grid_col.sort = col.sort || 0;
                         // all saved columns are assumed to be true
                         grid_col.visible = true;
-                        new_cols.push(grid_col);
+                        if (new_cols
+                                .filter(function (c) {
+                                    return c.name == grid_col.name;
+                                }).length == 0
+                        )
+                            new_cols.push(grid_col);
                     });
 
                     // columns which are not expressed within the saved 

commit 1b8c205b7d45b78a5eee0715ad89cb21857436dc
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Sep 28 11:33:31 2015 -0400

    webstaff: Add a Save & Exit shortcut to the copy attribute editing pane
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
index 14b46bd..cb40f24 100644
--- a/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/volcopy/t_edit.tt2
@@ -103,6 +103,8 @@
         
                           <eg-grid-menu-item handler="workingToComplete"
                            label="[% l('Store Selected') %]"></eg-grid-menu-item>
+                          <eg-grid-menu-item handler="workingSaveAndExit"
+                           label="[% l('Save & Exit') %]"></eg-grid-menu-item>
         
                         
                           <eg-grid-field label="[% l('Barcode') %]"     path='barcode' visible></eg-grid-field>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index 5863f92..273c0df 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -1315,6 +1315,11 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
             $scope.saveCompletedCopies(false);
         }
 
+        $scope.workingSaveAndExit = function () {
+            $scope.workingToComplete();
+            $scope.saveAndExit();
+        }
+
         $scope.saveAndExit = function () {
             $scope.saveCompletedCopies(true);
         }

commit 4521fcbcf39b07f2cd8921111ee3512ce0b0b8b4
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Sep 28 11:03:17 2015 -0400

    webstaff: Avoid blowing away the volume value when there is no merge target
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm
index 91566c2..9c357d6 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Cat.pm
@@ -972,7 +972,7 @@ sub fleshed_volume_update {
             $logger->info("vol-update: update volume");
             my $resp = update_volume($vol, $editor, ($oargs->{all} or grep { $_ eq 'VOLUME_LABEL_EXISTS' } @{$oargs->{events}} or $auto_merge_vols));
             return $resp->{evt} if $resp->{evt};
-            $vol = $resp->{merge_vol};
+            $vol = $resp->{merge_vol} if $resp->{merge_vol};
         }
 
         # now update any attached copies

commit 84c93d33f241c02f1f901baf3857e5828364c4b9
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Sep 28 11:02:44 2015 -0400

    webstaff: Pass IDs instead of objects for prefix and suffix
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index d38cdd7..5863f92 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -1286,6 +1286,8 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                     perCnCopies[cn_id].push(cp);
                 }
                 cp.call_number(cn_id); // prevent loops in JSON-ification
+                cnHash[cn_id].prefix(cnHash[cn_id].prefix().id()); // un-object-ize some fields
+                cnHash[cn_id].suffix(cnHash[cn_id].suffix().id()); // un-object-ize some fields
             });
 
             angular.forEach(perCnCopies, function (v, k) {

commit d0437cd1517425c8ceb9742a92c92e5fb3d4e439
Author: Mike Rylander <mrylander at gmail.com>
Date:   Mon Sep 28 10:41:29 2015 -0400

    webstaff: Special case for egOrgSelector value reading/writing in copy templates
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
index f922cd6..d38cdd7 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/volcopy/app.js
@@ -846,7 +846,9 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
 
          $scope.applyTemplate = function (n) {
             angular.forEach($scope.templates[n], function (v,k) {
-                if (!angular.isObject(v)) {
+                if (k == 'circ_lib') {
+                    $scope.working[k] = egCore.org.get(v);
+                } else if (!angular.isObject(v)) {
                     $scope.working[k] = angular.copy(v);
                 } else {
                     angular.forEach(v, function (sv,sk) {
@@ -1446,7 +1448,9 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
             
                 $scope.applyTemplate = function (n) {
                     angular.forEach($scope.templates[n], function (v,k) {
-                        if (!angular.isObject(v)) {
+                        if (k == 'circ_lib') {
+                            $scope.working[k] = egCore.org.get(v);
+                        } else if (!angular.isObject(v)) {
                             $scope.working[k] = angular.copy(v);
                         } else {
                             angular.forEach(v, function (sv,sk) {
@@ -1621,7 +1625,6 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore ,
                 });
                 createSimpleUpdateWatcher('age_protect');
             
-                createSimpleUpdateWatcher('circ_lib');
                 createSimpleUpdateWatcher('circulate');
                 createSimpleUpdateWatcher('holdable');
                 createSimpleUpdateWatcher('fine_level');

commit 68bcea20845e50ff15bb28ae13522c39c15c278f
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Sep 24 21:08:35 2015 +0000

    webstaff: stylistic fix for MARC editor
    
    Prevent record type display from overlapping the
    bibliographic source selector.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 a487624..5ee22e4 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
@@ -32,13 +32,13 @@
         [% l('Flat Text Editor') %]
       </label>
     </div>
-    <div class="col-md-2">
+    <div class="col-md-3">
       <div class="input-group">
         <span class="input-group-addon"><b>[% l('Record Type') %]</b></span>
         <span class="input-group-addon">{{calculated_record_type}}</span>
       </div>
     </div>
-    <div ng-if="bre" class="col-md-2">
+    <div ng-if="bre" class="col-md-3">
       <eg-marc-edit-bibsource/>
     </div>
     <div class="col-md-3">

commit c5f8418b70d088bbd0f2e3ee08adbe9745a2b2c1
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Sep 24 20:57:55 2015 +0000

    webstaff: tweak MARC editor in "edit overlay record"
    
    The save button is now labeled "Modify"
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/z3950/t_edit_overlay_record.tt2 b/Open-ILS/src/templates/staff/cat/z3950/t_edit_overlay_record.tt2
index e99fb42..0362fe3 100644
--- a/Open-ILS/src/templates/staff/cat/z3950/t_edit_overlay_record.tt2
+++ b/Open-ILS/src/templates/staff/cat/z3950/t_edit_overlay_record.tt2
@@ -6,7 +6,7 @@
   </div>
   <div class="modal-body">
     <eg-marc-edit-record dirty-flag="dirty_flag" record-id="record_id" marc-xml="args.marc_xml"
-                         in-place-mode="true" record-type="bre" />
+                         in-place-mode="true" record-type="bre" save-label="[% l('Modify') %]" />
   </div>
   <div class="modal-footer">
     <input type="submit" ng-click="ok(args)"

commit 45a3db6fae09212d5860d270684aa9a7d83750b3
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Sep 24 20:52:50 2015 +0000

    webstaff: give more feedback for Z39.50 edit-then-import
    
    The "edit then import" modal now varies the label of the
    MARC editor's save button based on whether one has yet
    to import the current record or if one is just making
    changes to it after having imported it. The dialog now
    also has a button for going to the newly-imported record
    in catalog view.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 4c1adbe..a487624 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
@@ -47,7 +47,7 @@
           <button class="btn btn-default" ng-show="record_type == 'bre'" ng-click="validateHeadings()">[% l('Validate') %]</button>
         </span>
         <span class="btn-group">
-          <button class="btn btn-default" ng-click="saveRecord()">[% l('Save') %]</button>
+          <button class="btn btn-primary" ng-click="saveRecord()">{{ saveLabel || "[% l('Save') %]"}}</button>
         </span>
         <span class="btn-group">
           <button ng-hide="brandNewRecord || Record().deleted()" class="btn btn-default" ng-click="deleteRecord()">[% l('Delete') %]</button>
diff --git a/Open-ILS/src/templates/staff/cat/share/z3950_strings.tt2 b/Open-ILS/src/templates/staff/cat/share/z3950_strings.tt2
index 50cc1a7..7be4638 100644
--- a/Open-ILS/src/templates/staff/cat/share/z3950_strings.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/z3950_strings.tt2
@@ -6,5 +6,7 @@ angular.module('egCoreMod').run(['egStrings', function(s) {
     s.IMPORTED_RECORD_FROM_Z3950_AS_ID  = "[% l('Record imported as ID [_1]', '{{id}}') %]";
     s.GO_TO_RECORD                      = "[% l('Go to record') %]";
     s.GO_BACK                           = "[% l('Go back') %]";
+    s.IMPORT_BUTTON_LABEL               = "[% l('Import') %]";
+    s.SAVE_BUTTON_LABEL                 = "[% l('Save') %]";
 }]);
 </script>
diff --git a/Open-ILS/src/templates/staff/cat/z3950/t_marc_edit.tt2 b/Open-ILS/src/templates/staff/cat/z3950/t_marc_edit.tt2
index ab70dc9..9e0fe21 100644
--- a/Open-ILS/src/templates/staff/cat/z3950/t_marc_edit.tt2
+++ b/Open-ILS/src/templates/staff/cat/z3950/t_marc_edit.tt2
@@ -6,10 +6,12 @@
   </div>
   <div class="modal-body">
     <eg-marc-edit-record dirty-flag="dirty_flag" record-id="record_id" marc-xml="marc_xml"
-                         record-type="bre"
+                         record-type="bre" save-label="{{save_label}}"
+                         on-save="import_record_callback"
     />
   </div>
   <div class="modal-footer">
+    <button class="btn btn-primary" ng-click="ok()" ng-disabled="!record_id">[% l('Go to imported record') %]</button>
     <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
   </div>
 </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 64f4b4e..3ec54d6 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
@@ -582,7 +582,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
             // than to (immediately) update the database
             inPlaceMode : '@',
             recordType : '@',
-            maxUndo : '@'
+            maxUndo : '@',
+            saveLabel : '@'
         },
         link: function (scope, element, attrs) {
 
diff --git a/Open-ILS/web/js/ui/default/staff/cat/z3950/app.js b/Open-ILS/web/js/ui/default/staff/cat/z3950/app.js
index 4cb33d3..85c0ac0 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/z3950/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/z3950/app.js
@@ -251,20 +251,28 @@ function($scope , $q , $location , $timeout , $window,  egCore , egGridDataProvi
 
     $scope.spawn_editor = function() {
         var items = $scope.gridControls.selectedItems();
+        var recId = 0;
         $modal.open({
             templateUrl: './cat/z3950/t_marc_edit',
             size: 'lg',
             controller:
                 ['$scope', '$modalInstance', function($scope, $modalInstance) {
                 $scope.focusMe = true;
-                $scope.record_id = 0;
+                $scope.record_id = recId;
                 $scope.dirty_flag = false;
                 $scope.marc_xml = items[0]['marcxml'];
                 $scope.ok = function(args) { $modalInstance.close(args) }
                 $scope.cancel = function () { $modalInstance.dismiss() }
+                $scope.save_label = egCore.strings.IMPORT_BUTTON_LABEL;
+                $scope.import_record_callback = function (record_id) {
+                    recId = record_id;
+                    $scope.save_label = egCore.strings.SAVE_BUTTON_LABEL;
+                };
             }]
-        }).result.then(function (args) {
-            if (!args || !args.name) return;
+        }).result.then(function () {
+            if (recId) {
+                $window.location.href = egCore.env.basePath + 'cat/catalog/record/' + recId;
+            }
         });
     }
 

commit 197ab7554b99d7db07bf5ce3c850bfb666d46201
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Sep 24 20:07:56 2015 +0000

    webstaff: provide feedback after record imported via Z39.50
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/cat/share/z3950_strings.tt2 b/Open-ILS/src/templates/staff/cat/share/z3950_strings.tt2
new file mode 100644
index 0000000..50cc1a7
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/share/z3950_strings.tt2
@@ -0,0 +1,10 @@
+[%# Strings for cat/z3950/app.js %]
+
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+    s.IMPORTED_RECORD_FROM_Z3950        = "[% l('Imported record') %]";
+    s.IMPORTED_RECORD_FROM_Z3950_AS_ID  = "[% l('Record imported as ID [_1]', '{{id}}') %]";
+    s.GO_TO_RECORD                      = "[% l('Go to record') %]";
+    s.GO_BACK                           = "[% l('Go back') %]";
+}]);
+</script>
diff --git a/Open-ILS/src/templates/staff/cat/z3950/index.tt2 b/Open-ILS/src/templates/staff/cat/z3950/index.tt2
index 291b335..51eeecc 100644
--- a/Open-ILS/src/templates/staff/cat/z3950/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/z3950/index.tt2
@@ -8,6 +8,7 @@
 [% 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>
+[% INCLUDE 'staff/cat/share/z3950_strings.tt2' %]
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/z3950/app.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/z3950.js"></script>
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/marcrecord.js"></script>
diff --git a/Open-ILS/web/js/ui/default/staff/cat/z3950/app.js b/Open-ILS/web/js/ui/default/staff/cat/z3950/app.js
index 3d8d9d1..4cb33d3 100644
--- a/Open-ILS/web/js/ui/default/staff/cat/z3950/app.js
+++ b/Open-ILS/web/js/ui/default/staff/cat/z3950/app.js
@@ -27,7 +27,9 @@ angular.module('egCatZ3950Search',
  */
 .controller('Z3950SearchCtrl',
        ['$scope','$q','$location','$timeout','$window','egCore','egGridDataProvider','egZ3950TargetSvc','$modal',
-function($scope , $q , $location , $timeout , $window,  egCore , egGridDataProvider,  egZ3950TargetSvc,  $modal) {
+        'egConfirmDialog',
+function($scope , $q , $location , $timeout , $window,  egCore , egGridDataProvider,  egZ3950TargetSvc,  $modal,
+         egConfirmDialog) {
 
     // get list of targets
     egZ3950TargetSvc.loadTargets();
@@ -225,7 +227,17 @@ function($scope , $q , $location , $timeout , $window,  egCore , egGridDataProvi
             function() { deferred.resolve() },
             null, // onerror
             function(result) {
-                console.debug('imported');
+                egConfirmDialog.open(
+                    egCore.strings.IMPORTED_RECORD_FROM_Z3950,
+                    egCore.strings.IMPORTED_RECORD_FROM_Z3950_AS_ID,
+                    { id : result.id() },
+                    egCore.strings.GO_TO_RECORD,
+                    egCore.strings.GO_BACK
+                ).result.then(function() {
+                    // NOTE: $location.path('/cat/catalog/record/' + result.id()) did not work
+                    // for some reason
+                    $window.location.href = egCore.env.basePath + 'cat/catalog/record/' + result.id();
+                });
             }
         );
 

commit 9bcc91a67131a496ae5c6658eaf684bed1b7a10f
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Sep 24 18:33:58 2015 +0000

    webstaff: teach egConfirmDialog how to set custom labels for buttons
    
    egConfirmDialog() now accepts optional fourth and fifth
    arguments to set custom labels for the "OK" and "cancel"
    buttons.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

diff --git a/Open-ILS/src/templates/staff/share/t_confirm_dialog.tt2 b/Open-ILS/src/templates/staff/share/t_confirm_dialog.tt2
index 45e8ca1..a9d6ac1 100644
--- a/Open-ILS/src/templates/staff/share/t_confirm_dialog.tt2
+++ b/Open-ILS/src/templates/staff/share/t_confirm_dialog.tt2
@@ -11,8 +11,8 @@
   <div class="modal-footer">
     [% dialog_footer %]
     <input type="submit" class="btn btn-primary" 
-      ng-click="ok()" value="[% l('OK/Continue') %]"/>
+      ng-click="ok()" value="{{ ok_button_label || '[% l('OK/Continue') %]'}}"/>
     <button class="btn btn-warning" 
-      ng-click="cancel()">[% l('Cancel') %]</button>
+      ng-click="cancel()">{{ cancel_button_label || "[% l('Cancel') %]"}}</button>
   </div>
 </div>
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 4960317..5264d68 100644
--- a/Open-ILS/web/js/ui/default/staff/services/ui.js
+++ b/Open-ILS/web/js/ui/default/staff/services/ui.js
@@ -109,7 +109,8 @@ function($modal , $interpolate) {
 
 /**
  * egConfirmDialog.open("some message goes {{here}}", {
- *  here : 'foo', ok : function() {}, cancel : function() {}});
+ *  here : 'foo', ok : function() {}, cancel : function() {}},
+ *  'OK', 'Cancel');
  */
 .factory('egConfirmDialog', 
     
@@ -117,13 +118,15 @@ function($modal , $interpolate) {
 function($modal, $interpolate) {
     var service = {};
 
-    service.open = function(title, message, msg_scope) {
+    service.open = function(title, message, msg_scope, ok_button_label, cancel_button_label) {
         return $modal.open({
             templateUrl: './share/t_confirm_dialog',
             controller: ['$scope', '$modalInstance',
                 function($scope, $modalInstance) {
                     $scope.title = $interpolate(title)(msg_scope);
                     $scope.message = $interpolate(message)(msg_scope);
+                    $scope.ok_button_label = $interpolate(ok_button_label || '')(msg_scope);
+                    $scope.cancel_button_label = $interpolate(cancel_button_label || '')(msg_scope);
                     $scope.ok = function() {
                         if (msg_scope.ok) msg_scope.ok();
                         $modalInstance.close()

commit 8106d873c2d21bcf2ceb34f063804743186ede15
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Sep 24 15:47:07 2015 +0000

    webstaff: fix deleting fields in MARC editor
    
    When editing a record that has not yet been saved
    to Evergreen (either because it is new or because it
    is a record coming from a Z39.50 target), ensure that
    the context menu 'delete field' operation works.
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 05d1643..64f4b4e 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
@@ -683,7 +683,9 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                 var deleteDatafield = function (e) {
                     var del_field = e.data.scope.field.position;
 
-                    var domnode = $('#r'+e.data.scope.field.record.subfield('901','c')[1] + 'f' + del_field);
+                    var sf901c = e.data.scope.field.record.subfield('901','c');
+                    var recId = (sf901c === null) ? '' : sf901c[1];
+                    var domnode = $('#r' + recId + 'f' + del_field);
 
                     e.data.scope.field.record.deleteFields(
                         e.data.scope.field

commit 641c8ec0ce89b215f00458beeb0763e84bb81a43
Author: Galen Charlton <gmc at esilibrary.com>
Date:   Thu Sep 17 22:45:36 2015 +0000

    webstaff: make some MARC editor strings translateable
    
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 145ddd3..4f3c1ae 100644
--- a/Open-ILS/src/templates/staff/cat/bucket/record/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/bucket/record/index.tt2
@@ -11,6 +11,7 @@
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/marcrecord.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/tagtable.js"></script>
+[% INCLUDE 'staff/cat/share/marcedit_strings.tt2' %]
 <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/holds.js"></script>
 [% INCLUDE 'staff/circ/share/hold_strings.tt2' %]
diff --git a/Open-ILS/src/templates/staff/cat/catalog/index.tt2 b/Open-ILS/src/templates/staff/cat/catalog/index.tt2
index 5abae22..c46a026 100644
--- a/Open-ILS/src/templates/staff/cat/catalog/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/catalog/index.tt2
@@ -10,6 +10,7 @@
 <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/tagtable.js"></script>
+[% INCLUDE 'staff/cat/share/marcedit_strings.tt2' %]
 <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' %]
diff --git a/Open-ILS/src/templates/staff/cat/share/marcedit_strings.tt2 b/Open-ILS/src/templates/staff/cat/share/marcedit_strings.tt2
new file mode 100644
index 0000000..81b3190
--- /dev/null
+++ b/Open-ILS/src/templates/staff/cat/share/marcedit_strings.tt2
@@ -0,0 +1,12 @@
+[%# Strings for cat/services/marcedit.js %]
+
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+    s.INSERT_FIELD_AFTER        = "[% l('Insert field after') %]";
+    s.INSERT_FIELD_BEFORE       = "[% l('Insert field before') %]";
+    s.ADD_006                   = "[% l('Add 006') %]";
+    s.ADD_007                   = "[% l('Add 007') %]";
+    s.ADD_REPLACE_008           = "[% l('Add/Replace 008') %]";
+    s.DELETE_FIELD              = "[% l('Delete field') %]";
+}]);
+</script>
diff --git a/Open-ILS/src/templates/staff/cat/z3950/index.tt2 b/Open-ILS/src/templates/staff/cat/z3950/index.tt2
index af38a12..291b335 100644
--- a/Open-ILS/src/templates/staff/cat/z3950/index.tt2
+++ b/Open-ILS/src/templates/staff/cat/z3950/index.tt2
@@ -13,6 +13,7 @@
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/marcrecord.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/tagtable.js"></script>
+[% INCLUDE 'staff/cat/share/marcedit_strings.tt2' %]
 <script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/marcedit.js"></script>
 [% END %]
 
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 1a92f13..05d1643 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
@@ -381,25 +381,25 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                       '/></span>',
         scope: { tag : '=', field: '=', onKeydown: '=', contextFunctions: '=' },
         replace: true,
-        controller : ['$scope', 'egTagTable',
-            function ( $scope ,  egTagTable) {
+        controller : ['$scope', 'egTagTable', 'egCore',
+            function ( $scope ,  egTagTable,   egCore) {
 
                 $scope.tag_options = [
                     function () {
                         var options = [
-                            { label : 'Add 006', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.add006(e) } },
-                            { label : 'Add 007', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.add007(e) } },
-                            { label : 'Add/Replace 008', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.reify008(e) } },
+                            { label : egCore.strings.ADD_006, action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.add006(e) } },
+                            { label : egCore.strings.ADD_007, action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.add007(e) } },
+                            { label : egCore.strings.ADD_REPLACE_008, action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.reify008(e) } },
                         ];
 
                         if (!$scope.field.isControlfield()) {
                             options = options.concat([
-                                { label : 'Insert field after ', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.addDatafield(e) } },
-                                { label : 'Insert field before', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.addDatafield(e,true) } },
+                                { label : egCore.strings.INSERT_FIELD_AFTER, action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.addDatafield(e) } },
+                                { label : egCore.strings.INSERT_FIELD_BEFORE, action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.addDatafield(e,true) } },
                             ]);
                         }
 
-                        options.push({ label : 'Delete Field', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.deleteDatafield(e) } });
+                        options.push({ label : egCore.strings.DELETE_FIELD, action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.deleteDatafield(e) } });
                         return options;
                     },
                     function () { return egTagTable.getFieldTags() }

commit 8778d16cbd47dc864173fe5afa05da0b25cc9256
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Sep 17 18:11:11 2015 -0400

    webstaff: Select on focus for tag, indicators, and sf codes
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 ceb0e5b..1a92f13 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
@@ -64,7 +64,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
             contextItemContainer: '@',
             contextItemGenerator: '=',
             max: '@',
-            itype: '@'
+            itype: '@',
+            selectOnFocus: '='
         },
         controller : ['$scope',
             function ( $scope ) {
@@ -142,6 +143,11 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 
             if (scope.onKeydown) element.bind('keydown', {scope : scope}, scope.onKeydown);
 
+            if (Boolean(scope.selectOnFocus)) {
+                element.addClass('noSelection');
+                element.bind('focus', function () { element.select() });
+            }
+
             element.bind('change', function (e) { element.size = scope.max || parseInt(scope.content.length * 1.1) });
 
             element.bind('contextmenu', {scope : scope}, scope.showContext);
@@ -293,6 +299,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                         'for="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}s{{subfield[2]}}code" '+
                         '>‡</label><eg-marc-edit-editable '+
                         'itype="sfc" '+
+                        'select-on-focus="true" '+
                         'class="marcedit marcsf marcsfcode" '+
                         'field="field" '+
                         'subfield="subfield" '+
@@ -336,6 +343,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         template: '<span><eg-marc-edit-editable '+
                       'itype="ind" '+
                       'class="marcedit marcind" '+
+                      'select-on-focus="true" '+
                       'field="field" '+
                       'content="ind" '+
                       'max="1" '+
@@ -363,6 +371,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         template: '<span><eg-marc-edit-editable '+
                       'itype="tag" '+
                       'class="marcedit marctag" '+
+                      'select-on-focus="true" '+
                       'field="field" '+
                       'content="tag" '+
                       'max="3" '+
@@ -385,8 +394,8 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 
                         if (!$scope.field.isControlfield()) {
                             options = options.concat([
-                                { label : 'Add Datafield', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.addDatafield(e) } },
-                                { label : 'Insert Datafield', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.addDatafield(e,true) } },
+                                { label : 'Insert field after ', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.addDatafield(e) } },
+                                { label : 'Insert field before', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.addDatafield(e,true) } },
                             ]);
                         }
 
@@ -577,6 +586,14 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         },
         link: function (scope, element, attrs) {
 
+            element.bind('mouseup', function(e) {;
+                scope.current_event_target = $(e.target).attr('id');
+                if (scope.current_event_target && $(e.target).hasClass('noSelection')) {
+                    e.preventDefault()
+                    return false;
+                }
+            });
+
             element.bind('click', function(e) {;
                 scope.current_event_target = $(e.target).attr('id');
                 if (scope.current_event_target) {

commit 313a5fd02b887477662c0b4f2ac9208a4e350b77
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Sep 17 17:19:50 2015 -0400

    webstaff: Provide context menu options for adding and removing fields
    
    TODO: i18n on strings
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>
    Signed-off-by: Galen Charlton <gmc at esilibrary.com>
    Signed-off-by: Kathy Lussier <klussier at masslnc.org>

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 59596a5..4c1adbe 100644
--- a/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
+++ b/Open-ILS/src/templates/staff/cat/share/t_marcedit.tt2
@@ -163,6 +163,7 @@
           ng-repeat="field in controlfields" 
           field="field" on-keydown="onKeydown"
           id="r{{field.record.subfield('901','c')[1]}}f{{field.position}}"
+          context-functions="context_functions"
         />
       </div>
       <div>
@@ -170,6 +171,7 @@
           ng-repeat="field in datafields" 
           field="field" on-keydown="onKeydown" 
           id="r{{field.record.subfield('901','c')[1]}}f{{field.position}}"
+          context-functions="context_functions"
         />
       </div>
     </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 d4686eb..ceb0e5b 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
@@ -9,22 +9,34 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         restrict: 'E',
         replace: true,
         template: '<li><a ng-click="setContent(item.value,item.action)">{{item.label}}</a></li>',
-        scope: { item: '=', content: '=' },
+        scope: { item: '=', content: '=', contextMenuEvent: '=' },
         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';
+                    $element.css('borderTop','solid 1px');
                 }
 
                 $scope.setContent = function (v, a) {
                     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);
+
+                    if (a) {
+                        replace_with = a(
+                            $scope,
+                            $element,
+                            $scope.item.value,
+                            $scope.$parent.$parent.content,
+                            $scope.contextMenuEvent
+                        );
+                    }
+
+                    if (typeof replace_with !== 'undefined') {
+                        $timeout(function(){
+                            $scope.$parent.$parent.$apply(function(){
+                                $scope.$parent.$parent.content = replace_with
+                            })
+                        }, 0);
+                    }
                     $($element).parent().css({display: 'none'});
                 }
             }
@@ -69,7 +81,22 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     } else if ($scope.item_generator) {
                         // always recalculate; tag and/or subfield
                         // codes may have changed
-                        $scope.item_list = $scope.item_generator();
+
+                        var generator = $scope.item_generator;
+                        if (!angular.isArray(generator)) generator = [generator];
+
+                        var is_first = true;
+                        angular.forEach(generator, function (g) {
+                            var sub_list = g();
+
+                            if (is_first)
+                                is_first = false;
+                            else if (Boolean(sub_list[0]))
+                                sub_list[0].divider = true;
+
+                            $scope.item_list = $scope.item_list.concat(sub_list);
+                        });
+
                     } else {
                         return true;
                     }
@@ -78,9 +105,10 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                         console.log('Showing context menu...');
                         $('body').trigger('click');
 
+                        $scope.contextMenuEvent = event;
                         var tmpl = 
                             '<ul class="dropdown-menu" role="menu" style="z-index: 2000;">'+
-                                '<eg-context-menu-item ng-repeat="item in item_list" item="item" content="content"/>'+
+                                '<eg-context-menu-item context-menu-event="contextMenuEvent" ng-repeat="item in item_list" item="item" content="content"/>'+
                             '</ul>';
             
                         var tnode = angular.element(tmpl);
@@ -116,7 +144,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
 
             element.bind('change', function (e) { element.size = scope.max || parseInt(scope.content.length * 1.1) });
 
-            element.bind('contextmenu', scope.showContext);
+            element.bind('contextmenu', {scope : scope}, scope.showContext);
         }
     }
 }])
@@ -342,14 +370,32 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                       'context-item-generator="tag_options" '+
                       'id="r{{field.record.subfield(\'901\',\'c\')[1]}}f{{field.position}}tag"'+
                       '/></span>',
-        scope: { tag : '=', field: '=', onKeydown: '=' },
+        scope: { tag : '=', field: '=', onKeydown: '=', contextFunctions: '=' },
         replace: true,
         controller : ['$scope', 'egTagTable',
             function ( $scope ,  egTagTable) {
 
-                $scope.tag_options = function () {
-                    return egTagTable.getFieldTags();
-                }
+                $scope.tag_options = [
+                    function () {
+                        var options = [
+                            { label : 'Add 006', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.add006(e) } },
+                            { label : 'Add 007', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.add007(e) } },
+                            { label : 'Add/Replace 008', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.reify008(e) } },
+                        ];
+
+                        if (!$scope.field.isControlfield()) {
+                            options = options.concat([
+                                { label : 'Add Datafield', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.addDatafield(e) } },
+                                { label : 'Insert Datafield', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.addDatafield(e,true) } },
+                            ]);
+                        }
+
+                        options.push({ label : 'Delete Field', action : function(j1,j2,j3,j4,e) { $scope.contextFunctions.deleteDatafield(e) } });
+                        return options;
+                    },
+                    function () { return egTagTable.getFieldTags() }
+                ];
+
             }
         ]
     }
@@ -360,7 +406,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         transclude: true,
         restrict: 'E',
         template: '<div>'+
-                    '<span><eg-marc-edit-tag field="field" tag="field.tag" on-keydown="onKeydown"/></span>'+
+                    '<span><eg-marc-edit-tag context-functions="contextFunctions" 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-class="{ \'unvalidatedheading\' : field.heading_checked && !field.heading_valid}" ng-repeat="subfield in field.subfields" subfield="subfield" field="field" on-keydown="onKeydown"/></span>'+
@@ -376,7 +422,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     '<span ng-show="field.heading_checked && field.heading_valid" class="glyphicon glyphicon-ok-sign"></span>'+
                     '<span ng-show="field.heading_checked && !field.heading_valid" class="glyphicon glyphicon-question-sign"></span>'+
                   '</div>',
-        scope: { field: "=", onKeydown: '=' },
+        scope: { field: "=", onKeydown: '=', contextFunctions: '=' },
         replace: true,
         controller : ['$scope','$modal',
             function ( $scope,  $modal ) {
@@ -427,7 +473,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
         transclude: true,
         restrict: 'E',
         template: '<div>'+
-                    '<span><eg-marc-edit-tag field="field" tag="field.tag" on-keydown="onKeydown"/></span>'+
+                    '<span><eg-marc-edit-tag context-functions="contextFunctions" field="field" tag="field.tag" on-keydown="onKeydown"/></span>'+
                     '<span><eg-marc-edit-editable '+
                       'itype="cfld" '+
                       'field="field" '+
@@ -445,7 +491,7 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                       '<span class="glyphicon glyphicon-link"></span>'+
                       '</button>'+
                   '</div>',
-        scope: { field: "=", onKeydown: '=' },
+        scope: { field: "=", onKeydown: '=', contextFunctions: '=' },
         controller : ['$scope','$modal',
             function ( $scope,  $modal) {
                 $scope.showPhysCharLink = function () {
@@ -584,6 +630,116 @@ angular.module('egMarcMod', ['egCoreMod', 'ui.bootstrap'])
                     }
                 };
 
+                var addDatafield = function (e,before) {
+                    var element = $(e.target);
+
+                    var index_field = e.data.scope.field.position;
+                    var new_field = index_field + 1;
+
+                    var new_field = new MARC21.Field({
+                        tag : '999',
+                        subfields : [[' ','',0]]
+                    });
+
+                    if (Boolean(before)) {
+                        e.data.scope.field.record.insertFieldsBefore(
+                            e.data.scope.field,
+                            new_field
+                        );
+                    } else {
+                        e.data.scope.field.record.insertFieldsAfter(
+                            e.data.scope.field,
+                            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);
+                };
+
+                var deleteDatafield = function (e) {
+                    var del_field = e.data.scope.field.position;
+
+                    var domnode = $('#r'+e.data.scope.field.record.subfield('901','c')[1] + 'f' + del_field);
+
+                    e.data.scope.field.record.deleteFields(
+                        e.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);
+                };
+
+                var add006 = function (e) {
+                    e.data.scope.field.record.insertOrderedFields(
+                        new MARC21.Field({
+                            tag : '006',
+                            data : '                                        '
+                        })
+                    );
+
+                    $scope.force_render = true;
+                    $timeout(function(){$scope.$digest()}).then(setCaret);
+                };
+
+                var add007 = function (e) {
+                    e.data.scope.field.record.insertOrderedFields(
+                        new MARC21.Field({
+                            tag : '007',
+                            data : '                                        '
+                        })
+                    );
+
+                    $scope.force_render = true;
+                    $timeout(function(){$scope.$digest()}).then(setCaret);
+                };
+
+                var reify008 = function (e) {
+                    var new_008_data = e.data.scope.field.record.generate008();
+
+
+                    var old_008s = e.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();
+                        e.data.scope.field.record.deleteFields(o);
+                    });
+
+                    e.data.scope.field.record.insertOrderedFields(
+                        new MARC21.Field({
+                            tag : '008',
+                            data : new_008_data
+                        })
+                    );
+
+                    $scope.force_render = true;
+                    $timeout(function(){$scope.$digest()}).then(setCaret);
+                };
+
+                $scope.context_functions = {
+                    addDatafield : addDatafield,
+                    deleteDatafield : deleteDatafield,
+                    add006 : add006,
+                    add007 : add007,
+                    reify008 : reify008
+                };
+
                 $scope.onKeydown = function (event) {
                     var event_return = true;
 
@@ -645,103 +801,23 @@ 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 MARC21.Field({
-                                tag : '006',
-                                data : '                                        '
-                            })
-                        );
-
-                        $scope.force_render = true;
-                        $timeout(function(){$scope.$digest()}).then(setCaret);
-
+                        add006(event);
                         event_return = false;
 
                     } else if (event.which == 118 && event.shiftKey) { // shift + F7, insert 007
-                        event.data.scope.field.record.insertOrderedFields(
-                            new MARC21.Field({
-                                tag : '007',
-                                data : '                                        '
-                            })
-                        );
-
-                        $scope.force_render = true;
-                        $timeout(function(){$scope.$digest()}).then(setCaret);
-
+                        add007(event);
                         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 MARC21.Field({
-                                tag : '008',
-                                data : new_008_data
-                            })
-                        );
-
-                        $scope.force_render = true;
-                        $timeout(function(){$scope.$digest()}).then(setCaret);
-
+                        reify008(event);
                         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 MARC21.Field({
-                                tag : '999',
-                                subfields : [[' ','',0]]
-                            })
-                        );
-
-                        $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);
-
+                        addDatafield(event, event.shiftKey); // shift key inserts before
                         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);
-
+                        deleteDatafield(event);
                         event_return = false;
 
                     } else if (event.which == 46 && event.shiftKey && $(event.target).hasClass('marcsf')) { // shift+del, remove subfield

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

Summary of changes:
 Open-ILS/examples/apache/eg_vhost.conf.in          |    1 +
 Open-ILS/examples/apache_24/eg_vhost.conf.in       |    1 +
 Open-ILS/examples/fm_IDL.xml                       |    7 +-
 .../src/perlmods/lib/OpenILS/Application/Cat.pm    |    2 +-
 .../src/templates/opac/parts/record/copy_table.tt2 |    7 +-
 .../staff/admin/local/circ/age_to_lost.tt2         |   91 ++++
 .../staff/admin/local/circ/neg_balance_users.tt2   |   55 +++
 .../staff/admin/local/config/auto_print.tt2        |   86 ++++
 Open-ILS/src/templates/staff/admin/local/index.tt2 |   16 +
 .../src/templates/staff/admin/local/t_splash.tt2   |   53 +++
 Open-ILS/src/templates/staff/base_js.tt2           |    2 +
 .../src/templates/staff/cat/bucket/copy/t_view.tt2 |    4 +-
 .../templates/staff/cat/bucket/record/index.tt2    |    3 +
 .../staff/cat/bucket/record/t_merge_records.tt2    |   28 +-
 .../templates/staff/cat/bucket/record/t_view.tt2   |    6 +-
 Open-ILS/src/templates/staff/cat/catalog/index.tt2 |    2 +
 .../staff/cat/catalog/t_add_to_bucket.tt2          |    2 +-
 .../src/templates/staff/cat/catalog/t_catalog.tt2  |   42 ++-
 .../staff/cat/catalog/t_choose_vol_target_lib.tt2  |   25 +
 .../src/templates/staff/cat/catalog/t_holdings.tt2 |   24 +-
 .../src/templates/staff/cat/catalog/t_new_bib.tt2  |    2 +-
 Open-ILS/src/templates/staff/cat/item/t_list.tt2   |    4 +-
 .../templates/staff/cat/item/t_summary_pane.tt2    |    2 +-
 Open-ILS/src/templates/staff/cat/item/t_view.tt2   |    2 +-
 .../templates/staff/cat/share/marcedit_strings.tt2 |   16 +
 .../staff/cat/share/t_embedded_volcopy.tt2         |   14 +
 .../src/templates/staff/cat/share/t_marcedit.tt2   |   67 ++-
 .../staff/cat/share/t_physchar_wizard.tt2          |    2 +-
 .../templates/staff/cat/share/t_record_summary.tt2 |   16 +-
 .../templates/staff/cat/share/t_volume_list.tt2    |   15 +
 .../templates/staff/cat/share/z3950_strings.tt2    |   12 +
 .../templates/staff/cat/volcopy/t_attr_edit.tt2    |   34 ++-
 .../src/templates/staff/cat/volcopy/t_defaults.tt2 |   12 +-
 .../src/templates/staff/cat/volcopy/t_edit.tt2     |   17 +-
 .../src/templates/staff/cat/volcopy/t_view.tt2     |   14 +-
 Open-ILS/src/templates/staff/cat/z3950/index.tt2   |    2 +
 .../staff/cat/z3950/t_edit_overlay_record.tt2      |    2 +-
 .../src/templates/staff/cat/z3950/t_marc_edit.tt2  |    4 +-
 .../templates/staff/cat/z3950/t_search_fields.tt2  |   15 +-
 Open-ILS/src/templates/staff/css/admin.css.tt2     |   30 ++
 Open-ILS/src/templates/staff/css/cat.css.tt2       |   19 +
 Open-ILS/src/templates/staff/css/style.css.tt2     |   38 ++
 Open-ILS/src/templates/staff/navbar.tt2            |    6 +
 Open-ILS/src/templates/staff/share/t_autogrid.tt2  |   17 +-
 .../src/templates/staff/share/t_confirm_dialog.tt2 |    4 +-
 Open-ILS/web/js/ui/default/staff/Gruntfile.js      |    2 +
 .../web/js/ui/default/staff/admin/actor/app.js     |   48 ++
 .../web/js/ui/default/staff/admin/local/app.js     |  105 +++++
 .../default/staff/admin/local/circ/age_to_lost.js  |   67 +++
 .../staff/admin/local/circ/neg_balance_users.js    |   46 ++
 .../default/staff/admin/local/config/auto_print.js |   75 +++
 Open-ILS/web/js/ui/default/staff/bower.json        |    4 +-
 .../web/js/ui/default/staff/cat/bucket/copy/app.js |   40 +-
 .../js/ui/default/staff/cat/bucket/record/app.js   |   24 +-
 .../web/js/ui/default/staff/cat/catalog/app.js     |  495 ++++++++------------
 Open-ILS/web/js/ui/default/staff/cat/item/app.js   |   37 ++-
 .../js/ui/default/staff/cat/services/holdings.js   |  373 +++++++++++++++
 .../js/ui/default/staff/cat/services/marcedit.js   |  476 +++++++++++++------
 .../web/js/ui/default/staff/cat/services/record.js |   92 ++++-
 .../web/js/ui/default/staff/cat/volcopy/app.js     |  470 +++++++++++++------
 Open-ILS/web/js/ui/default/staff/cat/z3950/app.js  |   30 +-
 .../extern/angular-location-update/.bower.json     |   31 ++
 .../staff/extern/angular-location-update/LICENSE   |   22 +
 .../staff/extern/angular-location-update/README.md |   52 ++
 .../angular-location-update.js                     |   22 +
 .../angular-location-update.min.js                 |    1 +
 .../extern/angular-location-update/bower.json      |   22 +
 .../extern/angular-location-update/package.json    |   25 +
 Open-ILS/web/js/ui/default/staff/marcrecord.js     |   10 +-
 Open-ILS/web/js/ui/default/staff/services/core.js  |    2 +-
 .../web/js/ui/default/staff/services/eframe.js     |    6 +-
 Open-ILS/web/js/ui/default/staff/services/file.js  |   18 +-
 Open-ILS/web/js/ui/default/staff/services/grid.js  |   11 +-
 Open-ILS/web/js/ui/default/staff/services/hatch.js |   16 +
 Open-ILS/web/js/ui/default/staff/services/org.js   |   19 +-
 Open-ILS/web/js/ui/default/staff/services/ui.js    |   73 +++-
 .../web/js/ui/default/staff/test/unit/egOrg.js     |    5 +
 .../xul/staff_client/server/admin/closed_dates.js  |   28 +-
 .../staff_client/server/admin/closed_dates.xhtml   |    2 -
 79 files changed, 2822 insertions(+), 750 deletions(-)
 create mode 100644 Open-ILS/src/templates/staff/admin/local/circ/age_to_lost.tt2
 create mode 100644 Open-ILS/src/templates/staff/admin/local/circ/neg_balance_users.tt2
 create mode 100644 Open-ILS/src/templates/staff/admin/local/config/auto_print.tt2
 create mode 100644 Open-ILS/src/templates/staff/admin/local/index.tt2
 create mode 100644 Open-ILS/src/templates/staff/admin/local/t_splash.tt2
 create mode 100644 Open-ILS/src/templates/staff/cat/catalog/t_choose_vol_target_lib.tt2
 create mode 100644 Open-ILS/src/templates/staff/cat/share/marcedit_strings.tt2
 create mode 100644 Open-ILS/src/templates/staff/cat/share/t_embedded_volcopy.tt2
 create mode 100644 Open-ILS/src/templates/staff/cat/share/t_volume_list.tt2
 create mode 100644 Open-ILS/src/templates/staff/cat/share/z3950_strings.tt2
 create mode 100644 Open-ILS/src/templates/staff/css/admin.css.tt2
 create mode 100644 Open-ILS/web/js/ui/default/staff/admin/actor/app.js
 create mode 100644 Open-ILS/web/js/ui/default/staff/admin/local/app.js
 create mode 100644 Open-ILS/web/js/ui/default/staff/admin/local/circ/age_to_lost.js
 create mode 100644 Open-ILS/web/js/ui/default/staff/admin/local/circ/neg_balance_users.js
 create mode 100644 Open-ILS/web/js/ui/default/staff/admin/local/config/auto_print.js
 create mode 100644 Open-ILS/web/js/ui/default/staff/cat/services/holdings.js
 create mode 100644 Open-ILS/web/js/ui/default/staff/extern/angular-location-update/.bower.json
 create mode 100644 Open-ILS/web/js/ui/default/staff/extern/angular-location-update/LICENSE
 create mode 100644 Open-ILS/web/js/ui/default/staff/extern/angular-location-update/README.md
 create mode 100644 Open-ILS/web/js/ui/default/staff/extern/angular-location-update/angular-location-update.js
 create mode 100644 Open-ILS/web/js/ui/default/staff/extern/angular-location-update/angular-location-update.min.js
 create mode 100644 Open-ILS/web/js/ui/default/staff/extern/angular-location-update/bower.json
 create mode 100644 Open-ILS/web/js/ui/default/staff/extern/angular-location-update/package.json


hooks/post-receive
-- 
Evergreen ILS




More information about the open-ils-commits mailing list