]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/acq/common/li_table.js
Acq: Towards slick integration of granular un-receive in the PO interface.
[working/Evergreen.git] / Open-ILS / web / js / ui / default / acq / common / li_table.js
1 dojo.require('dojo.date.locale');
2 dojo.require('dojo.date.stamp');
3 dojo.require('dijit.form.Button');
4 dojo.require('dijit.form.TextBox');
5 dojo.require('dijit.form.FilteringSelect');
6 dojo.require('dijit.form.Textarea');
7 dojo.require('dijit.ProgressBar');
8 dojo.require('openils.User');
9 dojo.require('openils.Util');
10 dojo.require('openils.acq.Lineitem');
11 dojo.require('openils.acq.PO');
12 dojo.require('openils.acq.Picklist');
13 dojo.require('openils.widget.AutoFieldWidget');
14 dojo.require('dojo.data.ItemFileReadStore');
15 dojo.require('openils.widget.ProgressDialog');
16 dojo.require('openils.PermaCrud');
17 dojo.require('openils.XUL');
18
19 dojo.requireLocalization('openils.acq', 'acq');
20 var localeStrings = dojo.i18n.getLocalization('openils.acq', 'acq');
21 const XUL_OPAC_WRAPPER = 'chrome://open_ils_staff_client/content/cat/opac.xul';
22 var li_exportable_attrs = ["issn", "isbn", "upc"];
23
24 function nodeByName(name, context) {
25     return dojo.query('[name='+name+']', context)[0];
26 }
27
28
29 var liDetailBatchFields = ['fund', 'owning_lib', 'location', 'collection_code', 'circ_modifier', 'cn_label'];
30 var liDetailFields = liDetailBatchFields.concat(['barcode', 'note']);
31
32 function AcqLiTable() {
33
34     var self = this;
35     this.liCache = {};
36     this.plCache = {};
37     this.poCache = {};
38     this.dfaCache = [];
39     this.dfeOffset = 0;
40     this.toggleState = false;
41     this.tbody = dojo.byId('acq-lit-tbody');
42     this.selectors = [];
43     this.authtoken = openils.User.authtoken;
44     this.rowTemplate = this.tbody.removeChild(dojo.byId('acq-lit-row'));
45     this.copyTbody = dojo.byId('acq-lit-li-details-tbody');
46     this.copyRow = this.copyTbody.removeChild(dojo.byId('acq-lit-li-details-row'));
47     this.copyBatchRow = dojo.byId('acq-lit-li-details-batch-row');
48     this.copyBatchWidgets = {};
49     this.liNotesTbody = dojo.byId('acq-lit-notes-tbody');
50     this.liNotesRow = this.liNotesTbody.removeChild(dojo.byId('acq-lit-notes-row'));
51     this.realCopiesTbody = dojo.byId('acq-lit-real-copies-tbody');
52     this.realCopiesRow = this.realCopiesTbody.removeChild(dojo.byId('acq-lit-real-copies-row'));
53     this._copy_fields_for_acqdf = ['owning_lib', 'location'];
54
55     dojo.connect(acqLitLiActionsSelector, 'onChange', 
56         function() { 
57             self.applySelectedLiAction(this.attr('value')) 
58             acqLitLiActionsSelector.attr('value', '_');
59         });
60
61     acqLitCreatePoSubmit.onClick = function() {
62         acqLitPoCreateDialog.hide();
63         self._createPO(acqLitPoCreateDialog.getValues());
64     }
65
66     acqLitSavePlButton.onClick = function() {
67         acqLitSavePlDialog.hide();
68         self._savePl(acqLitSavePlDialog.getValues());
69     }
70
71     acqLitCancelLiStateButton.onClick = function() {
72         acqLitChangeLiStateDialog.hide();
73     }
74     acqLitSaveLiStateButton.onClick = function() {
75         acqLitChangeLiStateDialog.hide();
76         self._updateLiState(acqLitChangeLiStateDialog.getValues(), acqLitChangeLiStateDialog.attr('state'));
77     }
78
79
80     //dojo.byId('acq-lit-notes-new-button').onclick = function(){acqLitCreateLiNoteDialog.show();}
81
82     dojo.byId('acq-lit-select-toggle').onclick = function(){self.toggleSelect()};
83     dojo.byId('acq-lit-info-back-button').onclick = function(){self.show('list')};
84     dojo.byId('acq-lit-copies-back-button').onclick = function(){self.show('list')};
85     dojo.byId('acq-lit-notes-back-button').onclick = function(){self.show('list')};
86     dojo.byId('acq-lit-real-copies-back-button').onclick = function(){self.show('list')};
87
88     this.reset = function() {
89         while(self.tbody.childNodes[0])
90             self.tbody.removeChild(self.tbody.childNodes[0]);
91         self.selectors = [];
92     };
93     
94     this.setNext = function(handler) {
95         var link = dojo.byId('acq-lit-next');
96         if(handler) {
97             dojo.style(link, 'visibility', 'visible');
98             link.onclick = handler;
99         } else {
100             dojo.style(link, 'visibility', 'hidden');
101         }
102     };
103
104     this.setPrev = function(handler) {
105         var link = dojo.byId('acq-lit-prev');
106         if(handler) {
107             dojo.style(link, 'visibility', 'visible'); 
108             link.onclick = handler; 
109         } else {
110             dojo.style(link, 'visibility', 'hidden');
111         }
112     };
113
114     this.show = function(div) {
115         openils.Util.hide('acq-lit-table-div');
116         openils.Util.hide('acq-lit-info-div');
117         openils.Util.hide('acq-lit-li-details');
118         openils.Util.hide('acq-lit-notes-div');
119         openils.Util.hide('acq-lit-real-copies-div');
120         switch(div) {
121             case 'list':
122                 openils.Util.show('acq-lit-table-div');
123                 break;
124             case 'info':
125                 openils.Util.show('acq-lit-info-div');
126                 break;
127             case 'copies':
128                 openils.Util.show('acq-lit-li-details');
129                 break;
130             case 'real-copies':
131                 openils.Util.show('acq-lit-real-copies-div');
132                 break;
133             case 'notes':
134                 openils.Util.show('acq-lit-notes-div');
135                 break;
136             default:
137                 if(div) 
138                     openils.Util.show(div);
139         }
140     }
141
142     this.hide = function() {
143         this.show(null);
144     }
145
146     this.toggleSelect = function() {
147         if(self.toggleState) 
148             dojo.forEach(self.selectors, function(i){i.checked = false});
149         else 
150             dojo.forEach(self.selectors, function(i){i.checked = true});
151         self.toggleState = !self.toggleState;
152     };
153
154
155     /** @param all If true, assume all are selected */
156     this.getSelected = function(all) {
157         var selected = [];
158         dojo.forEach(self.selectors, 
159             function(i) { 
160                 if(i.checked || all)
161                     selected.push(self.liCache[i.parentNode.parentNode.getAttribute('li')]);
162             }
163         );
164         return selected;
165     };
166
167     this.setRowAttr = function(td, liWrapper, field, type) {
168         var val = liWrapper.findAttr(field, type || 'lineitem_marc_attr_definition') || '';
169         td.appendChild(document.createTextNode(val));
170     };
171
172     /**
173      * Inserts a single lineitem into the growing table of lineitems
174      * @param {Object} li The lineitem object to insert
175      */
176     this.addLineitem = function(li, skip_final_placement) {
177         this.liCache[li.id()] = li;
178
179         // sort the lineitem notes on edit_time
180         if(!li.lineitem_notes()) li.lineitem_notes([]);
181
182         var liWrapper = new openils.acq.Lineitem({lineitem:li});
183         var row = self.rowTemplate.cloneNode(true);
184         row.setAttribute('li', li.id());
185         var tds = dojo.query('[attr]', row);
186         dojo.forEach(tds, function(td) {self.setRowAttr(td, liWrapper, td.getAttribute('attr'), td.getAttribute('attr_type'));});
187         dojo.query('[name=source_label]', row)[0].appendChild(document.createTextNode(li.source_label()));
188
189         var isbn = liWrapper.findAttr('isbn', 'lineitem_marc_attr_definition');
190         if(isbn) {
191             // XXX media prefix for added content
192             dojo.query('[name=jacket]', row)[0].setAttribute('src', '/opac/extras/ac/jacket/small/' + isbn);
193         }
194
195         dojo.query('[attr=title]', row)[0].onclick = function() {self.drawInfo(li.id())};
196         dojo.query('[name=copieslink]', row)[0].onclick = function() {self.drawCopies(li.id())};
197         dojo.query('[name=notes_count]', row)[0].innerHTML = li.lineitem_notes().length;
198         dojo.query('[name=noteslink]', row)[0].onclick = function() {self.drawLiNotes(li)};
199
200         // show which PO this lineitem is a member of
201         if(li.purchase_order() && !this.isPO) {
202             var po = 
203                 this.poCache[li.purchase_order()] =
204                 this.poCache[li.purchase_order()] ||
205                 fieldmapper.standardRequest(
206                     ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
207                     {params: [
208                         this.authtoken, li.purchase_order(), {
209                             "flesh_price_summary": true,
210                             "flesh_lineitem_count": true
211                         }
212                     ]});
213             if(po && !this.isMeta) {
214                 openils.Util.show(nodeByName('po', row), 'inline');
215                 var link = nodeByName('po_link', row);
216                 link.setAttribute('href', oilsBasePath + '/acq/po/view/' + li.purchase_order());
217                 link.innerHTML = 'PO: ' + po.name(); // TODO i18n
218             }
219         }
220
221         // show which picklist this lineitem is a member of
222         if(li.picklist() && (this.isPO || this.isMeta)) {
223             var pl = 
224                 this.plCache[li.picklist()] = 
225                 this.plCache[li.picklist()] || 
226                 fieldmapper.standardRequest(
227                     ['open-ils.acq', 'open-ils.acq.picklist.retrieve'],
228                     {params: [this.authtoken, li.picklist()]});
229             if(pl) {
230                 openils.Util.show(nodeByName('pl', row), 'inline');
231                 var link = nodeByName('pl_link', row);
232                 link.setAttribute('href', oilsBasePath + '/acq/picklist/view/' + li.picklist());
233                 link.innerHTML = 'PL: '+pl.name(); // TODO i18n
234             }
235         }
236
237         var countNode = nodeByName('count', row);
238         var count = li.item_count() || 0;
239         if (typeof(this._copy_count_cb) == "function") {
240             this._copy_count_cb(li.id(), count);
241         }
242         countNode.innerHTML = count;
243         countNode.id = 'acq-lit-copy-count-label-' + li.id();
244
245         // lineitem state
246         nodeByName('li_state', row).innerHTML = li.state(); // TODO i18n state labels
247         // lineitem price
248         var priceInput = dojo.query('[name=price]', row)[0];
249         var priceData = liWrapper.getPrice();
250         priceInput.value = (priceData) ? priceData.price : '';
251         priceInput.onchange = function() { self.updateLiPrice(priceInput, li) };
252
253         // show either "mark received" or "unreceive" as appropriate
254         this.updateReceivedness(li, row);
255
256         if (!skip_final_placement) {
257             self.tbody.appendChild(row);
258             self.selectors.push(dojo.query('[name=selectbox]', row)[0]);
259         } else {
260             return row;
261         }
262     };
263
264     this.updateReceivedness = function(li, row) {
265         if (typeof(row) == "undefined")
266             row = dojo.query('tr[li="' + li.id() + '"]', "acq-lit-tbody")[0];
267
268         var recv_link = nodeByName("receive_link", row);
269         var unrecv_link = nodeByName("unreceive_link", row);
270         var real_copies_link = nodeByName("real_copies_link", row);
271
272         /* handle row coloring for based on LI state */
273         openils.Util.removeCSSClass(row, /^oils-acq-li-state-/);
274         openils.Util.addCSSClass(row, "oils-acq-li-state-" + li.state());
275
276         /* handle links that appear/disappear based on whether LI is received */
277         if (this.isPO) {
278             var self = this;
279             switch(li.state()) {
280                 case "on-order":
281                     openils.Util.hide(real_copies_link);
282                     openils.Util.hide(unrecv_link);
283                     openils.Util.show(recv_link, "inline");
284                     if (typeof(recv_link.onclick) != "function")
285                         recv_link.onclick = function() { self.receiveLi(li); };
286                     return;
287                 case "received":
288                     openils.Util.hide(recv_link);
289                     openils.Util.show(unrecv_link, "inline");
290                     if (typeof(unrecv_link.onclick) != "function") {
291                         unrecv_link.onclick = function() {
292                             if (confirm(localeStrings.UNRECEIVE_LI))
293                                 self.unReceiveLi(li);
294                         };
295                     }
296                     // TODO we should allow editing before receipt, in which case the
297                     // test should be "if 1 or more real (acp) copies exist
298                     openils.Util.show(real_copies_link);
299                     real_copies_link.onclick = function() {
300                         self.showRealCopies(li);
301                     }
302                     return;
303             }
304         }
305
306         openils.Util.hide(recv_link);
307         openils.Util.hide(unrecv_link);
308         openils.Util.hide(real_copies_link);
309     };
310
311
312     /**
313      * Draws and shows the lineitem notes pane
314      */
315     this.drawLiNotes = function(li) {
316         var self = this;
317
318         li.lineitem_notes(
319             li.lineitem_notes().sort(
320                 function(a, b) { 
321                     if(a.edit_time() < b.edit_time()) return 1;
322                     return -1;
323                 }
324             )
325         );
326
327         while(this.liNotesTbody.childNodes[0])
328             this.liNotesTbody.removeChild(this.liNotesTbody.childNodes[0]);
329         this.show('notes');
330
331         acqLitCreateLiNoteSubmit.onClick = function() {
332             var value = acqLitCreateNoteText.attr('value');
333             if(!value) return;
334             var note = new fieldmapper.acqlin();
335             note.isnew(true);
336             note.value(value);
337             note.lineitem(li.id());
338             self.updateLiNotes(li, note);
339         }
340
341         dojo.byId('acq-lit-notes-save-button').onclick = function() {
342             self.updateLiNotes(li);
343         }
344
345         dojo.forEach(li.lineitem_notes(), function(note) { self.addLiNote(li, note) });
346     }
347
348     /**
349      * Draws a single lineitem note in the notes pane
350      */
351     this.addLiNote = function(li, note) {
352         if(note.isdeleted()) return;
353         var self = this;
354         var row = self.liNotesRow.cloneNode(true);
355         dojo.query('[name=value]', row)[0].innerHTML = note.value();
356
357         dojo.query('[name=delete]', row)[0].onclick = function() {
358             note.isdeleted(true);
359             self.liNotesTbody.removeChild(row);
360         };
361
362         if(note.edit_time()) {
363             dojo.query('[name=edit_time]', row)[0].innerHTML = 
364                 dojo.date.locale.format(
365                     dojo.date.stamp.fromISOString(note.edit_time()), 
366                     {formatLength:'short'});
367         }
368
369         self.liNotesTbody.appendChild(row);
370     }
371
372     /**
373      * Updates any new/changed/deleted notes on the server
374      */
375     this.updateLiNotes = function(li, newNote) {
376
377         var notes;
378         if(newNote) {
379             notes = [newNote];
380         } else {
381             notes = li.lineitem_notes().filter(
382                 function(note) {
383                     if(note.ischanged() || note.isnew() || note.isdeleted())
384                         return note;
385                 }
386             );
387         }
388
389         if(notes.length == 0) return;
390         progressDialog.show();
391
392         fieldmapper.standardRequest(
393             ['open-ils.acq', 'open-ils.acq.lineitem_note.cud.batch'],
394             {   async : true,
395                 params : [this.authtoken, notes],
396                 onresponse : function(r) {
397                     var resp = openils.Util.readResponse(r);
398
399                     if(resp.complete) {
400
401                         if(!newNote) {
402                             // remove the old changed notes
403                             var list = [];
404                             dojo.forEach(li.lineitem_notes(), 
405                                 function(note) {
406                                     if(!(note.ischanged() || note.isnew() || note.isdeleted()))
407                                         list.push(note);
408                                 }
409                             );
410                             li.lineitem_notes(list);
411                         }
412
413                         progressDialog.hide();
414                         self.drawLiNotes(li);
415                         return;
416                     }
417
418                     progressDialog.update(resp);
419                     var newnote = resp.note;
420
421                     if(!newnote.isdeleted()) {
422                         newnote.isnew(false);
423                         newnote.ischanged(false);
424                         li.lineitem_notes().push(newnote);
425                     }
426                 },
427             }
428         );
429     }
430
431     this.updateLiPrice = function(input, li) {
432
433         var price = input.value;
434         var liWrapper = new openils.acq.Lineitem({lineitem:li});
435         var oldPrice = liWrapper.getPrice() || null;
436
437         if(oldPrice) oldPrice = oldPrice.price;
438         if(price == oldPrice) return;
439
440         fieldmapper.standardRequest(
441             ['open-ils.acq', 'open-ils.acq.lineitem.price.set'],
442             {   async : true,
443                 params : [this.authtoken, li.id(), price],
444                 oncomplete : function(r) {
445                     openils.Util.readResponse(r);
446                 }
447             }
448         );
449     }
450
451     this.removeLineitem = function(liId) {
452         this.tbody.removeChild(dojo.query('[li='+liId+']', this.tbody)[0]);
453         delete this.liCache[liId];
454         //selected.push(self.liCache[i.parentNode.parentNode.getAttribute('li')]);
455     }
456
457     this.drawInfo = function(liId) {
458         this.show('info');
459         openils.acq.Lineitem.fetchAttrDefs(
460             function() { 
461                 self._fetchLineitem(liId, function(li){self._drawInfo(li);}); 
462             } 
463         );
464     };
465
466     this._fetchLineitem = function(liId, handler) {
467
468         var li = this.liCache[liId];
469         if(li && li.marc() && li.lineitem_details())
470             return handler(li);
471         
472         fieldmapper.standardRequest(
473             ['open-ils.acq', 'open-ils.acq.lineitem.retrieve'],
474             {   async: true,
475
476                 params: [self.authtoken, liId, {
477                     flesh_attrs: true,
478                     flesh_li_details: true,
479                     flesh_fund_debit: true }],
480
481                 oncomplete: function(r) {
482                     var li = openils.Util.readResponse(r);
483                     handler(li)
484                 }
485             }
486         );
487     };
488
489     this._drawInfo = function(li) {
490
491         acqLitEditOrderMarc.onClick = function() { self.editOrderMarc(li); }
492
493         if(li.eg_bib_id()) {
494             openils.Util.hide('acq-lit-marc-order-record-label');
495             openils.Util.hide(acqLitEditOrderMarc.domNode);
496             openils.Util.show('acq-lit-marc-real-record-label');
497         } else {
498             openils.Util.show('acq-lit-marc-order-record-label');
499             openils.Util.show(acqLitEditOrderMarc.domNode);
500             openils.Util.hide('acq-lit-marc-real-record-label');
501         }
502
503         this.drawMarcHTML(li);
504         this.infoTbody = dojo.byId('acq-lit-info-tbody');
505
506         if(!this.infoRow)
507             this.infoRow = this.infoTbody.removeChild(dojo.byId('acq-lit-info-row'));
508         while(this.infoTbody.childNodes[0])
509             this.infoTbody.removeChild(this.infoTbody.childNodes[0]);
510
511         for(var i = 0; i < li.attributes().length; i++) {
512             var attr = li.attributes()[i];
513             var row = this.infoRow.cloneNode(true);
514
515             var type = attr.attr_type().replace(/lineitem_(.*)_attr_definition/, '$1');
516             var name = openils.acq.Lineitem.attrDefs[type].filter(
517                 function(a) {
518                     return (a.code() == attr.attr_name());
519                 }
520             ).pop().description();
521
522             dojo.query('[name=label]', row)[0].appendChild(document.createTextNode(name));
523             dojo.query('[name=value]', row)[0].appendChild(document.createTextNode(attr.attr_value()));
524             this.infoTbody.appendChild(row);
525         }
526
527         if(li.eg_bib_id()) {
528             openils.Util.show('acq-lit-info-cat-link');
529             var link = dojo.byId('acq-lit-info-cat-link').getElementsByTagName('a')[0];
530
531             if(openils.XUL.isXUL()) {
532
533                 var makeRecTab = function() {
534                                     xulG.new_tab(
535                         XUL_OPAC_WRAPPER,
536                                             {tab_name: localeStrings.XUL_RECORD_DETAIL_PAGE, browser:false},
537                                             {
538                             no_xulG : false, 
539                             show_nav_buttons : true, 
540                             show_print_button : true, 
541                             opac_url : xulG.url_prefix(xulG.urls.opac_rdetail + '?r=' + li.eg_bib_id())
542                         }
543                     );
544                 }
545                 link.setAttribute('href', 'javascript:void(0);');
546                 link.onclick = makeRecTab;
547
548             } else {
549                 var href = link.getAttribute('href');
550                 if(href.match(/=$/))
551                     link.setAttribute('href',  href + li.eg_bib_id());
552             }
553         } else {
554             openils.Util.hide('acq-lit-info-cat-link');
555         }
556     };
557
558     this.drawMarcHTML = function(li) {
559         var params = [null, true, li.marc()];
560         if(li.eg_bib_id()) 
561             params = [li.eg_bib_id(), true];
562
563         fieldmapper.standardRequest(
564             ['open-ils.search', 'open-ils.search.biblio.record.html'],
565             {   async: true,
566                 params: params,
567                 oncomplete: function(r) {
568                     dojo.byId('acq-lit-marc-div').innerHTML = 
569                         openils.Util.readResponse(r);
570                 }
571             }
572         );
573     }
574
575     this.drawCopies = function(liId) {
576         this.show('copies');
577         var self = this;
578         this.copyCache = {};
579         this.copyWidgetCache = {};
580         this.oldCopyWidgetCache = {};
581         this.dfaCache = [];
582         this.dfeOffset = 0;
583
584         acqLitSaveCopies.onClick = function() { self.saveCopyChanges(liId) };
585         acqLitBatchUpdateCopies.onClick = function() { self.batchCopyUpdate() };
586         acqLitCopyCountInput.attr('value', '0');
587
588         while(this.copyTbody.childNodes[0])
589             this.copyTbody.removeChild(this.copyTbody.childNodes[0]);
590
591         this._drawBatchCopyWidgets();
592
593         this._fetchDistribFormulas(
594             function() {
595                 openils.acq.Lineitem.fetchAttrDefs(
596                     function() { 
597                         self._fetchLineitem(liId, function(li){self._drawCopies(li);}); 
598                     } 
599                 );
600             }
601         );
602     };
603
604     /**
605      * Insert a new row into the distribution formula selection form
606      */
607     this._addDistribFormulaRow = function() {
608         var self = this;
609
610         if(!self.distribFormulaStore) {
611             // no formulas, hide the form
612             openils.Util.hide('acq-lit-distrib-formula-tbody');
613             return;
614         }
615
616         if(!this.distribFormulaTemplate) 
617             this.distribFormulaTemplate = 
618                 dojo.byId('acq-lit-distrib-formula-tbody').removeChild(dojo.byId('acq-lit-distrib-form-row'));
619
620         var row = dojo.byId('acq-lit-distrib-formula-tbody').appendChild(this.distribFormulaTemplate.cloneNode(true));
621
622         var selector = new dijit.form.FilteringSelect(
623             {store : self.distribFormulaStore}, 
624             nodeByName('selector', row)
625         );
626
627         var apply = new dijit.form.Button(
628             {"label": localeStrings.APPLY},
629             nodeByName('set_button', row)
630         ); 
631
632         var reset = new dijit.form.Button(
633             {"label": localeStrings.RESET_FORMULAE, "disabled": true},
634             nodeByName("reset_button", row)  
635         );
636
637         dojo.connect(apply, 'onClick', 
638             function() {
639                 var form_id = selector.attr('value');
640                 if(!form_id) return;
641                 self._applyDistribFormula(form_id);
642                 reset.attr("disabled", false);
643             }
644         );
645
646         dojo.connect(reset, 'onClick', 
647             function() {
648                 self.restoreCopyFieldsBeforeDF();
649                 self.dfaCache = [];
650                 self.dfeOffset = 0;
651                 reset.attr("disabled", "true");
652             }
653         );
654
655     };
656
657     /**
658      * Applies a distrib formula to the current set of copies
659      */
660     this._applyDistribFormula = function(formula) {
661         if(!formula) return;
662
663         formula = this.distribForms.filter(
664             function(form) { return form.id() == formula; }
665         )[0];
666
667         var copyRows = dojo.query('tr', self.copyTbody);
668
669         if (this.dfeOffset >= copyRows.length) {
670             alert(localeStrings.OUT_OF_COPIES);
671             return;
672         }
673
674         var entries_applied = 0;
675         for(
676             var rowIndex = this.dfeOffset;
677             rowIndex < copyRows.length;
678             rowIndex++
679         ) {
680             
681             var row = copyRows[rowIndex];
682             var copy_id = row.getAttribute('copy_id');
683             var copyWidgets = this.copyWidgetCache[copy_id];
684             var entryIndex = this.dfeOffset;
685             var entry = null;
686
687             // find the correct entry for the current row
688             dojo.forEach(formula.entries(), 
689                 function(e) {
690                     if(!entry) {
691                         entryIndex += e.item_count();
692                         if(entryIndex > rowIndex)
693                             entry = e;
694                     }
695                 }
696             );
697
698             if(entry) {
699                 
700                 //console.log("rowIndex = " + rowIndex + ", entry = " + entry.id() + ", entryIndex=" + 
701                 //  entryIndex + ", owning_lib = " + entry.owning_lib() + ", location = " + entry.location());
702     
703                 entries_applied++;
704                 this.saveCopyFieldsBeforeDF(copy_id);
705                 this._copy_fields_for_acqdf.forEach(
706                     function(field) {
707                         if(entry[field]()) {
708                             copyWidgets[field].attr('value', (entry[field]()));
709                         }
710                     }
711                 );
712             }
713         }
714
715         if (entries_applied) {
716             this.dfaCache.push(formula.id());
717             this.dfeOffset += entries_applied;
718         };
719     };
720
721     this.saveCopyFieldsBeforeDF = function(copy_id) {
722         var self = this;
723         if (!this.oldCopyWidgetCache[copy_id]) {
724             var copyWidgets = this.copyWidgetCache[copy_id];
725
726             this.oldCopyWidgetCache[copy_id] = {};
727             this._copy_fields_for_acqdf.forEach(
728                 function(f) {
729                     self.oldCopyWidgetCache[copy_id][f] =
730                         copyWidgets[f].attr("value");
731                 }
732             );
733         }
734     };
735
736     this.restoreCopyFieldsBeforeDF = function() {
737         var self = this;
738         for (var copy_id in this.oldCopyWidgetCache) {
739             this._copy_fields_for_acqdf.forEach(
740                 function(f) {
741                     self.copyWidgetCache[copy_id][f].attr(
742                         "value", self.oldCopyWidgetCache[copy_id][f]
743                     );
744                 }
745             );
746         }
747     };
748
749     this._fetchDistribFormulas = function(onload) {
750         if(this.distribForms) {
751             onload();
752         } else {
753             var self = this;
754             fieldmapper.standardRequest(
755                 ['open-ils.acq', 'open-ils.acq.distribution_formula.ranged.retrieve.atomic'],
756                 {   async: true,
757                     params: [openils.User.authtoken],
758                     oncomplete: function(r) {
759                         self.distribForms = openils.Util.readResponse(r);
760                         if(!self.distribForms || self.distribForms.length == 0) {
761                             self.distribForms = [];
762                         } else {
763                             self.distribFormulaStore =
764                                 new dojo.data.ItemFileReadStore(
765                                     {data:acqdf.toStoreData(self.distribForms)}
766                                 );
767                         }
768                         self._addDistribFormulaRow();
769                         onload();
770                     }
771                 }
772             );
773         }
774     }
775
776     this._drawBatchCopyWidgets = function() {
777         var row = this.copyBatchRow;
778         dojo.forEach(liDetailBatchFields, 
779             function(field) {
780                 if(self.copyBatchRowDrawn) {
781                     self.copyBatchWidgets[field].attr('value', null);
782                 } else {
783                     var widget = new openils.widget.AutoFieldWidget({
784                         fmField : field,
785                         fmClass : 'acqlid',
786                         parentNode : dojo.query('[name='+field+']', row)[0],
787                         orgLimitPerms : ['CREATE_PICKLIST'],
788                         dijitArgs : {required:false},
789                         forceSync : true
790                     });
791                     widget.build(
792                         function(w, ww) {
793                             self.copyBatchWidgets[field] = w;
794                         }
795                     );
796                 }
797             }
798         );
799         this.copyBatchRowDrawn = true;
800     };
801
802     this.batchCopyUpdate = function() {
803         var self = this;
804         for(var k in this.copyWidgetCache) {
805             var cache = this.copyWidgetCache[k];
806             dojo.forEach(liDetailBatchFields, function(f) {
807                 var newval = self.copyBatchWidgets[f].attr('value');
808                 if(newval) cache[f].attr('value', newval);
809             });
810         }
811     };
812
813     this._drawCopies = function(li) {
814         var self = this;
815
816         // this button sets the total number of copies for a given lineitem
817         acqLitAddCopyCount.onClick = function() { 
818             var count = acqLitCopyCountInput.attr('value');
819
820             // add new rows
821             while(self.copyCount() < count)
822                 self.addCopy(li); 
823             
824             // delete rows if necessary
825             var diff = self.copyCount() - count;
826             if(diff > 0) {
827                 var rows = dojo.query('tr', self.copyTbody).reverse().slice(0, diff);
828                 if(confirm(dojo.string.substitute(localeStrings.DELETE_LI_COPIES_CONFIRM, [diff]))) {
829                     dojo.forEach(rows, function(row) {self.deleteCopy(row); });
830                 } else {
831                     acqLitCopyCountInput.attr('value', self.copyCount()+'');
832                 }
833             }
834         }
835
836
837         if(li.lineitem_details().length > 0) {
838             dojo.forEach(li.lineitem_details(),
839                 function(copy) {
840                     self.addCopy(li, copy);
841                 }
842             );
843         } else {
844             self.addCopy(li);
845         }
846     };
847
848     this.copyCount = function() {
849         var count = 0;
850         for(var id in this.copyCache) {
851             if(!this.copyCache[id].isdeleted())
852                 count++;
853         }
854         return count;
855     }
856
857     this.virtCopyId = -1;
858     this.addCopy = function(li, copy) {
859         var row = this.copyRow.cloneNode(true);
860         this.copyTbody.appendChild(row);
861         var self = this;
862
863         if(!copy) {
864             copy = new fieldmapper.acqlid();
865             copy.isnew(true);
866             copy.id(this.virtCopyId--);
867             copy.lineitem(li.id());
868         }
869
870         this.copyCache[copy.id()] = copy;
871         row.setAttribute('copy_id', copy.id());
872         self.copyWidgetCache[copy.id()] = {};
873
874         acqLitCopyCountInput.attr('value', self.copyCount()+'');
875
876         dojo.forEach(liDetailFields,
877             function(field) {
878                 var widget = new openils.widget.AutoFieldWidget({
879                     fmObject : copy,
880                     fmField : field,
881                     fmClass : 'acqlid',
882                     parentNode : dojo.query('[name='+field+']', row)[0],
883                     orgLimitPerms : ['CREATE_PICKLIST', 'CREATE_PURCHASE_ORDER'],
884                     readOnly : Boolean(copy.eg_copy_id())
885                 });
886                 widget.build(
887                     // make sure we capture the value from any async widgets
888                     function(w, ww) { 
889                         copy[field](ww.getFormattedValue()) 
890                         self.copyWidgetCache[copy.id()][field] = w;
891                     }
892                 );
893                 dojo.connect(widget.widget, 'onChange', 
894                     function(val) { 
895                         if(copy.isnew() || val != copy[field]()) {
896                             // prevent setting ischanged() automatically on widget load for existing copies
897                             copy[field](widget.getFormattedValue()) 
898                             copy.ischanged(true);
899                         }
900                     }
901                 );
902             }
903         );
904
905         var recv_link = dojo.query('[name=receive]', row)[0];
906         if(copy.recv_time()) {
907             openils.Util.hide(recv_link);
908         } else {
909             recv_link.onclick = function() {
910                 self.receiveLid(copy);
911                 openils.Util.hide(recv_link);
912             }
913         }
914
915         if(this.isPO) {
916             openils.Util.hide(dojo.query('[name=delete]', row)[0].parentNode);
917         } else {
918             dojo.query('[name=delete]', row)[0].onclick = 
919                 function() { self.deleteCopy(row) };
920         }
921     };
922
923     this.deleteCopy = function(row) {
924         var copy = this.copyCache[row.getAttribute('copy_id')];
925         copy.isdeleted(true);
926         if(copy.isnew())
927             delete this.copyCache[copy.id()];
928         this.copyTbody.removeChild(row);
929     }
930
931     this.saveCopyChanges = function(liId) {
932         var self = this;
933         var copies = [];
934
935
936         var total = 0;
937         for(var id in this.copyCache) {
938             var c = this.copyCache[id];
939             if(!c.isdeleted()) total++;
940             if(c.isnew() || c.ischanged() || c.isdeleted()) {
941                 if(c.id() < 0) c.id(null);
942                 copies.push(c);
943             }
944         }
945
946         if (typeof(this._copy_count_cb) == "function") {
947             this._copy_count_cb(liId, total);
948         }
949
950         dojo.byId('acq-lit-copy-count-label-' + liId).innerHTML = total;
951
952
953         if (copies.length > 0) {
954             openils.Util.show("acq-lit-update-copies-progress");
955             fieldmapper.standardRequest(
956                 ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],
957                 {   async: true,
958                     params: [openils.User.authtoken, copies],
959                     onresponse: function(r) {
960                         var res = openils.Util.readResponse(r);
961                         litUpdateCopiesProgress.update(res);
962                     },
963                     oncomplete: function() {
964                         self.drawCopies(liId);
965                         openils.Util.hide("acq-lit-update-copies-progress");
966                     }
967                 }
968             );
969         }
970
971         if (this.dfaCache.length > 0) {
972             var oldlength = this.dfaCache.length;
973
974             fieldmapper.standardRequest(
975                 ["open-ils.acq",
976                 "open-ils.acq.distribution_formula.record_application"],
977                 {
978                     "async": true,
979                     "params": [openils.User.authtoken, this.dfaCache, liId],
980                     "onresponse": function(r) {
981                         var res = openils.Util.readResponse(r);
982                         if (res && res.length != oldlength)
983                             alert(localeStrings.DFA_NOT_ALL);
984                     }
985                 }
986             );
987             this.dfaCache = [];
988         }
989     }
990
991     this.applySelectedLiAction = function(action) {
992         var self = this;
993         switch(action) {
994
995             case 'delete_selected':
996                 this._deleteLiList(self.getSelected());
997                 break;
998
999             case 'create_order':
1000
1001                 if(!this.createPoProviderSelector) {
1002                     var widget = new openils.widget.AutoFieldWidget({
1003                         fmField : 'provider',
1004                         fmClass : 'acqpo',
1005                         searchFilter: {"active": "t"},
1006                         parentNode : dojo.byId('acq-lit-po-provider'),
1007                     });
1008                     widget.build(
1009                         function(w) { self.createPoProviderSelector = w; }
1010                     );
1011                 }
1012
1013                 if(!this.createPoAgencySelector) {
1014                     var widget = new openils.widget.AutoFieldWidget({
1015                         fmField : 'ordering_agency',
1016                         fmClass : 'acqpo',
1017                         parentNode : dojo.byId('acq-lit-po-agency'),
1018                         orgLimitPerms : ['CREATE_PURCHASE_ORDER'],
1019                     });
1020                     widget.build(
1021                         function(w) { self.createPoAgencySelector = w; }
1022                     );
1023                 }
1024
1025          
1026                 acqLitPoCreateDialog.show();
1027                 break;
1028
1029             case 'save_picklist':
1030                 this._loadPLSelect();
1031                 acqLitSavePlDialog.show();
1032                 break;
1033
1034             case 'selector_ready':
1035             case 'order_ready':
1036                 acqLitChangeLiStateDialog.attr('state', action.replace('_', '-'));
1037                 acqLitChangeLiStateDialog.show();
1038                 break;
1039
1040             case 'print_po':
1041                 this.printPO();
1042                 break;
1043
1044             case 'receive_po':
1045                 this.receivePO();
1046                 break;
1047
1048             case 'rollback_receive_po':
1049                 this.rollbackPoReceive();
1050                 break;
1051
1052             case 'create_assets':
1053                 this.createAssets();
1054                 break;
1055
1056             case 'export_attr_list':
1057                 this.chooseExportAttr();
1058                 break;
1059
1060             case 'add_brief_record':
1061                 if(this.isPO)
1062                     location.href = oilsBasePath + '/acq/picklist/brief_record?po=' + this.isPO;
1063                 else
1064                     location.href = oilsBasePath + '/acq/picklist/brief_record?pl=' + this.isPL;
1065         }
1066     }
1067
1068     this.createAssets = function() {
1069         if(!this.isPO) return;
1070         if(!confirm(localeStrings.CREATE_PO_ASSETS_CONFIRM)) return;
1071         this.show('acq-lit-progress-numbers');
1072         var self = this;
1073         fieldmapper.standardRequest(
1074             ['open-ils.acq', 'open-ils.acq.purchase_order.assets.create'],
1075             {   async: true,
1076                 params: [this.authtoken, this.isPO],
1077                 onresponse: function(r) {
1078                     var resp = openils.Util.readResponse(r);
1079                     self._updateProgressNumbers(resp, true);
1080                 }
1081             }
1082         );
1083     }
1084
1085     this.chooseExportAttr = function() {
1086         if (!acqLitExportAttrSelector._li_setup) {
1087             var self = this;
1088             acqLitExportAttrSelector.store = new dojo.data.ItemFileReadStore(
1089                 {
1090                     "data": acqliad.toStoreData(
1091                         (new openils.PermaCrud()).search(
1092                             "acqliad", {"code": li_exportable_attrs}
1093                         )
1094                     )
1095                 }
1096             );
1097             acqLitExportAttrSelector.setValue();
1098             acqLitExportAttrButton.onClick = function(){self.exportAttrList();};
1099             acqLitExportAttrSelector._li_setup = true;
1100         }
1101         openils.Util.show("acq-lit-export-attr-holder", "inline");
1102     };
1103
1104     this.exportAttrList = function() {
1105         var attr_def = acqLitExportAttrSelector.item;
1106         var li_list = this.getSelected();
1107         var value_list = li_list.map(
1108             function(li) {
1109                 return (new openils.acq.Lineitem({"lineitem": li})).findAttr(
1110                     attr_def.code, "lineitem_marc_attr_definition"
1111                 );
1112             }
1113         ).filter(function(attr) { return Boolean(attr); });
1114
1115         if (value_list.length > 0) {
1116             if (value_list.length < li_list.length) {
1117                 if (!confirm(
1118                     dojo.string.substitute(
1119                         localeStrings.EXPORT_SHORT_LIST, [attr_def.description]
1120                     )
1121                 )) {
1122                     return;
1123                 }
1124             }
1125             try {
1126                 openils.XUL.contentToFileSaveDialog(
1127                     value_list.join("\n"),
1128                     localeStrings.EXPORT_SAVE_DIALOG_TITLE
1129                 );
1130             } catch (E) {
1131                 alert(E);
1132             }
1133         } else {
1134             alert(dojo.string.substitute(
1135                 localeStrings.EXPORT_EMPTY_LIST, [attr_def.description]
1136             ));
1137         }
1138
1139         openils.Util.hide("acq-lit-export-attr-holder");
1140     };
1141
1142     this.printPO = function() {
1143         if(!this.isPO) return;
1144         progressDialog.show(true);
1145         fieldmapper.standardRequest(
1146             ['open-ils.acq', 'open-ils.acq.purchase_order.format'],
1147             {   async: true,
1148                 params: [this.authtoken, this.isPO, 'html'],
1149                 oncomplete: function(r) {
1150                     progressDialog.hide();
1151                     var evt = openils.Util.readResponse(r);
1152                     if(evt && evt.template_output()) {
1153                         win = window.open('','', 'resizable,width=800,height=600,scrollbars=1');
1154                         win.document.body.innerHTML = evt.template_output().data();
1155                     }
1156                 }
1157             }
1158         );
1159     }
1160
1161
1162     this.receivePO = function() {
1163         if(!this.isPO) return;
1164         this.show('acq-lit-progress-numbers');
1165         var self = this;
1166         fieldmapper.standardRequest(
1167             ['open-ils.acq', 'open-ils.acq.purchase_order.receive'],
1168             {   async: true,
1169                 params: [this.authtoken, this.isPO],
1170                 onresponse : function(r) {
1171                     var resp = openils.Util.readResponse(r);
1172                     self._updateProgressNumbers(resp, true);
1173                 },
1174             }
1175         );
1176     }
1177
1178     this.receiveLi = function(li) {
1179         /* (For now) there shall be no marking LIs received except from the
1180          * actual "view PO" interface. */
1181         if (!this.isPO) return;
1182
1183         var self = this;
1184         progressDialog.show(true);
1185         fieldmapper.standardRequest(
1186             ["open-ils.acq", "open-ils.acq.lineitem.receive"], {
1187                 "async": true,
1188                 "params": [this.authtoken, li.id()],
1189                 "onresponse": function(r) {
1190                     self.handleReceiveOrRollback(openils.Util.readResponse(r));
1191                 },
1192                 "oncomplete": function() {
1193                     progressDialog.hide();
1194                 }
1195             }
1196         );
1197     }
1198
1199     this.handleReceiveOrRollback = function(resp) {
1200         if (resp) {
1201             if (resp.li) {
1202                 for (var li_id in resp.li) {
1203                     for (var key in resp.li[li_id])
1204                         self.liCache[li_id][key](resp.li[li_id][key]);
1205                     self.updateReceivedness(self.liCache[li_id]);
1206                 }
1207             }
1208             if (resp.po) {
1209                 if (typeof(self.poUpdateCallback) == "function")
1210                     self.poUpdateCallback(resp.po);
1211             }
1212         }
1213     }
1214
1215     this.unReceiveLi = function(li) {
1216         /* (For now) there shall be no marking LIs un-received except from the
1217          * actual "view PO" interface. */
1218         if (!this.isPO) return;
1219
1220         var self = this;
1221         progressDialog.show(true);
1222         fieldmapper.standardRequest(
1223             ["open-ils.acq", "open-ils.acq.lineitem.receive.rollback"], {
1224                 "async": true,
1225                 "params": [this.authtoken, li.id()],
1226                 "onresponse": function(r) {
1227                     self.handleReceiveOrRollback(openils.Util.readResponse(r));
1228                 },
1229                 "oncomplete": function() {
1230                     progressDialog.hide();
1231                 }
1232             }
1233         );
1234     }
1235
1236     this.receiveLid = function(li) {
1237         if(!this.isPO) return;
1238         progressDialog.show(true);
1239         fieldmapper.standardRequest(
1240             ['open-ils.acq', 'open-ils.acq.lineitem_detail.receive'],
1241             {   async: true,
1242                 params: [this.authtoken, li.id()],
1243                 onresponse : function(r) {
1244                     var resp = openils.Util.readResponse(r);
1245                     progressDialog.hide();
1246                 },
1247             }
1248         );
1249     }
1250
1251     this.rollbackPoReceive = function() {
1252         if(!this.isPO) return;
1253         if(!confirm(localeStrings.ROLLBACK_PO_RECEIVE_CONFIRM)) return;
1254         this.show('acq-lit-progress-numbers');
1255         var self = this;
1256         fieldmapper.standardRequest(
1257             ['open-ils.acq', 'open-ils.acq.purchase_order.receive.rollback'],
1258             {   async: true,
1259                 params: [this.authtoken, this.isPO],
1260                 onresponse : function(r) {
1261                     var resp = openils.Util.readResponse(r);
1262                     self._updateProgressNumbers(resp, true);
1263                 },
1264             }
1265         );
1266     }
1267
1268     this._updateProgressNumbers = function(resp, reloadOnComplete) {
1269         if(!resp) return;
1270         dojo.byId('acq-pl-lit-li-processed').innerHTML = resp.li;
1271         dojo.byId('acq-pl-lit-lid-processed').innerHTML = resp.lid;
1272         dojo.byId('acq-pl-lit-debits-processed').innerHTML = resp.debits_accrued;
1273         dojo.byId('acq-pl-lit-bibs-processed').innerHTML = resp.bibs;
1274         dojo.byId('acq-pl-lit-indexed-processed').innerHTML = resp.indexed;
1275         dojo.byId('acq-pl-lit-copies-processed').innerHTML = resp.copies;
1276         if(resp.complete && reloadOnComplete) 
1277             location.href = location.href;
1278     }
1279
1280
1281     this._createPO = function(fields) {
1282         this.show('acq-lit-progress-numbers');
1283         var po = new fieldmapper.acqpo();
1284         po.provider(this.createPoProviderSelector.attr('value'));
1285         po.ordering_agency(this.createPoAgencySelector.attr('value'));
1286
1287         var selected = this.getSelected( (fields.create_from == 'all') );
1288         if(selected.length == 0) return;
1289
1290         var max = selected.length * 3;
1291
1292         var self = this;
1293         fieldmapper.standardRequest(
1294             ['open-ils.acq', 'open-ils.acq.purchase_order.create'],
1295             {   async: true,
1296                 params: [
1297                     openils.User.authtoken, 
1298                     po, 
1299                     {
1300                         lineitems : selected.map(function(li) { return li.id() }),
1301                         create_assets : fields.create_assets[0],
1302                     }
1303                 ],
1304
1305                 onresponse : function(r) {
1306                     var resp = openils.Util.readResponse(r);
1307                     self._updateProgressNumbers(resp);
1308                     if(resp.complete) 
1309                         location.href = oilsBasePath + '/eg/acq/po/view/' + resp.purchase_order.id();
1310                 }
1311             }
1312         );
1313     }
1314
1315     this._deleteLiList = function(list, idx) {
1316         if(idx == null) idx = 0;
1317         if(idx >= list.length) return;
1318         var liId = list[idx].id();
1319         fieldmapper.standardRequest(
1320             ['open-ils.acq', 'open-ils.acq.lineitem.delete'],
1321             {   async: true,
1322                 params: [openils.User.authtoken, liId],
1323                 oncomplete: function(r) {
1324                     self.removeLineitem(liId);
1325                     self._deleteLiList(list, ++idx);
1326                 }
1327             }
1328         );
1329     }
1330
1331     this.editOrderMarc = function(li) {
1332
1333         /*  To run in Firefox directly, must set signed.applets.codebase_principal_support
1334             to true in about:config */
1335
1336         if(!openils.XUL.enableXPConnect()) return;
1337
1338         if(openils.XUL.isXUL()) {
1339             win = window.open('/xul/' + openils.XUL.buildId() + '/server/cat/marcedit.xul');
1340         } else {
1341             win = window.open('/xul/server/cat/marcedit.xul'); 
1342         }
1343         var self = this;
1344         win.xulG = {
1345             record : {marc : li.marc()},
1346             save : {
1347                 label: 'Save Record', // XXX I18N
1348                 func: function(xmlString) {
1349                     li.marc(xmlString);
1350                     fieldmapper.standardRequest(
1351                         ['open-ils.acq', 'open-ils.acq.lineitem.update'],
1352                         {   async: true,
1353                             params: [openils.User.authtoken, li],
1354                             oncomplete: function(r) {
1355                                 openils.Util.readResponse(r);
1356                                 win.close();
1357                                 self.drawInfo(li.id())
1358                             }
1359                         }
1360                     );
1361                 },
1362             }
1363         };
1364     }
1365
1366     this._savePl = function(values) {
1367         var self = this;
1368         var selected = this.getSelected( (values.which == 'all') );
1369         openils.Util.show('acq-lit-generic-progress');
1370
1371         if(values.new_name) {
1372             openils.acq.Picklist.create(
1373                 {name: values.new_name}, 
1374                 function(id) {
1375                     self._updateLiList(id, selected, 0, 
1376                         function(){
1377                             location.href = oilsBasePath + '/eg/acq/picklist/view/' + id;
1378                         });
1379                 }
1380             );
1381         } else if(values.existing_pl) {
1382             // update lineitems to use an existing picklist
1383             self._updateLiList(values.existing_pl, selected, 0, 
1384                 function(){
1385                     location.href = oilsBasePath + '/eg/acq/picklist/view/' + values.existing_pl;
1386                 });
1387         }
1388     }
1389
1390     this._updateLiState = function(values, state) {
1391         var self = this;
1392         var selected = this.getSelected( (values.which == 'all') );
1393         if(!selected.length) return;
1394         dojo.forEach(selected, function(li) {li.state(state);});
1395         self._updateLiList(null, selected, 0, 
1396             // TODO consider inline updates for efficiency
1397             function() { location.href = location.href }
1398         );
1399     }
1400
1401     this._updateLiList = function(pl, list, idx, oncomplete) {
1402         if(idx >= list.length) return oncomplete();
1403         var li = list[idx];
1404         if(pl != null) li.picklist(pl);
1405         litGenericProgress.update({maximum: list.length, progress: idx});
1406         new openils.acq.Lineitem({lineitem:li}).update(
1407             function(r) {
1408                 self._updateLiList(pl, list, ++idx, oncomplete);
1409             }
1410         );
1411     }
1412
1413     this._loadPLSelect = function() {
1414         if(this._plSelectLoaded) return;
1415         var plList = [];
1416         function handleResponse(r) {
1417             plList.push(r.recv().content());
1418         }
1419         var method = 'open-ils.acq.picklist.user.retrieve';
1420         fieldmapper.standardRequest(
1421             ['open-ils.acq', method],
1422             {   async: true,
1423                 params: [this.authtoken],
1424                 onresponse: handleResponse,
1425                 oncomplete: function() {
1426                     self._plSelectLoaded = true;
1427                     acqLitAddExistingSelect.store = 
1428                         new dojo.data.ItemFileReadStore({data:acqpl.toStoreData(plList)});
1429                     acqLitAddExistingSelect.setValue();
1430                 }
1431             }
1432         );
1433     }
1434
1435     // grab the li-details for this lineitem, grab the linked copies and volumes, add them to the table
1436     this.showRealCopies = function(li) {
1437         while(this.realCopiesTbody.childNodes[0])
1438             this.realCopiesTbody.removeChild(this.realCopiesTbody.childNodes[0]);
1439         this.show('real-copies');
1440
1441         var pcrud = new openils.PermaCrud({authtoken : this.authtoken});
1442         this.realCopyList = [];
1443         this.volCache = {};
1444         var tabIndex = 1000;
1445         var self = this;
1446
1447         acqLitSaveRealCopies.onClick = function() {
1448             self.saveRealCopies();
1449         }
1450
1451         this._fetchLineitem(li.id(), 
1452             function(fullLi) {
1453                 li = self.liCache[li.id()] = fullLi;
1454
1455                 pcrud.search(
1456                     'acp', {
1457                         id : li.lineitem_details().map(
1458                             function(item) { return item.eg_copy_id() }
1459                         )
1460                     }, {
1461                         async : true,
1462                         streaming : true,
1463                         onresponse : function(r) {
1464                             var copy = openils.Util.readResponse(r);
1465                             var volId = copy.call_number();
1466                             var volume = self.volCache[volId];
1467                             if(!volume) {
1468                                 volume = self.volCache[volId] = pcrud.retrieve('acn', volId);
1469                             }
1470                             self.addRealCopy(volume, copy, tabIndex++);
1471                         }
1472                     }
1473                 );
1474             }
1475         );
1476     }
1477
1478     this.addRealCopy = function(volume, copy, tabIndex) {
1479         var row = this.realCopiesRow.cloneNode(true);
1480         this.realCopyList.push(copy);
1481
1482         var selectNode;
1483         dojo.forEach(
1484             ['owning_lib', 'location', 'circ_modifier', 'label', 'barcode'],
1485
1486             function(field) {
1487                 var isvol = (field == 'owning_lib' || field == 'label');
1488                 var widget = new openils.widget.AutoFieldWidget({
1489                     fmField : field,
1490                     fmObject : isvol ? volume : copy,
1491                     parentNode : nodeByName(field, row),
1492                     readOnly : (field != 'barcode'),
1493                 });
1494
1495                 var widgetDrawn = null;
1496
1497                 if(field == 'barcode') {
1498
1499                     widgetDrawn = function(w, ww) {
1500                         var node = w.domNode;
1501                         node.setAttribute('tabindex', ''+tabIndex);
1502
1503                         // on enter, select the next barcode input
1504                         dojo.connect(w, 'onKeyDown',
1505                             function(e) {
1506                                 if(e.keyCode == dojo.keys.ENTER) {
1507                                     var ti = node.getAttribute('tabindex');
1508                                     var nextNode = dojo.query('[tabindex=' + String(Number(ti) + 1) + ']', self.realCopiesTbody)[0];
1509                                     if(nextNode) nextNode.select();
1510                                 }
1511                             }
1512                         );
1513
1514                         dojo.connect(w, 'onChange', 
1515                             function(val) { 
1516                                 if(!val || val == copy.barcode()) return;
1517                                 copy.ischanged(true);
1518                                 copy.barcode(val);
1519                             }
1520                         );
1521
1522
1523                         if(self.realCopiesTbody.getElementsByTagName('TR').length == 0)
1524                             selectNode = node;
1525                     }
1526                 }
1527
1528                 widget.build(widgetDrawn);
1529             }
1530         );
1531
1532         this.realCopiesTbody.appendChild(row);
1533         if(selectNode) selectNode.select();
1534     };
1535
1536     this.saveRealCopies = function() {
1537         var pcrud = new openils.PermaCrud({authtoken : this.authtoken});
1538         progressDialog.show(true);
1539         var list = this.realCopyList.filter(function(copy) { return copy.ischanged(); });
1540         pcrud.update(list, {oncomplete: function() { 
1541             progressDialog.hide();
1542             self.show('list');
1543         }});
1544     }
1545 }