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