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