9c7b54b18f8a6d4834b04e9ea4c38605f5c6f188
[Evergreen.git] / Evergreen / xul / staff_client / server / patron / ue_config.js
1 /* -----------------------------------------------------------------------
2         ----------------------------------------------------------------------- */
3 const SC_FETCH_ALL              = 'open-ils.circ:open-ils.circ.stat_cat.actor.retrieve.all';
4 const SC_CREATE_MAP             = 'open-ils.circ:open-ils.circ.stat_cat.actor.user_map.create';
5 const SV_FETCH_ALL              = 'open-ils.circ:open-ils.circ.survey.retrieve.all';
6 const FETCH_ID_TYPES            = 'open-ils.actor:open-ils.actor.user.ident_types.retrieve';
7 const FETCH_GROUPS              = 'open-ils.actor:open-ils.actor.groups.tree.retrieve';
8 const FETCH_NET_LEVELS  = 'open-ils.actor:open-ils.actor.net_access_level.retrieve.all';
9 const UPDATE_PATRON             = 'open-ils.actor:open-ils.actor.patron.update';
10 const PATRON_SEARCH             = 'open-ils.actor:open-ils.actor.patron.search.advanced';
11 const ZIP_SEARCH                        = 'open-ils.search:open-ils.search.zip';
12 const FETCH_ADDR_MEMS   = 'open-ils.actor:open-ils.actor.address.members';
13 const FETCH_GRP_MEMS            = 'open-ils.actor:open-ils.actor.usergroup.members.retrieve';
14 const CREATE_USER_NOTE  = 'open-ils.actor:open-ils.actor.note.create';
15 const CHECK_BARCODE             = 'open-ils.actor:open-ils.actor.barcode.exists';
16 const defaultState              = 'GA';
17 const defaultCountry            = 'USA';
18 const defaultNetAccess  = 'None';
19 const CSS_INVALID_DATA  = 'invalid_value';
20 const ADULT_AGE                 = 18;
21 //const GUARDIAN_NOTE           = 'SYSTEM: Parent/Guardian';
22
23 /* if they don't have these perms, they shouldn't be here */
24 var myPerms = [ 
25         'BAR_PATRON',
26         'group_application.user',
27         'group_application.user.patron',
28         'group_application.user.staff',
29         'group_application.user.staff.circ',
30         'group_application.user.staff.cat',
31         'group_application.user.staff.admin.global_admin',
32         'group_application.user.staff.admin.local_admin',
33         'group_application.user.staff.admin.lib_manager',
34         'group_application.user.staff.cat.cat1',
35         'group_application.user.staff.supercat',
36         'group_application.user.sip_client',
37         'group_application.user.vendor'
38         ];
39
40 var dataFields;
41 const numRegex          = /^\d+$/;
42 const wordRegex = /^\w+$/;
43 const ssnRegex          = /^\d{3}-\d{2}-\d{4}$/;
44 const dlRegex           = /^[a-zA-Z]{2}-\w+/; /* driver's license */
45 const phoneRegex        = /^\d{3}-\d{3}-\d{4}(| ex\d+)$/i;
46 const nonumRegex        = /^[a-zA-Z]\D*$/; /* no numbers, no beginning whitespace */
47 const dateRegex = /^\d{4}-\d{2}-\d{2}/;
48 const zipRegex          = /^\d{5}(-\d{4}|$)/; /* 12345 or 12345-6789 */
49
50 var barredAlerted = false;
51
52
53 function uEditUsrnameBlur(field) {
54         var usrname = uEditNodeVal(field);
55         if(!usrname) return;
56         var req = new Request(CHECK_USERNAME, SESSION, usrname);
57         req.callback( 
58                 function(r) {
59                         var res = r.getResultObject();
60                         if( res && res != patron.id() ) {
61                                 field.widget.onblur = null; /* prevent alert storm */
62                                 alertId('ue_dup_username');
63                                 field.widget.onblur = uEditUsrnameBlur;
64                                 setTimeout( 
65                                         function() {
66                                                 field.widget.node.focus();
67                                                 field.widget.node.select();
68                                         }, 10 
69                                 );
70                         }
71                 }
72         );
73         req.send();
74 }
75
76
77 function uEditBarcodeBlur(field) {
78         var barcode = uEditNodeVal(field);
79         if(!barcode) return;
80         var req = new Request(CHECK_BARCODE, SESSION, barcode);
81         req.callback( 
82                 function(r) {
83                         var res = r.getResultObject();
84                         if( res && res != patron.id() ) {
85                                 field.widget.onblur = null; /* prevent alert storm */
86                                 alertId('ue_dup_barcode');
87                                 field.widget.onblur = uEditBarcodeBlur;
88                                 setTimeout( 
89                                         function() {
90                                                 field.widget.node.focus();
91                                                 field.widget.node.select();
92                                         }, 10 
93                                 );
94                         } else {
95                                 var node = uEditFindFieldByWId("ue_username");
96                                 if(!node.widget.node.value) {
97                                         node.widget.node.value = barcode;
98                                         node.widget.node.onchange();
99                                 }
100                         }
101                 }
102         );
103         req.send();
104 }
105
106
107 function uEditDefineData(patron) {
108
109         var fields = [
110                 {
111                         required : true,
112                         object  : patron.card(),
113                         key             : 'barcode',
114                         errkey  : 'ue_bad_barcode',
115                         widget  : {
116                                 id              : 'ue_barcode',
117                                 regex   : wordRegex,
118                                 type    : 'input',
119                                 onblur : uEditBarcodeBlur
120                         }
121                 },
122                 {
123                         required : true,
124                         object  : patron,
125                         key             : 'usrname',
126                         errkey  : 'ue_bad_username',
127                         widget  : {
128                                 id              : 'ue_username',
129                                 regex   : wordRegex,
130                                 type    : 'input',
131                                 onblur : uEditUsrnameBlur
132                         }
133                 },
134                 {
135                         required : (patron.isnew()) ? true : false,
136                         object  : patron,
137                         key             : 'passwd',
138                         errkey  : 'ue_bad_password',
139                         widget  : {
140                                 id              : 'ue_password1',
141                                 type    : 'input',
142                                 onpostchange : function(field, newval) {
143                                         var pw2 = uEditFindFieldByWId('ue_password2');
144                                         /* tell the second passsword input to re-validate */
145                                         pw2.widget.node.onchange();
146                                 }
147
148                         }
149                 },
150                 {
151                         required : (patron.isnew()) ? true : false,
152                         object  : patron,
153                         key             : 'passwd',
154                         errkey  : 'ue_bad_password',
155                         widget  : {
156                                 id              : 'ue_password2',
157                                 type    : 'input',
158                                 onpostchange : function(field, newval) {
159                                         var pw1f = uEditFindFieldByWId('ue_password1');
160                                         var pw1 = uEditNodeVal(pw1f);
161                                         field.widget.regex = new RegExp('^'+pw1+'$');
162                                 }
163                         }
164                 },
165                 {
166                         required : true,
167                         object  : patron,
168                         key             : 'first_given_name',
169                         errkey  : 'ue_bad_firstname',
170                         widget  : {
171                                 id              : 'ue_firstname',
172                                 regex   : nonumRegex,
173                                 type    : 'input',
174                                 onblur : function(field) {
175                                         uEditCheckNamesDup('first', field );
176                                 }
177                         }
178                 },
179
180                 {
181                         required : false,
182                         object  : patron,
183                         key             : 'second_given_name',
184                         errkey  : 'ue_bad_middlename',
185                         widget  : {
186                                 id              : 'ue_middlename',
187                                 regex   : nonumRegex,
188                                 type    : 'input'
189                         }
190                 },
191                 {
192                         required : true,
193                         object  : patron,
194                         key             : 'family_name',
195                         errkey  : 'ue_bad_lastname',
196                         widget  : {
197                                 id              : 'ue_lastname',
198                                 regex   : nonumRegex,
199                                 type    : 'input',
200                                 onblur : function(field) {
201                                         uEditCheckNamesDup('last', field );
202                                 }
203                         }
204                 },
205                 {
206                         required : false,
207                         object  : patron,
208                         key             : 'suffix',
209                         widget  : {
210                                 id                      : 'ue_suffix',
211                                 type            : 'input',
212                                 onload  : function(val) {
213                                         setSelector($('ue_suffix_selector'), val);
214                                         $('ue_suffix_selector').onchange = function() {
215                                                 uEditFindFieldByKey('suffix').widget.node.onchange();
216                                         }
217                                 },
218                         }
219                 },
220                 {
221                         required : false,
222                         object  : patron,
223                         key             : 'dob',
224                         errkey  : 'ue_bad_dob',
225                         widget  : {
226                                 id                      : 'ue_dob',
227                                 regex           : dateRegex,
228                                 type            : 'input',
229                                 onpostchange    : function(field) { uEditCheckDOB(field); },
230                                 onblur  : function(field) { uEditCheckDOB(field); }
231                         }
232                 },
233                 {
234                         required : true,
235                         object  : patron,
236                         key             : 'ident_type',
237                         errkey  : 'ue_no_ident',
238                         widget  : {
239                                 id              : 'ue_primary_ident_type',
240                                 regex   : numRegex,
241                                 type    : 'select',
242                                 onpostchange : function(field, newval) 
243                                         { _uEditIdentPostchange('primary', field, newval); }
244                         }
245                 },
246                 {
247                         required : false,
248                         object  : patron,
249                         key             : 'ident_value',
250                         widget  : {
251                                 id                      : 'ue_primary_ident',
252                                 type            : 'input',
253                                 onblur : function(field) {
254                                         uEditCheckIdentDup(field);
255                                 }
256                         }
257                 },
258                 {
259                         required : false,
260                         object  : patron,
261                         key             : 'ident_value2',
262                         widget  : {
263                                 id                      : 'ue_secondary_ident',
264                                 type            : 'input'
265                         }
266                 },
267                 {
268                         required : false,
269                         object  : patron,
270                         key             : 'email',
271                         errkey  : 'ue_bad_email',
272                         widget  : {
273                                 id                      : 'ue_email',
274                                 type            : 'input',
275                                 regex           :  /.+\@.+\..+/,  /* make me better */
276                                 onblur  : function(field) {
277                                         var val = uEditNodeVal(field);
278                                         if( val && val != field.oldemail ) {
279                                                 uEditRunDupeSearch('email',
280                                                         { email : { value : val, group : 0 } });
281                                                 field.oldemail = val;
282                                         }
283                                 }
284                         }
285                 },
286                 {
287                         required : false,
288                         object  : patron,
289                         key             : 'day_phone',
290                         errkey  : 'ue_bad_phone',
291                         widget  : {
292                                 id                      : 'ue_day_phone',
293                                 type            : 'input',
294                                 regex           :  phoneRegex,
295                         }
296                 },
297                 {
298                         required : false,
299                         object  : patron,
300                         key             : 'evening_phone',
301                         errkey  : 'ue_bad_phone',
302                         widget  : {
303                                 id                      : 'ue_night_phone',
304                                 type            : 'input',
305                                 regex           :  phoneRegex,
306                         }
307                 },
308                 {
309                         required : false,
310                         object  : patron,
311                         key             : 'other_phone',
312                         errkey  : 'ue_bad_phone',
313                         widget  : {
314                                 id                      : 'ue_other_phone',
315                                 type            : 'input',
316                                 regex           :  phoneRegex,
317                         }
318                 },
319                 {
320                         required : true,
321                         object  : patron,
322                         key             : 'home_ou',
323                         widget  : {
324                                 id                      : 'ue_org_selector',
325                                 type            : 'select',
326                                 regex           :  numRegex,
327                         }
328                 },
329                 {
330                         required : true,
331                         object  : patron,
332                         key             : 'expire_date',
333                         errkey  : 'ue_bad_expire',
334                         widget  : {
335                                 id                      : 'ue_expire',
336                                 type            : 'input',
337                                 regex           :  dateRegex,
338                         }
339                 },
340                 {
341                         required : false,
342                         object  : patron,
343                         key             : 'active',
344                         widget  : {
345                                 id                      : 'ue_active',
346                                 type            : 'checkbox',
347                         }
348                 },
349                 {
350                         required : false,
351                         object  : patron,
352                         key             : 'barred',
353                         widget  : {
354                                 id                      : 'ue_barred',
355                                 type            : 'checkbox',
356                                 onpostchange : function(field, val) {
357                                         var afield = uEditFindFieldByKey('alert_message');
358                                         if( val ) {
359                                                 if( !barredAlerted ) {
360                                                         barredAlerted = true;
361                                                         alertId('ue_made_barred');
362                                                 }
363                                                 afield.required = true; 
364                                         } else {
365                                                 afield.required = false;
366                                         }
367                                 }
368                         }
369                 },
370                 {
371                         required : true,
372                         object  : patron,
373                         key             : 'profile',
374                         errkey  : 'ue_no_profile',
375                         widget  : {
376                                 id                      : 'ue_profile',
377                                 type            : 'select',
378                                 regex           : numRegex,
379                                 onpostchange : function(field, value) {
380                                         var type                        = groupsCache[value];
381                                         if(!type) return;
382                                         var interval    = type.perm_interval();
383
384                                         /* interval_to_seconds expects 'M' for months, 'm' for minutes */
385                                         interval                        = interval.replace(/mon/, 'Mon'); 
386                                         var intsecs             = parseInt(interval_to_seconds(interval));
387
388                                         var expdate             = new Date();
389                                         var exptime             = expdate.getTime();
390                                         exptime                 += intsecs * 1000;
391                                         expdate.setTime(exptime);
392
393                                         var year                        = expdate.getYear() + 1900;
394                                         var month               = (expdate.getMonth() + 1) + '';
395                                         var day                 = (expdate.getDate()) + '';
396
397                                         if(!month.match(/\d{2}/)) month = '0' + month;
398                                         if(!day.match(/\d{2}/)) day = '0' + day;
399
400                                         var node = $('ue_expire');
401                                         node.value = year+'-'+month+'-'+day;
402                                 }
403                         }
404                 },
405                 {
406                         required : false,
407                         object  : patron,
408                         key             : 'net_access_level',
409                         widget  : {
410                                 id              : 'ue_net_level',
411                                 type    : 'select'
412                         }
413                 },
414                 {
415                         required : false,
416                         object  : patron,
417                         key             : 'master_account',
418                         widget  : {
419                                 id                      : 'ue_group_lead',
420                                 type            : 'checkbox',
421                         }
422                 },
423                 {
424                         required : true,
425                         object  : patron,
426                         key             : 'claims_returned_count',
427                         widget  : {
428                                 id                      : 'ue_claims_returned',
429                                 type            : 'input',
430                                 regex           : numRegex,
431                         }
432                 },
433                 {
434                         required : false,
435                         object  : patron,
436                         key             : 'alert_message',
437                         widget  : {
438                                 id                      : 'ue_alert_message',
439                                 type            : 'input',
440                         }
441                 }
442         ];
443
444         for( var f in fields ) 
445                 dataFields.push(fields[f]);
446
447         uEditBuildAddrs(patron);
448         uEditBuildPatronSCM(patron);
449 }
450
451 var uEditOldFirstName;
452 var uEditOldMiddleName; /* future */
453 var uEditOldLastName;
454 function uEditCheckNamesDup(type, field) {
455         var newval = uEditNodeVal(field);
456         if(!newval) return;
457
458         var dosearch = false;
459
460         if(type =='first') {
461                 if( newval != uEditOldFirstName )
462                         dosearch = true;
463                 uEditOldFirstName = newval;
464         }
465
466         if(type =='last') {
467                 if( newval != uEditOldLastName )
468                         dosearch = true;
469                 uEditOldLastName = newval;
470         }
471
472         if( dosearch && uEditOldFirstName && uEditOldLastName ) {
473                 var search_hash = {};
474                 search_hash['first_given_name'] = { value : uEditOldFirstName, group : 0 };
475                 search_hash['family_name'] = { value : uEditOldLastName, group : 0 };
476                 uEditRunDupeSearch('names', search_hash);
477         }
478 }
479
480 var uEditOldIdentValue;
481 function uEditCheckIdentDup(field) {
482         var newval = uEditNodeVal(field);
483         if( newval && newval != uEditOldIdentValue ) {
484                 /* searches all ident_value fields */
485                 var search_hash  = { ident : { value : newval, group : 2 } };
486                 uEditRunDupeSearch('ident', search_hash);
487                 uEditOldIdentValue = newval;
488         }
489 }
490
491
492 /* Adds all of the addresses attached to the patron object
493         to the fields array */
494 var uEditAddrTemplate;
495 function uEditBuildAddrs(patron) {
496         var tbody = $('ue_address_tbody');
497         if(!uEditAddrTemplate)
498                 uEditAddrTemplate = tbody.removeChild($('ue_address_template'));
499         for( var a in patron.addresses() ) 
500                 uEditBuildAddrFields( patron, patron.addresses()[a]);
501 }
502
503
504 function uEditDeleteAddr( tbody, row, address, detach ) {
505         if(!confirm($('ue_delete_addr_warn').innerHTML)) return;
506         if(address.isnew()) { 
507                 patron.addresses(
508                         grep( patron.addresses(), 
509                                 function(i) {
510                                         return (i.id() != address.id());
511                                 }
512                         )
513                 );
514
515                 /* XXX */
516                 for( var f in dataFields ) {
517                         if( dataFields[f].object == address ) {
518                                 dataFields[f] = null;
519                         }
520                 }
521
522                 dataFields = compactArray(dataFields);
523
524         } else {
525
526                 if( detach ) { /* remove the offending address from the list */
527                         patron.addresses(
528                                 grep( 
529                                         patron.addresses(), 
530                                         function(i) {
531                                                 return (i.id() != address.id());
532                                         }
533                                 )
534                         );
535
536                 } else {
537                         address.isdeleted(1);
538                 }
539         }
540
541         tbody.removeChild(row);
542
543         var bid = patron.billing_address();
544         bid = (typeof bid == 'object') ? bid.id() : bid;
545
546         var mid = patron.mailing_address();
547         mid = (typeof mid == 'object') ? mid.id() : mid;
548
549
550         /* -----------------------------------------------------------------------
551                 if we're deleting a billing or mailing address 
552                 make sure some other address is automatically
553                 assigned as the billing or mailng address 
554                 ----------------------------------------------------------------------- */
555
556         if( bid == address.id() ) {
557                 for( var a in patron.addresses() ) {
558                         var addr = patron.addresses()[a];
559                         if(!addr.isdeleted() && addr.id() != address.id()) {
560                                 var node = uEditFindAddrInput('billing', addr.id());
561                                 node.checked = true;
562                                 uEditAddrTypeClick(node, 'billing');
563                                 break;
564                         }
565                 }
566         }
567
568         if( mid == address.id() ) {
569                 for( var a in patron.addresses() ) {
570                         var addr = patron.addresses()[a];
571                         if(!addr.isdeleted() && addr.id() != address.id()) {
572                                 var node = uEditFindAddrInput('mailing', addr.id());
573                                 node.checked = true;
574                                 uEditAddrTypeClick(node, 'mailing');
575                                 break;
576                         }
577                 }
578         }
579
580 }
581
582
583 function uEditFindAddrInput(type, id) {
584         var tbody = $('ue_address_tbody');
585         var rows = tbody.getElementsByTagName('tr');
586         for( var r in rows ) {
587                 var row = rows[r];
588                 if(row.parentNode != tbody) continue;
589                 var node = $n(row, 'ue_addr_'+type+'_yes');
590                 if( node.getAttribute('address') == id )
591                         return node;
592         }
593 }
594
595
596 function uEditAddrTypeClick(input, type) {
597         var tbody = $('ue_address_tbody');
598         var rows = tbody.getElementsByTagName('tr');
599         for( var r in rows ) {
600                 var row = rows[r];
601                 if(row.parentNode != tbody) continue;
602                 var node = $n(row, 'ue_addr_'+type+'_yes');
603                 removeCSSClass(node.parentNode,'addr_info_checked');
604         }
605
606         addCSSClass(input.parentNode,'addr_info_checked');
607         patron[type+'_address'](input.getAttribute('address'));
608         patron.ischanged(1);
609 }
610
611
612
613
614 /* Creates the field entries for an address object. */
615 function uEditBuildAddrFields(patron, address) {
616
617         var tbody = $('ue_address_tbody');
618
619         var row = tbody.appendChild(
620                 uEditAddrTemplate.cloneNode(true));
621
622         uEditCheckSharedAddr(patron, address, tbody, row);
623
624         $n(row, 'ue_addr_delete').onclick = 
625                 function() { uEditDeleteAddr(tbody, row, address); }
626
627         if( patron.billing_address() &&
628                         address.id() == patron.billing_address().id() ) 
629                 $n(row, 'ue_addr_billing_yes').checked = true;
630
631         if( patron.mailing_address() &&
632                         address.id() == patron.mailing_address().id() ) 
633                 $n(row, 'ue_addr_mailing_yes').checked = true;
634
635         $n(row, 'ue_addr_billing_yes').setAttribute('address', address.id());
636         $n(row, 'ue_addr_mailing_yes').setAttribute('address', address.id());
637
638         /* currently, non-owners cannot edit an address */
639         var disabled = (address.usr() != patron.id())
640
641         var fields = [
642                 { 
643                         required : false,
644                         object  : address, 
645                         key             : 'address_type', 
646                         widget  : {
647                                 base    : row,
648                                 name    : 'ue_addr_label',
649                                 type    : 'input',
650                                 disabled : disabled,
651                         }
652                 },
653                 { 
654                         required : true,
655                         object  : address, 
656                         key             : 'street1', 
657                         errkey  : 'ue_bad_addr_street',
658                         widget  : {
659                                 base    : row,
660                                 name    : 'ue_addr_street1',
661                                 type    : 'input',
662                                 disabled : disabled,
663                         }
664                 },
665                 { 
666                         required : false,
667                         object  : address, 
668                         key             : 'street2', 
669                         errkey  : 'ue_bad_addr_street',
670                         widget  : {
671                                 base    : row,
672                                 name    : 'ue_addr_street2',
673                                 type    : 'input',
674                                 disabled : disabled,
675                         }
676                 },
677                 { 
678                         required : true,
679                         object  : address, 
680                         key             : 'city', 
681                         errkey  : 'ue_bad_addr_city',
682                         widget  : {
683                                 base    : row,
684                                 name    : 'ue_addr_city',
685                                 type    : 'input',
686                                 disabled : disabled,
687                         }
688                 },
689                 { 
690                         required : false,
691                         object  : address, 
692                         key             : 'county', 
693                         widget  : {
694                                 base    : row,
695                                 name    : 'ue_addr_county',
696                                 type    : 'input',
697                                 disabled : disabled,
698                         }
699                 },
700                 { 
701                         required : true,
702                         object  : address, 
703                         key             : 'state', 
704                         errkey  : 'ue_bad_addr_state',
705                         widget  : {
706                                 base    : row,
707                                 name    : 'ue_addr_state',
708                                 type    : 'input',
709                                 disabled : disabled,
710                         }
711                 },
712                 { 
713                         required : true,
714                         object  : address, 
715                         key             : 'country', 
716                         errkey  : 'ue_bad_addr_country',
717                         widget  : {
718                                 base    : row,
719                                 name    : 'ue_addr_country',
720                                 type    : 'input',
721                                 disabled : disabled,
722                         }
723                 },
724                 { 
725                         required : true,
726                         object  : address, 
727                         key             : 'post_code',
728                         errkey  : 'ue_bad_addr_zip',
729                         widget  : {
730                                 base    : row,
731                                 name    : 'ue_addr_zip',
732                                 type    : 'input',
733                                 disabled : disabled,
734                                 regex   : zipRegex,
735                                 onblur : function(f) {
736                                         var v = uEditNodeVal(f);
737                                         var req = new Request(ZIP_SEARCH, v);
738                                         req.callback( 
739                                                 function(r) {
740                                                         var info = r.getResultObject();
741                                                         if(!info) return;
742                                                         var state = $n(f.widget.base, 'ue_addr_state');
743                                                         var county = $n(f.widget.base, 'ue_addr_county');
744                                                         var city = $n(f.widget.base, 'ue_addr_city');
745                                                         if(!state.value) {
746                                                                 state.value = info.state;
747                                                                 state.onchange();
748                                                         }
749                                                         if(!county.value) {
750                                                                 county.value = info.county;
751                                                                 county.onchange();
752                                                         }
753                                                         if(!city.value) {
754                                                                 city.value = info.city;
755                                                                 city.onchange();
756                                                         }
757                                                 }
758                                         );
759                                         req.send();
760                                 }
761                         }
762                 },
763                 { 
764                         required : false,
765                         object  : address, 
766                         key             : 'within_city_limits',
767                         widget  : {
768                                 base    : row,
769                                 name    : 'ue_addr_inc_yes',
770                                 type    : 'checkbox',
771                                 disabled : disabled,
772                         }
773                 },
774                 { 
775                         required : false,
776                         object  : address, 
777                         key             : 'valid',
778                         widget  : {
779                                 base    : row,
780                                 name    : 'ue_addr_valid_yes',
781                                 type    : 'checkbox',
782                                 disabled : disabled,
783                         }
784                 }
785         ];
786
787         for( var f in fields ) {
788                 dataFields.push(fields[f]);
789                 uEditActivateField(fields[f]);
790         }
791 }
792
793 function uEditBuildPatronSCM(patron) {
794         /* get the list of pre-defined maps */
795         var fields = uEditFindFieldsByKey('stat_cat_entry');
796         var map;
797         var newmaps = [];
798
799         /* for each user stat cat, pop it off the list,
800         updated the existing stat map field to match
801         the popped map and shove the existing stat
802         map field onto the user's list of stat maps */
803         while( (map = patron.stat_cat_entries().pop()) ) {
804
805                 var field = grep(fields, 
806                         function(item) {
807                                 return (item.object.stat_cat() == map.stat_cat());
808                         }
809                 );
810
811                 if(field) {
812                         var val = map.stat_cat_entry();
813                         field = field[0];
814                         $n(field.widget.base, field.widget.name).value = val;
815                         setSelector($n(field.widget.base, 'ue_stat_cat_selector'), val );
816                         field.object.stat_cat_entry(val);
817                         field.object.id(map.id());
818                         newmaps.push(field.object);
819                 }
820         }
821
822         for( var m in newmaps ) 
823                 patron.stat_cat_entries().push(newmaps[m]);
824 }
825
826
827 function uEditBuildSCMField(statcat, row) {
828
829         var map = new actscecm();
830         map.stat_cat(statcat.id());
831         map.target_usr(patron.id());
832
833         var field = {
834                 required : false,
835                 object  : map,
836                 key             : 'stat_cat_entry',
837                 widget  : {
838                         base    : row,
839                         name    : 'ue_stat_cat_newval',
840                         type    : 'input',
841
842                         onpostchange : function( field, newval ) {
843
844                                 /* see if the current map already resides in 
845                                         the patron entry list */
846                                 var exists = grep( patron.stat_cat_entries(),
847                                         function(item) {
848                                                 return (item.stat_cat() == statcat.id()); 
849                                         }
850                                 );
851
852                                 if(newval) {
853                                         map.isdeleted(0);
854                                         setSelector($n(row, 'ue_stat_cat_selector'), newval);
855                                 }
856
857                                 if(exists) {
858                                         if(!newval) {
859
860                                                 /* if the map is new but currently contains no value
861                                                         remove it from the set of new maps */
862                                                 if(map.isnew()) {
863                                                         patron.stat_cat_entries(
864                                                                 grep( patron.stat_cat_entries(),
865                                                                         function(item) {
866                                                                                 return (item.stat_cat() != map.stat_cat());
867                                                                         }
868                                                                 )
869                                                         );
870
871                                                 } else {
872                                                         map.isdeleted(1);
873                                                         map.ischanged(0);
874                                                 }
875                                         } 
876
877                                 } else {
878
879                                         /* map does not exist in the map array but now has data */
880                                         if(newval) { 
881                                                 map.isnew(1);
882                                                 patron.stat_cat_entries().push(map);
883                                         }
884                                 }
885                         }
886                 }
887         }
888
889         dataFields.push(field);
890 }
891
892
893
894 /** Run this after a new ident type is selected */
895 function _uEditIdentPostchange(type, field, newval) {
896
897         if(!newval) return;
898
899         /* When the ident type is changed, we change the
900         regex on the ident_value to match the selected type */
901         var vfname = 'ident_value';
902         if(type == 'secondary') vfname = 'ident_value2';
903         var vfield = uEditFindFieldByKey(vfname);
904         var name = identTypesCache[uEditNodeVal(field)].name();
905
906         hideMe($(type+'_ident_ssn_help'));
907         hideMe($(type+'_ident_dl_help'));
908
909         if(name.match(/ssn/i)) {
910                 vfield.widget.regex = ssnRegex;
911                 vfield.errkey = 'ue_bad_ident_ssn';
912                 unHideMe($(type+'_ident_ssn_help'));
913
914         } else {
915
916                 if(name.match(/driver/i)) {
917                         vfield.widget.regex = dlRegex;
918                         vfield.errkey = 'ue_bad_ident_dl';
919                         unHideMe($(type+'_ident_dl_help'));
920                         if(!uEditNodeVal(vfield))
921                                 vfield.widget.node.value = defaultState + '-';
922
923                 } else {
924                         vfield.widget.regex = null;
925                         vfield.errkey = null;
926                 }
927         }
928
929         /* focus then valdate the value field */
930         vfield.widget.node.onchange();
931         vfield.widget.node.focus();
932 }
933
934
935 /* checks to see if the given address is shared by others.
936  * if so, the address row is styled and ...
937  */
938
939 function uEditCheckSharedAddr(patron, address, tbody, row) {
940
941         if( address.isnew() || (patron.isnew() && !clone) ) return;
942
943         var req = new Request(FETCH_ADDR_MEMS, SESSION, address.id());
944         req.callback( 
945                 function(r) {
946
947                         var members = r.getResultObject();
948                         var shared = false;
949
950                         for( var m in members ) {
951                                 var id = members[m];
952
953                                 if( id != patron.id() ) {
954
955                                         addCSSClass(row.getElementsByTagName('table')[0], 'shared_address');
956                                         unHideMe($n(row, 'shared_row'));
957                                         $n(row, 'ue_addr_delete').disabled = true;
958
959                                         if( address.usr() != patron.id() ) {
960                                                 var button = $n(row, 'ue_addr_detach');
961                                                 unHideMe(button);
962                                                 button.onclick = 
963                                                         function() { uEditDeleteAddr( tbody, row, address, true ); }
964                                         }
965
966                                         shared = true;
967                                         break;
968                                 }
969                         }
970
971                         if( shared ) {
972
973                                 /* if this is a shared address, set the owner field and 
974                                         give the staff a chance to edit the owner if it's not this user */
975
976                                 var nnode = $n(row, 'addr_owner_name');
977                                 var link = $n(row, 'addr_owner');
978                                 var id = address.usr();
979                         
980                                 if( id == patron.id() ) {
981                         
982                                         nnode.appendChild(text(
983                                                 patron.first_given_name() + ' ' + patron.family_name()));
984                                         hideMe($n(row, 'owner_link_div'));
985                         
986                                 } else {
987                         
988                                         link.onclick = 
989                                                 function() { window.xulG.spawn_editor({ses:cgi.param('ses'),usr:id}) };
990                                 
991                                         if( userCache[id] ) {
992                                                 nnode.appendChild(text(
993                                                         usr.first_given_name() + ' ' +  usr.family_name()));
994                                 
995                                         } else {
996                                 
997                                                 fetchFleshedUser( id, 
998                                                         function(usr) {
999                                                                 userCache[usr.id()] = usr;
1000                                                                 nnode.appendChild(text(
1001                                                                         usr.first_given_name() + ' ' + usr.family_name()));
1002                                                         }
1003                                                 );
1004                                         }
1005                                 }
1006                         }
1007                 }
1008         );
1009
1010         req.send();
1011 }
1012
1013
1014
1015
1016 var __lastdob;
1017 function uEditCheckDOB(field) {
1018
1019         var dob = uEditNodeVal(field);
1020
1021         /* don't bother if the data isn't valid */
1022         if(!dob || !dob.match(field.widget.regex)) 
1023                 return;
1024
1025         if( dob == __lastdob ) return;
1026
1027         __lastdob = dob;
1028
1029         var parts = dob.split(/-/);
1030         var d = new Date( parts[0], parts[1] - 1, parts[2] );
1031
1032         dob = buildDate( parts[0], parts[1], parts[2] );
1033
1034         var today = new Date();
1035
1036         if(!dob || dob > today) {
1037                 addCSSClass(field.widget.node, CSS_INVALID_DATA);
1038                 alertId('ue_bad_date');
1039                 return;
1040         }
1041
1042         var base = new Date();
1043         base.setYear( today.getYear() + 1900 - ADULT_AGE );
1044
1045         /* patron is at least 18 */
1046
1047         var f = uEditFindFieldByKey('ident_value2');
1048
1049         if( dob < base ) { /* patron is of age */
1050                 f.required = false;
1051                 hideMe(f.widget.node.parentNode.parentNode.parentNode);
1052                 return;
1053         }
1054
1055         unHideMe(f.widget.node.parentNode.parentNode.parentNode);
1056         f.required = true;
1057         uEditCheckErrors();
1058 }
1059
1060
1061