--- /dev/null
+[%
+ WRAPPER "staff/base.tt2";
+ ctx.page_title = l("Z39.50");
+ ctx.page_app = "egCatZ3950Search";
+ ctx.page_ctrl = "Z3950SearchCtrl";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/z3950/app.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/cat/services/z3950.js"></script>
+[% END %]
+
+<div ng-view></div>
+
+[% END %]
--- /dev/null
+<div class="row">
+ <div class="col-xs-6">
+ <strong>[% l('Query') %]</strong>
+ <form ng-keyup="$event.keyCode == 13 && search()">
+ <eg-z3950-search-field-list></eg-z3950-search-field-list>
+ </form>
+ </div>
+ <div class="col-xs-6">
+ <strong>[% l('Service and Credentials') %]</strong>
+ <eg-z3950-target-list>
+ </div>
+</div>
+
+<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="z3950SearchGridProvider"
+ grid-controls="gridControls"
+ persist-key="cat.z3950_results">
+
+ <eg-grid-menu-item handler="showInCatalog" disabled="cant_showInCatalog"
+ label="[% l('Show in Catalog') %]"></eg-grid-menu>
+ <eg-grid-menu-item handler="import" disabled="cant_import"
+ label="[% l('Import') %]"></eg-grid-menu>
+
+ <!--
+ FIXME: it would be nice to make this column link
+ to record display page when the service is
+ 'native-evergreen-catalog', but can't do ng-if
+ inside of column value templates at the moment
+ -->
+ <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>
--- /dev/null
+<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>
--- /dev/null
+<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>
</li>
<li class="divider"></li>
<li>
+ <a href="./cat/z3950/index" target="_self">
+ <span class="glyphicon glyphicon-cloud-download"></span>
+ [% l('Import Record from Z39.50') %]
+ </a>
+ </li>
+ <li>
<a href="./cat/catalog/vandelay" target="_self">
<span class="glyphicon glyphicon-transfer"></span>
[% l('MARC Batch Import/Export') %]
--- /dev/null
+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) {
+ // keep the reference, just clear the list
+ service.targets.length = 0;
+ // 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
+ var curFormInput = {};
+ for (var field in service.searchFields) {
+ curFormInput[field] = service.searchFields[field].query;
+ 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 : (key in curFormInput) ? curFormInput[key] : ''
+ };
+ });
+ }
+ });
+ };
+
+ service.clearSearchFields = function() {
+ for (var field in service.searchFields) {
+ service.searchFields[field].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,
+ restrict: 'AE',
+ scope: {
+
+ },
+ templateUrl: './cat/z3950/t_target',
+ controller:
+ ['$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;
+ }
+ };
+ }
+]);
--- /dev/null
+/*
+ * 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','$window','egCore','egGridDataProvider','egZ3950TargetSvc',
+function($scope , $q , $location , $timeout , $window, 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();
+ if (Object.keys(query.search).length == 0) {
+ return $q.when();
+ }
+
+ query['limit'] = count;
+ query['offset'] = offset;
+
+ var resultIndex = 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;
+ result.records[i].mvr['index'] = resultIndex++;
+ result.records[i].mvr['marcxml'] = result.records[i].marcxml;
+ deferred.notify(result.records[i].mvr);
+ }
+ }
+ );
+
+ return deferred.promise;
+ };
+
+ $scope.z3950SearchGridProvider = provider;
+ $scope.gridControls = {};
+
+ $scope.search = function() {
+ $scope.z3950SearchGridProvider.refresh();
+ };
+ $scope.clearForm = function() {
+ egZ3950TargetSvc.clearSearchFields();
+ };
+
+ $scope.showInCatalog = function() {
+ var items = $scope.gridControls.selectedItems();
+ // relying on cant_showInCatalog to protect us
+ var url = egCore.env.basePath +
+ 'cat/catalog/record/' + items[0].tcn();
+ $timeout(function() { $window.open(url, '_blank') });
+ };
+ $scope.cant_showInCatalog = function() {
+ var items = $scope.gridControls.selectedItems();
+ if (items.length != 1) return true;
+ if (items[0]['service'] == 'native-evergreen-catalog') return false;
+ return true;
+ };
+
+ $scope.import = function() {
+ var deferred = $q.defer();
+ var items = $scope.gridControls.selectedItems();
+ egCore.net.request(
+ 'open-ils.cat',
+ 'open-ils.cat.biblio.record.xml.import',
+ egCore.auth.token(),
+ items[0]['marcxml']
+ // FIXME and more
+ ).then(
+ function() { deferred.resolve() },
+ null, // onerror
+ function(result) {
+ console.debug('imported');
+ }
+ );
+
+ return deferred.promise;
+ };
+ $scope.cant_import = function() {
+ var items = $scope.gridControls.selectedItems();
+ if (items.length == 1) return false;
+ return true;
+ };
+}])