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