<div ng-if="patron_id"
class="strong-text-2">[% l('Patron Edit') %]</div>
-
<div id="reg-alert-pane">
<div id="reg-dupe-links">
</div>
</div>
-
-[% MACRO formfield(cls, field, path, input_type) BLOCK;
-
- # input field generator for common text/number/checkbox fields
-
- IF NOT input_type; input_type = 'text'; END %]
-
-<div class="row reg-field-row"
- ng-show="show_field('[% cls _ '.' _ field %]')">
-
+[% MACRO draw_field_label (cls, field) BLOCK %]
<div class="col-md-3 reg-field-label"> <!-- field label -->
-
<label>{{idl_fields.[% cls %].[% field %].label}}</label>
-
<!-- field documentation img/link -->
<img ng-show="field_doc.[% cls %].[% field %]"
ng-click="set_selected_field_doc('[% cls %]','[% field %]')"
src='[% DOC_IMG %]'></img>
</div>
+[% END %]
- <div class="col-md-3 reg-field-input"> <!-- field form input -->
- [% model = path ? 'patron.' _ path _ '.' _ field : 'patron.' _ field %]
+[%
+# draws a vanilla form input field for inputs that require no
+# special additions.
+MACRO draw_form_input(cls, field, path, type, disable) BLOCK;
+ IF !type; type = 'text'; END;
+ base_obj = path ? 'patron.' _ path : 'patron';
+ model = base_obj _ '.' _ field;
+%]
+ <div class="col-md-3 reg-field-input">
+ <input
+ type="[% type %]"
+ class="form-control"
+ name="[% model %]"
+ ng-change="field_modified()"
+ ng-required="field_required('[% cls %]', '[% field %]')"
+ ng-blur="handle_field_changed([% base_obj %], '[% field %]')"
+ ng-pattern="field_pattern('[% cls %]', '[% field %]')"
+ [% IF disable %]ng-disabled="[% disable %]"[% END %]
+ ng-model="[% model %]"/>
+ </div>
+[% END %]
- [% IF input_type == 'checkbox' %]
+[% MACRO draw_example_text(cls, field) BLOCK;
+ set_str = "org_settings['ui.patron.edit." _ cls _ "." _ field _ ".example']";
+%]
+ <span ng-if="[% set_str %]">
+ [% l('Example: [_1]', '{{' _ set_str _ '}}') %]
+ </span>
+[% END %]
- <div class='checkbox'>
- <input type='checkbox' ng-model='[% model %]'/>
+<!-- progress dialog displayed as we await all data to finish loading -->
+<div class="row" ng-show="!page_data_loaded">
+ <div class="col-md-6 pad-vert">
+ <div class="progress progress-striped active">
+ <div class="progress-bar" role="progressbar" aria-valuenow="100"
+ aria-valuemin="0" aria-valuemax="100" style="width: 100%">
+ <span class="sr-only">[% l('Loading...') %]</span>
+ </div>
</div>
+ </div>
+</div>
- [% ELSE %]
- <!-- text / number input -->
-
- [% IF field == 'alert_message' %]
- <textarea ng-change="field_modified()"
- class="form-control" ng-model="[% model %]"/>
- [% ELSIF field == 'post_code' %]
- <input type="text" ng-change="field_modified()"
- ng-blur="post_code_changed(patron.[% path %])"
- class="form-control" ng-model="[% model %]"/>
- [% ELSIF field == 'barcode' %]
+<!--
+MAIN FORM
+This div wraps the entire form so we can hide it until all needed data
+has been loaded. Setting ng-form and a name lets us refer to fields
+within the "form" by name for validation.
+-->
+<div ng-form id="patron-reg-container"
+ name="reg_form" ng-show="page_data_loaded">
+
+<!-- BARCODE -->
+
+<div class="row reg-field-row" ng-show="show_field('ac.barcode')">
+ [% draw_field_label('ac', 'barcode') %]
+ <div class="col-md-3 reg-field-input"> <!-- field form input -->
<input type="text"
+ name="barcode"
+ ng-model="patron.card.barcode"
+ ng-pattern="field_pattern('ac', 'barcode')"
+ ng-required="field_required('ac', 'barcode')"
focus-me="focus_bc"
ng-change="field_modified()"
ng-disabled="disable_bc"
- ng-blur="barcode_changed(patron.card.barcode)"
- class="form-control" ng-model="[% model %]"/>
- [% ELSIF field == 'usrname' %]
- <input type="text"
- focus-me="focus_usrname"
- ng-change="field_modified()"
- ng-blur="usrname_changed(patron.usrname)"
- class="form-control" ng-model="[% model %]"/>
- [% ELSIF field == 'day_phone' %]
- <input type="text"
- ng-blur="day_phone_changed(patron.day_phone)"
- ng-change="field_modified()"
- class="form-control" ng-model="[% model %]"/>
- [% ELSIF field.match('phone') %]
- <input type="text"
- ng-change="field_modified()"
- ng-blur="dupe_value_changed('phone', patron.[% field %])"
- class="form-control" ng-model="[% model %]"/>
- [% ELSIF field.match('ident_value') %]
- <input type="text"
- ng-change="field_modified()"
- ng-blur="dupe_value_changed('ident', patron.[% field %])"
- class="form-control" ng-model="[% model %]"/>
- [% ELSIF field == 'first_given_name' OR field == 'family_name' %]
- <input type="text"
- ng-change="field_modified()"
- ng-blur="dupe_value_changed('name', patron.[% field %])"
- class="form-control" ng-model="[% model %]"/>
- [% ELSIF field == 'email' %]
- <input type="[% input_type %]"
- ng-change="field_modified()"
- ng-blur="dupe_value_changed('email', patron.email)"
- class="form-control" ng-model="[% model %]"/>
- [% ELSIF field.match('street') OR field == 'city' %]
- <!-- note: passing address object to dupe_value_changed -->
- <input type="[% input_type %]"
- ng-change="field_modified()"
- ng-blur="dupe_value_changed('address', patron.[% path %])"
- class="form-control" ng-model="[% model %]"/>
- [% ELSE %]
- <input type="[% input_type %]"
- ng-change="field_modified()"
- class="form-control" ng-model="[% model %]"/>
- [% END %]
- [% END %]
-
+ class="form-control"
+ ng-blur="handle_field_changed(patron.card, 'barcode')"/>
</div>
-
- <!-- supplemental actions and example text -->
<div class="col-md-6 patron-reg-example">
-
- [% IF field == 'barcode' %]
-
<button class="btn btn-default" ng-show="!patron.isnew"
ng-click="replace_card()">[% l('Replace Barcode') %]</button>
<button class="btn btn-default"
ng-click="cards_dialog()">[% l('See All') %]</button>
+ <div ng-show="dupe_barcode" class="patron-reg-validation-alert">
+ <span>[% l('Barcode is already in use') %]</span>
+ </div>
+ </div>
+</div>
- [% ELSIF field == 'passwd' %]
+<!-- USRNAME -->
- <button class="btn btn-default" ng-click="generate_password()">
- [% l('Generate Password') %]</button>
+<div class="row reg-field-row" ng-show="show_field('au.usrname')">
+ [% draw_field_label('au', 'usrname') %]
+ <div class="col-md-3 reg-field-input">
+ <input type="text"
+ name='usrname'
+ ng-required="field_required('au', 'usrname')"
+ focus-me="focus_usrname"
+ ng-change="field_modified()"
+ ng-pattern="field_pattern('au', 'usrname')"
+ ng-blur="handle_field_changed(patron, 'usrname')"
+ class="form-control"
+ ng-model="patron.usrname"/>
+ </div>
+ <div class="col-md-6 patron-reg-example">
+ <div ng-show="dupe_username" class="patron-reg-validation-alert">
+ <span>[% l('Username is already in use') %]</span>
+ </div>
+ </div>
+</div>
- [% ELSE %]
+<!-- PASSWD -->
- <!-- invalidate buttons -->
+<div class="row reg-field-row" ng-show="show_field('au.passwd')">
+ [% draw_field_label('au', 'passwd') %]
+ [% draw_form_input('au', 'passwd'); %]
+ <div class="col-md-6 patron-reg-example">
+ <button class="btn btn-default" ng-click="generate_password()">
+ [% l('Generate Password') %]</button>
+ </div>
+</div>
- [% IF field.match('phone') OR field.match('email') %]
- <button ng-show="patron.[% field %] && !patron.isnew"
- class="btn btn-default"
- ng-click="invalidate_field('[% field %]')">
- [% l('Invalidate') %]
- </button>
- [% END %]
+<!-- PREFIX -->
- <!-- example strings -->
+<div class="row reg-field-row" ng-show="show_field('au.prefix')">
+ [% draw_field_label('au', 'prefix') %]
+ [% draw_form_input('au', 'prefix'); %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('au', 'prefix') %]
+ </div>
+</div>
- [% set_str = "org_settings['ui.patron.edit." _
- cls _ "." _ field _ ".example']"; %]
+<!-- FIRST_GIVEN_NAME -->
- <span ng-if="[% set_str %]">
- [% l('Example: [_1]', "{{" _ set_str _ "}}") %]
- </span>
+<div class="row reg-field-row" ng-show="show_field('au.first_given_name')">
+ [% draw_field_label('au', 'first_given_name') %]
+ [% draw_form_input('au', 'first_given_name'); %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('au', 'first_given_name') %]
+ </div>
+</div>
- <!-- phones have a fall-through example strings -->
- [% IF field.match('phone') %]
- <span ng-if="![% set_str %] && org_settings['ui.patron.edit.phone.example']">
- [% l('Example: [_1]',
- "{{org_settings['ui.patron.edit.phone.example']}}") %]
- </span>
- [% END %]
- [% END %]
+<!-- SECOND_GIVEN_NAME -->
+
+<div class="row reg-field-row" ng-show="show_field('au.second_given_name')">
+ [% draw_field_label('au', 'second_given_name') %]
+ [% draw_form_input('au', 'second_given_name'); %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('au', 'second_given_name') %]
</div>
</div>
-[% END %]
-<!-- progress dialog displayed as we await all data to finish loading -->
-<div class="row" ng-show="!page_data_loaded">
- <div class="col-md-6 pad-vert">
- <div class="progress progress-striped active">
- <div class="progress-bar" role="progressbar" aria-valuenow="100"
- aria-valuemin="0" aria-valuemax="100" style="width: 100%">
- <span class="sr-only">[% l('Loading...') %]</span>
- </div>
- </div>
+<!-- FAMILY_NAME -->
+
+<div class="row reg-field-row" ng-show="show_field('au.family_name')">
+ [% draw_field_label('au', 'family_name') %]
+ [% draw_form_input('au', 'family_name'); %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('au', 'family_name') %]
</div>
</div>
-<!-- this div wraps the entire form so we can hide it
- until all needed data has been loaded -->
-<div ng-show="page_data_loaded"><!-- form wrapper -->
+<!-- SUFFIX -->
-[% formfield('ac', 'barcode', 'card') %]
-[% formfield('au', 'usrname') %]
-[% formfield('au', 'passwd') %]
-[% formfield('au', 'prefix') %]
-[% formfield('au', 'first_given_name') %]
-[% formfield('au', 'second_given_name') %]
-[% formfield('au', 'family_name') %]
-[% formfield('au', 'suffix') %]
-[% formfield('au', 'alias') %]
+<div class="row reg-field-row" ng-show="show_field('au.suffix')">
+ [% draw_field_label('au', 'suffix') %]
+ [% draw_form_input('au', 'suffix'); %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('au', 'suffix') %]
+ </div>
+</div>
-<div class="row reg-field-row" ng-show="show_field('au.dob')">
- <div class="col-md-3 reg-field-label">
- <label>{{idl_fields.au.dob.label}}</label>
- <img ng-show="field_doc.au.dob"
- ng-click="selected_field_doc=field_doc.au.dob"
- src='[% DOC_IMG %]'></img>
+<!-- ALIAS -->
+
+<div class="row reg-field-row" ng-show="show_field('au.alias')">
+ [% draw_field_label('au', 'alias') %]
+ [% draw_form_input('au', 'alias'); %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('au', 'alias') %]
</div>
+</div>
+
+<!-- DOB -->
+
+<div class="row reg-field-row" ng-show="show_field('au.dob')">
+ [% draw_field_label('au', 'dob') %]
<div class="col-md-3 reg-field-input">
<input eg-date-input
+ name="dob"
ng-change="field_modified()"
+ ng-required="field_required('au', 'dob')"
+ ng-blur="handle_field_changed(patron, 'dob')"
class="form-control" ng-model="patron.dob"/>
</div>
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('au', 'dob') %]
+ </div>
</div>
-[% formfield('au', 'juvenile', '', 'checkbox') %]
+<!-- JUVENILE -->
+
+<div class="row reg-field-row" ng-show="show_field('au.juvenile')">
+ [% draw_field_label('au', 'juvenile') %]
+ [% draw_form_input('au', 'juvenile', '', 'checkbox'); %]
+</div>
<!-- ident_type -->
<div class="row reg-field-row" ng-show="show_field('au.ident_type')">
- <div class="col-md-3 reg-field-label">
- <label>{{idl_fields.au.ident_type.label}}</label>
- <img ng-show="field_doc.au.ident_type"
- ng-click="selected_field_doc=field_doc.au.ident_type"
- src='[% DOC_IMG %]'></img>
- </div>
+ [% draw_field_label('au', 'ident_type') %]
<div class="col-md-3 reg-field-input">
- <div class="btn-group" dropdown>
- <button type="button" class="btn btn-default dropdown-toggle">
- <span style="padding-right: 5px;">
- {{patron.ident_type.name() || "[% l('Primary Ident Type') %]"}}
- </span>
- <span class="caret"></span>
- </button>
- <ul class="dropdown-menu">
- <li ng-repeat="type in ident_types">
- <a href ng-click="patron.ident_type = type; field_modified()">
- {{type.name()}}
- </a>
- </li>
- </ul>
- </div>
+ <select
+ class="form-control"
+ ng-model="patron.ident_type"
+ ng-required="field_required('au', 'ident_type')"
+ ng-blur="handle_field_changed(patron, 'ident_type')"
+ ng-options="type.name() for type in ident_types track by type.id()">
+ </select>
</div>
</div>
+<!-- IDENT_VALUE -->
+
+<div class="row reg-field-row" ng-show="show_field('au.ident_value')">
+ [% draw_field_label('au', 'ident_value') %]
+ [% draw_form_input('au', 'ident_value') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('au', 'ident_value') %]
+ </div>
+</div>
+
+<!-- IDENT_VALUE2 -->
+<div class="row reg-field-row" ng-show="show_field('au.ident_value2')">
+ [% draw_field_label('au', 'ident_value2') %]
+ [% draw_form_input('au', 'ident_value2') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('au', 'ident_value2') %]
+ </div>
+</div>
-[% formfield('au', 'ident_value') %]
-[% formfield('au', 'ident_value2') %]
-[% formfield('au', 'email', '', 'email') %]
-[% formfield('au', 'day_phone') %]
-[% formfield('au', 'evening_phone') %]
-[% formfield('au', 'other_phone') %]
+
+<!-- EMAIL -->
+<div class="row reg-field-row" ng-show="show_field('au.email')">
+ [% draw_field_label('au', 'email') %]
+ [% draw_form_input('au', 'email', '', 'email') %]
+ <div class="col-md-6 patron-reg-example">
+ <button ng-show="patron.email && !patron.isnew"
+ class="btn btn-default"
+ ng-click="invalidate_field('email')">[% l('Invalidate') %]</button>
+ <span ng-if="org_settings['ui.patron.edit.au.email.example']">
+ [% l('Example: [_1]',
+ "{{org_settings['ui.patron.edit.au.email.example']}}") %]
+ </span>
+ </div>
+</div>
+
+<!-- DAY_PHONE -->
+
+<div class="row reg-field-row" ng-show="show_field('au.day_phone')">
+ [% draw_field_label('au', 'day_phone') %]
+ [% draw_form_input('au', 'day_phone') %]
+ <div class="col-md-6 patron-reg-example">
+ <button ng-show="patron.day_phone && !patron.isnew"
+ class="btn btn-default"
+ ng-click="invalidate_field('day_phone')">[% l('Invalidate') %]</button>
+ [% draw_example_text('au', 'day_phone') %]
+ <!-- phones have a fall-through example strings -->
+ <span ng-if="!org_settings['ui.patron.edit.au.day_phone.example'] && org_settings['ui.patron.edit.phone.example']">
+ [% l('Example: [_1]',
+ "{{org_settings['ui.patron.edit.phone.example']}}") %]
+ </span>
+ </div>
+</div>
+
+<!-- EVENING_PHONE -->
+
+<div class="row reg-field-row" ng-show="show_field('au.evening_phone')">
+ [% draw_field_label('au', 'evening_phone') %]
+ [% draw_form_input('au', 'evening_phone') %]
+ <div class="col-md-6 patron-reg-example">
+ <button ng-show="patron.evening_phone && !patron.isnew"
+ class="btn btn-default"
+ ng-click="invalidate_field('evening_phone')">[% l('Invalidate') %]</button>
+ [% draw_example_text('au', 'evening_phone') %]
+ <!-- phones have a fall-through example strings -->
+ <span ng-if="!org_settings['ui.patron.edit.au.evening_phone.example'] && org_settings['ui.patron.edit.phone.example']">
+ [% l('Example: [_1]',
+ "{{org_settings['ui.patron.edit.phone.example']}}") %]
+ </span>
+ </div>
+</div>
+
+<!-- OTHER_PHONE -->
+
+<div class="row reg-field-row" ng-show="show_field('au.other_phone')">
+ [% draw_field_label('au', 'other_phone') %]
+ [% draw_form_input('au', 'other_phone') %]
+ <div class="col-md-6 patron-reg-example">
+ <button ng-show="patron.other_phone && !patron.isnew"
+ class="btn btn-default"
+ ng-click="invalidate_field('other_phone')">[% l('Invalidate') %]</button>
+ [% draw_example_text('au', 'other_phone') %]
+ <!-- phones have a fall-through example strings -->
+ <span ng-if="!org_settings['ui.patron.edit.au.other_phone.example'] && org_settings['ui.patron.edit.phone.example']">
+ [% l('Example: [_1]',
+ "{{org_settings['ui.patron.edit.phone.example']}}") %]
+ </span>
+ </div>
+</div>
<!-- home org unit selector -->
<div class="row reg-field-row" ng-show="show_field('au.home_ou')">
- <div class="col-md-3 reg-field-label">
- <label>{{idl_fields.au.home_ou.label}}</label>
- <img ng-show="field_doc.au.home_ou"
- ng-click="selected_field_doc=field_doc.au.home_ou"
- src='[% DOC_IMG %]'></img>
- </div>
- <div class="col-md-3 reg-field-input">
- <eg-org-selector selected="patron.home_ou" onchange="field_modified">
- </eg-org-selector>
+ [% draw_field_label('au', 'home_ou') %]
+ <div class="col-md-3 reg-field-input">
+ <eg-org-selector
+ selected="patron.home_ou"
+ onchange="handle_home_org_changed"
+ disable-test="disable_home_org">
+ </eg-org-selector>
</div>
</div>
<!-- profile selector -->
<div class="row reg-field-row" ng-show="show_field('au.profile')">
- <div class="col-md-3 reg-field-label">
- <label>{{idl_fields.au.profile.label}}</label>
- <img ng-show="field_doc.au.profile"
- ng-click="selected_field_doc=field_doc.au.profile"
- src='[% DOC_IMG %]'></img>
- </div>
+ [% draw_field_label('au', 'profile') %]
<div class="col-md-3 reg-field-input">
<div class="btn-group" dropdown>
- <button type="button" class="btn btn-default dropdown-toggle">
+ <button type="button" class="btn btn-default dropdown-toggle"
+ ng-class="{'ng-invalid' : invalid_profile()}">
<span style="padding-right: 5px;">
{{patron.profile.name() || "[% l('Profile Group') %]"}}
</span>
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
- <li ng-repeat="grp in edit_profiles">
+ <li ng-repeat="grp in edit_profiles"
+ ng-class="{disabled : grp.usergroup() == 'f'}">
<a href
style="padding-left: {{pgt_depth(grp) * 10 + 5}}px"
ng-click="set_profile(grp)">{{grp.name()}}</a>
</div>
</div>
<div class="col-md-3">
- <button class="btn btn-default" ng-disabled="!has_group_link_perm"
+ <button class="btn btn-default" ng-disabled="!perms.CREATE_USER_GROUP_LINK"
ng-click="secondary_groups_dialog()">[% l('Secondary Groups') %]</button>
</div>
</div>
<div class="row reg-field-row" ng-show="show_field('au.expire_date')">
- <div class="col-md-3 reg-field-label">
- <label>{{idl_fields.au.expire_date.label}}</label>
- <img ng-show="field_doc.au.expire_date"
- ng-click="selected_field_doc=field_doc.au.expire_date"
- src='[% DOC_IMG %]'></img>
- </div>
+ [% draw_field_label('au', 'expire_date') %]
<div class="col-md-3 reg-field-input">
<input eg-date-input
- ng-change="field_modified()"
+ ng-blur="handle_field_changed(patron, 'expire_date')"
class="form-control" ng-model="patron.expire_date"/>
</div>
<div class="col-md-3">
<!-- net_access_level -->
<div class="row reg-field-row" ng-show="show_field('au.net_access_level')">
- <div class="col-md-3 reg-field-label">
- <label>{{idl_fields.au.net_access_level.label}}</label>
- <img ng-show="field_doc.au.net_access_level"
- ng-click="selected_field_doc=field_doc.au.net_access_level"
- src='[% DOC_IMG %]'></img>
- </div>
+ [% draw_field_label('au', 'net_access_level') %]
<div class="col-md-3 reg-field-input">
- <div class="btn-group" dropdown>
- <button type="button" class="btn btn-default dropdown-toggle">
- <span style="padding-right: 5px;">
- {{patron.net_access_level.name() || "[% l('Net Access Level') %]"}}
- </span>
- <span class="caret"></span>
- </button>
- <ul class="dropdown-menu">
- <li ng-repeat="level in net_access_levels">
- <a href
- ng-click="patron.net_access_level = level">{{level.name()}}</a>
- </li>
- </ul>
- </div>
+ <select
+ class="form-control"
+ ng-model="patron.net_access_level"
+ ng-required="field_required('au', 'net_access_level')"
+ ng-blur="handle_field_changed(patron, 'net_access_level')"
+ ng-options="level.name() for level in net_access_levels track by level.id()">
+ </select>
+ </div>
+</div>
+
+<!-- ACTIVE -->
+
+<div class="row reg-field-row" ng-show="show_field('au.active')">
+ [% draw_field_label('au', 'active') %]
+ [% draw_form_input('au', 'active', '', 'checkbox') %]
+</div>
+
+<!-- BARRED -->
+
+<div class="row reg-field-row" ng-show="show_field('au.barred')">
+ [% draw_field_label('au', 'barred') %]
+ [% draw_form_input('au', 'barred', '', 'checkbox') %]
+</div>
+
+<!-- MASTER_ACCOUNT -->
+
+<div class="row reg-field-row" ng-show="show_field('au.master_account')">
+ [% draw_field_label('au', 'master_account') %]
+ [% draw_form_input('au', 'master_account', '', 'checkbox') %]
+</div>
+
+<!-- CLAIMS_RETURNED_COUNT -->
+
+<div class="row reg-field-row" ng-show="show_field('au.claims_returned_count')">
+ [% draw_field_label('au', 'claims_returned_count') %]
+ [% draw_form_input('au', 'claims_returned_count',
+ '', 'number', '!perms.UPDATE_PATRON_CLAIM_RETURN_COUNT') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('au', 'claims_returned_count') %]
+ </div>
+</div>
+
+<!-- CLAIMS_NEVER_CHECKED_OUT_COUNT -->
+
+<div class="row reg-field-row" ng-show="show_field('au.claims_never_checked_out_count')">
+ [% draw_field_label('au', 'claims_never_checked_out_count') %]
+ [% draw_form_input('au', 'claims_never_checked_out_count',
+ '', 'number', '!perms.UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('au', 'claims_never_checked_out_count') %]
</div>
</div>
-[% formfield('au', 'active', '', 'checkbox') %]
-[% formfield('au', 'barred', '', 'checkbox') %]
-[% formfield('au', 'master_account', '', 'checkbox') %]
-[% formfield('au', 'claims_returned_count', '', 'number') %]
-[% formfield('au', 'claims_never_checked_out_count', '', 'number') %]
-[% formfield('au', 'alert_message') %]
+<!-- ALERT_MESSAGE -->
+
+<div class="row reg-field-row" ng-show="show_field('au.alert_message')">
+ [% draw_field_label('au', 'alert_message') %]
+ <div class="col-md-3 reg-field-input">
+ <textarea
+ class="form-control"
+ ng-model="patron.alert_message"
+ ng-pattern="field_pattern('au', 'alert_message')"
+ ng-change="field_modified()"
+ ng-blur="handle_field_changed(patron, 'alert_message')">
+ </textarea>
+ </div>
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('au', 'alert_message') %]
+ </div>
+</div>
<div class="alert alert-success row" role="alert">
<div class="col-md-6">[% l('User Settings') %]</div>
</div>
</div>
+<!-- TODO: Add circ.collections.exempt to master SQL seed data -->
+<div class="row reg-field-row"
+ ng-if="user_setting_types['circ.collections.exempt']">
+ <div class="col-md-3 reg-field-label">
+ <label>{{user_setting_types['circ.collections.exempt'].label()}}</label>
+ </div>
+ <div class="col-md-3 reg-field-input">
+ <div class='checkbox'>
+ <input
+ type='checkbox'
+ ng-change="field_modified()"
+ ng-disabled="!perms.UPDATE_PATRON_COLLECTIONS_EXEMPT"
+ ng-model="user_settings['circ.collections.exempt']"/>
+ </div>
+ </div>
+</div>
+
<div class="row reg-field-row">
<div class="col-md-3 reg-field-label">
<label>[% l('Holds Notices') %]</label>
</div>
</div>
- [% formfield('aua', 'address_type', 'addresses[$index]') %]
- [% formfield('aua', 'post_code', 'addresses[$index]') %]
- [% formfield('aua', 'street1', 'addresses[$index]') %]
- [% formfield('aua', 'street2', 'addresses[$index]') %]
- [% formfield('aua', 'city', 'addresses[$index]') %]
- [% formfield('aua', 'county', 'addresses[$index]') %]
- [% formfield('aua', 'state', 'addresses[$index]') %]
- [% formfield('aua', 'country', 'addresses[$index]') %]
- [% formfield('aua', 'valid', 'addresses[$index]', 'checkbox') %]
- [% formfield('aua', 'within_city_limits', 'addresses[$index]', 'checkbox') %]
+ <!-- ADDRESS_TYPE -->
+ <div class="row reg-field-row" ng-show="show_field('aua.address_type')">
+ [% draw_field_label('aua', 'address_type') %]
+ [% draw_form_input('aua', 'address_type', 'addresses[$index]') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('aua', 'address_type') %]
+ </div>
+ </div>
+
+ <!-- POST_CODE -->
+
+ <div class="row reg-field-row" ng-show="show_field('aua.post_code')">
+ [% draw_field_label('aua', 'post_code') %]
+ [% draw_form_input('aua', 'post_code', 'addresses[$index]') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('aua', 'post_code') %]
+ </div>
+ </div>
+
+ <!-- STREET1 -->
+
+ <div class="row reg-field-row" ng-show="show_field('aua.street1')">
+ [% draw_field_label('aua', 'street1') %]
+ [% draw_form_input('aua', 'street1', 'addresses[$index]') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('aua', 'street1') %]
+ </div>
+ </div>
+
+ <!-- STREET2 -->
+
+ <div class="row reg-field-row" ng-show="show_field('aua.street2')">
+ [% draw_field_label('aua', 'street2') %]
+ [% draw_form_input('aua', 'street2', 'addresses[$index]') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('aua', 'street2') %]
+ </div>
+ </div>
+
+ <!-- CITY -->
+
+ <div class="row reg-field-row" ng-show="show_field('aua.city')">
+ [% draw_field_label('aua', 'city') %]
+ [% draw_form_input('aua', 'city', 'addresses[$index]') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('aua', 'city') %]
+ </div>
+ </div>
+
+ <!-- COUNTY -->
+
+ <div class="row reg-field-row" ng-show="show_field('aua.county')">
+ [% draw_field_label('aua', 'county') %]
+ [% draw_form_input('aua', 'county', 'addresses[$index]') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('aua', 'county') %]
+ </div>
+ </div>
+
+ <!-- STATE -->
+
+ <div class="row reg-field-row" ng-show="show_field('aua.state')">
+ [% draw_field_label('aua', 'state') %]
+ [% draw_form_input('aua', 'state', 'addresses[$index]') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('aua', 'state') %]
+ </div>
+ </div>
+
+ <!-- COUNTRY -->
+
+ <div class="row reg-field-row" ng-show="show_field('aua.country')">
+ [% draw_field_label('aua', 'country') %]
+ [% draw_form_input('aua', 'country', 'addresses[$index]') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('aua', 'country') %]
+ </div>
+ </div>
+
+ <!-- VALID -->
+
+ <div class="row reg-field-row" ng-show="show_field('aua.valid')">
+ [% draw_field_label('aua', 'valid') %]
+ [% draw_form_input('aua', 'valid', 'addresses[$index]', 'checkbox') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('aua', 'valid') %]
+ </div>
+ </div>
+
+ <!-- WITHIN_CITY_LIMITS -->
+
+ <div class="row reg-field-row" ng-show="show_field('aua.within_city_limits')">
+ [% draw_field_label('aua', 'within_city_limits') %]
+ [% draw_form_input('aua', 'within_city_limits', 'addresses[$index]', 'checkbox') %]
+ <div class="col-md-6 patron-reg-example">
+ [% draw_example_text('aua', 'within_city_limits') %]
+ </div>
+ </div>
<div class="row" ng-if="$last">
<button type="button" ng-click="new_address()"
return last + ', ' + first + (middle ? ' ' + middle : '');
}
+ service.check_dupe_username = function(usrname) {
+ return egCore.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.username.exists',
+ egCore.auth.token(), usrname);
+ }
+
//service.check_grp_app_perm = function(grp_id) {
// determine which user groups our user is not allowed to modify
);
}
- service.has_group_link_perms = function(org_id) {
- return egCore.perm.hasPermAt('CREATE_USER_GROUP_LINK', true)
- .then(function(p) { return p.indexOf(org_id) > -1; });
+ // resolves to a hash of perm-name => boolean value indicating
+ // wether the user has the permission at org_id.
+ service.has_perms_for_org = function(org_id) {
+
+ var perms_needed = [
+ 'UPDATE_USER',
+ 'CREATE_USER',
+ 'CREATE_USER_GROUP_LINK',
+ 'UPDATE_PATRON_COLLECTIONS_EXEMPT',
+ 'UPDATE_PATRON_CLAIM_RETURN_COUNT',
+ 'UPDATE_PATRON_CLAIM_NEVER_CHECKED_OUT_COUNT',
+ 'UPDATE_PATRON_ACTIVE_CARD',
+ 'UPDATE_PATRON_PRIMARY_CARD'
+ ];
+
+ return egCore.perm.hasPermAt(perms_needed, true)
+ .then(function(perm_map) {
+
+ angular.forEach(perms_needed, function(perm) {
+ perm_map[perm] =
+ Boolean(perm_map[perm].indexOf(org_id) > -1);
+ });
+
+ return perm_map;
+ });
}
service.get_surveys = function() {
service.dupe_patron_search = function(patron, type, value) {
var search;
- console.log('Dupe search called with "' +
- type +"' and value " + value);
+ console.log('Dupe search called with "'+ type +'" and value '+ value);
switch (type) {
case 'name':
var fname = patron.first_given_name;
var lname = patron.family_name;
- if (!(fname && lname)) return;
+ if (!(fname && lname)) return $q.when({count:0});
search = {
first_given_name : {value : fname, group : 0},
family_name : {value : lname, group : 0}
_is_mailing : true,
_is_billing : true,
within_city_limits : false,
- stat_cat_entries : []
+ country : service.org_settings['ui.patron.default_country'],
};
var card = {
card : card,
cards : [card],
home_ou : egCore.org.get(egCore.auth.user().ws_ou()),
+ stat_cat_entries : [],
addresses : [addr]
};
'open-ils.actor.patron.settings.update',
egCore.auth.token(), new_user.id(), settings
).then(function(resp) {
- console.log('settings returned ' + resp);
return resp;
});
}
+ // Applies field-specific validation regex's from org settings
+ // to form fields. Be careful not remove any pattern data we
+ // are not explicitly over-writing in the provided patterns obj.
+ service.set_field_patterns = function(patterns) {
+ if (service.org_settings['opac.username_regex']) {
+ patterns.au.usrname =
+ new RegExp(service.org_settings['opac.username_regex']);
+ }
+
+ if (service.org_settings['opac.barcode_regex']) {
+ patterns.ac.barcode =
+ new RegExp(service.org_settings['opac.barcode_regex']);
+ }
+
+ if (service.org_settings['global.password_regex']) {
+ patterns.au.passwd =
+ new RegExp(service.org_settings['global.password_regex']);
+ }
+
+ var phone_reg = service.org_settings['ui.patron.edit.phone.regex'];
+ if (phone_reg) {
+ // apply generic phone regex first, replace below as needed.
+ patterns.au.day_phone = new RegExp(phone_reg);
+ patterns.au.evening_phone = new RegExp(phone_reg);
+ patterns.au.other_phone = new RegExp(phone_reg);
+ }
+
+ // the remaining patterns fit a well-known key name pattern
+
+ angular.forEach(service.org_settings, function(val, key) {
+ if (!val) return;
+ var parts = key.match(/ui.patron.edit\.(\w+)\.(\w+)\.regex/);
+ if (!parts) return;
+ var cls = parts[1];
+ var name = parts[2];
+ patterns[cls][name] = new RegExp(val);
+ });
+ }
+
return service;
}]);
$scope.focus_bc = !Boolean($scope.patron_id);
$scope.dupe_counts = {};
+ // map of perm name to true/false for perms the logged in user
+ // has at the currently selected patron home org unit.
+ $scope.perms = {};
+
if (!$scope.edit_passthru) {
// in edit more, scope.edit_passthru is delivered to us by
// the enclosing controller. In register mode, there is
// 0=all, 1=suggested, 2=all
$scope.edit_passthru.vis_level = 0;
- // TODO: add save/clone handlers here
-
- $scope.field_modified = function() {
- // Call attach with every field change, regardless of whether
- // it's been called before. This will allow for re-attach after
- // the user clicks through the unload warning. egUnloadPrompt
- // will ensure we only attach once.
- egUnloadPrompt.attach($scope);
- }
// Apply default values for new patrons during initial registration
// prs is shorthand for patronSvc
}
}
- function handle_home_org_changed() {
- org_id = $scope.patron.home_ou.id();
-
- patronRegSvc.has_group_link_perms(org_id)
- .then(function(bool) {$scope.has_group_link_perm = bool});
+ // A null or undefined pattern leads to exceptions. Before the
+ // patterns are loaded from the server, default all patterns
+ // to an innocuous regex. To avoid re-creating numerous
+ // RegExp objects, cache the stub RegExp after initial creation.
+ // note: angular docs say ng-pattern accepts a regexp or string,
+ // but as of writing, it only works with a regexp object.
+ // (Likely an angular 1.2 vs. 1.4 issue).
+ var field_patterns = {au : {}, ac : {}, aua : {}};
+ $scope.field_pattern = function(cls, field) {
+ if (!field_patterns[cls][field])
+ field_patterns[cls][field] = new RegExp('.*');
+ return field_patterns[cls][field];
}
+ // Main page load function. Kicks off tab init and data loading.
$q.all([
$scope.initTab ? // initTab comes from patron app
});
extract_hold_notify();
- handle_home_org_changed();
+ $scope.handle_home_org_changed();
if ($scope.org_settings['ui.patron.edit.default_suggested'])
$scope.edit_passthru.vis_level = 1;
$scope.page_data_loaded = true;
+ prs.set_field_patterns(field_patterns);
});
// update the currently displayed field documentation
};
// field visibility cache. Some fields are universally required.
+ // 3 == value universally required
+ // 2 == field is visible by default
+ // 1 == field is suggested by default
var field_visibility = {
- 'ac.barcode' : 2,
- 'au.usrname' : 2,
- 'au.passwd' : 2,
- // TODO passwd2 2,
- 'au.first_given_name' : 2,
- 'au.family_name' : 2,
- 'au.ident_type' : 2,
- 'au.home_ou' : 2,
- 'au.profile' : 2,
- 'au.expire_date' : 2,
- 'au.net_access_level' : 2,
- 'aua.address_type' : 2,
- 'aua.post_code' : 2,
- 'aua.street1' : 2,
+ 'ac.barcode' : 3,
+ 'au.usrname' : 3,
+ 'au.passwd' : 3,
+ 'au.first_given_name' : 3,
+ 'au.family_name' : 3,
+ 'au.ident_type' : 3,
+ 'au.home_ou' : 3,
+ 'au.profile' : 3,
+ 'au.expire_date' : 3,
+ 'au.net_access_level' : 3,
+ 'aua.address_type' : 3,
+ 'aua.post_code' : 3,
+ 'aua.street1' : 3,
'aua.street2' : 2,
- 'aua.city' : 2,
+ 'aua.city' : 3,
'aua.county' : 2,
'aua.state' : 2,
- 'aua.country' : 2,
+ 'aua.country' : 3,
'aua.valid' : 2,
'aua.within_city_limits' : 2,
'stat_cats' : 1,
var sug_set = 'ui.patron.edit.' + field_key + '.suggest';
if ($scope.org_settings[req_set]) {
- field_visibility[field_key] = 2;
+ field_visibility[field_key] = 3;
} else if ($scope.org_settings[sho_set]) {
field_visibility[field_key] = 2;
} else if ($scope.org_settings[sug_set]) {
return field_visibility[field_key] >= $scope.edit_passthru.vis_level;
}
+ // See $scope.show_field().
+ // A field with visbility level 3 means it's required.
+ $scope.field_required = function(cls, field) {
+
+ // Value in the password field is not required
+ // for existing patrons.
+ if (field == 'passwd' && $scope.patron && !$scope.patron.isnew)
+ return false;
+
+ return (field_visibility[cls + '.' + field] == 3);
+ }
+
// generates a random 4-digit password
$scope.generate_password = function() {
$scope.patron.passwd = Math.floor(Math.random()*9000) + 1000;
$scope.field_modified();
}
+ $scope.invalid_profile = function() {
+ return !(
+ $scope.patron &&
+ $scope.patron.profile &&
+ $scope.patron.profile.usergroup() == 't'
+ );
+ }
+
$scope.new_address = function() {
var addr = egCore.idl.toHash(new egCore.idl.aua());
patronRegSvc.ingest_address($scope.patron, addr);
addr.isnew = true;
addr.valid = true;
addr.within_city_limits = true;
+ addr.country = $scope.org_settings['ui.patron.default_country'];
$scope.patron.addresses.push(addr);
}
$scope.barcode_changed = function(bc) {
if (!bc) return;
+ $scope.dupe_barcode = false;
egCore.net.request(
'open-ils.actor',
'open-ils.actor.barcode.exists',
egCore.auth.token(), bc
).then(function(resp) {
if (resp == '1') {
+ $scope.dupe_barcode = true;
console.log('duplicate barcode detected: ' + bc);
// DUPLICATE CARD
} else {
$modal.open({
templateUrl: './circ/patron/t_patron_cards_dialog',
controller:
- ['$scope','$modalInstance','cards',
- function($scope , $modalInstance , cards) {
+ ['$scope','$modalInstance','cards', 'perms',
+ function($scope , $modalInstance , cards, perms) {
// scope here is the modal-level scope
$scope.args = {cards : cards};
+ $scope.perms = perms;
$scope.ok = function() { $modalInstance.close($scope.args) }
$scope.cancel = function () { $modalInstance.dismiss() }
}],
cards : function() {
// scope here is the controller-level scope
return $scope.patron.cards;
+ },
+ perms : function() {
+ return $scope.perms;
}
}
}).result.then(
patronRegSvc.invalidate_field($scope.patron, field);
}
+
$scope.dupe_value_changed = function(type, value) {
$scope.dupe_counts[type] = 0;
patronRegSvc.dupe_patron_search($scope.patron, type, value)
.then(function(res) {
$scope.dupe_counts[type] = res.count;
- $scope.dupe_search_encoded =
- encodeURIComponent(js2JSON(res.search));
+ if (res.count) {
+ $scope.dupe_search_encoded =
+ encodeURIComponent(js2JSON(res.search));
+ } else {
+ $scope.dupe_search_encoded = '';
+ }
+ });
+ }
+
+ $scope.handle_home_org_changed = function() {
+ org_id = $scope.patron.home_ou.id();
+ patronRegSvc.has_perms_for_org(org_id).then(function(map) {
+ angular.forEach(map, function(v, k) { $scope.perms[k] = v });
});
}
+ // This is called with every character typed in a form field,
+ // since that's the only way to gaurantee something has changed.
+ // See handle_field_changed for ng-change vs. ng-blur.
+ $scope.field_modified = function() {
+ // Call attach with every field change, regardless of whether
+ // it's been called before. This will allow for re-attach after
+ // the user clicks through the unload warning. egUnloadPrompt
+ // will ensure we only attach once.
+ egUnloadPrompt.attach($scope);
+ }
+
+ // obj could be the patron, an address, etc.
+ // This is called any time a form field achieves then loses focus.
+ // It does not necessarily mean the field has changed.
+ // The alternative is ng-change, but it's called with each character
+ // typed, which would be overkill for many of the actions called here.
+ $scope.handle_field_changed = function(obj, field_name) {
+ var cls = obj.classname; // set by egIdl
+ var value = obj[field_name];
+
+ console.log('changing field ' + field_name + ' to ' + value);
+
+ switch (field_name) {
+ case 'day_phone' :
+ if ($scope.patron.day_phone &&
+ $scope.patron.isnew &&
+ $scope.org_settings['patron.password.use_phone']) {
+ $scope.patron.passwd = phone.substr(-4);
+ }
+ case 'evening_phone' :
+ case 'other_phone' :
+ $scope.dupe_value_changed('phone', value);
+ break;
+
+ case 'ident_value':
+ case 'ident_value2':
+ $scope.dupe_value_changed('ident', value);
+ break;
+
+ case 'first_given_name':
+ case 'family_name':
+ $scope.dupe_value_changed('name', value);
+ break;
+
+ case 'email':
+ $scope.dupe_value_changed('email', value);
+ break;
+
+ case 'street1':
+ case 'street2':
+ case 'city':
+ // dupe search on address wants the address object as the value.
+ $scope.dupe_value_changed('address', obj);
+ break;
+
+ case 'post_code':
+ $scope.post_code_changed(obj);
+ break;
+
+ case 'usrname':
+ patronRegSvc.check_dupe_username(value)
+ .then(function(yes) {$scope.dupe_username = Boolean(yes)});
+ break;
+
+ case 'barcode':
+ // TODO: finish barcode_changed handler.
+ $scope.barcode_changed(value);
+ break;
+
+ case 'dob':
+ maintain_juvenile_flag();
+ break;
+ }
+ }
+
+ // patron.juvenile is set to true if the user was born after
+ function maintain_juvenile_flag() {
+ if ( !($scope.patron && $scope.patron.dob) ) return;
+
+ var juv_interval =
+ $scope.org_settings['global.juvenile_age_threshold']
+ || '18 years';
+
+ var base = new Date();
+
+ base.setTime(base.getTime() -
+ Number(egCore.date.intervalToSeconds(juv_interval) + '000'));
+
+ $scope.patron.juvenile = ($scope.patron.dob > base);
+ }
+
+ // returns true (disable) for orgs that cannot have users.
+ $scope.disable_home_org = function(org_id) {
+ if (!org_id) return;
+ var org = egCore.org.get(org_id);
+ return (
+ org &&
+ org.ou_type() &&
+ org.ou_type().can_have_users() == 'f'
+ );
+ }
+
+ // Returns true if any input elements are tagged as invalid
+ $scope.edit_passthru.has_invalid_fields = function() {
+ return $('#patron-reg-container .ng-invalid').length > 0;
+ }
+
+ // Returns true if the Save and Save & Clone buttons should be disabled.
+ $scope.edit_passthru.hide_save_actions = function() {
+ var can_save = $scope.patron.isnew ?
+ $scope.perms.CREATE_USER : $scope.perms.UPDATE_USER;
+
+ return (
+ !can_save ||
+ $scope.dupe_username ||
+ $scope.dupe_barcode ||
+ $scope.edit_passthru.has_invalid_fields()
+ );
+ }
+
$scope.edit_passthru.save = function(save_args) {
if (!save_args) save_args = {};