LP#1638299: adjust staff-side authority browse
[working/Evergreen.git] / Open-ILS / web / js / ui / default / cat / authority / list.js
1 dojo.require('dijit.Dialog');
2 dojo.require('dijit.form.Button');
3 dojo.require('dijit.form.DropDownButton');
4 dojo.require('dijit.form.FilteringSelect');
5 dojo.require('dijit.form.Form');
6 dojo.require('dijit.form.NumberSpinner');
7 dojo.require('dijit.form.TextBox');
8 dojo.require("dijit.Menu");
9 dojo.require("dijit.MenuItem");
10 dojo.require('dojox.xml.parser');
11 dojo.require('DojoSRF');
12 dojo.require("fieldmapper.Fieldmapper");
13 dojo.require('openils.CGI');
14 dojo.require('openils.PermaCrud');
15 dojo.require('openils.XUL');
16 dojo.require('openils.Util');
17 dojo.require('openils.widget.OrgUnitFilteringSelect');
18 dojo.require("openils.widget.PCrudAutocompleteBox");
19 dojo.require("MARC.FixedFields");
20 dojo.requireLocalization("openils.authority", "authority");
21 var auth_strings = dojo.i18n.getLocalization("openils.authority", "authority");
22
23 var cgi = new openils.CGI();
24 var pcrud = new openils.PermaCrud();
25
26 var _thes_short_code_map = {
27     "a" : "lcsh",
28     "b" : "lcshac",
29     "c" : "mesh",
30     "d" : "nal",
31     "k" : "cash",
32     "r" : 'aat',
33     "s" : "sears",
34     "v" : "rvm"
35 }
36 var _acs_cache_by_at = {};
37 function fetch_control_set(thesaurus) {
38     var thes_code = (thesaurus in _thes_short_code_map) ?
39                         _thes_short_code_map[thesaurus] :
40                         thesaurus;
41     if (!_acs_cache_by_at[thes_code]) {
42         var at = pcrud.retrieve(
43             "at", thes_code,
44             {"flesh": 1, "flesh_fields": {"at": ["control_set"]}}
45         );
46         var cs;
47         if (at && at.control_set()) {
48             cs = at.control_set();
49         } else {
50             cs = new fieldmapper.acs();
51             cs.name("None");    // XXX i18n
52
53         }
54         _acs_cache_by_at[thes_code] = cs;
55     }
56     return _acs_cache_by_at[thes_code];
57 }
58
59 /*
60 // OrgUnits do not currently affect the retrieval of authority records,
61 // but this is how to display them if they become OrgUnit-aware
62 function authOUListInit() {
63     new openils.User().buildPermOrgSelector(
64         "STAFF_LOGIN", // anywhere you can log in
65         dijit.byId("authOU"),
66         null, // pre-selected org
67         null
68     );
69 }
70 dojo.addOnLoad(authOUListInit);
71 */
72 function displayAuthorities(data) { 
73
74     var idArr = [];
75     // Grab each record from the returned authority records
76     dojo.query("record", data).forEach(function(node) {
77         var auth = {};
78         auth.text = '';
79         auth.thesaurus = '|';
80         auth.id = 0;
81
82         // Grab each authority record field from the authority record
83         dojo.query("datafield[tag^='1']", node).forEach(function(dfNode) {
84             auth.text += dojox.xml.parser.textContent(dfNode); 
85             auth.name = dojo.attr(dfNode, 'tag');
86             auth.ind1 = dojo.attr(dfNode, 'ind1');
87             auth.ind2 = dojo.attr(dfNode, 'ind2');
88         });
89
90         
91         // Grab the ID of the authority record
92         dojo.query("datafield[tag='901']", node).query("subfield[code='c']").forEach(function(dfNode) {
93             auth.id = dojox.xml.parser.textContent(dfNode);
94         });
95
96         /* I wrap this in try/catch only because:
97          *  a) this interface hasn't hitherto relied on MARC.Record, and
98          *  b) the functionality we need it for is optional
99          */
100         try {
101             var marc = new MARC.Record({"rtype": "AUT", "xml": node});
102             auth.thesaurus = marc.extractFixedField("Subj", "|");
103         } catch (E) {
104             console.warn(
105                 "MARC.Record didn't work for authority record " +
106                 auth.id + ": " + E
107             );
108         }
109
110         idArr.push(parseInt(auth.id));
111
112         // Create the authority record listing entry. XXX i18n
113         dojo.place(
114             '<div class="authEntry" id="auth' + auth.id + '">' +
115             '<div class="text" id="authLabel' + auth.id + '">' +
116             '<span class="text">' + auth.text + '</span></div>' +
117             '<div class="authority-control-set-thesaurus"><span class="authority-control-set">Control Set: <span class="acs-name">' +
118             fetch_control_set(auth.thesaurus).name() +
119             '</span> <span class="acs-id">(#' +
120             fetch_control_set(auth.thesaurus).id() + ')</span>' +
121             '</span> <span class="authority-thesaurus">Thes: ' + auth.thesaurus + '</span></div></div>',
122             "authlist-div", "last"
123         );
124
125         // Add the menu of new/edit/delete/mark-for-merge options
126         var auth_menu = new dijit.Menu({});
127
128         // "Edit" menu item
129         new dijit.MenuItem({"id": "edit_" + auth.id, "onClick": function(){
130             var auth_rec = pcrud.retrieve("are", auth.id);
131             if (auth_rec) {
132                 loadMarcEditor(pcrud, auth_rec);
133             }
134         }, "label":auth_strings.MENU_EDIT}).placeAt(auth_menu, "first");
135
136         // "Merge" menu item
137         new dijit.MenuItem({"id": "merge_" + auth.id, "onClick":function(){
138             auth.text = '';
139             dojo.query('#auth' + auth.id).query('span.text').forEach(function(node) {
140                 auth.text += dojox.xml.parser.textContent(node); 
141             });
142
143             // If there is a toMerge item already, this is a target record
144             var mergeRole = '<td style="border: 1px solid black; padding-left: 0.5em; padding-right: 1em;">';
145             var isTarget = dojo.query('.toMerge').length;
146             if (isTarget) {
147                 mergeRole += auth_strings.TARGET_RECORD + '</td>';
148             } else {
149                 mergeRole += auth_strings.MASTER_RECORD + '</td>';
150             }
151
152             dojo.place('<tr class="toMerge" id="toMerge_' + auth.id + '"><td>' + mergeRole + '</td><td  style="border: 1px solid black;" id="mergeMeta_' + auth.id + '"></td><td style="border: 1px solid black; padding-left: 1em; padding-right: 1em;" >' + auth.text + '</td></tr>', 'mergebox-tbody', 'last');
153             dojo.place('<span class="authmeta" style="font-family: monospace;">' + auth.name + ' ' + auth.ind1 + auth.ind2 + '</span>', 'mergeMeta_' + auth.id, 'last');
154             dojo.removeClass('mergebox-div', 'hidden');
155         }, "label":auth_strings.MENU_MERGE}).placeAt(auth_menu, "last");
156
157         // "Delete" menu item
158         new dijit.MenuItem({
159             "id": "delete_" + auth.id,
160             "onClick":function(){
161                 auth.text = '';
162
163                 var auth_rec = pcrud.retrieve("are", auth.id);
164
165                 // Bit of a hack to get the linked bib count until an explicit ID
166                 var linkedBibs = dojox.xml.parser.textContent(
167                     dojo.query("#authLabel" + auth.id)[0].previousSibling
168                 );
169
170                 var delDlg = dijit.byId("delDialog_" + auth.id);
171
172                 dojo.query('#auth' + auth.id).query('span.text').forEach(function(node) {
173                     auth.text += dojo.trim(dojox.xml.parser.textContent(node)); 
174                 });
175
176                 if (!delDlg) {
177                     var content = '<div>' + dojo.string.substitute(auth_strings.CONFIRM_DELETE_TITLE, [auth.text]) + '</div>';
178                     if (parseInt(linkedBibs) > 0) {
179                         content = "<div id='delAuthSum_" + auth.id + "'>"
180                             + dojo.string.substitute(auth_strings.LINKED_BIBS, [linkedBibs])
181                             + "</div>";
182                     }
183                     content += "<div id='authMARC" + auth.id + "' style='width: 100%; display:none;'>";
184                     content += "<hr style='width: 100%;' />";
185                     content += marcToHTML(auth_rec.marc());
186                     content += "</div><hr style='width: 100%;' /><div>";
187                     content += "<input type='button' dojoType='dijit.form.Button' label='" + auth_strings.CANCEL + "' onClick='cancelDelete(" + auth.id + ")'/>";
188                     content += "<input type='button' dojoType='dijit.form.Button' label='" + auth_strings.DELETE + "' onClick='confirmDelete(" + auth.id + ")'/>";
189                     content += "<input id='viewMARC" + auth.id + "' type='button' "
190                         + "style='float:right;' dojoType='dijit.form.Button' "
191                         + "label='" + auth_strings.VIEW_MARC + "' onClick='viewMARC(" + auth.id + ")'/>";
192                     content += "<input id='hideMARC" + auth.id + "' type='button' "
193                         + "style='display: none; float:right;' dojoType='dijit.form.Button' "
194                         + "label='" + auth_strings.HIDE_MARC + "' onClick='hideMARC(" + auth.id + ")'/>";
195                     content += "</div>";
196                     delDlg = new dijit.Dialog({
197                         "id":"delDialog_" + auth.id,
198                         "title": dojo.string.substitute(auth_strings.CONFIRM_DELETE_PROMPT, [auth.id]),
199                         "content": content
200                     });
201                 }
202                 delDlg.show();
203
204         }, "label":auth_strings.DELETE}).placeAt(auth_menu, "last");
205
206         auth_mb = new dijit.form.DropDownButton({dropDown: auth_menu, label: auth_strings.ACTIONS, id:"menu" + auth.id});
207         auth_mb.placeAt(dojo.create("div", null, "auth" + auth.id, "first"), "first");
208         auth_menu.startup();
209     });
210
211     showBibCount(idArr);
212 }
213
214 function viewMARC(recId) {
215     dojo.style(dojo.byId("authMARC" + recId), 'display', 'block');
216     dojo.style(dijit.byId("viewMARC" + recId).domNode, 'display', 'none');
217     dojo.style(dijit.byId("hideMARC" + recId).domNode, 'display', 'block');
218 }
219
220 function hideMARC(recId) {
221     dojo.style(dojo.byId("authMARC" + recId), 'display', 'none');
222     dojo.style(dijit.byId("hideMARC" + recId).domNode, 'display', 'none');
223     dojo.style(dijit.byId("viewMARC" + recId).domNode, 'display', 'block');
224 }
225
226 function marcToHTML(marc) {
227     var html = '<table><tbody>';
228     marc = dojox.xml.parser.parse(marc);
229     dojo.query('leader', marc).forEach(function(node) {
230         html += '<tr><td>LDR</td><td>&nbsp;</td><td>&nbsp;</td><td>' + dojox.xml.parser.textContent(node) + '</td></tr>';
231     });
232     dojo.query('controlfield', marc).forEach(function(node) {
233         html += '<tr><td>' + dojo.attr(node, "tag") + '</td><td>&nbsp;</td><td>&nbsp;</td><td>' + dojox.xml.parser.textContent(node) + '</td></tr>';
234     });
235     dojo.query('datafield', marc).forEach(function(node) {
236         var cnt = 0;
237         html += '<tr><td>' + dojo.attr(node, "tag") + '</td><td>' + dojo.attr(node, "ind1") + '</td><td>' + dojo.attr(node, "ind2") + '</td>';
238         dojo.query('subfield', node).forEach(function(sf) {
239             if (cnt == 0) {
240                 html += '<td>$' + dojo.attr(sf, "code") + ' ' + dojox.xml.parser.textContent(sf) + '</td></tr>';
241                 cnt = 1;
242             } else {
243                 html += '<tr><td colspan="3"></td><td>$' + dojo.attr(sf, "code") + ' ' + dojox.xml.parser.textContent(sf) + '</td></tr>';
244             }
245         });
246     });
247     html += '</tbody></table>';
248     return html;
249 }
250
251 function cancelDelete(recId) {
252     dijit.byId("delDialog_" + recId).hide();
253 }
254
255 function confirmDelete(recId) {
256     var auth_rec = pcrud.retrieve("are", recId);
257     if (auth_rec) {
258         pcrud.eliminate(auth_rec);
259         dijit.byId("delDialog_" + recId).attr("content", dojo.string.substitute(auth_strings.CONFIRM_DELETE_RESULT, [recId]));
260         setTimeout(function() {
261             dijit.byId("delDialog_" + recId).hide();
262         }, 3000);
263     }
264 }
265
266 function showBibCount(authIds) {
267     /* Decorate the list with # of bibs linked to each authority record */
268     var ses = new OpenSRF.ClientSession('open-ils.cat');
269     var req = ses.request('open-ils.cat.authority.records.count_linked_bibs', authIds);
270     var linkedIds = [];
271     req.oncomplete = function(r) {
272         var msg = r.recv().content();
273         dojo.forEach(msg, function(auth) {
274                 linkedIds.push(auth.authority);
275                 dojo.place('<span class="bibcount">' + auth.bibs + '</span> ', 'authLabel' + auth.authority, 'first');
276             }
277         );
278
279         /* Assign counts of 0 for every non-linked authority */
280         dojo.forEach(authIds, function (id) {
281             var found = false;
282             dojo.forEach(linkedIds, function (lid) {
283                 if (id == lid) {
284                     found = true;
285                 }
286             });
287             if (!found) {
288                 dojo.place('<span class="bibcount">0</span> ', 'authLabel' + id, 'first');
289             }
290         });
291     }
292     req.send();
293 }
294
295 function loadMarcEditor(pcrud, rec) {
296
297     /* Prevent the spawned MARC editor from making its title bar inaccessible */
298     var initHeight = self.outerHeight - 40;
299     /* Setting an explicit height results in a super skinny window, so fix that up */
300     var initWidth = self.outerWidth / 2;
301
302     if (openils.Util.inIframe()) {
303         initWidth = initHeight;
304         win = window.open('/eg/staff/cat/catalog/authority/' + rec.id() + '/marc_edit','',    // XXX version?
305             'chrome,resizable=yes,height=' + initHeight + ',width=' + initWidth);
306         return;
307     }
308
309     /*
310        To run in Firefox directly, must set signed.applets.codebase_principal_support
311        to true in about:config
312      */
313     win = window.open('/xul/server/cat/marcedit.xul','',    // XXX version?
314         'chrome,resizable=yes,height=' + initHeight + ',width=' + initWidth);
315
316     win.xulG = {
317         "record": {"marc": rec.marc(), "rtype": "are"},
318         "save": {
319             "label": auth_strings.SAVE,
320             "func": function(xmlString) {
321                 rec.marc(xmlString);
322                 rec.edit_date('now');
323                 rec.ischanged(true);
324                 pcrud.update(rec);
325                 alert(auth_strings.SAVE_RESULT_SUCCESS);
326                 win.close();
327             }
328         },
329         'lock_tab' : typeof xulG != 'undefined' ? (typeof xulG['lock_tab'] != 'undefined' ? xulG.lock_tab : undefined) : undefined,
330         'unlock_tab' : typeof xulG != 'undefined' ? (typeof xulG['unlock_tab'] != 'undefined' ? xulG.unlock_tab : undefined) : undefined
331     };
332 }
333
334 function authListInit() {
335     var term = cgi.param('authTerm') || '';
336     var page = cgi.param('authPage') || 0;
337     var axis = cgi.param('authAxis') || 'authority.author';
338     if (axis) {
339         dijit.byId('authAxis').attr('value', axis);
340     }
341     if (page) {
342         dijit.byId('authPage').attr('value', page);
343     }
344     if (term) {
345         dijit.byId('authTerm').attr('value', term);
346         displayRecords();
347     }
348
349     dojo.connect(dijit.byId('authAxis'), 'onKeyPress', function(evt) {
350         if (evt.keyCode == dojo.keys.ENTER) {
351             dijit.byId('authPage').attr('value', 0);
352             displayRecords();
353         }
354     }); 
355
356     dojo.connect(dijit.byId('authPage'), 'onKeyPress', function(evt) {
357         if (evt.keyCode == dojo.keys.ENTER) {
358             dijit.byId('authPage').attr('value', 0);
359             displayRecords();
360         }
361     });
362
363     dojo.connect(dijit.byId('authTerm'), 'onKeyPress', function(evt) {
364         if (evt.keyCode == dojo.keys.ENTER) {
365             dijit.byId('authPage').attr('value', 0);
366             displayRecords();
367         }
368     });
369
370     dijit.byId('authTerm').focus();
371
372 }
373 dojo.addOnLoad(authListInit);
374
375 function displayRecords(parms) {
376
377     if (parms && parms.page) {
378         if (parms.page == 'next') {
379             page = dijit.byId('authPage').attr('value');
380             dijit.byId('authPage').attr('value', page + 1);
381         } else if (parms.page == 'prev') {
382             page = dijit.byId('authPage').attr('value');
383             dijit.byId('authPage').attr('value', page - 1);
384         } else {
385             dijit.byId('authPage').attr('value', parms.page);
386         }
387     }
388
389     /* Protect against null input */
390     if (!dijit.byId('authTerm').attr('value')) {
391         return;
392     }
393
394     /* Clear out the current contents of the page */
395     var widgets = dijit.findWidgets(dojo.byId('authlist-div'));
396     dojo.forEach(widgets, function(w) { w.destroyRecursive(true); });
397
398     dojo.query("#authlist-div").query("div").orphan();
399
400     var url = '/opac/extras/browse/marcxml/authority.'
401         + dijit.byId('authAxis').attr('value')
402         // + '/' + dijit.byId('authOU').attr('value')
403         + '/1' // replace with preceding line if OUs gain some meaning
404         + '/' + encodeURIComponent(dijit.byId('authTerm').attr('value'))
405         + '/' + dijit.byId('authPage').attr('value')
406         + '/' + '20' // 20 results per page
407     ;
408     dojo.xhrGet({"url":url, "handleAs":"xml", "content":{"format":"marcxml"}, "preventCache": true, "load":displayAuthorities });
409 }
410
411 function clearMergeRecords() {
412     var records = dojo.query('.toMerge').orphan();
413     dojo.addClass('mergebox-div', 'hidden');
414 }
415
416 function mergeRecords() {
417     var records = dojo.query('.toMerge').attr('id');
418     dojo.forEach(records, function(item, idx) {
419         records[idx] = parseInt(item.slice(item.lastIndexOf('_') + 1));
420     });
421
422     /* Take the first record in the list and use that as the master */
423     fieldmapper.standardRequest(
424         ['open-ils.cat', 'open-ils.cat.authority.records.merge'],
425         {   async: false,
426             params: [openils.User.authtoken, records.shift(), records],
427             oncomplete : function(r) {
428                 alert(auth_strings.MERGE_RESULT_SUCCESS);
429                 clearMergeRecords();
430                 displayRecords();
431             }
432         }
433     );
434 }