]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/actor/user/register.js
disable non-usergroup profiles in user registration profile group selector
[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 card 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     var wargs = {
553         idlField : fieldIdl,
554         fmObject : fmObject,
555         fmClass : fmcls,
556         parentNode : span,
557         widgetClass : wclass,
558         dijitArgs : dijitArgs,
559         orgDefaultsToWs : true,
560         orgLimitPerms : ['UPDATE_USER'],
561     };
562
563     if(fmfield == 'profile') {
564         // fetch profile groups non-async so existing expire_date is
565         // not overwritten when the profile groups arrive and update
566         wargs.forceSync = true;
567         wargs.disableQuery = {usergroup : 'f'};
568     } else {
569         wargs.forceSync = false;
570     }
571
572     var widget = new openils.widget.AutoFieldWidget(wargs);
573     widget.build();
574
575     // now put it back before we register the widget
576     if(isPasswd2) fmfield = 'passwd2';
577
578     widget._wtype = fmcls;
579     widget._fmfield = fmfield;
580     widget._addr = args.addr;
581     widgetPile.push(widget);
582     attachWidgetEvents(fmcls, fmfield, widget);
583     return widget;
584 }
585
586 function findWidget(wtype, fmfield, callback) {
587     return widgetPile.filter(
588         function(i){
589             if(i._wtype == wtype && i._fmfield == fmfield) {
590                 if(callback) return callback(i);
591                 return true;
592             }
593         }
594     ).pop();
595 }
596
597 /**
598  * if the user does not have the UPDATE_PATRON_CLAIM_RETURN_COUNT, 
599  * they are not allowed to directly alter the claim return count. 
600  * This function checks the perm and disable/enables the widget.
601  */
602 function checkClaimsReturnCountPerm() {
603     new openils.User().getPermOrgList(
604         'UPDATE_PATRON_CLAIM_RETURN_COUNT',
605         function(orgList) { 
606             var cr = findWidget('au', 'claims_returned_count');
607             if(orgList.indexOf(patron.home_ou()) == -1) 
608                 cr.widget.attr('disabled', true);
609             else
610                 cr.widget.attr('disabled', false);
611         },
612         true, 
613         true
614     );
615 }
616
617
618 function checkClaimsNoCheckoutCountPerm() {
619     new openils.User().getPermOrgList(
620         'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
621         function(orgList) { 
622             var cr = findWidget('au', 'claims_never_checked_out_count');
623             if(orgList.indexOf(patron.home_ou()) == -1) 
624                 cr.widget.attr('disabled', true);
625             else
626                 cr.widget.attr('disabled', false);
627         },
628         true, 
629         true
630     );
631 }
632
633
634 function attachWidgetEvents(fmcls, fmfield, widget) {
635
636     if(fmcls == 'ac') {
637         if(fmfield == 'barcode') {
638             dojo.connect(widget.widget, 'onChange',
639                 function() {
640                     var barcode = this.attr('value');
641                     dupeBarcode = false;
642                     dojo.addClass(dojo.byId('uedit-dupe-barcode-warning'), 'hidden');
643                     fieldmapper.standardRequest(
644                         ['open-ils.actor', 'open-ils.actor.barcode.exists'],
645                         {
646                             params: [openils.User.authtoken, barcode],
647                             oncomplete : function(r) {
648                                 var res = openils.Util.readResponse(r);
649                                 if(res == '1') {
650                                     dupeBarcode = true;
651                                     dojo.removeClass(dojo.byId('uedit-dupe-barcode-warning'), 'hidden');
652                                 } else {
653                                     dupeBarcode = false;
654                                     dojo.addClass(dojo.byId('uedit-dupe-barcode-warning'), 'hidden');
655                                     var un = findWidget('au', 'usrname');
656                                     if(!un.widget.attr('value'))
657                                         un.widget.attr('value', barcode);
658                                 }
659                             }
660                         }
661                     );
662                 }
663             );
664             return;
665         }
666     }
667
668     if(fmcls == 'au') {
669         switch(fmfield) {
670
671             case 'usrname':
672                 dojo.connect(widget.widget, 'onChange', 
673                     function() {
674                         var input = findWidget('au', 'usrname');
675                         var usrname = input.widget.attr('value');
676
677                         if(!usrname) {
678                             dupeUsrname = false;
679                             dojo.addClass(dojo.byId('uedit-dupe-username-warning'), 'hidden');
680                             return;
681                         }
682
683                         fieldmapper.standardRequest(
684                             ['open-ils.actor', 'open-ils.actor.username.exists'],
685                             {
686                                 params: [openils.User.authtoken, usrname],
687                                 oncomplete : function(r) {
688                                     var res = openils.Util.readResponse(r);
689                                     if(res) {
690                                         dupeUsrname = true;
691                                         dojo.removeClass(dojo.byId('uedit-dupe-username-warning'), 'hidden');
692                                     } else {
693                                         dupeUsrname = false;
694                                         dojo.addClass(dojo.byId('uedit-dupe-username-warning'), 'hidden');
695                                     }
696                                 }
697                             }
698                         );
699                     }   
700                 );
701
702                 return;
703
704             case 'profile': // when the profile changes, update the expire date
705                 dojo.connect(widget.widget, 'onChange', 
706                     function() {
707                         var self = this;
708                         var expireWidget = findWidget('au', 'expire_date');
709                         function found(items) {
710                             if(items.length == 0) return;
711                             var item = items[0];
712                             var interval = self.store.getValue(item, 'perm_interval');
713                             expireWidget.widget.attr('value', dojo.date.add(new Date(), 
714                                 'second', openils.Util.intervalToSeconds(interval)));
715                         }
716                         this.store.fetch({onComplete:found, query:{id:this.attr('value')}});
717                     }
718                 );
719                 return;
720
721             case 'dob':
722                 dojo.connect(widget.widget, 'onChange',
723                     function(newDob) {
724                         if(!newDob) return;
725                         var oldDob = patron.dob();
726                         if(dojo.date.stamp.fromISOString(oldDob) == newDob) return;
727
728                         var juvInterval = orgSettings['global.juvenile_age_threshold'] || '18 years';
729                         var juvWidget = findWidget('au', 'juvenile');
730                         var base = new Date();
731                         base.setTime(base.getTime() - Number(openils.Util.intervalToSeconds(juvInterval) + '000'));
732
733                         if(newDob <= base) // older than global.juvenile_age_threshold
734                             juvWidget.widget.attr('value', false);
735                         else
736                             juvWidget.widget.attr('value', true);
737                     }
738                 );
739                 return;
740
741             case 'first_given_name':
742             case 'family_name':
743                 dojo.connect(widget.widget, 'onChange',
744                     function(newVal) { uEditDupeSearch('name', newVal); });
745                 return;
746
747             case 'email':
748                 dojo.connect(widget.widget, 'onChange',
749                     function(newVal) { uEditDupeSearch('email', newVal); });
750                 return;
751
752             case 'ident_value':
753             case 'ident_value2':
754                 dojo.connect(widget.widget, 'onChange',
755                     function(newVal) { uEditDupeSearch('ident', newVal); });
756                 return;
757
758             case 'day_phone':
759                 // if configured, use the last for digits of the day phone number as the password
760                 if(uEditUsePhonePw && patron.isnew()) {
761                     dojo.connect(widget.widget, 'onChange',
762                         function(newVal) {
763                             if(newVal && newVal.length >= 4) {
764                                 var pw1 = findWidget('au', 'passwd').widget;
765                                 var pw2 = findWidget('au', 'passwd2').widget;
766                                 pw1.attr('value', newVal.substring(newVal.length - 4));
767                                 pw2.attr('value', newVal.substring(newVal.length - 4));
768                             }
769                         }
770                     );
771                 }
772             case 'evening_phone':
773             case 'other_phone':
774                 dojo.connect(widget.widget, 'onChange',
775                     function(newVal) { uEditDupeSearch('phone', newVal); });
776                 return;
777
778             case 'home_ou':
779                 dojo.connect(widget.widget, 'onChange',
780                     function(newVal) { 
781                         checkClaimsReturnCountPerm(); 
782                         checkClaimsNoCheckoutCountPerm();
783                     }
784                 );
785                 return;
786
787             case 'passwd':
788                 dojo.connect(widget.widget, 'onChange',
789                     function(newVal) {
790                         var pw1 = findWidget('au', 'passwd').widget;
791                         var pw2 = findWidget('au', 'passwd2').widget;
792                         var preserved_value = pw2.attr('value');
793                         // Ensure that the pw2 field match the pw1 field to validate
794                         pw2.regExp = newVal.replace(/([.\\^$*+?\(\)\[\]\{\}])/g, '\\$1');
795                         pw2.reset();
796                         pw2.attr('value',preserved_value);
797                     });
798                 return;
799         }
800     }
801
802     if(fmclass = 'aua') {
803         switch(fmfield) {
804             case 'post_code':
805                 dojo.connect(widget.widget, 'onChange',
806                     function(e) { 
807                         fieldmapper.standardRequest(
808                             ['open-ils.search', 'open-ils.search.zip'],
809                             {   async: true,
810                                 params: [e],
811                                 oncomplete : function(r) {
812                                     var res = openils.Util.readResponse(r);
813                                     if(!res) return;
814                                     var callback = function(w) { return w._addr == widget._addr; };
815                                     if(res.city) findWidget('aua', 'city', callback).widget.attr('value', res.city);
816                                     if(res.state) findWidget('aua', 'state', callback).widget.attr('value', res.state);
817                                     if(res.county) findWidget('aua', 'county', callback).widget.attr('value', res.county);
818                                     if(res.alert) alert(res.alert);
819                                 }
820                             }
821                         );
822                     }
823                 );
824                 return;
825
826             case 'street1':
827             case 'street2':
828             case 'city':
829                 dojo.connect(widget.widget, 'onChange',
830                     function(e) {
831                         var callback = function(w) { return w._addr == widget._addr; };
832                         var args = {
833                             street1 : findWidget('aua', 'street1', callback).widget.attr('value'),
834                             street2 : findWidget('aua', 'street2', callback).widget.attr('value'),
835                             city : findWidget('aua', 'city', callback).widget.attr('value'),
836                             post_code : findWidget('aua', 'post_code', callback).widget.attr('value')
837                         };
838                         if(args.street1 && args.city && args.post_code)
839                             uEditDupeSearch('address', args); 
840                     }
841                 );
842                 return;
843         }
844     }
845 }
846
847 function uEditDupeSearch(type, value) {
848     if(!value) return;
849     var search;
850     switch(type) {
851
852         case 'name':
853             openils.Util.hide('uedit-dupe-names-link');
854             var fname = findWidget('au', 'first_given_name').widget.attr('value');
855             var lname = findWidget('au', 'family_name').widget.attr('value');
856             if( !(fname && lname) ) return;
857             search = {
858                 first_given_name : {value : fname, group : 0},
859                 family_name : {value : lname, group : 0},
860             };
861             break;
862
863         case 'email':
864             openils.Util.hide('uedit-dupe-email-link');
865             search = {email : {value : value, group : 0}};
866             break;
867
868         case 'ident':
869             openils.Util.hide('uedit-dupe-ident-link');
870             search = {ident : {value : value, group : 2}};
871             break;
872
873         case 'phone':
874             openils.Util.hide('uedit-dupe-phone-link');
875             search = {phone : {value : value, group : 2}};
876             break;
877
878         case 'address':
879             openils.Util.hide('uedit-dupe-address-link');
880             search = {};
881             dojo.forEach(['street1', 'street2', 'city', 'post_code'],
882                 function(field) {
883                     if(value[field])
884                         search[field] = {value : value[field], group: 1};
885                 }
886             );
887             break;
888     }
889
890     // find possible duplicate patrons
891     fieldmapper.standardRequest(
892         ['open-ils.actor', 'open-ils.actor.patron.search.advanced'],
893         {   async: true,
894             params: [openils.User.authtoken, search],
895             oncomplete : function(r) {
896                 var resp = openils.Util.readResponse(r);
897                 resp = resp.filter(function(id) { return (id != patron.id()); });
898
899                 if(resp && resp.length > 0) {
900
901                     openils.Util.hide('uedit-help-div');
902                     openils.Util.show('uedit-dupe-div');
903                     var link;
904
905                     switch(type) {
906                         case 'name':
907                             link = dojo.byId('uedit-dupe-names-link');
908                             link.innerHTML = dojo.string.substitute(localeStrings.DUPE_PATRON_NAME, [resp.length]);
909                             break;
910                         case 'email':
911                             link = dojo.byId('uedit-dupe-email-link');
912                             link.innerHTML = dojo.string.substitute(localeStrings.DUPE_PATRON_EMAIL, [resp.length]);
913                             break;
914                         case 'ident':
915                             link = dojo.byId('uedit-dupe-ident-link');
916                             link.innerHTML = dojo.string.substitute(localeStrings.DUPE_PATRON_IDENT, [resp.length]);
917                             break;
918                         case 'phone':
919                             link = dojo.byId('uedit-dupe-phone-link');
920                             link.innerHTML = dojo.string.substitute(localeStrings.DUPE_PATRON_PHONE, [resp.length]);
921                             break;
922                         case 'address':
923                             link = dojo.byId('uedit-dupe-address-link');
924                             link.innerHTML = dojo.string.substitute(localeStrings.DUPE_PATRON_ADDR, [resp.length]);
925                             break;
926                     }
927
928                     openils.Util.show(link);
929                     link.onclick = function() {
930                         search.search_sort = js2JSON(["penalties", "family_name", "first_given_name"]);
931                         if(window.xulG)
932                             window.xulG.spawn_search(search);
933                         else
934                             console.log("running XUL patron search " + js2JSON(search));
935                     }
936                 }
937             }
938         }
939     );
940 }
941
942 function getByName(node, name) {
943     return dojo.query('[name='+name+']', node)[0];
944 }
945
946
947 function ueLoadContextHelp(fmcls, fmfield) {
948     openils.Util.hide('uedit-dupe-div');
949     openils.Util.show('uedit-help-div');
950     dojo.byId('uedit-help-field').innerHTML = fieldmapper.IDL.fmclasses[fmcls].field_map[fmfield].label;
951     dojo.byId('uedit-help-text').innerHTML = fieldDoc[fmcls][fmfield].string();
952 }
953
954
955 /* creates a new patron object with card attached */
956 function uEditNewPatron() {
957     patron = new au();
958     patron.isnew(1);
959     patron.id(-1);
960     card = new ac();
961     card.id(uEditCardVirtId--);
962     card.isnew(1);
963     patron.active(1);
964     patron.card(card);
965     patron.cards([card]);
966     patron.net_access_level(orgSettings['ui.patron.default_inet_access_level'] || 1);
967     patron.stat_cat_entries([]);
968     patron.survey_responses([]);
969     patron.addresses([]);
970     uEditMakeRandomPw(patron);
971     return patron;
972 }
973
974 function uEditMakeRandomPw(patron) {
975     if(uEditUsePhonePw) return;
976     var rand  = Math.random();
977     rand = parseInt(rand * 10000) + '';
978     while(rand.length < 4) rand += '0';
979 /*
980     appendClear($('ue_password_plain'),text(rand));
981     unHideMe($('ue_password_gen'));
982 */
983     patron.passwd(rand);
984     return rand;
985 }
986
987 function uEditWidgetVal(w) {
988     var val = (w.getFormattedValue) ? w.getFormattedValue() : w.attr('value');
989     if(val === '') val = null;
990     return val;
991 }
992
993 function uEditSave() { _uEditSave(); }
994 function uEditSaveClone() { _uEditSave(true); }
995
996 function _uEditSave(doClone) {
997
998     if ( (! myForm.isValid()) || dupeUsrname || dupeBarcode ) {
999         alert(localeStrings.INVALID_FORM);
1000         return;
1001     }
1002
1003     for(var idx in widgetPile) {
1004         var w = widgetPile[idx];
1005         var val = uEditWidgetVal(w);
1006
1007         switch(w._wtype) {
1008             case 'au':
1009                 if(w._fmfield != 'passwd2')
1010                     patron[w._fmfield](val);
1011                 break;
1012
1013             case 'ac':
1014                 patron.card()[w._fmfield](val);
1015                 break;
1016
1017             case 'aua':
1018                 var addr = patron.addresses().filter(function(i){return (i.id() == w._addr)})[0];
1019                 if(!addr) {
1020                     addr = new fieldmapper.aua();
1021                     addr.id(w._addr);
1022                     addr.isnew(1);
1023                     addr.usr(patron.id());
1024                     var t = patron.addresses();
1025                         if (!t) { t = []; }
1026                         t.push(addr);
1027                         patron.addresses(t);
1028                 } else {
1029                     if(addr[w._fmfield]() != val)
1030                         addr.ischanged(1);
1031                 }
1032                 addr[w._fmfield](val);
1033
1034                 if(dojo.byId('uedit-billing-address-' + addr.id()).checked) 
1035                     patron.billing_address(addr.id());
1036
1037                 if(dojo.byId('uedit-mailing-address-' + addr.id()).checked)
1038                     patron.mailing_address(addr.id());
1039
1040                 break;
1041
1042             case 'survey':
1043                 if(val == null) break;
1044                 var resp = new fieldmapper.asvr();
1045                 resp.isnew(1);
1046                 resp.survey(w._survey)
1047                 resp.usr(patron.id());
1048                 resp.question(w._question)
1049                 resp.answer(val);
1050                 var t = patron.survey_responses();
1051                     if (!t) { t = []; }
1052                     t.push(resp);
1053                     patron.survey_responses(t);
1054                 break;
1055
1056             case 'statcat':
1057                 if(val == null) break;
1058
1059                 var map = patron.stat_cat_entries().filter(
1060                     function(m){
1061                         return (m.stat_cat() == w._statcat) })[0];
1062
1063                 if(map) {
1064                     if(map.stat_cat_entry() == val) 
1065                         break;
1066                     map.ischanged(1);
1067                 } else {
1068                     map = new fieldmapper.actscecm();
1069                     map.isnew(1);
1070                 }
1071
1072                 map.stat_cat(w._statcat);
1073                 map.stat_cat_entry(val);
1074                 map.target_usr(patron.id());
1075                 var t = patron.stat_cat_entries();
1076                     if (!t) { t = []; }
1077                     t.push(map);
1078                     patron.stat_cat_entries(t);
1079                 break;
1080         }
1081     }
1082
1083     patron.ischanged(1);
1084     fieldmapper.standardRequest(
1085         ['open-ils.actor', 'open-ils.actor.patron.update'],
1086         {   async: true,
1087             params: [openils.User.authtoken, patron],
1088             oncomplete: function(r) {
1089                 newPatron = openils.Util.readResponse(r);
1090                 if(newPatron) {
1091                     uEditUpdateUserSettings(newPatron.id());
1092                     if(stageUser) uEditRemoveStage();
1093                     uEditFinishSave(newPatron, doClone);
1094                 }
1095             }
1096         }
1097     );
1098 }
1099
1100 function uEditRemoveStage() {
1101     var resp = fieldmapper.standardRequest(
1102         ['open-ils.actor', 'open-ils.actor.user.stage.delete'],
1103         { params : [openils.User.authtoken, stageUser.row_id()] }
1104     )
1105 }
1106
1107 function uEditFinishSave(newPatron, doClone) {
1108
1109     if(doClone && cloneUser == null)
1110         cloneUser = newPatron.id();
1111
1112         if( doClone ) {
1113
1114                 if(xulG && typeof xulG.spawn_editor == 'function' && !patron.isnew() ) {
1115             window.xulG.spawn_editor({ses:openils.User.authtoken,clone:cloneUser});
1116             uEditRefresh();
1117
1118                 } else {
1119                         location.href = location.href.replace(/\?.*/, '') + '?clone=' + cloneUser;
1120                 }
1121
1122         } else {
1123
1124                 uEditRefresh();
1125         }
1126
1127         uEditRefreshXUL(newPatron);
1128 }
1129
1130 function uEditRefresh() {
1131     var usr = cgi.param('usr');
1132     var href = location.href.replace(/\?.*/, '');
1133     href += ((usr) ? '?usr=' + usr : '');
1134     location.href = href;
1135 }
1136
1137 function uEditRefreshXUL(newuser) {
1138         if (window.xulG && typeof window.xulG.on_save == 'function') 
1139                 window.xulG.on_save(newuser);
1140 }
1141
1142
1143 /**
1144  * Create a new address and insert it into the DOM
1145  * @param evt ignored
1146  * @param id The address id
1147  * @param mkLinks If true, set the new address as the 
1148  *  mailing/billing address for the user
1149  */
1150 function uEditNewAddr(evt, id, mkLinks) {
1151
1152     if(id == null) 
1153         id = --uEditAddrVirtId; // new address
1154
1155     var addr =  patron.addresses().filter(
1156         function(i) { return (i.id() == id) })[0];
1157
1158     dojo.forEach(addrTemplateRows, 
1159         function(row) {
1160
1161             row = tbody.insertBefore(row.cloneNode(true), dojo.byId('new-addr-row'));
1162             row.setAttribute('type', '');
1163             row.setAttribute('addr', id+'');
1164
1165             if(row.getAttribute('fmclass')) {
1166                 var widget = fleshFMRow(row, 'aua', {addr:id});
1167
1168                 // make new addresses valid by default
1169                 if(id < 0 && row.getAttribute('fmfield') == 'valid') 
1170                     widget.widget.attr('value', true); 
1171
1172             } else if(row.getAttribute('name') == 'uedit-addr-pending-row') {
1173
1174                 // if it's a pending address, show the 'approve' button
1175                 if(addr && openils.Util.isTrue(addr.pending())) {
1176                     openils.Util.show(row, 'table-row');
1177                     dojo.query('[name=approve-button]', row)[0].onclick = 
1178                         function() { uEditApproveAddress(addr); };
1179
1180                     if(addr.replaces()) {
1181                         var div = dojo.query('[name=replaced-addr]', row)[0]
1182                         var replaced =  patron.addresses().filter(
1183                             function(i) { return (i.id() == addr.replaces()) })[0];
1184
1185                         div.innerHTML = dojo.string.substitute(localeStrings.REPLACED_ADDRESS, [
1186                             replaced.address_type() || '',
1187                             replaced.street1() || '',
1188                             replaced.street2() || '',
1189                             replaced.city() || '',
1190                             replaced.state() || '',
1191                             replaced.post_code() || ''
1192                         ]);
1193
1194                     } else {
1195                         openils.Util.hide(dojo.query('[name=replaced-addr-div]', row)[0]);
1196                     }
1197                 }
1198
1199             } else if(row.getAttribute('name') == 'uedit-addr-owner-row') {
1200                 // address is owned by someone else.  provide option to load the
1201                 // user in a different tab
1202                 
1203                 if(addr && addr.usr() != patron.id()) {
1204                     openils.Util.show(row, 'table-row');
1205                     var link = getByName(row, 'addr-owner');
1206
1207                     // fetch the linked user so we can present their name in the UI
1208                     var addrUser;
1209                     if(cloneUserObj && cloneUserObj.id() == addr.usr()) {
1210                         addrUser = [
1211                             cloneUserObj.first_given_name(), 
1212                             cloneUserObj.second_given_name(), 
1213                             cloneUserObj.family_name()
1214                         ];
1215                     } else {
1216                         addrUser = fieldmapper.standardRequest(
1217                             ['open-ils.actor', 'open-ils.actor.user.retrieve.parts'],
1218                             {params: [
1219                                 openils.User.authtoken, 
1220                                 addr.usr(), 
1221                                 ['first_given_name', 'second_given_name', 'family_name']
1222                             ]}
1223                         );
1224                     }
1225
1226                     link.innerHTML = (addrUser.map(function(name) { return (name) ? name+' ' : '' })+'').replace(/,/g,''); // TODO i18n
1227                     link.onclick = function() {
1228                         if(openils.XUL.isXUL()) { 
1229                             window.xulG.spawn_editor({ses:openils.User.authtoken, usr:addr.usr()})
1230                         } else {
1231                             parent.location.href = location.href.replace(/clone=\d+/, 'usr=' + addr.usr());
1232                         }
1233                     }
1234                 }
1235
1236             } else if(row.getAttribute('name') == 'uedit-addr-divider') {
1237                 // link up the billing/mailing address and give the inputs IDs so we can acces the later
1238                 
1239                 // billing address
1240                 var ba = getByName(row, 'billing_address');
1241                 ba.id = 'uedit-billing-address-' + id;
1242                 if(mkLinks || (patron.billing_address() && patron.billing_address().id() == id))
1243                     ba.checked = true;
1244
1245                 // mailing address
1246                 var ma = getByName(row, 'mailing_address');
1247                 ma.id = 'uedit-mailing-address-' + id;
1248                 if(mkLinks || (patron.mailing_address() && patron.mailing_address().id() == id))
1249                     ma.checked = true;
1250
1251                 var btn = dojo.query('[name=delete-button]', row)[0];
1252                 if(btn) btn.onclick = function(){ uEditDeleteAddr(id) };
1253             }
1254         }
1255     );
1256 }
1257
1258 function uEditApproveAddress(addr) {
1259     fieldmapper.standardRequest(
1260         ['open-ils.actor', 'open-ils.actor.user.pending_address.approve'],
1261         {   async: true,
1262             params:  [openils.User.authtoken, addr],
1263
1264             oncomplete : function(r) {
1265                 var oldId = openils.Util.readResponse(r);
1266                     
1267                 // remove addrs from UI
1268                 dojo.forEach(
1269                     patron.addresses(), 
1270                     function(addr) { uEditDeleteAddr(addr.id(), true); }
1271                 );
1272
1273                 if(oldId != null) {
1274                     
1275                     // remove the replaced address 
1276                     if(oldId != addr.id()) {
1277                                 patron.addresses(
1278                             patron.addresses().filter(
1279                                                 function(i) { return (i.id() != oldId); }
1280                                         )
1281                                 );
1282                     }
1283                     
1284                     // fix the the new address
1285                     addr.id(oldId);
1286                     addr.replaces(null);
1287                     addr.pending('f');
1288
1289                 }
1290
1291                 // redraw addrs
1292                 loadAllAddrs();
1293             }
1294         }
1295     );
1296 }
1297
1298
1299 function uEditDeleteAddr(id, noAlert) {
1300     if(!noAlert) {
1301         if(!confirm('Delete address ' + id)) return; /* XXX i18n */
1302     }
1303     var addr = patron.addresses().filter(function(i){return (i.id() == id)})[0];
1304     if (addr) { addr.isdeleted(1); }
1305     var m_a = patron.mailing_address();
1306         if (typeof m_a == 'object' && m_a != null) { m_a = m_a.id(); }
1307         if (m_a == id) { patron.mailing_address(null); }
1308     var b_a = patron.billing_address();
1309         if (typeof b_a == 'object' && b_a != null) { b_a = b_a.id(); }
1310         if (b_a == id) { patron.billing_address(null); }
1311
1312     var rows = dojo.query('tr[addr='+id+']', tbody);
1313     for(var i = 0; i < rows.length; i++)
1314         rows[i].parentNode.removeChild(rows[i]);
1315     widgetPile = widgetPile.filter(function(w){return (w._addr != id)});
1316 }
1317
1318 function uEditToggleRequired() {
1319     if((tbody.className +'').match(/hide-non-required/)) 
1320         openils.Util.removeCSSClass(tbody, 'hide-non-required');
1321     else
1322         openils.Util.addCSSClass(tbody, 'hide-non-required');
1323     openils.Util.toggle('uedit-show-required');
1324     openils.Util.toggle('uedit-show-all');
1325 }
1326
1327 function printable_output() {
1328     var temp; var s = '=-=-=-=\r\n';
1329     for (var idx in widgetPile) {
1330         var w = widgetPile[idx];
1331         var val = uEditWidgetVal(w);
1332         var label;
1333         if (typeof w.idlField == 'undefined') {
1334             label = w._wtype;
1335             if (w._wtype == 'statcat') {
1336                 var stat = statCats.filter(
1337                     function(m){
1338                         return (m.id() == w._statcat) })[0];
1339                 label = stat.name();
1340             } else if (w._wtype == 'survey') {
1341                 var survey = surveys.filter(
1342                     function(m){
1343                         return (m.id() == w._survey) })[0];
1344                 var question = survey.questions().filter(
1345                     function(m){
1346                         return (m.id() == w._question) })[0];
1347                 label = survey.name() + ' : ' + question.question();
1348             } else {
1349                 label = 'FIXME';
1350             }
1351         } else {
1352             label = w.idlField.label;
1353         }
1354         if (temp != w._wtype) {
1355             temp = w._wtype;
1356             s += '-------\r\n';
1357         }
1358         s += label + ':\t' + (typeof val == 'object' ? '' : val) + '\r\n';
1359     }
1360     s += '=-=-=-=\r\n';
1361     return s;
1362 }
1363
1364 openils.Util.addOnLoad(load);