2 * Z39.50 search and import
5 angular.module('egCatZ3950Search',
6 ['ngRoute', 'ui.bootstrap', 'ngOrderObjectBy', 'egCoreMod', 'egUiMod', 'egGridMod', 'egZ3950Mod', 'egMarcMod'])
8 .config(function($routeProvider, $locationProvider, $compileProvider) {
9 $locationProvider.html5Mode(true);
10 $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|mailto|blob):/); // grid export
12 var resolver = {delay : function(egStartup) {return egStartup.go()}};
14 // search page shows the list view by default
15 $routeProvider.when('/cat/z3950/search', {
16 templateUrl: './cat/z3950/t_list',
17 controller: 'Z3950SearchCtrl',
21 // default page / bucket view
22 $routeProvider.otherwise({redirectTo : '/cat/z3950/search'});
26 * List view - grid stuff
28 .controller('Z3950SearchCtrl',
29 ['$scope','$q','$location','$timeout','$window','egCore','egGridDataProvider','egZ3950TargetSvc','$uibModal',
30 'egConfirmDialog','egAlertDialog',
31 function($scope , $q , $location , $timeout , $window, egCore , egGridDataProvider, egZ3950TargetSvc, $uibModal,
32 egConfirmDialog, egAlertDialog) {
34 // get list of targets
35 egZ3950TargetSvc.loadTargets();
36 egZ3950TargetSvc.loadActiveSearchFields();
38 $scope.field_strip_groups = [];
39 egCore.startup.go().then(function() {
40 // and list of field strip groups; need to ensure
41 // that enough of the startup has happened so that
42 // we have the user WS
43 egCore.pcrud.search('vibtg',
51 transform : 'actor.org_unit_ancestors',
57 id : egCore.auth.user().ws_ou()
62 { order_by : { vibtq : ['label'] } }
63 ).then(null, null, function(strip_group) {
64 strip_group.selected = false;
65 $scope.field_strip_groups.push(strip_group);
69 $scope.total_hits = 0;
71 var bib_sources = null;
72 egCore.pcrud.retrieveAll('cbs', {}, {atomic : true})
73 .then(function(l) { bib_sources = l; });
75 var provider = egGridDataProvider.instance({});
77 provider.get = function(offset, count) {
78 var deferred = $q.defer();
80 var query = egZ3950TargetSvc.currentQuery();
81 if (!query.raw_search && Object.keys(query.search).length == 0) {
85 var method = query.raw_search ?
86 'open-ils.search.z3950.search_service' :
87 'open-ils.search.z3950.search_class';
89 if (query.raw_search) {
90 query.query = query.raw_search;
91 delete query['search'];
92 delete query['raw_search'];
93 query.service = query.service[0];
94 query.username = query.username[0];
95 query.password = query.password[0];
98 query['limit'] = count;
99 query['offset'] = offset;
101 var resultIndex = offset;
102 $scope.total_hits = 0;
103 $scope.searchInProgress = true;
110 function() { $scope.searchInProgress = false; deferred.resolve() },
113 // FIXME when the search offset is > 0, the
114 // total hits count can be wrong if one of the
115 // Z39.50 targets has fewer than $offset hits; in that
116 // case, result.count is not supplied.
117 $scope.total_hits += (result.count || 0);
118 for (var i in result.records) {
119 result.records[i].mvr['bibid'] = result.records[i].bibid;
120 result.records[i].mvr['service'] = result.service;
121 result.records[i].mvr['index'] = resultIndex++;
122 result.records[i].mvr['marcxml'] = result.records[i].marcxml;
123 deferred.notify(result.records[i].mvr);
128 return deferred.promise;
131 $scope.z3950SearchGridProvider = provider;
132 $scope.gridControls = {};
134 $scope.search = function() {
135 $scope.z3950SearchGridProvider.refresh();
137 $scope.clearForm = function() {
138 egZ3950TargetSvc.clearSearchFields();
141 $scope.saveDefaultZ3950Targets = function() {
142 egZ3950TargetSvc.saveDefaultZ3950Targets();
145 var display_form = true;
146 $scope.show_search_form = function() {
149 $scope.toggle_search_form = function() {
150 display_form = !display_form;
153 $scope.raw_search_impossible = function() {
154 return egZ3950TargetSvc.rawSearchImpossible();
157 $scope.get_bibsrc_name_from_id = function(bs_id){
158 // var sel_bib_src = bib_src.id ? bib_src.list.filter(s => s.id() == bib_src.id) : null;
159 // TODO can we use arrow syntax yet???
160 if (!bs_id) return null;
161 var cbs = bib_sources.filter(function(s){ return s.id() == bs_id });
163 return (cbs && cbs[0] ? cbs[0].source() : null);
166 $scope.showRawSearchForm = function() {
168 templateUrl: './cat/z3950/t_raw_search',
172 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
173 egZ3950TargetSvc.setRawSearch('');
174 $scope.focusMe = true;
175 $scope.ok = function(args) { $uibModalInstance.close(args) }
176 $scope.cancel = function () { $uibModalInstance.dismiss() }
178 }).result.then(function (args) {
179 if (!args || !args.raw_search) return;
181 egZ3950TargetSvc.setRawSearch(args.raw_search);
182 $scope.z3950SearchGridProvider.refresh();
186 $scope.showInCatalog = function() {
187 var items = $scope.gridControls.selectedItems();
188 // relying on cant_showInCatalog to protect us
189 var url = '/eg2/staff/catalog/record/' + items[0]['bibid'];
190 $timeout(function() { $window.open(url, '_blank') });
192 $scope.cant_showInCatalog = function() {
193 var items = $scope.gridControls.selectedItems();
194 if (items.length != 1) return true;
195 if (items[0]['service'] == 'native-evergreen-catalog') return false;
199 $scope.local_overlay_target = egCore.hatch.getLocalItem('eg.cat.marked_overlay_record') || 0;
201 if($scope.local_overlay_target) {
202 var currTarget = $scope.local_overlay_target;
204 get_tcn(currTarget).then(
205 function(tcn) { $scope.local_overlay_target_tcn = tcn; });
207 $scope.mark_as_overlay_target = function() {
208 var items = $scope.gridControls.selectedItems();
209 if ($scope.local_overlay_target == items[0]['bibid']) {
210 $scope.local_overlay_target = 0;
211 $scope.local_overlay_target_tcn = 0;
213 $scope.local_overlay_target = items[0]['bibid'];
214 var currTarget = items[0] ['bibid'];
215 get_tcn(currTarget).then(
216 function(tcn) { $scope.local_overlay_target_tcn = tcn; });
219 egCore.hatch.setLocalItem('eg.cat.marked_overlay_record',$scope.local_overlay_target);
222 // Returns promise of TCN value
223 function get_tcn(currTarget) {
224 return egCore.pcrud.retrieve('bre', currTarget, {
225 select: {bre: ['tcn_value']}
226 }).then(function(rec) {
227 return rec.tcn_value();
231 $scope.cant_overlay = function() {
232 if (!$scope.local_overlay_target) return true;
233 var items = $scope.gridControls.selectedItems();
234 if (items.length != 1) return true;
236 items[0]['service'] == 'native-evergreen-catalog' &&
237 items[0]['bibid'] == $scope.local_overlay_target
242 $scope.selectFieldStripGroups = function() {
244 angular.forEach($scope.field_strip_groups, function(grp, idx) {
246 groups.push(grp.id());
252 $scope.import = function() {
253 var items = $scope.gridControls.selectedItems();
254 return $scope._import(items[0]['marcxml']);
257 $scope._import = function(marc_xml,bib_source) {
259 var bibsrc_name = $scope.get_bibsrc_name_from_id(bib_source);
261 var deferred = $q.defer();
264 'open-ils.cat.biblio.record.xml.import',
270 $scope.selectFieldStripGroups()
272 function(result) { deferred.resolve(result) },
275 var evt = egCore.evt.parse(result);
277 if (evt.textcode == 'TCN_EXISTS') {
279 egCore.strings.TCN_EXISTS
282 // we shouldn't get here
283 egAlertDialog.open(egCore.strings.TCN_EXISTS_ERR);
286 egConfirmDialog.open(
287 egCore.strings.IMPORTED_RECORD_FROM_Z3950,
288 egCore.strings.IMPORTED_RECORD_FROM_Z3950_AS_ID,
289 { id : result.id() },
290 egCore.strings.GO_TO_RECORD,
291 egCore.strings.GO_BACK
292 ).result.then(function() {
293 $window.open('/eg2/staff/catalog/record/' + result.id());
299 return deferred.promise;
301 $scope.need_one_selected = function() {
302 var items = $scope.gridControls.selectedItems();
303 if (items.length == 1) return false;
307 $scope.spawn_editor = function() {
308 var items = $scope.gridControls.selectedItems();
310 var _import = $scope._import;
312 templateUrl: './cat/z3950/t_marc_edit',
316 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
317 $scope.focusMe = true;
318 $scope.record_id = recId;
319 $scope.dirty_flag = false;
321 $scope.args.marc_xml = items[0]['marcxml'];
322 $scope.args.bib_source = null;
323 $scope.ok = function(args) { $uibModalInstance.close(args) }
324 $scope.cancel = function () { $uibModalInstance.dismiss() }
325 $scope.save_label = egCore.strings.IMPORT_BUTTON_LABEL;
326 // Wiring up angular inPlaceMode for editing later
327 $scope.in_place_mode = true;
328 $scope.import_record_callback = function () {
329 if($scope.in_place_mode) {
330 // This timeout is required to allow angular to finish variable assignments
331 // in the marcediter app. Allowing marc_xml to propigate here.
332 $timeout( function() {
333 _import($scope.args.marc_xml, $scope.args.bib_source).then( function(record_obj) {
334 if( record_obj.id ) {
335 $scope.record_id = record_obj.id();
336 $scope.save_label = egCore.strings.SAVE_BUTTON_LABEL;
337 // Successful import, no longer want this special z39.50 callback to execute.
338 $scope.in_place_mode = undefined;
345 }).result.then(function () {
347 $window.location.href = '/eg2/staff/catalog/record/' + recId;
352 $scope.view_marc = function() {
353 var items = $scope.gridControls.selectedItems();
355 templateUrl: './cat/z3950/t_marc_html',
359 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
360 $scope.focusMe = true;
361 $scope.marc_xml = items[0]['marcxml'];
362 $scope.isbn = (items[0].isbn() || '').replace(/ .*/, '');
363 $scope.ok = function(args) { $uibModalInstance.close(args) }
364 $scope.cancel = function () { $uibModalInstance.dismiss() }
366 }).result.then(function (args) {
367 if (!args || !args.name) return;
371 $scope.overlay_record = function() {
373 var live_overlay_target =
374 egCore.hatch.getLocalItem('eg.cat.marked_overlay_record') || 0;
376 if ($scope.local_overlay_target == live_overlay_target) {
377 // Avoid the extra network call when the target is unchanged.
378 overlay_record_modal(
379 $scope.local_overlay_target, $scope.local_overlay_target_tcn);
383 // Target changed. Fetch the new TCN.
384 get_tcn(live_overlay_target).then(
386 overlay_record_modal(live_overlay_target, tcn)
391 function overlay_record_modal(live_overlay_target, live_overlay_target_tcn) {
393 var overlay_target = $scope.local_overlay_target;
394 var overlay_target_tcn = $scope.local_overlay_target_tcn;
396 var items = $scope.gridControls.selectedItems();
398 'marc_xml' : items[0]['marcxml'],
403 templateUrl: './cat/z3950/t_overlay',
407 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
409 $scope.immediate_merge = function () {
410 $scope.overlay_target.marc_xml = args.marc_xml;
411 egCore.pcrud.retrieve('bre', $scope.overlay_target.id)
412 .then(function(rec) {
413 $scope.overlay_target.orig_marc_xml = rec.marc();
414 $scope.merge_marc(); // in case a sticky value was already set
415 $scope.overlay_target_tcn = overlay_target_tcn;
419 $scope.merge_marc = function() {
420 if (!$scope.merge_profile) return;
423 'open-ils.cat.merge.marc.per_profile',
425 $scope.merge_profile,
426 [ args.marc_xml, $scope.overlay_target.orig_marc_xml ]
427 ).then(function(merged) {
429 $scope.overlay_target.marc_xml = merged;
430 $scope.overlay_target.merged = true;
435 $scope.editOverlayRecord = function() {
437 templateUrl: './cat/z3950/t_edit_overlay_record',
441 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
442 $scope.focusMe = true;
443 $scope.record_id = 0;
444 $scope.dirty_flag = false;
446 $scope.ok = function() { $uibModalInstance.close($scope.args) }
447 $scope.cancel = function () { $uibModalInstance.dismiss() }
449 }).result.then(function (args) {
451 if (!args || !args.name) return;
455 $scope.focusMe = true;
456 $scope.merge_profile = null;
457 $scope.overlay_target = {
459 live_id : live_overlay_target,
463 $scope.$watch('merge_profile', function(newVal, oldVal) {
464 if (newVal && newVal !== oldVal) {
470 args.overlay_target = $scope.overlay_target;
471 $scope.ok = function(args) { $uibModalInstance.close(args) };
472 $scope.cancel = function () { $uibModalInstance.dismiss() };
474 if (overlay_target != live_overlay_target) {
475 var confirm_title = egCore.strings.OVERLAY_CHANGED_TITLE;
476 var confirm_msg = egCore.strings.OVERLAY_CHANGED;
478 if (live_overlay_target == 0) { // someone unset the target...
479 confirm_title = egCore.strings.OVERLAY_REMOVED_TITLE;
480 confirm_msg = egCore.strings.OVERLAY_REMOVED;
483 egConfirmDialog.open(
486 { id : overlay_target, live_id : live_overlay_target,
487 tcn : overlay_target_tcn, live_tcn: live_overlay_target_tcn}
489 function () { // proceed -- but check live overlay for unset-ness
490 if (live_overlay_target != 0) {
491 $scope.overlay_target.id = $scope.overlay_target.live_id;
492 overlay_target = live_overlay_target;
493 overlay_target_tcn = live_overlay_target_tcn;
495 $scope.immediate_merge();
502 $scope.immediate_merge();
506 }).result.then(function (args) {
507 var bibsrc_name = $scope.get_bibsrc_name_from_id(args.bib_source);
510 'open-ils.cat.biblio.record.marc.replace',
513 (args.overlay_target.merged ? args.overlay_target.marc_xml : args.marc_xml),
516 $scope.selectFieldStripGroups()
519 $scope.local_overlay_target = 0;
520 $scope.local_overlay_target_tcn = 0;
521 egCore.hatch.removeLocalItem('eg.cat.marked_overlay_record');
522 console.debug('overlay complete, target removed');
523 $window.open('/eg2/staff/catalog/record/' + overlay_target);