]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/actor/user/register.js
Patron address copy on clone
[working/Evergreen.git] / Open-ILS / web / js / ui / default / actor / user / register.js
1 dojo.require('dojo.data.ItemFileReadStore');
2 dojo.require('dijit.form.Form');
3 dojo.require('dijit.form.Textarea');
4 dojo.require('dijit.form.FilteringSelect');
5 dojo.require('dijit.form.ComboBox');
6 dojo.require('dijit.form.NumberSpinner');
7 dojo.require('fieldmapper.IDL');
8 dojo.require('openils.PermaCrud');
9 dojo.require('openils.widget.AutoGrid');
10 dojo.require('openils.widget.AutoFieldWidget');
11 dojo.require('dijit.form.CheckBox');
12 dojo.require('dijit.form.Button');
13 dojo.require('dojo.date');
14 dojo.require('openils.CGI');
15 dojo.require('openils.XUL');
16 dojo.require('openils.Util');
17 dojo.require('openils.Event');
18
19 dojo.requireLocalization('openils.actor', 'register');
20 var localeStrings = dojo.i18n.getLocalization('openils.actor', 'register');
21
22
23 var pcrud;
24 var fmClasses = ['au', 'ac', 'aua', 'actsc', 'asv', 'asvq', 'asva'];
25 var fieldDoc = {};
26 var statCats;
27 var statCatTemplate;
28 var surveys;
29 var staff;
30 var patron;
31 var uEditUsePhonePw = false;
32 var widgetPile = [];
33 var uEditCardVirtId = -1;
34 var uEditAddrVirtId = -1;
35 var orgSettings = {};
36 var userSettings = {};
37 var userSettingsToUpdate = {};
38 var userSettingTypes;
39 var tbody;
40 var addrTemplateRows;
41 var cgi;
42 var cloneUser;
43 var cloneUserObj;
44 var stageUser;
45 var optInSettings;
46 var allCardsTemplate;
47 var uEditCloneCopyAddr; // if true, copy addrs on clone instead of link
48
49 var dupeUsrname = false;
50 var dupeBarcode = false;
51
52 if(!window.xulG) var xulG = null;
53
54
55 function load() {
56     staff = new openils.User().user;
57     pcrud = new openils.PermaCrud();
58     cgi = new openils.CGI();
59     cloneUser = cgi.param('clone');
60     var userId = cgi.param('usr');
61     var stageUname = cgi.param('stage');
62
63     if(xulG) {
64             if(xulG.ses) openils.User.authtoken = xulG.ses;
65             if(typeof xulG.clone != 'undefined') cloneUser = xulG.clone;
66         if(typeof xulG.usr != 'undefined') userId = xulG.usr
67         if(typeof xulG.params != 'undefined') {
68             var parms = xulG.params;
69                 if(typeof parms.ses != 'undefined') 
70                 openils.User.authtoken = parms.ses;
71                 if(typeof parms.clone != 'undefined') 
72                 cloneUser = parms.clone;
73             if(typeof parms.usr != 'undefined')
74                 userId = parms.usr;
75             if(typeof parms.stage != 'undefined')
76                 stageUname = parms.stage
77         }
78     }
79
80     orgSettings = fieldmapper.aou.fetchOrgSettingBatch(staff.ws_ou(), [
81         'global.password_regex',
82         'global.juvenile_age_threshold',
83         'patron.password.use_phone',
84         'ui.patron.default_inet_access_level',
85         'circ.holds.behind_desk_pickup_supported',
86         'circ.patron_edit.clone.copy_address'
87     ]);
88
89     for(k in orgSettings)
90         if(orgSettings[k])
91             orgSettings[k] = orgSettings[k].value;
92
93     uEditCloneCopyAddr = orgSettings['circ.patron_edit.clone.copy_address'];
94     uEditUsePhonePw = orgSettings['patron.password.use_phone'];
95     uEditFetchUserSettings(userId);
96
97     if(userId) {
98         patron = uEditLoadUser(userId);
99     } else {
100         if(stageUname) {
101             patron = uEditLoadStageUser(stageUname);
102         } else {
103             patron = uEditNewPatron();
104             if(cloneUser) 
105                 uEditCopyCloneData(patron);
106         }
107     }
108
109
110     var list = pcrud.search('fdoc', {fm_class:fmClasses});
111     for(var i in list) {
112         var doc = list[i];
113         if(!fieldDoc[doc.fm_class()])
114             fieldDoc[doc.fm_class()] = {};
115         fieldDoc[doc.fm_class()][doc.field()] = doc;
116     }
117
118     tbody = dojo.byId('uedit-tbody');
119
120     addrTemplateRows = dojo.query('tr[type=addr-template]', tbody);
121     dojo.forEach(addrTemplateRows, function(row) { row.parentNode.removeChild(row); } );
122     statCatTemplate = tbody.removeChild(dojo.byId('stat-cat-row-template'));
123     surveyTemplate = tbody.removeChild(dojo.byId('survey-row-template'));
124     surveyQuestionTemplate = tbody.removeChild(dojo.byId('survey-question-row-template'));
125
126     loadStaticFields();
127     if(patron.isnew() && patron.addresses().length == 0) 
128         uEditNewAddr(null, uEditAddrVirtId, true);
129     else loadAllAddrs();
130     loadStatCats();
131     loadSurveys();
132     checkClaimsReturnCountPerm();
133     checkClaimsNoCheckoutCountPerm();
134
135     dojo.connect(replaceBarcode, 'onClick', replaceCardHandler);
136     dojo.connect(allCards, 'onClick', drawAllCards);
137     if(patron.cards().length > 1)
138         dojo.removeClass(dojo.byId('uedit-all-barcodes'), 'hidden');
139
140     var input = findWidget('ac', 'barcode');
141     if (patron.isnew()) {
142         replaceBarcode.attr('disabled', true);
143     } else {
144         input.widget.attr('disabled', true).attr('readOnly', true);
145     }
146 }
147
148
149 function drawAllCards() {
150
151     var tbody = dojo.byId('uedit-all-cards-tbody');
152     if(!allCardsTemplate) {
153         allCardsTemplate = tbody.removeChild(dojo.byId('uedit-all-cards-tr-template'));
154     } else {
155         while(tbody.childNodes[0])
156             tbody.removeChild(tbody.childNodes[0]);
157     }
158
159     var first = true;
160     dojo.forEach(
161         [patron.card()].concat(patron.cards()), // grab the main card first
162         function(card) {
163             if(!first) {
164                 if(card.id() == patron.card().id())
165                     return;
166             }
167             var row = allCardsTemplate.cloneNode(true);
168             getByName(row, 'barcode').innerHTML = card.barcode();
169             getByName(row, 'active').appendChild(
170                 openils.Util.isTrue(card.active()) ? 
171                     dojo.byId('true').cloneNode(true) :
172                     dojo.byId('false').cloneNode(true)
173             ); 
174
175             tbody.appendChild(row);
176             first = false;
177         }
178     );
179
180     allCardsDialog.show();
181 }
182
183 /**
184  * Mark the current card inactive, create a new primary card
185  */
186 function replaceCardHandler() {
187     var input = findWidget('ac', 'barcode');
188     input.widget.attr('disabled', false).attr('readOnly', false).attr('value', null).focus();
189     replaceBarcode.attr('disabled', true);
190     
191     // pull old card off the cards list so we don't have a dupe sitting in there
192     var old = patron.cards().filter(function(c){return (c.id() == patron.card().id())})[0];
193     old.active('f');
194     old.ischanged(1);
195
196     var newc = new fieldmapper.ac();
197     newc.id(uEditCardVirtId--);
198     newc.isnew(1);
199     newc.active('t');
200     patron.card(newc);
201     var t = patron.cards();
202         if (!t) { t = []; }
203         t.push(newc);
204         patron.cards(t);
205 }
206
207
208 /**
209  * Loads a staged user and turns them into something the editor can understand
210  */
211 function uEditLoadStageUser(stageUname) {
212
213     var data = fieldmapper.standardRequest(
214         ['open-ils.actor', 'open-ils.actor.user.stage.retrieve.by_username'],
215         { params : [openils.User.authtoken, stageUname] }
216     );
217
218     stageUser = data.user;
219     patron = uEditNewPatron();
220
221     if(!stageUser) 
222         return patron;
223
224     // copy the data into our new user object
225     for(var key in fieldmapper.IDL.fmclasses.stgu.field_map) {
226         if(fieldmapper.IDL.fmclasses.au.field_map[key] && !fieldmapper.IDL.fmclasses.stgu.field_map[key].virtual) {
227             if(data.user[key]() !== null)
228                 patron[key]( data.user[key]() );
229         }
230     }
231
232     // copy the data into our new address objects
233     // TODO: uses the first mailing address only
234     if(data.mailing_addresses.length) {
235
236         var mail_addr = new fieldmapper.aua();
237         mail_addr.id(-1); // virtual ID
238         mail_addr.usr(-1);
239         mail_addr.isnew(1);
240         patron.mailing_address(mail_addr);
241         var t = patron.addresses();
242             if (!t) { t = []; }
243             t.push(mail_addr);
244             patron.addresses(t);
245
246         for(var key in fieldmapper.IDL.fmclasses.stgma.field_map) {
247             if(fieldmapper.IDL.fmclasses.aua.field_map[key] && !fieldmapper.IDL.fmclasses.stgma.field_map[key].virtual) {
248                 if(data.mailing_addresses[0][key]() !== null)
249                     mail_addr[key]( data.mailing_addresses[0][key]() );
250             }
251         }
252     }
253     
254     // copy the data into our new address objects
255     // TODO uses the first billing address only
256     if(data.billing_addresses.length) {
257
258         var bill_addr = new fieldmapper.aua();
259         bill_addr.id(-2); // virtual ID
260         bill_addr.usr(-1);
261         bill_addr.isnew(1);
262         patron.billing_address(bill_addr);
263         var t = patron.addresses();
264             if (!t) { t = []; }
265             t.push(bill_addr);
266             patron.addresses(t);
267
268         for(var key in fieldmapper.IDL.fmclasses.stgba.field_map) {
269             if(fieldmapper.IDL.fmclasses.aua.field_map[key] && !fieldmapper.IDL.fmclasses.stgba.field_map[key].virtual) {
270                 if(data.billing_addresses[0][key]() !== null)
271                     bill_addr[key]( data.billing_addresses[0][key]() );
272             }
273         }
274     }
275
276     // TODO: uses the first card only
277     if(data.cards.length) {
278         var card = new fieldmapper.ac();
279         card.id(-1); // virtual ID
280         patron.card().barcode(data.cards[0].barcode());
281     }
282
283     return patron;
284 }
285
286 /*
287  * clone the home org, phone numbers, and billing/mailing address
288  */
289 function uEditCopyCloneData(patron) {
290     cloneUserObj = uEditLoadUser(cloneUser);
291
292     var cloneFields = [
293         'home_ou', 
294         'day_phone', 
295         'evening_phone', 
296         'other_phone',
297         'usrgroup'
298     ];
299
300     if(!uEditCloneCopyAddr) 
301         cloneFields = cloneFields.concat(['mailing_address', 'billing_address']);
302
303     dojo.forEach(
304         cloneFields, 
305         function(field) {
306             patron[field](cloneUserObj[field]());
307         }
308     );
309
310     if(uEditCloneCopyAddr) {
311         var billAddr, mailAddr;
312
313         // copy the billing and mailing addresses into new addresses
314         function cloneAddr(addr) {
315             var newAddr = addr.clone();
316             newAddr.isnew(true);
317             newAddr.id(uEditAddrVirtId--);
318             newAddr.usr(patron.id());
319             patron.addresses().push(newAddr);
320             return newAddr;
321         }
322
323         if(billAddr = cloneUserObj.billing_address()) 
324             patron.billing_address(cloneAddr(billAddr));
325
326         if(mailAddr = cloneUserObj.mailing_address()) {
327             if (billAddr && billAddr.id() == mailAddr.id()) {
328                 patron.mailing_address(patron.billing_address());
329             } else {
330                 patron.mailing_address(cloneAddr(mailAddr));
331             }
332         }
333
334         if(!billAddr) // if there was no billing addr, use the mailing addr
335             patron.billing_address(patron.mailing_address());
336
337     } else {
338
339         // link the billing and mailing addresses
340         if(patron.billing_address()) {
341             var t = patron.addresses();
342                 if (!t) { t = []; }
343                 t.push(patron.billing_address());
344                 patron.addresses(t);
345         }
346
347         if(patron.mailing_address() && (
348                 patron.addresses().length == 0 || 
349                 patron.mailing_address().id() != patron.billing_address().id()) ) {
350             var t = patron.addresses();
351                 if (!t) { t = []; }
352                 t.push(patron.mailing_address());
353                 patron.addresses(t);
354         }
355     }
356 }
357
358
359 function uEditFetchUserSettings(userId) {
360     
361     var baseNode = fieldmapper.aou.findOrgUnit(staff.ws_ou());
362     var orgs = fieldmapper.aou.orgNodeTrail(baseNode);
363     orgs = orgs.map(function(node) { return node.id(); });
364
365     /* fetch any user setting types we need + any that offer opt-in */
366     userSettingTypes = pcrud.search('cust', {
367         '-or' : [
368             {name:['circ.holds_behind_desk']}, 
369             {name : {
370                 'in': {
371                     select : {atevdef : ['opt_in_setting']}, 
372                     from : 'atevdef',
373                     // we only care about opt-in settings for event_defs our users encounter
374                     where : {'+atevdef' : {owner : orgs}}
375                 }
376             }}
377         ]
378     });
379
380     var names = userSettingTypes.map(function(obj) { return obj.name() });
381
382     /* fetch any values set for this user */
383     userSettings = fieldmapper.standardRequest(
384         ['open-ils.actor', 'open-ils.actor.patron.settings.retrieve'],
385         {params : [openils.User.authtoken, userId, names]});
386 }
387
388
389 function uEditLoadUser(userId) {
390     var patron = fieldmapper.standardRequest(
391         ['open-ils.actor', 'open-ils.actor.user.fleshed.retrieve'],
392         {params : [openils.User.authtoken, userId]}
393     );
394     openils.Event.parse_and_raise(patron);
395     return patron;
396 }
397
398 function loadStaticFields() {
399     for(var idx = 0; tbody.childNodes[idx]; idx++) {
400         var row = tbody.childNodes[idx];
401         if(row.nodeType != row.ELEMENT_NODE) continue;
402         var fmcls = row.getAttribute('fmclass');
403         if(fmcls) {
404             fleshFMRow(row, fmcls);
405         } else {
406
407             if(row.id == 'uedit-settings-divider') {
408
409                 var template = tbody.removeChild(dojo.byId('uedit-user-setting-template'));
410                 dojo.forEach(userSettingTypes, function(type) { uEditDrawSettingRow(tbody, row, template, type); } );
411
412                 if(userSettingTypes.length > 1 || orgSettings['circ.holds.behind_desk_pickup_supported']) {
413                     openils.Util.show('uedit-settings-divider', 'table-row');
414                 }
415             }
416         }
417     }
418 }
419
420 function uEditDrawSettingRow(tbody, dividerRow, template, stype) {
421     var row = template.cloneNode(true);
422     row.setAttribute('user_setting', stype.name());
423     getByName(row, 'label').innerHTML = stype.label();
424     var cb = new dijit.form.CheckBox({scrollOnFocus:false}, getByName(row, 'widget'));
425     cb.attr('value', userSettings[stype.name()]);
426     dojo.connect(cb, 'onChange', function(newVal) { userSettingsToUpdate[stype.name()] = newVal; });
427     tbody.insertBefore(row, dividerRow.nextSibling);
428     openils.Util.show(row, 'table-row');
429 }
430
431 function uEditUpdateUserSettings(userId) {
432     return fieldmapper.standardRequest(
433         ['open-ils.actor', 'open-ils.actor.patron.settings.update'],
434         {params : [openils.User.authtoken, userId, userSettingsToUpdate]});
435 }
436
437 function loadAllAddrs() {
438     dojo.forEach(patron.addresses(),
439         function(addr) {
440             uEditNewAddr(null, addr.id());
441         }
442     );
443 }
444
445 function loadStatCats() {
446
447     statCats = fieldmapper.standardRequest(
448         ['open-ils.circ', 'open-ils.circ.stat_cat.actor.retrieve.all'],
449         {params : [openils.User.authtoken, staff.ws_ou()]}
450     );
451
452     // draw stat cats
453     for(var idx in statCats) {
454         var stat = statCats[idx];
455         var row = statCatTemplate.cloneNode(true);
456         row.id = 'stat-cat-row-' + idx;
457         tbody.appendChild(row);
458         getByName(row, 'name').innerHTML = stat.name();
459         var valtd = getByName(row, 'widget');
460         var span = valtd.appendChild(document.createElement('span'));
461         var store = new dojo.data.ItemFileReadStore(
462                 {data:fieldmapper.actsc.toStoreData(stat.entries())});
463         var comboBox = new dijit.form.ComboBox({store:store,scrollOnFocus:false}, span);
464         comboBox.labelAttr = 'value';
465         comboBox.searchAttr = 'value';
466
467         comboBox._wtype = 'statcat';
468         comboBox._statcat = stat.id();
469         widgetPile.push(comboBox); 
470
471         // populate existing cats
472         var map = patron.stat_cat_entries().filter(
473             function(mp) { return (mp.stat_cat() == stat.id()) })[0];
474         if(map) comboBox.attr('value', map.stat_cat_entry()); 
475
476     }
477 }
478
479 function loadSurveys() {
480
481     surveys = fieldmapper.standardRequest(
482         ['open-ils.circ', 'open-ils.circ.survey.retrieve.all'],
483         {params : [openils.User.authtoken]}
484     );
485
486     // draw surveys
487     for(var idx in surveys) {
488         var survey = surveys[idx];
489         var srow = surveyTemplate.cloneNode(true);
490         tbody.appendChild(srow);
491         getByName(srow, 'name').innerHTML = survey.name();
492
493         for(var q in survey.questions()) {
494             var quest = survey.questions()[q];
495             var qrow = surveyQuestionTemplate.cloneNode(true);
496             tbody.appendChild(qrow);
497             getByName(qrow, 'question').innerHTML = quest.question();
498
499             var span = getByName(qrow, 'answers').appendChild(document.createElement('span'));
500             var store = new dojo.data.ItemFileReadStore(
501                 {data:fieldmapper.asva.toStoreData(quest.answers())});
502             var select = new dijit.form.FilteringSelect({store:store,scrollOnFocus:false}, span);
503             if (! openils.Util.isTrue(survey.required())) {
504                 select.isValid = function() { return true; };
505             }
506             select.labelAttr = 'answer';
507             select.searchAttr = 'answer';
508
509             select._wtype = 'survey';
510             select._survey = survey.id();
511             select._question = quest.id();
512             widgetPile.push(select); 
513         }
514     }
515 }
516
517
518 function fleshFMRow(row, fmcls, args) {
519     var fmfield = row.getAttribute('fmfield');
520     var wclass = row.getAttribute('wclass');
521     var wstyle = row.getAttribute('wstyle');
522     var wconstraints = row.getAttribute('wconstraints');
523
524     var isPasswd2 = (fmfield == 'passwd2');
525     if(isPasswd2) fmfield = 'passwd';
526     var fieldIdl = fieldmapper.IDL.fmclasses[fmcls].field_map[fmfield];
527     if(!args) args = {};
528
529     var existing = dojo.query('td', row);
530     var htd = existing[0] || row.appendChild(document.createElement('td'));
531     var ltd = existing[1] || row.appendChild(document.createElement('td'));
532     var wtd = existing[2] || row.appendChild(document.createElement('td'));
533
534     openils.Util.addCSSClass(htd, 'uedit-help');
535     if(fieldDoc[fmcls] && fieldDoc[fmcls][fmfield]) {
536         var link = dojo.byId('uedit-help-template').cloneNode(true);
537         link.id = '';
538         link.onclick = function() { ueLoadContextHelp(fmcls, fmfield) };
539         openils.Util.removeCSSClass(link, 'hidden');
540         htd.appendChild(link);
541     }
542
543     if(!ltd.textContent) {
544         var span = document.createElement('span');
545         ltd.appendChild(document.createTextNode(fieldIdl.label));
546     }
547
548     span = document.createElement('span');
549     wtd.appendChild(span);
550
551     var fmObject = null;
552     var disabled = false;
553     switch(fmcls) {
554         case 'au' : fmObject = patron; break;
555         case 'ac' : fmObject = patron.card(); break;
556         case 'aua' : 
557             fmObject = patron.addresses().filter(
558                 function(i) { return (i.id() == args.addr) })[0];
559             if(fmObject && fmObject.usr() != patron.id())
560                 disabled = true;
561             break;
562     }
563
564     var required = row.getAttribute('required') == 'required';
565
566     // password data is not fetched/required/displayed for existing users
567     if(!patron.isnew() && 'passwd' == fmfield)
568         required = false;
569
570     var dijitArgs = {
571         style: wstyle, 
572         required : required,
573         constraints : (wconstraints) ? eval('('+wconstraints+')') : {}, // the ()'s prevent Invalid Label errors with eval
574         disabled : disabled
575     };
576
577     if(fmcls == 'au' && fmfield == 'passwd') {
578         if (orgSettings['global.password_regex']) {
579             dijitArgs.regExp = orgSettings['global.password_regex'];
580         }
581     }
582
583     // TODO RSN: Add Setting!
584     if(fmcls == 'au' && fmfield == 'dob')
585         dijitArgs.popupClass = "";
586
587     var value = row.getAttribute('wvalue');
588     if(value !== null)
589         dijitArgs.value = value;
590
591     var wargs = {
592         idlField : fieldIdl,
593         fmObject : fmObject,
594         fmClass : fmcls,
595         parentNode : span,
596         widgetClass : wclass,
597         dijitArgs : dijitArgs,
598         orgDefaultsToWs : true,
599         orgLimitPerms : ['UPDATE_USER'],
600     };
601
602     if(fmfield == 'profile') {
603         // fetch profile groups non-async so existing expire_date is
604         // not overwritten when the profile groups arrive and update
605         wargs.forceSync = true;
606         wargs.disableQuery = {usergroup : 'f'};
607     } else {
608         wargs.forceSync = false;
609     }
610
611     var widget = new openils.widget.AutoFieldWidget(wargs);
612     widget.build();
613
614     // now put it back before we register the widget
615     if(isPasswd2) fmfield = 'passwd2';
616
617     widget._wtype = fmcls;
618     widget._fmfield = fmfield;
619     widget._addr = args.addr;
620     widgetPile.push(widget);
621     attachWidgetEvents(fmcls, fmfield, widget);
622     return widget;
623 }
624
625 function findWidget(wtype, fmfield, callback) {
626     return widgetPile.filter(
627         function(i){
628             if(i._wtype == wtype && i._fmfield == fmfield) {
629                 if(callback) return callback(i);
630                 return true;
631             }
632         }
633     ).pop();
634 }
635
636 /**
637  * if the user does not have the UPDATE_PATRON_CLAIM_RETURN_COUNT, 
638  * they are not allowed to directly alter the claim return count. 
639  * This function checks the perm and disable/enables the widget.
640  */
641 function checkClaimsReturnCountPerm() {
642     new openils.User().getPermOrgList(
643         'UPDATE_PATRON_CLAIM_RETURN_COUNT',
644         function(orgList) { 
645             var cr = findWidget('au', 'claims_returned_count');
646             if(orgList.indexOf(patron.home_ou()) == -1) 
647                 cr.widget.attr('disabled', true);
648             else
649                 cr.widget.attr('disabled', false);
650         },
651         true, 
652         true
653     );
654 }
655
656
657 function checkClaimsNoCheckoutCountPerm() {
658     new openils.User().getPermOrgList(
659         'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
660         function(orgList) { 
661             var cr = findWidget('au', 'claims_never_checked_out_count');
662             if(orgList.indexOf(patron.home_ou()) == -1) 
663                 cr.widget.attr('disabled', true);
664             else
665                 cr.widget.attr('disabled', false);
666         },
667         true, 
668         true
669     );
670 }
671
672
673 function attachWidgetEvents(fmcls, fmfield, widget) {
674
675     if(fmcls == 'ac') {
676         if(fmfield == 'barcode') {
677             dojo.connect(widget.widget, 'onChange',
678                 function() {
679                     var barcode = this.attr('value');
680                     dupeBarcode = false;
681                     dojo.addClass(dojo.byId('uedit-dupe-barcode-warning'), 'hidden');
682                     fieldmapper.standardRequest(
683                         ['open-ils.actor', 'open-ils.actor.barcode.exists'],
684                         {
685                             params: [openils.User.authtoken, barcode],
686                             oncomplete : function(r) {
687                                 var res = openils.Util.readResponse(r);
688                                 if(res == '1') {
689                                     dupeBarcode = true;
690                                     dojo.removeClass(dojo.byId('uedit-dupe-barcode-warning'), 'hidden');
691                                 } else {
692                                     dupeBarcode = false;
693                                     dojo.addClass(dojo.byId('uedit-dupe-barcode-warning'), 'hidden');
694                                     var un = findWidget('au', 'usrname');
695                                     if(!un.widget.attr('value'))
696                                         un.widget.attr('value', barcode);
697                                 }
698                             }
699                         }
700                     );
701                 }
702             );
703             return;
704         }
705     }
706
707     if(fmcls == 'au') {
708         switch(fmfield) {
709
710             case 'usrname':
711                 dojo.connect(widget.widget, 'onChange', 
712                     function() {
713                         var input = findWidget('au', 'usrname');
714                         var usrname = input.widget.attr('value');
715
716                         if(!usrname) {
717                             dupeUsrname = false;
718                             dojo.addClass(dojo.byId('uedit-dupe-username-warning'), 'hidden');
719                             return;
720                         }
721
722                         fieldmapper.standardRequest(
723                             ['open-ils.actor', 'open-ils.actor.username.exists'],
724                             {
725                                 params: [openils.User.authtoken, usrname],
726                                 oncomplete : function(r) {
727                                     var res = openils.Util.readResponse(r);
728                                     if(res) {
729                                         dupeUsrname = true;
730                                         dojo.removeClass(dojo.byId('uedit-dupe-username-warning'), 'hidden');
731                                     } else {
732                                         dupeUsrname = false;
733                                         dojo.addClass(dojo.byId('uedit-dupe-username-warning'), 'hidden');
734                                     }
735                                 }
736                             }
737                         );
738                     }   
739                 );
740
741                 return;
742
743             case 'profile': // when the profile changes, update the expire date
744                 dojo.connect(widget.widget, 'onChange', 
745                     function() {
746                         var self = this;
747                         var expireWidget = findWidget('au', 'expire_date');
748                         function found(items) {
749                             if(items.length == 0) return;
750                             var item = items[0];
751                             var interval = self.store.getValue(item, 'perm_interval');
752                             expireWidget.widget.attr('value', dojo.date.add(new Date(), 
753                                 'second', openils.Util.intervalToSeconds(interval)));
754                         }
755                         this.store.fetch({onComplete:found, query:{id:this.attr('value')}});
756                     }
757                 );
758                 return;
759
760             case 'dob':
761                 dojo.connect(widget.widget, 'onChange',
762                     function(newDob) {
763                         if(!newDob) return;
764                         var oldDob = patron.dob();
765                         if(dojo.date.stamp.fromISOString(oldDob) == newDob) return;
766
767                         var juvInterval = orgSettings['global.juvenile_age_threshold'] || '18 years';
768                         var juvWidget = findWidget('au', 'juvenile');
769                         var base = new Date();
770                         base.setTime(base.getTime() - Number(openils.Util.intervalToSeconds(juvInterval) + '000'));
771
772                         if(newDob <= base) // older than global.juvenile_age_threshold
773                             juvWidget.widget.attr('value', false);
774                         else
775                             juvWidget.widget.attr('value', true);
776                     }
777                 );
778                 return;
779
780             case 'first_given_name':
781             case 'family_name':
782                 dojo.connect(widget.widget, 'onChange',
783                     function(newVal) { uEditDupeSearch('name', newVal); });
784                 return;
785
786             case 'email':
787                 dojo.connect(widget.widget, 'onChange',
788                     function(newVal) { uEditDupeSearch('email', newVal); });
789                 return;
790
791             case 'ident_value':
792             case 'ident_value2':
793                 dojo.connect(widget.widget, 'onChange',
794                     function(newVal) { uEditDupeSearch('ident', newVal); });
795                 return;
796
797             case 'day_phone':
798                 // if configured, use the last for digits of the day phone number as the password
799                 if(uEditUsePhonePw && patron.isnew()) {
800                     dojo.connect(widget.widget, 'onChange',
801                         function(newVal) {
802                             if(newVal && newVal.length >= 4) {
803                                 var pw1 = findWidget('au', 'passwd').widget;
804                                 var pw2 = findWidget('au', 'passwd2').widget;
805                                 pw1.attr('value', newVal.substring(newVal.length - 4));
806                                 pw2.attr('value', newVal.substring(newVal.length - 4));
807                             }
808                         }
809                     );
810                 }
811             case 'evening_phone':
812             case 'other_phone':
813                 dojo.connect(widget.widget, 'onChange',
814                     function(newVal) { uEditDupeSearch('phone', newVal); });
815                 return;
816
817             case 'home_ou':
818                 dojo.connect(widget.widget, 'onChange',
819                     function(newVal) { 
820                         checkClaimsReturnCountPerm(); 
821                         checkClaimsNoCheckoutCountPerm();
822                     }
823                 );
824                 return;
825
826             case 'passwd':
827                 dojo.connect(widget.widget, 'onChange',
828                     function(newVal) {
829                         var pw1 = findWidget('au', 'passwd').widget;
830                         var pw2 = findWidget('au', 'passwd2').widget;
831                         var preserved_value = pw2.attr('value');
832                         // Ensure that the pw2 field match the pw1 field to validate
833                         pw2.regExp = newVal.replace(/([.\\^$*+?\(\)\[\]\{\}])/g, '\\$1');
834                         pw2.reset();
835                         pw2.attr('value',preserved_value);
836                     });
837                 return;
838         }
839     }
840
841     if(fmclass = 'aua') {
842         switch(fmfield) {
843             case 'post_code':
844                 dojo.connect(widget.widget, 'onChange',
845                     function(e) { 
846                         fieldmapper.standardRequest(
847                             ['open-ils.search', 'open-ils.search.zip'],
848                             {   async: true,
849                                 params: [e],
850                                 oncomplete : function(r) {
851                                     var res = openils.Util.readResponse(r);
852                                     if(!res) return;
853                                     var callback = function(w) { return w._addr == widget._addr; };
854                                     if(res.city) findWidget('aua', 'city', callback).widget.attr('value', res.city);
855                                     if(res.state) findWidget('aua', 'state', callback).widget.attr('value', res.state);
856                                     if(res.county) findWidget('aua', 'county', callback).widget.attr('value', res.county);
857                                     if(res.alert) alert(res.alert);
858                                 }
859                             }
860                         );
861                     }
862                 );
863                 return;
864
865             case 'street1':
866             case 'street2':
867             case 'city':
868                 dojo.connect(widget.widget, 'onChange',
869                     function(e) {
870                         var callback = function(w) { return w._addr == widget._addr; };
871                         var args = {
872                             street1 : findWidget('aua', 'street1', callback).widget.attr('value'),
873                             street2 : findWidget('aua', 'street2', callback).widget.attr('value'),
874                             city : findWidget('aua', 'city', callback).widget.attr('value'),
875                             post_code : findWidget('aua', 'post_code', callback).widget.attr('value')
876                         };
877                         if(args.street1 && args.city && args.post_code)
878                             uEditDupeSearch('address', args); 
879                     }
880                 );
881                 return;
882         }
883     }
884 }
885
886 function uEditDupeSearch(type, value) {
887     if(!value) return;
888     var search;
889     switch(type) {
890
891         case 'name':
892             openils.Util.hide('uedit-dupe-names-link');
893             var fname = findWidget('au', 'first_given_name').widget.attr('value');
894             var lname = findWidget('au', 'family_name').widget.attr('value');
895             if( !(fname && lname) ) return;
896             search = {
897                 first_given_name : {value : fname, group : 0},
898                 family_name : {value : lname, group : 0},
899             };
900             break;
901
902         case 'email':
903             openils.Util.hide('uedit-dupe-email-link');
904             search = {email : {value : value, group : 0}};
905             break;
906
907         case 'ident':
908             openils.Util.hide('uedit-dupe-ident-link');
909             search = {ident : {value : value, group : 2}};
910             break;
911
912         case 'phone':
913             openils.Util.hide('uedit-dupe-phone-link');
914             search = {phone : {value : value, group : 2}};
915             break;
916
917         case 'address':
918             openils.Util.hide('uedit-dupe-address-link');
919             search = {};
920             dojo.forEach(['street1', 'street2', 'city', 'post_code'],
921                 function(field) {
922                     if(value[field])
923                         search[field] = {value : value[field], group: 1};
924                 }
925             );
926             break;
927     }
928
929     // find possible duplicate patrons
930     fieldmapper.standardRequest(
931         ['open-ils.actor', 'open-ils.actor.patron.search.advanced'],
932         {   async: true,
933             params: [openils.User.authtoken, search],
934             oncomplete : function(r) {
935                 var resp = openils.Util.readResponse(r);
936                 resp = resp.filter(function(id) { return (id != patron.id()); });
937
938                 if(resp && resp.length > 0) {
939
940                     openils.Util.hide('uedit-help-div');
941                     openils.Util.show('uedit-dupe-div');
942                     var link;
943
944                     switch(type) {
945                         case 'name':
946                             link = dojo.byId('uedit-dupe-names-link');
947                             link.innerHTML = dojo.string.substitute(localeStrings.DUPE_PATRON_NAME, [resp.length]);
948                             break;
949                         case 'email':
950                             link = dojo.byId('uedit-dupe-email-link');
951                             link.innerHTML = dojo.string.substitute(localeStrings.DUPE_PATRON_EMAIL, [resp.length]);
952                             break;
953                         case 'ident':
954                             link = dojo.byId('uedit-dupe-ident-link');
955                             link.innerHTML = dojo.string.substitute(localeStrings.DUPE_PATRON_IDENT, [resp.length]);
956                             break;
957                         case 'phone':
958                             link = dojo.byId('uedit-dupe-phone-link');
959                             link.innerHTML = dojo.string.substitute(localeStrings.DUPE_PATRON_PHONE, [resp.length]);
960                             break;
961                         case 'address':
962                             link = dojo.byId('uedit-dupe-address-link');
963                             link.innerHTML = dojo.string.substitute(localeStrings.DUPE_PATRON_ADDR, [resp.length]);
964                             break;
965                     }
966
967                     openils.Util.show(link);
968                     link.onclick = function() {
969                         search.search_sort = js2JSON(["penalties", "family_name", "first_given_name"]);
970                         if(window.xulG)
971                             window.xulG.spawn_search(search);
972                         else
973                             console.log("running XUL patron search " + js2JSON(search));
974                     }
975                 }
976             }
977         }
978     );
979 }
980
981 function getByName(node, name) {
982     return dojo.query('[name='+name+']', node)[0];
983 }
984
985
986 function ueLoadContextHelp(fmcls, fmfield) {
987     openils.Util.hide('uedit-dupe-div');
988     openils.Util.show('uedit-help-div');
989     dojo.byId('uedit-help-field').innerHTML = fieldmapper.IDL.fmclasses[fmcls].field_map[fmfield].label;
990     dojo.byId('uedit-help-text').innerHTML = fieldDoc[fmcls][fmfield].string();
991 }
992
993
994 /* creates a new patron object with card attached */
995 function uEditNewPatron() {
996     patron = new au();
997     patron.isnew(1);
998     patron.id(-1);
999     card = new ac();
1000     card.id(uEditCardVirtId--);
1001     card.isnew(1);
1002     patron.active(1);
1003     patron.card(card);
1004     patron.cards([card]);
1005     patron.net_access_level(orgSettings['ui.patron.default_inet_access_level'] || 1);
1006     patron.stat_cat_entries([]);
1007     patron.survey_responses([]);
1008     patron.addresses([]);
1009     uEditMakeRandomPw(patron);
1010     return patron;
1011 }
1012
1013 function uEditMakeRandomPw(patron) {
1014     if(uEditUsePhonePw) return;
1015     var rand  = Math.random();
1016     rand = parseInt(rand * 10000) + '';
1017     while(rand.length < 4) rand += '0';
1018 /*
1019     appendClear($('ue_password_plain'),text(rand));
1020     unHideMe($('ue_password_gen'));
1021 */
1022     patron.passwd(rand);
1023     return rand;
1024 }
1025
1026 function uEditWidgetVal(w) {
1027     var val = (w.getFormattedValue) ? w.getFormattedValue() : w.attr('value');
1028     if(val === '') val = null;
1029     return val;
1030 }
1031
1032 function uEditSave() { _uEditSave(); }
1033 function uEditSaveClone() { _uEditSave(true); }
1034
1035 function _uEditSave(doClone) {
1036
1037     if ( (! myForm.isValid()) || dupeUsrname || dupeBarcode ) {
1038         alert(localeStrings.INVALID_FORM);
1039         return;
1040     }
1041
1042     for(var idx in widgetPile) {
1043         var w = widgetPile[idx];
1044         var val = uEditWidgetVal(w);
1045
1046         switch(w._wtype) {
1047             case 'au':
1048                 if(w._fmfield != 'passwd2')
1049                     patron[w._fmfield](val);
1050                 break;
1051
1052             case 'ac':
1053                 patron.card()[w._fmfield](val);
1054                 break;
1055
1056             case 'aua':
1057                 var addr = patron.addresses().filter(function(i){return (i.id() == w._addr)})[0];
1058                 if(!addr) {
1059                     addr = new fieldmapper.aua();
1060                     addr.id(w._addr);
1061                     addr.isnew(1);
1062                     addr.usr(patron.id());
1063                     var t = patron.addresses();
1064                         if (!t) { t = []; }
1065                         t.push(addr);
1066                         patron.addresses(t);
1067                 } else {
1068                     if(addr[w._fmfield]() != val)
1069                         addr.ischanged(1);
1070                 }
1071                 addr[w._fmfield](val);
1072
1073                 if(dojo.byId('uedit-billing-address-' + addr.id()).checked) 
1074                     patron.billing_address(addr.id());
1075
1076                 if(dojo.byId('uedit-mailing-address-' + addr.id()).checked)
1077                     patron.mailing_address(addr.id());
1078
1079                 break;
1080
1081             case 'survey':
1082                 if(val == null) break;
1083                 var resp = new fieldmapper.asvr();
1084                 resp.isnew(1);
1085                 resp.survey(w._survey)
1086                 resp.usr(patron.id());
1087                 resp.question(w._question)
1088                 resp.answer(val);
1089                 var t = patron.survey_responses();
1090                     if (!t) { t = []; }
1091                     t.push(resp);
1092                     patron.survey_responses(t);
1093                 break;
1094
1095             case 'statcat':
1096                 if(val == null) break;
1097
1098                 var map = patron.stat_cat_entries().filter(
1099                     function(m){
1100                         return (m.stat_cat() == w._statcat) })[0];
1101
1102                 if(map) {
1103                     if(map.stat_cat_entry() == val) 
1104                         break;
1105                     map.ischanged(1);
1106                 } else {
1107                     map = new fieldmapper.actscecm();
1108                     map.isnew(1);
1109                 }
1110
1111                 map.stat_cat(w._statcat);
1112                 map.stat_cat_entry(val);
1113                 map.target_usr(patron.id());
1114                 var t = patron.stat_cat_entries();
1115                     if (!t) { t = []; }
1116                     t.push(map);
1117                     patron.stat_cat_entries(t);
1118                 break;
1119         }
1120     }
1121
1122     patron.ischanged(1);
1123     fieldmapper.standardRequest(
1124         ['open-ils.actor', 'open-ils.actor.patron.update'],
1125         {   async: true,
1126             params: [openils.User.authtoken, patron],
1127             oncomplete: function(r) {
1128                 newPatron = openils.Util.readResponse(r);
1129                 if(newPatron) {
1130                     uEditUpdateUserSettings(newPatron.id());
1131                     if(stageUser) uEditRemoveStage();
1132                     uEditFinishSave(newPatron, doClone);
1133                 }
1134             }
1135         }
1136     );
1137 }
1138
1139 function uEditRemoveStage() {
1140     var resp = fieldmapper.standardRequest(
1141         ['open-ils.actor', 'open-ils.actor.user.stage.delete'],
1142         { params : [openils.User.authtoken, stageUser.row_id()] }
1143     )
1144 }
1145
1146 function uEditFinishSave(newPatron, doClone) {
1147
1148     if(doClone && cloneUser == null)
1149         cloneUser = newPatron.id();
1150
1151         if( doClone ) {
1152
1153                 if(xulG && typeof xulG.spawn_editor == 'function' && !patron.isnew() ) {
1154             window.xulG.spawn_editor({ses:openils.User.authtoken,clone:cloneUser});
1155             uEditRefresh();
1156
1157                 } else {
1158                         location.href = location.href.replace(/\?.*/, '') + '?clone=' + cloneUser;
1159                 }
1160
1161         } else {
1162
1163                 uEditRefresh();
1164         }
1165
1166         uEditRefreshXUL(newPatron);
1167 }
1168
1169 function uEditRefresh() {
1170     var usr = cgi.param('usr');
1171     var href = location.href.replace(/\?.*/, '');
1172     href += ((usr) ? '?usr=' + usr : '');
1173     location.href = href;
1174 }
1175
1176 function uEditRefreshXUL(newuser) {
1177         if (window.xulG && typeof window.xulG.on_save == 'function') 
1178                 window.xulG.on_save(newuser);
1179 }
1180
1181
1182 /**
1183  * Create a new address and insert it into the DOM
1184  * @param evt ignored
1185  * @param id The address id
1186  * @param mkLinks If true, set the new address as the 
1187  *  mailing/billing address for the user
1188  */
1189 function uEditNewAddr(evt, id, mkLinks) {
1190
1191     if(id == null) 
1192         id = --uEditAddrVirtId; // new address
1193
1194     var addr =  patron.addresses().filter(
1195         function(i) { return (i.id() == id) })[0];
1196
1197     dojo.forEach(addrTemplateRows, 
1198         function(row) {
1199
1200             row = tbody.insertBefore(row.cloneNode(true), dojo.byId('new-addr-row'));
1201             row.setAttribute('type', '');
1202             row.setAttribute('addr', id+'');
1203
1204             if(row.getAttribute('fmclass')) {
1205                 var widget = fleshFMRow(row, 'aua', {addr:id});
1206
1207                 // make new addresses valid by default
1208                 if(id < 0 && row.getAttribute('fmfield') == 'valid') 
1209                     widget.widget.attr('value', true); 
1210
1211             } else if(row.getAttribute('name') == 'uedit-addr-pending-row') {
1212
1213                 // if it's a pending address, show the 'approve' button
1214                 if(addr && openils.Util.isTrue(addr.pending())) {
1215                     openils.Util.show(row, 'table-row');
1216                     dojo.query('[name=approve-button]', row)[0].onclick = 
1217                         function() { uEditApproveAddress(addr); };
1218
1219                     if(addr.replaces()) {
1220                         var div = dojo.query('[name=replaced-addr]', row)[0]
1221                         var replaced =  patron.addresses().filter(
1222                             function(i) { return (i.id() == addr.replaces()) })[0];
1223
1224                         div.innerHTML = dojo.string.substitute(localeStrings.REPLACED_ADDRESS, [
1225                             replaced.address_type() || '',
1226                             replaced.street1() || '',
1227                             replaced.street2() || '',
1228                             replaced.city() || '',
1229                             replaced.state() || '',
1230                             replaced.post_code() || ''
1231                         ]);
1232
1233                     } else {
1234                         openils.Util.hide(dojo.query('[name=replaced-addr-div]', row)[0]);
1235                     }
1236                 }
1237
1238             } else if(row.getAttribute('name') == 'uedit-addr-owner-row') {
1239                 // address is owned by someone else.  provide option to load the
1240                 // user in a different tab
1241                 
1242                 if(addr && addr.usr() != patron.id()) {
1243                     openils.Util.show(row, 'table-row');
1244                     var link = getByName(row, 'addr-owner');
1245
1246                     // fetch the linked user so we can present their name in the UI
1247                     var addrUser;
1248                     if(cloneUserObj && cloneUserObj.id() == addr.usr()) {
1249                         addrUser = [
1250                             cloneUserObj.first_given_name(), 
1251                             cloneUserObj.second_given_name(), 
1252                             cloneUserObj.family_name()
1253                         ];
1254                     } else {
1255                         addrUser = fieldmapper.standardRequest(
1256                             ['open-ils.actor', 'open-ils.actor.user.retrieve.parts'],
1257                             {params: [
1258                                 openils.User.authtoken, 
1259                                 addr.usr(), 
1260                                 ['first_given_name', 'second_given_name', 'family_name']
1261                             ]}
1262                         );
1263                     }
1264
1265                     link.innerHTML = (addrUser.map(function(name) { return (name) ? name+' ' : '' })+'').replace(/,/g,''); // TODO i18n
1266                     link.onclick = function() {
1267                         if(openils.XUL.isXUL()) { 
1268                             window.xulG.spawn_editor({ses:openils.User.authtoken, usr:addr.usr()})
1269                         } else {
1270                             parent.location.href = location.href.replace(/clone=\d+/, 'usr=' + addr.usr());
1271                         }
1272                     }
1273                 }
1274
1275             } else if(row.getAttribute('name') == 'uedit-addr-divider') {
1276                 // link up the billing/mailing address and give the inputs IDs so we can acces the later
1277                 
1278                 // billing address
1279                 var ba = getByName(row, 'billing_address');
1280                 ba.id = 'uedit-billing-address-' + id;
1281                 if(mkLinks || (patron.billing_address() && patron.billing_address().id() == id))
1282                     ba.checked = true;
1283
1284                 // mailing address
1285                 var ma = getByName(row, 'mailing_address');
1286                 ma.id = 'uedit-mailing-address-' + id;
1287                 if(mkLinks || (patron.mailing_address() && patron.mailing_address().id() == id))
1288                     ma.checked = true;
1289
1290                 var btn = dojo.query('[name=delete-button]', row)[0];
1291                 if(btn) btn.onclick = function(){ uEditDeleteAddr(id) };
1292             }
1293         }
1294     );
1295 }
1296
1297 function uEditApproveAddress(addr) {
1298     fieldmapper.standardRequest(
1299         ['open-ils.actor', 'open-ils.actor.user.pending_address.approve'],
1300         {   async: true,
1301             params:  [openils.User.authtoken, addr],
1302
1303             oncomplete : function(r) {
1304                 var oldId = openils.Util.readResponse(r);
1305                     
1306                 // remove addrs from UI
1307                 dojo.forEach(
1308                     patron.addresses(), 
1309                     function(addr) { uEditDeleteAddr(addr.id(), true); }
1310                 );
1311
1312                 if(oldId != null) {
1313                     
1314                     // remove the replaced address 
1315                     if(oldId != addr.id()) {
1316                                 patron.addresses(
1317                             patron.addresses().filter(
1318                                                 function(i) { return (i.id() != oldId); }
1319                                         )
1320                                 );
1321                     }
1322                     
1323                     // fix the the new address
1324                     addr.id(oldId);
1325                     addr.replaces(null);
1326                     addr.pending('f');
1327
1328                 }
1329
1330                 // redraw addrs
1331                 loadAllAddrs();
1332             }
1333         }
1334     );
1335 }
1336
1337
1338 function uEditDeleteAddr(id, noAlert) {
1339     if(!noAlert) {
1340         if(!confirm('Delete address ' + id)) return; /* XXX i18n */
1341     }
1342     var addr = patron.addresses().filter(function(i){return (i.id() == id)})[0];
1343     if (addr) { addr.isdeleted(1); }
1344     var m_a = patron.mailing_address();
1345         if (typeof m_a == 'object' && m_a != null) { m_a = m_a.id(); }
1346         if (m_a == id) { patron.mailing_address(null); }
1347     var b_a = patron.billing_address();
1348         if (typeof b_a == 'object' && b_a != null) { b_a = b_a.id(); }
1349         if (b_a == id) { patron.billing_address(null); }
1350
1351     var rows = dojo.query('tr[addr='+id+']', tbody);
1352     for(var i = 0; i < rows.length; i++)
1353         rows[i].parentNode.removeChild(rows[i]);
1354     widgetPile = widgetPile.filter(function(w){return (w._addr != id)});
1355 }
1356
1357 function uEditToggleRequired() {
1358     if((tbody.className +'').match(/hide-non-required/)) 
1359         openils.Util.removeCSSClass(tbody, 'hide-non-required');
1360     else
1361         openils.Util.addCSSClass(tbody, 'hide-non-required');
1362     openils.Util.toggle('uedit-show-required');
1363     openils.Util.toggle('uedit-show-all');
1364 }
1365
1366 function printable_output() {
1367     var temp; var s = '=-=-=-=\r\n';
1368     for (var idx in widgetPile) {
1369         var w = widgetPile[idx];
1370         var val = uEditWidgetVal(w);
1371         var label;
1372         if (typeof w.idlField == 'undefined') {
1373             label = w._wtype;
1374             if (w._wtype == 'statcat') {
1375                 var stat = statCats.filter(
1376                     function(m){
1377                         return (m.id() == w._statcat) })[0];
1378                 label = stat.name();
1379             } else if (w._wtype == 'survey') {
1380                 var survey = surveys.filter(
1381                     function(m){
1382                         return (m.id() == w._survey) })[0];
1383                 var question = survey.questions().filter(
1384                     function(m){
1385                         return (m.id() == w._question) })[0];
1386                 label = survey.name() + ' : ' + question.question();
1387             } else {
1388                 label = 'FIXME';
1389             }
1390         } else {
1391             label = w.idlField.label;
1392         }
1393         if (temp != w._wtype) {
1394             temp = w._wtype;
1395             s += '-------\r\n';
1396         }
1397         s += label + ':\t' + (typeof val == 'object' ? '' : val) + '\r\n';
1398     }
1399     s += '=-=-=-=\r\n';
1400     return s;
1401 }
1402
1403 openils.Util.addOnLoad(load);