WIP: add more Z39.50 search functionality to webstaff
authorGalen Charlton <gmc@esilibrary.com>
Thu, 9 Jul 2015 16:25:25 +0000 (16:25 +0000)
committerGalen Charlton <gmc@esilibrary.com>
Thu, 9 Jul 2015 22:03:57 +0000 (22:03 +0000)
- can now select targets
- search field form now active
- can now retrieve results

Next steps:

- start adding actions to the results grid
- add estimated hits count
- add caching of results already retrieved
- add ability to move the search field and target list out
  of the way

Signed-off-by: Galen Charlton <gmc@esilibrary.com>
Open-ILS/src/templates/staff/cat/z3950/t_list.tt2
Open-ILS/src/templates/staff/cat/z3950/t_search_fields.tt2 [new file with mode: 0644]
Open-ILS/src/templates/staff/cat/z3950/t_target.tt2
Open-ILS/web/js/ui/default/staff/cat/services/z3950.js
Open-ILS/web/js/ui/default/staff/cat/z3950/app.js [new file with mode: 0644]

index 5f12a1f..0158c45 100644 (file)
@@ -1,18 +1,43 @@
-<div>
-<strong>[% l('Search') %]</strong>
+<div class="row">
+    <div class="col-xs-6">
+        <strong>[% l('Query') %]</strong>
+        <eg-z3950-search-field-list></eg-z3950-search-field-list>
+    </div>
+    <div class="col-xs-6">
+        <strong>[% l('Service and Credentials') %]</strong>
+        <eg-z3950-target-list>
+    </div>
 </div>
 
-<div>
-<strong>[% l('Targets') %]</strong>
-<eg-z3950-target-list></eg-z3950-target-list>
+<div class="row" id="z3950-search-form-row">
+    <form ng-submit="search()" id="z3950-search-form"
+        role="form" class="form-inline">
+        <div class="button-group">
+                <input type="submit" class="btn btn-primary" value="[% l('Search') %]"/>
+
+                <input type="reset" class="btn btn-primary" ng-click="clearForm()"
+                value="[% l('Clear Form') %]"/>
+        </div>
+    </form>
 </div>
 
 <eg-grid
   id-field="index"
+  idl-class="mvr"
   features="-display,-sort,-multisort"
   main-label="[% l('Results') %]"
-  items-provider="gridDataProvider"
+  items-provider="z3950SearchGridProvider"
   grid-controls="gridControls"
   persist-key="cat.z3950_results">
 
+    <eg-grid-field label="[% l('Title') %]" path="title" visible></eg-grid-field>
+    <eg-grid-field label="[% l('Author') %]" path="author" visible></eg-grid-field>
+    <eg-grid-field label="[% l('Edition') %]" path="edition" visible></eg-grid-field>
+    <eg-grid-field label="[% l('ISBN') %]" path="isbn" visible></eg-grid-field>
+    <eg-grid-field label="[% l('Publication Date') %]" path="pubdate" visible></eg-grid-field>
+    <eg-grid-field label="[% l('Publisher') %]" path="publisher" visible></eg-grid-field>
+    <eg-grid-field label="[% l('Service') %]" path="service" visible></eg-grid-field>
+    <eg-grid-field label="[% l('TCN') %]" path="tcn" visible></eg-grid-field>
+    <eg-grid-field label
+    <eg-grid-field path="*" hidden></eg-grid-field>
 </eg-grid>
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
new file mode 100644 (file)
index 0000000..377695e
--- /dev/null
@@ -0,0 +1,8 @@
+<div class="form-horizontal">
+    <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">
+        </div>
+    </div>
+</div>
index 5290300..9365ec2 100644 (file)
@@ -1,4 +1,19 @@
-<div ng-repeat="target in targets">
-    <span>{{target.code}} / {{target.settings.label}}}</span>
-    <span ng-if="target.settings.auth == 't'">requires auth</span>
+<div ng-repeat="target in targets" class="z3950-target-list">
+    <div class="checkbox">
+        <input ng-model="target.selected" type="checkbox">
+    <div>
+    <div ng-if="target.code == 'native-evergreen-catalog'">[% l('Local Catalog') %]</div>
+    <div ng-if="target.code != 'native-evergreen-catalog'">{{target.settings.label}}</div>
+    <div ng-if="target.settings.auth == 't'" class="form-inline row">
+        <div class="form-group col-xs-6">
+            <label for="username-for-z3950-{{target.code}}">[% l('Username') %]</label>
+            <input type="text" class="form-control" id="username-for-z3950-{{target.code}}" ng-model="target.username">
+        </div>
+        <div class="form-group col-xs-6">
+            <label for="password-for-z3950-{{target.code}}">[% l('Password') %]</label>
+            <input type="text" class="form-control" id="password-for-z3950-{{target.code}}" ng-model="target.password">
+        </div>
+    </div>
+</div>
+    </div>
 </div>
