From b98f8efea5a1937704c94b3de534d9028fd09d40 Mon Sep 17 00:00:00 2001 From: Mike Rylander Date: Tue, 3 Jul 2018 16:57:27 -0400 Subject: [PATCH] LP#1732761: Batch item edit and multiple values per field Previous to this commit, the display of multiple different values for a field in the item attribute editor was simply to display no value. Here we add a UI component that presents the list of unique values, the number of selected copies that use each value, and the ability to select just those copies using a particular value by clicking on the desired value. Signed-off-by: Mike Rylander Signed-off-by: Kathy Lussier Conflicts: Open-ILS/src/templates/staff/cat/volcopy/index.tt2 Open-ILS/src/templates/staff/cat/volcopy/t_attr_edit.tt2 --- .../src/templates/staff/cat/volcopy/index.tt2 | 8 ++ .../staff/cat/volcopy/t_attr_edit.tt2 | 54 +++++++++++ .../templates/staff/share/t_listcounts.tt2 | 11 +++ .../js/ui/default/staff/cat/volcopy/app.js | 89 +++++++++++++++++++ .../web/js/ui/default/staff/services/grid.js | 19 ++++ .../web/js/ui/default/staff/services/ui.js | 44 +++++++++ 6 files changed, 225 insertions(+) create mode 100644 Open-ILS/src/templates/staff/share/t_listcounts.tt2 diff --git a/Open-ILS/src/templates/staff/cat/volcopy/index.tt2 b/Open-ILS/src/templates/staff/cat/volcopy/index.tt2 index 385c904226..d50b450980 100644 --- a/Open-ILS/src/templates/staff/cat/volcopy/index.tt2 +++ b/Open-ILS/src/templates/staff/cat/volcopy/index.tt2 @@ -15,6 +15,14 @@ angular.module('egCoreMod').run(['egStrings', function(s) { s.VOL_COPY_TEMPLATE_SUCCESS_SAVE = "[% l('Saved holdings template(s)') %]"; s.VOL_COPY_TEMPLATE_SUCCESS_DELETE = "[% l('Deleted holdings template') %]"; + s.SHORT = "[% l('Short') %]"; + s.LOW = "[% l('Low') %]"; + s.NORMAL = "[% l('Normal') %]"; + s.EXTENDED = "[% l('Extended') %]"; + s.HIGH = "[% l('High') %]"; + s.UNSET = "[% l('UNSET') %]"; + s.YES = "[% l('Yes') %]"; + s.NO = "[% l('No') %]"; [%# Note the "~" characters escape the gettext brackets %] s.COPY_NOTE_INITIALS = "[% l('[_1] ~[ [_2] @ [_3] ~]', '{{value}}', '{{initials}}', '{{ws_ou}}') %]" 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 ffbaebaba7..b133c7ae31 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 @@ -120,12 +120,18 @@ +
+ +
+
+ +
@@ -149,6 +155,9 @@ label="[% l('(Unset)') %]" disable-test="cant_have_vols" > +
+ +
@@ -165,6 +174,9 @@
+
+ +
@@ -185,6 +197,9 @@ ng-disabled="!defaults.attributes.location" ng-model="working.location" ng-options="l.id() as i18n.ou_qualified_location_name(l) for l in location_list" > +
+ +
@@ -201,6 +216,9 @@
+
+ +
@@ -223,9 +241,15 @@ > +
+ +
+
+ +
@@ -247,9 +271,15 @@ +
+ +
+
+ +
@@ -271,6 +301,9 @@ ng-options="t.code() as t.value() for t in circ_type_list"> +
+ +
@@ -287,6 +320,9 @@
+
+ +
@@ -317,9 +353,15 @@ +
+ +
+
+ +
@@ -341,6 +383,9 @@ ng-options="a.id() as a.name() for a in age_protect_list"> +
+ +
@@ -357,6 +402,9 @@
+
+ +
@@ -375,6 +423,9 @@ +
+ +
+ +
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 ec422072b6..2efb600b4e 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 @@ -1121,6 +1121,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore , var newval = $scope.working[field]; if (typeof newval != 'undefined') { + delete $scope.working.MultiMap[field]; if (angular.isObject(newval)) { // we'll use the pkey if (newval.id) newval = newval.id(); else if (newval.code) newval = newval.code(); @@ -1152,6 +1153,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore , } $scope.working = { + MultiMap: {}, statcats: {}, statcats_multi: {}, statcat_filter: undefined @@ -1312,6 +1314,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore , }); } }); + delete $scope.working.MultiMap[k]; egCore.hatch.setItem('cat.copy.last_template', n); } @@ -1395,6 +1398,87 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore , $scope.add_vols_copies = false; $scope.is_fast_add = false; + // Generate some functions for selecting items by column value in the working grid + angular.forEach( + ['circulate','status','circ_lib','ref','location','opac_visible','circ_modifier','price', + 'loan_duration','cost','circ_as_type','deposit','holdable','deposit_amount','age_protect', + 'mint_condition','fine_level','floating'], + function (field) { + $scope['select_by_' + field] = function (x) { + $scope.workingGridControls.selectItemsByValue(field,x); + } + } + ); + + var truthy = /^t|1/; + $scope.labelYesNo = function (x) { + return truthy.test(x) ? egCore.strings.YES : egCore.strings.NO; + } + + $scope.orgShortname = function (x) { + return egCore.org.get(x).shortname(); + } + + $scope.statusName = function (x) { + var s = $scope.status_list.filter(function(y) { + return y.id() == x; + }); + + return s[0].name(); + } + + $scope.locationName = function (x) { + var s = $scope.location_list.filter(function(y) { + return y.id() == x; + }); + + return $scope.i18n.ou_qualified_location_name(s[0]); + } + + $scope.durationLabel = function (x) { + return [egCore.strings.SHORT, egCore.strings.NORMAL, egCore.strings.EXTENDED][-1 + x] + } + + $scope.fineLabel = function (x) { + return [egCore.strings.LOW, egCore.strings.NORMAL, egCore.strings.HIGH][-1 + x] + } + + $scope.circTypeValue = function (x) { + if (x === null) return egCore.strings.UNSET; + var s = $scope.circ_type_list.filter(function(y) { + return y.code() == x; + }); + + return s[0].value(); + } + + $scope.ageprotectName = function (x) { + if (x === null) return egCore.strings.UNSET; + var s = $scope.age_protect_list.filter(function(y) { + return y.id() == x; + }); + + return s[0].name(); + } + + $scope.floatingName = function (x) { + if (x === null) return egCore.strings.UNSET; + var s = $scope.floating_list.filter(function(y) { + return y.id() == x; + }); + + return s[0].name(); + } + + $scope.circmodName = function (x) { + if (x === null) return egCore.strings.UNSET; + var s = $scope.circ_modifier_list.filter(function(y) { + return y.code() == x; + }); + + return s[0].name(); + } + egNet.request( 'open-ils.actor', 'open-ils.actor.anon_cache.get_value', @@ -1600,6 +1684,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore , angular.forEach(Object.keys($scope.defaults.attributes), function (attr) { var value_hash = {}; + var value_list = []; angular.forEach(item_list, function (item) { if (item[attr]) { var v = item[attr]() @@ -1607,10 +1692,13 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore , if (v.id) v = v.id(); else if (v.code) v = v.code(); } + value_list.push(v); value_hash[v] = 1; } }); + $scope.working.MultiMap[attr] = value_list; + if (Object.keys(value_hash).length == 1) { if (attr == 'circ_lib') { $scope.working[attr] = egCore.org.get(item_list[0][attr]()); @@ -2401,6 +2489,7 @@ function($scope , $q , $window , $routeParams , $location , $timeout , egCore , $scope.clearWorking = function () { angular.forEach($scope.working, function (v,k,o) { + $scope.working.MultiMap[k] = []; if (!angular.isObject(v)) { if (typeof v != 'undefined') $scope.working[k] = undefined; 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 47690ca913..a1f8d5fee0 100644 --- a/Open-ILS/web/js/ui/default/staff/services/grid.js +++ b/Open-ILS/web/js/ui/default/staff/services/grid.js @@ -283,6 +283,10 @@ angular.module('egGridMod', return grid.getSelectedItems() } + controls.selectItemsByValue = function(c,v) { + return grid.selectItemsByValue(c,v) + } + controls.allItems = function() { return $scope.items; } @@ -743,6 +747,21 @@ angular.module('egGridMod', $scope.selected[index] = true; } + // selects items by a column value, first clearing selected list. + // we overwrite the object so that we can watch $scope.selected + grid.selectItemsByValue = function(column, value) { + $scope.selected = {}; + angular.forEach($scope.items, function(item) { + var col_value; + if (angular.isFunction(item[column])) + col_value = item[column](); + else + col_value = item[column]; + + if (value == col_value) $scope.selected[grid.indexValue(item)] = true + }); + } + // selects or deselects an item, without affecting the others. // returns true if the item is selected; false if de-selected. // we overwrite the object so that we can watch $scope.selected 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 8f38e1cd8f..ed5f84f007 100644 --- a/Open-ILS/web/js/ui/default/staff/services/ui.js +++ b/Open-ILS/web/js/ui/default/staff/services/ui.js @@ -1003,6 +1003,50 @@ function($uibModal , $interpolate , egCore) { }; }) +.directive('egListCounts', function() { + return { + restrict: 'E', + replace: true, + scope: { + label: "@", + list: "=", // list of things + render: "=", // function to turn thing into string; default to stringification + onSelect: "=" // function to fire when option selected. passed one copy of the selected value + }, + templateUrl: './share/t_listcounts', + controller: ['$scope','$timeout', + function( $scope , $timeout ) { + + $scope.isopen = false; + $scope.count_hash = {}; + + $scope.renderer = $scope.render ? $scope.render : function (x) { return ""+x }; + + $scope.$watchCollection('list',function() { + $scope.count_hash = {}; + angular.forEach($scope.list, function (item) { + var str = $scope.renderer(item); + if (!$scope.count_hash[str]) { + $scope.count_hash[str] = { + count : 1, + value : str, + original : item + }; + } else { + $scope.count_hash[str].count++; + } + }); + }); + + $scope.selectValue = function (item) { + if ($scope.onSelect) $scope.onSelect(item); + } + + } + ] + }; +}) + /** * Nested org unit selector modeled as a Bootstrap dropdown button. */ -- 2.43.2