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