index 1d6d918..9cd97af 100644 (file)
@@ -1,5 +1,95 @@
 angular.module('egZ3950Mod', ['egCoreMod', 'ui.bootstrap'])
+.factory('egZ3950TargetSvc',
+       ['$q', 'egCore', 'egAuth',
+function($q,   egCore,   egAuth) {
+    
+    var service = {
+        targets : [ ],
+        searchFields : { }
+    };
+    
+    service.loadTargets = function() {
+        egCore.net.request(
+            'open-ils.search',
+            'open-ils.search.z3950.retrieve_services',
+            egAuth.token()
+        ).then(function(res) {
+            service.targets = [];
+            // native Evergreen search goes first
+            var localTarget = res['native-evergreen-catalog'];
+            delete res['native-evergreen-catalog'];
+            angular.forEach(res, function(value, key) {
+                this.push({
+                    code:       key,
+                    settings:   value,
+                    selected:   false,
+                    username:   '',
+                    password:   ''
+                });
+            }, service.targets);
+            service.targets.sort(function (a, b) {
+                a = a.settings.label;
+                b = b.settings.label;
+                return a < b ? -1 : (a > b ? 1 : 0);
+            }); 
+            service.targets.unshift({
+                code:       'native-evergreen-catalog',
+                settings:   localTarget,
+                selected:   false,
+                username:   '',
+                password:   ''
+            });
+        });
+    };
 
+    service.loadActiveSearchFields = function() {
+        // don't want to throw away the reference, otherwise
+        // directives bound to searchFields won't
+        // refresh
+        for (var field in service.searchFields) {
+            delete service.searchFields[field];
+        }
+        angular.forEach(service.targets, function(target, idx) {
+            if (target.selected) {
+                angular.forEach(target.settings.attrs, function(attr, key) {
+                    if (!(key in service.searchFields)) service.searchFields[key] = {
+                        label : attr.label,
+                        query : ''
+                    };
+                });
+            }
+        });
+    };
+
+    // return the selected Z39.50 targets and search strings
+    // in a format suitable for passing directly to
+    // open-ils.search.z3950.search_class
+    service.currentQuery = function() {
+        var query = {
+            service  : [],
+            username : [],
+            password : [],
+            search   : {}
+        };
+
+        angular.forEach(service.targets, function(target, idx) {
+            if (target.selected) {
+                query.service.push(target.code);
+                query.username.push(target.username);
+                query.password.push(target.password);
+            }
+        });
+        angular.forEach(service.searchFields, function(value, key) {
+            if (value.query && value.query.trim()) {
+                query.search[key] = value.query.trim();
+            }
+        });
+
+        return query;
+    }
+
+    return service;
+}])
 .directive("egZ3950TargetList", function () {
     return {
         transclude: true,
@@ -9,36 +99,25 @@ angular.module('egZ3950Mod', ['egCoreMod', 'ui.bootstrap'])
         },
         templateUrl: './cat/z3950/t_target',
         controller:
-                   ['$scope','egCore','egAuth',
-            function($scope , egCore,  egAuth) {
-                function loadTargets() {
-                    egCore.net.request(
-                        'open-ils.search',
-                        'open-ils.search.z3950.retrieve_services',
-                        egAuth.token()
-                    ).then(function(res) {
-                        $scope.targets = [];
-                        // native Evergreen search goes first
-                        var localTarget = res['native-evergreen-catalog'];
-                        delete res['native-evergreen-catalog'];
-                        angular.forEach(res, function(value, key) {
-                            this.push({
-                                code:       key,
-                                settings:   value
-                            });
-                        }, $scope.targets);
-                        $scope.targets.sort(function (a, b) {
-                            a = a.settings.label;
-                            b = b.settings.label;
-                            return a < b ? -1 : (a > b ? 1 : 0);
-                        }); 
-                        $scope.targets.unshift({
-                            code:       'native-evergreen-catalog',
-                            settings:   localTarget
-                        });
-                    });
-                }
-                loadTargets();
+                   ['$scope','egZ3950TargetSvc',
+            function($scope , egZ3950TargetSvc) {
+                $scope.targets = egZ3950TargetSvc.targets;
+                $scope.$watch('targets', function(oldVal, newVal) {
+                    egZ3950TargetSvc.loadActiveSearchFields();
+                }, true);
             }]
     }
 })
+.directive("egZ3950SearchFieldList", ['egZ3950TargetSvc',
+    function(egZ3950TargetSvc) {
+        return {
+            restrict:   'AE',
+            scope: {
+            },
+            templateUrl: './cat/z3950/t_search_fields',
+            link: function(scope, elem, attr) {
+                scope.fields = egZ3950TargetSvc.searchFields;
+            }
+        };
+    }
+]);
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
new file mode 100644 (file)
index 0000000..1e63d4e
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Z39.50 search and import
+ */
+
+angular.module('egCatZ3950Search',
+    ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod', 'egZ3950Mod'])
+
+.config(function($routeProvider, $locationProvider, $compileProvider) {
+    $locationProvider.html5Mode(true);
+    $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
+
+    var resolver = {delay : function(egStartup) {return egStartup.go()}};
+
+    // search page shows the list view by default
+    $routeProvider.when('/cat/z3950/search', {
+        templateUrl: './cat/z3950/t_list',
+        controller: 'Z3950SearchCtrl',
+        resolve : resolver
+    });
+
+    // default page / bucket view
+    $routeProvider.otherwise({redirectTo : '/cat/z3950/search'});
+})
+
+/**
+ * List view - grid stuff
+ */
+.controller('Z3950SearchCtrl',
+       ['$scope','$q','$location','$timeout','egCore','egGridDataProvider','egZ3950TargetSvc',
+function($scope , $q , $location , $timeout , egCore , egGridDataProvider,  egZ3950TargetSvc ) {
+
+    // get list of targets
+    egZ3950TargetSvc.loadTargets();
+    egZ3950TargetSvc.loadActiveSearchFields();
+
+    var provider = egGridDataProvider.instance({});
+
+    provider.get = function(offset, count) {
+        var deferred = $q.defer();
+
+        var query = egZ3950TargetSvc.currentQuery();
+        console.debug(query);
+        if (query.search.length == 0) {
+            return $q.when();
+        }
+
+        query['limit'] = count;
+        query['offset'] = offset;
+
+        egCore.net.request(
+            'open-ils.search',
+            'open-ils.search.z3950.search_class',
+            egCore.auth.token(),
+            query
+        ).then(
+            function() { deferred.resolve() },
+            null, // onerror
+            function(result) {
+                for (var i in result.records) {
+                    result.records[i].mvr['service'] = result.service;
+                    deferred.notify(result.records[i].mvr);
+                }
+            }
+        );
+
+        return deferred.promise;
+    };
+
+    $scope.z3950SearchGridProvider = provider;
+
+    $scope.search = function() {
+        $scope.z3950SearchGridProvider.refresh();
+    };
+}])