]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/web/js/ui/default/staff/cat/z3950/app.js
LP1786971 z39.50 incorporate Bill's changes
[Evergreen.git] / Open-ILS / web / js / ui / default / staff / cat / z3950 / app.js
1 /*
2  * Z39.50 search and import
3  */
4
5 angular.module('egCatZ3950Search',
6     ['ngRoute', 'ui.bootstrap', 'ngOrderObjectBy', 'egCoreMod', 'egUiMod', 'egGridMod', 'egZ3950Mod', 'egMarcMod'])
7
8 .config(function($routeProvider, $locationProvider, $compileProvider) {
9     $locationProvider.html5Mode(true);
10     $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|mailto|blob):/); // grid export
11         
12     var resolver = {delay : function(egStartup) {return egStartup.go()}};
13
14     // search page shows the list view by default
15     $routeProvider.when('/cat/z3950/search', {
16         templateUrl: './cat/z3950/t_list',
17         controller: 'Z3950SearchCtrl',
18         resolve : resolver
19     });
20
21     // default page / bucket view
22     $routeProvider.otherwise({redirectTo : '/cat/z3950/search'});
23 })
24
25 /**
26  * List view - grid stuff
27  */
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) {
33
34     // get list of targets
35     egZ3950TargetSvc.loadTargets();
36     egZ3950TargetSvc.loadActiveSearchFields();
37
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',
44             {
45                 always_apply : 'f',
46                 owner : {
47                     'in' : {
48                         select : {
49                             aou : [{
50                                 column : 'id',
51                                 transform : 'actor.org_unit_ancestors',
52                                 result_field : 'id'
53                             }]
54                         },
55                         from : 'aou',
56                         where : {
57                             id : egCore.auth.user().ws_ou()
58                         }
59                     }
60                 }
61             },
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);
66         });
67     });
68
69     $scope.total_hits = 0;
70
71     var bib_sources = null;
72     egCore.pcrud.retrieveAll('cbs', {}, {atomic : true})
73         .then(function(l) { bib_sources = l; });
74
75     var provider = egGridDataProvider.instance({});
76
77     provider.get = function(offset, count) {
78         var deferred = $q.defer();
79
80         var query = egZ3950TargetSvc.currentQuery();
81         if (!query.raw_search && Object.keys(query.search).length == 0) {
82             return $q.when();
83         }
84
85         var method = query.raw_search ?
86                        'open-ils.search.z3950.search_service' :
87                        'open-ils.search.z3950.search_class';
88
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];
96         }
97
98         query['limit'] = count;
99         query['offset'] = offset;
100
101         var resultIndex = offset;
102         $scope.total_hits = 0;
103         $scope.searchInProgress = true;
104         egCore.net.request(
105             'open-ils.search',
106             method,
107             egCore.auth.token(),
108             query
109         ).then(
110             function() { $scope.searchInProgress = false; deferred.resolve() },
111             null, // onerror
112             function(result) {
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);
124                 }
125             }
126         );
127
128         return deferred.promise;
129     };
130
131     $scope.z3950SearchGridProvider = provider;
132     $scope.gridControls = {};
133
134     $scope.search = function() {
135         $scope.z3950SearchGridProvider.refresh();
136     };
137     $scope.clearForm = function() {
138         egZ3950TargetSvc.clearSearchFields();
139     };
140
141     $scope.saveDefaultZ3950Targets = function() {
142         egZ3950TargetSvc.saveDefaultZ3950Targets();
143     }
144
145     var display_form = true;
146     $scope.show_search_form = function() {
147         return display_form;
148     }
149     $scope.toggle_search_form = function() {
150         display_form = !display_form;
151     }
152
153     $scope.raw_search_impossible = function() {
154         return egZ3950TargetSvc.rawSearchImpossible();
155     }
156
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 });
162
163         return (cbs && cbs[0] ? cbs[0].source() : null);
164     };
165
166     $scope.showRawSearchForm = function() {
167         $uibModal.open({
168             templateUrl: './cat/z3950/t_raw_search',
169             backdrop: 'static',
170             size: 'md',
171             controller:
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() }
177             }]
178         }).result.then(function (args) {
179             if (!args || !args.raw_search) return;
180             $scope.clearForm();
181             egZ3950TargetSvc.setRawSearch(args.raw_search);
182             $scope.z3950SearchGridProvider.refresh();
183         });
184     }
185
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') });        
191     };
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;
196         return true;
197     };
198
199     $scope.local_overlay_target = egCore.hatch.getLocalItem('eg.cat.marked_overlay_record') || 0;
200
201     if($scope.local_overlay_target) {
202         var currTarget = $scope.local_overlay_target;
203
204         get_tcn(currTarget).then(
205             function(tcn) { $scope.local_overlay_target_tcn = tcn; });
206     }
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;
212         } else {
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; });
217
218         }
219         egCore.hatch.setLocalItem('eg.cat.marked_overlay_record',$scope.local_overlay_target);
220     }
221
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();
228         });
229     };
230
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;
235         if (
236                 items[0]['service'] == 'native-evergreen-catalog' &&
237                 items[0]['bibid'] == $scope.local_overlay_target
238            ) return true;
239         return false;
240     }
241
242     $scope.selectFieldStripGroups = function() {
243         var groups = [];
244         angular.forEach($scope.field_strip_groups, function(grp, idx) {
245             if (grp.selected) {
246                 groups.push(grp.id());
247             }
248         });
249         return groups;
250     };
251
252     $scope.import = function() {
253         var items = $scope.gridControls.selectedItems();
254         return $scope._import(items[0]['marcxml']);
255     };
256
257     $scope._import = function(marc_xml,bib_source) {
258
259         var bibsrc_name = $scope.get_bibsrc_name_from_id(bib_source);
260
261         var deferred = $q.defer();
262         egCore.net.request(
263             'open-ils.cat',
264             'open-ils.cat.biblio.record.xml.import',
265             egCore.auth.token(),
266             marc_xml,
267             bibsrc_name,
268             null,
269             null,
270             $scope.selectFieldStripGroups()
271         ).then(
272             function(result) { deferred.resolve(result) },
273             null, // onerror
274             function(result) {
275                 var evt = egCore.evt.parse(result);
276                 if (evt) {
277                      if (evt.textcode == 'TCN_EXISTS') {
278                        egAlertDialog.open(
279                             egCore.strings.TCN_EXISTS
280                       );
281                      } else {
282                        // we shouldn't get here
283                        egAlertDialog.open(egCore.strings.TCN_EXISTS_ERR);
284                      }
285                 } else {
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());
294                     });
295                 }
296             }
297         );
298
299         return deferred.promise;
300     };
301     $scope.need_one_selected = function() {
302         var items = $scope.gridControls.selectedItems();
303         if (items.length == 1) return false;
304         return true;
305     };
306
307     $scope.spawn_editor = function() {
308         var items = $scope.gridControls.selectedItems();
309         var recId = 0;
310         var _import = $scope._import;
311         $uibModal.open({
312             templateUrl: './cat/z3950/t_marc_edit',
313             backdrop: 'static',
314             size: 'lg',
315             controller:
316                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
317                 $scope.focusMe = true;
318                 $scope.record_id = recId;
319                 $scope.dirty_flag = false;
320                 $scope.args = {};
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;
339                                 }
340                             });
341                         });
342                     }
343                 };
344             }]
345         }).result.then(function () {
346             if (recId) {
347                 $window.location.href = '/eg2/staff/catalog/record/' + recId;
348             }
349         });
350     }
351
352     $scope.view_marc = function() {
353         var items = $scope.gridControls.selectedItems();
354         $uibModal.open({
355             templateUrl: './cat/z3950/t_marc_html',
356             backdrop: 'static',
357             size: 'lg',
358             controller:
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() }
365             }]
366         }).result.then(function (args) {
367             if (!args || !args.name) return;
368         });
369     }
370
371     $scope.overlay_record = function() {
372
373         var live_overlay_target = 
374             egCore.hatch.getLocalItem('eg.cat.marked_overlay_record') || 0;
375
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);
380             return;
381         }
382
383         // Target changed.  Fetch the new TCN.
384         get_tcn(live_overlay_target).then(
385             function(tcn) { 
386                 overlay_record_modal(live_overlay_target, tcn)
387             }
388         );
389     }
390
391     function overlay_record_modal(live_overlay_target, live_overlay_target_tcn) {
392
393         var overlay_target = $scope.local_overlay_target;
394         var overlay_target_tcn = $scope.local_overlay_target_tcn;
395
396         var items = $scope.gridControls.selectedItems();
397         var args = {
398             'marc_xml' : items[0]['marcxml'],
399             'bib_source' : null
400         };
401
402         $uibModal.open({
403             templateUrl: './cat/z3950/t_overlay',
404             backdrop: 'static',
405             size: 'lg',
406             controller:
407                 ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
408
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;
416                     });
417                 }
418
419                 $scope.merge_marc = function() {
420                     if (!$scope.merge_profile) return;
421                     egCore.net.request(
422                         'open-ils.cat',
423                         'open-ils.cat.merge.marc.per_profile',
424                         egCore.auth.token(),
425                         $scope.merge_profile,
426                         [ args.marc_xml, $scope.overlay_target.orig_marc_xml ]
427                     ).then(function(merged) {
428                         if (merged) {
429                             $scope.overlay_target.marc_xml = merged;
430                             $scope.overlay_target.merged = true;
431                         }
432                     });
433                 }
434
435                 $scope.editOverlayRecord = function() {
436                     $uibModal.open({
437                         templateUrl: './cat/z3950/t_edit_overlay_record',
438                         backdrop: 'static',
439                         size: 'lg',
440                         controller:
441                             ['$scope', '$uibModalInstance', function($scope, $uibModalInstance) {
442                             $scope.focusMe = true;
443                             $scope.record_id = 0;
444                             $scope.dirty_flag = false;
445                             $scope.args = args;
446                             $scope.ok = function() { $uibModalInstance.close($scope.args) }
447                             $scope.cancel = function () { $uibModalInstance.dismiss() }
448                         }]
449                     }).result.then(function (args) {
450                         $scope.merge_marc();
451                         if (!args || !args.name) return;
452                     });
453                 };
454
455                 $scope.focusMe = true;
456                 $scope.merge_profile = null;
457                 $scope.overlay_target = {
458                     id : overlay_target,
459                     live_id : live_overlay_target,
460                     merged : false
461                 };
462
463                 $scope.$watch('merge_profile', function(newVal, oldVal) {
464                     if (newVal && newVal !== oldVal) {
465                         $scope.merge_marc();
466                     }
467                 });
468
469                 $scope.args = args;
470                 args.overlay_target = $scope.overlay_target;
471                 $scope.ok = function(args) { $uibModalInstance.close(args) };
472                 $scope.cancel = function () { $uibModalInstance.dismiss() };
473                 
474                 if (overlay_target != live_overlay_target) {
475                     var confirm_title = egCore.strings.OVERLAY_CHANGED_TITLE;
476                     var confirm_msg = egCore.strings.OVERLAY_CHANGED;
477
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;
481                     }
482
483                     egConfirmDialog.open(
484                         confirm_title,
485                         confirm_msg,
486                         { id : overlay_target, live_id : live_overlay_target,
487                             tcn : overlay_target_tcn, live_tcn: live_overlay_target_tcn}
488                     ).result.then(
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;
494                             }
495                             $scope.immediate_merge();
496                         },
497                         function () {
498                             $scope.cancel();
499                         }
500                     );
501                 } else {
502                     $scope.immediate_merge();
503                 }
504
505             }]
506         }).result.then(function (args) {
507             var bibsrc_name = $scope.get_bibsrc_name_from_id(args.bib_source);
508             egCore.net.request(
509                 'open-ils.cat',
510                 'open-ils.cat.biblio.record.marc.replace',
511                 egCore.auth.token(),
512                 overlay_target,
513                 (args.overlay_target.merged ? args.overlay_target.marc_xml : args.marc_xml),
514                 bibsrc_name,
515                 null,
516                 $scope.selectFieldStripGroups()
517             ).then(
518                 function(result) {
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);
524                 }
525             );            
526         });
527     }
528 }])