JBAS-1132 Self-register UI, KCLS-style
authorBill Erickson <berickxx@gmail.com>
Mon, 14 Mar 2016 14:41:59 +0000 (10:41 -0400)
committerBill Erickson <berickxx@gmail.com>
Thu, 21 Mar 2019 19:46:23 +0000 (15:46 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
KCLS/openils/var/templates_kcls/opac/parts/state_selector.tt2 [new file with mode: 0644]
KCLS/openils/var/templates_kcls/opac/register.tt2 [new file with mode: 0644]
Open-ILS/web/js/ui/default/opac/register.js [new file with mode: 0644]

diff --git a/KCLS/openils/var/templates_kcls/opac/parts/state_selector.tt2 b/KCLS/openils/var/templates_kcls/opac/parts/state_selector.tt2
new file mode 100644 (file)
index 0000000..d3818cf
--- /dev/null
@@ -0,0 +1,59 @@
+
+[% BLOCK state_selector %]
+<select name="[% name %]" id="[% id %]">
+<option value="Alabama">Alabama</option>
+<option value="Alaska">Alaska</option>
+<option value="Arizona">Arizona</option>
+<option value="Arkansas">Arkansas</option>
+<option value="California">California</option>
+<option value="Colorado">Colorado</option>
+<option value="Connecticut">Connecticut</option>
+<option value="Delaware">Delaware</option>
+<option value="District of Columbia">District of Columbia</option>
+<option value="Florida">Florida</option>
+<option value="Georgia">Georgia</option>
+<option value="Hawaii">Hawaii</option>
+<option value="Idaho">Idaho</option>
+<option value="Illinois">Illinois</option>
+<option value="Indiana">Indiana</option>
+<option value="Iowa">Iowa</option>
+<option value="Kansas">Kansas</option>
+<option value="Kentucky">Kentucky</option>
+<option value="Louisiana">Louisiana</option>
+<option value="Maine">Maine</option>
+<option value="Maryland">Maryland</option>
+<option value="Massachusetts">Massachusetts</option>
+<option value="Michigan">Michigan</option>
+<option value="Minnesota">Minnesota</option>
+<option value="Mississippi">Mississippi</option>
+<option value="Missouri">Missouri</option>
+<option value="Montana">Montana</option>
+<option value="Nebraska">Nebraska</option>
+<option value="Nevada">Nevada</option>
+<option value="New Hampshire">New Hampshire</option>
+<option value="New Jersey">New Jersey</option>
+<option value="New Mexico">New Mexico</option>
+<option value="New York">New York</option>
+<option value="North Carolina">North Carolina</option>
+<option value="North Dakota">North Dakota</option>
+<option value="Ohio">Ohio</option>
+<option value="Oklahoma">Oklahoma</option>
+<option value="Oregon">Oregon</option>
+<option value="Pennsylvania">Pennsylvania</option>
+<option value="Rhode Island">Rhode Island</option>
+<option value="South Carolina">South Carolina</option>
+<option value="South Dakota">South Dakota</option>
+<option value="Tennessee">Tennessee</option>
+<option value="Texas">Texas</option>
+<option value="Utah">Utah</option>
+<option value="Vermont">Vermont</option>
+<option value="Virginia">Virginia</option>
+<option value="WA" selected="selected">Washington</option>
+<option value="West Virginia">West Virginia</option>
+<option value="Wisconsin">Wisconsin</option>
+<option value="Wyoming">Wyoming</option>
+<option value="Armed Forces Americas">Armed Forces Americas</option>
+<option value="Armed Forces Europe">Armed Forces Europe</option>
+<option value="Armed Forces Pacific">Armed Forces Pacific</option>
+</select>
+[% END %]
diff --git a/KCLS/openils/var/templates_kcls/opac/register.tt2 b/KCLS/openils/var/templates_kcls/opac/register.tt2
new file mode 100644 (file)
index 0000000..ce8e746
--- /dev/null
@@ -0,0 +1,461 @@
+[%- PROCESS "opac/parts/header.tt2";
+    PROCESS "opac/parts/org_selector.tt2";
+    PROCESS "opac/parts/state_selector.tt2";
+    WRAPPER "opac/parts/base.tt2";
+    ctx.page_title = l("Request Library Card");
+
+# for privacy, reload the page after (default) 5 minutes
+refresh_time = ctx.register.settings.refresh_timeout || 300; 
+ctx.refresh = refresh_time _ '; ' _ ctx.opac_root _ '/home';
+
+# some useful variables and MACROs for display, 
+# field validation, and added info display
+ctx_org = ctx.physical_loc || ctx.search_ou || ctx.aou_tree.id;
+
+# TODO put these on the EG server?
+card_url = 'https://www.kcls.org/images/library/cards/Card_';
+wallet_cards = ['12man', 'blank', 'books', 'mirror'];
+keychain_cards = ['keychain'];
+
+MACRO input_field(fclass, fname, label, type, css_class) BLOCK;
+  field_path = fclass _ "." _ fname; 
+  value = ctx.register.values.$fclass.$fname;
+  type = type || 'text';
+  css_class = css_class || '';
+%]
+  <input 
+    maxlength="1000"
+    size="25"
+    class='[% css_class %]'
+    aria-label="[% label | html %]"
+    type='[% type %]' 
+    id='[% field_path %]'
+    name='[% field_path %]'
+    onchange="validate('[% field_path %]')"
+    value='[% value || CGI.param(field_path) | html %]'/>
+
+  [% IF invalid_require %]
+    <span class='patron-reg-required'>
+      [% l('This field is required') %]
+    </span>
+  [% ELSIF invalid_regex %]
+    <span class='patron-reg-required'>
+      [% l('The value entered does not have the correct format') %]
+    </span>
+  [% END %]
+[% 
+END; # input_field()
+
+%]
+
+<script type="text/javascript"
+  src="[% ctx.media_prefix %]/js/ui/default/opac/register.js"></script>
+
+<style>
+/* Keep the CSS here for now for simplicity.  
+   If it gets too big, move to a dedicated file. */
+.patron-reg-invalid {
+    font-weight: bold;
+    background-color: red;
+}
+.patron-reg-required {
+    font-weight: bold;
+    color: red;
+}
+.card-style-option {
+  text-align:center;
+}
+.patron-reg-action {
+    padding-right: 10px;
+}
+#main-content-register {
+    margin: 6px;                                                              
+    margin-bottom: 20px;                                                              
+    color: #585d5e;                                                            
+    font-family: 'Open Sans', sans-serif;                                      
+    letter-spacing: .5pt;                                                      
+    font-size: 15px;                                                           
+    width: 700px; /* to match bibliocms */
+}
+#main-content-register label {
+    font-weight: bold;
+}
+#main-content-register h2 {
+    font-weight: bold;
+    font-size: 18px;
+}
+#main-content-register li {
+    margin-top: 12px;
+    list-style-type: none;
+}
+.grid { width: 95%; }
+.grid-cell {
+  float: left;
+  margin-right: 10px;
+}
+.grid-clear {
+  clear:both;
+}
+#main-content-register .wide-input {
+    width: 90%;
+}
+#main-content-register .card-img {
+  width: 120px;
+  height:76px;
+}
+.mailing-address-hidden {
+  display:none;
+}
+</style>
+
+<div id="content-wrapper">
+  <div id="main-content-register">
+
+  <h1>[% l('Request a Library Card')%]</h1>
+
+  [% IF ctx.register.success %]
+    <h2>[% l('Registration successful!') %]<h3>
+    <h3>[% l('Please see library staff to complete your registration.') %]</h4>
+    <div>
+      <button onclick="location.href='https://kcls.bibliocms.com/'">
+        Return to KCLS
+      </button>
+    </div>
+    <hr/>
+  [% END %]
+
+  <!--DO WE NEED THIS?
+  <ul>
+    <li>
+      To fully activate your card you will need to visit a KCLS 
+      library with proof of address and photo ID that shows your 
+      date of birth. Parents/guardians can assist with proof of 
+      address for full activation for applicants under 18. Find a 
+      list of the documents you may use to provide proof of address 
+      on our 
+      <a href="https://www.kcls.org/usingthelibrary/card/applications/KCLS%20600%20ENG.pdf">
+          downloadable application.</a>
+    </li>
+    <li>
+      Want your card right away? Apply in person at any 
+      <a href="https://www.kcls.org/usingthelibrary/locations/">KCLS library.</a>
+      In-person applicants can also choose from more card 
+      designs, while supplies last.
+    </li>
+  </ul>
+  -->
+
+  <form method='POST' onsubmit="return onsub()">
+    <ul>
+      <li>
+        <label>Choose a card size....</label>
+        <span class="patron-reg-required">*</span>
+        <div>
+          <ul>
+            <li>
+              <input type="radio" name='card-type' id="card-type-wallet" 
+                onclick="show_card_types('wallet')">
+              <label for="">Wallet</label>
+            </li>
+            <li>
+              <input type="radio" id="card-type-keychain" name='card-type'
+                onclick="show_card_types('keychain')">
+              <label for="card-type-keychain">Keychain</label>
+            </li>
+          </ul>
+        </div>
+      </li>
+      <li id='wallet-cards' style='display:none'>
+        <label>Wallet Options</label>
+        <div class="grid">
+          [% FOR ctype IN wallet_cards %]
+          <div class="grid-cell">
+            <div><img class="card-img" src="[% card_url _ ctype _ '.jpg' %]"/></div>
+            <div class="card-style-option">
+              <input [% IF loop.first %]id='first-wallet-card'[% END %]
+                type="radio" name="stgu.card_style" value="[% ctype %]"/>
+            </div>
+          </div>
+          [% END %]
+        </div>
+        <div class="grid-clear"></div>
+      </li>
+      <li id='keychain-cards' style='display:none'>
+        <label>Keychain Options</label>
+        <div class="grid">
+          [% FOR ctype IN keychain_cards %]
+          <div class="grid-cell">
+            <div><img class="card-img" src="[% card_url _ ctype _ '.jpg' %]"/></div>
+            <div class="card-style-option">
+              <input [% IF loop.first %]id='first-keychain-card'[% END %]
+                type="radio" name="stgu.card_style" value="[% ctype %]"/>
+            </div>
+          </div>
+          [% END %]
+        </div>
+        <div class="grid-clear"></div>
+      </li>
+
+      <li><h2>Your Information</h2></li>
+      <hr/>
+
+      <li>
+        <div class="grid">
+          <div class="grid-cell">
+            <div>
+              <label for="sgtu.first_given_name">First Name</label>
+              <span class="patron-reg-required">*</span>
+            </div>
+            <div>[% input_field('stgu', 'first_given_name', 'First Name') %]</div>
+          </div>
+          <div class="grid-cell">
+            <div><label for="sgtu.second_given_name">Middle Name</label></div>
+            <div>[% input_field('stgu', 'second_given_name', 'Middle Name') %]</div>
+          </div>
+          <div class="grid-cell">
+            <div>
+              <label for="sgtu.family_name">Last Name</label>
+              <span class="patron-reg-required">*</span>
+            </div>
+            <div>[% input_field('stgu', 'family_name', 'Last Name') %]</div>
+          </div>
+        </div>
+        <div class="grid-clear"></div>
+      </li>
+
+      <li>
+        <label for="stgu.day_phone">Phone Number</label>
+        <div class="grid">
+          <div class="grid-cell">
+            <div>[% input_field('stgu', 'day_phone', 'Phone Number') %]</div>
+          </div>
+          <div class="grid-cell">(xxx-xxx-xxxx)</div>
+        </div>
+        <div class="grid-clear"></div>
+      </li>
+
+      <li>
+        <label for="stgu.dob">
+          Birth Date<span class="patron-reg-required">*</span>
+        </label>
+        <input type="hidden" name="stgu.dob" id="stgu.dob"/>
+        <div class="grid">
+          <div class="grid-cell">
+            <div><input onchange="compile_dob()" type="text" 
+              maxlength="2" size="2" id="dob.month"></div>
+            <div><label for="dob.month">MM</label></div>
+          </div>
+          <div class="grid-cell">
+            <div><input onchange="compile_dob()" type="text" 
+              maxlength="2" size="2" id="dob.day"></div>
+            <div><label for="dob.day">DD</label></div>
+          </div>
+          <div class="grid-cell">
+            <div><input onchange="compile_dob()" type="text" 
+              maxlength="4" size="4" id="dob.year"></div>
+            <div><label for="dob.year">YYYY</label></div>
+          </div>
+          <div class="grid-cell">
+            <div><span id='dob.display'></span></div>
+          </div>
+        </div>
+        <div class="grid-clear"></div>
+      </li>
+
+      <li>
+        <label for="stgu.email">
+          Your email address for faster library notices
+        </label>
+        <div>[% input_field('stgu', 'email', 'Email', 'email') %]</div>
+      </li>
+
+      <li>
+        <label>Optional: What is your gender identity?</label>
+        <div>[% input_field('stgsc', '1', 'Gender') %]</div>
+      </li>
+
+      <li>
+        <label class="gfield_label" for="">
+          If you are under age 18, list all parents<br/>and guardians living at your address:
+        </label>
+        <div>[% input_field('stgu', 'ident_value2', 'Parent / Guardian', 'text', 'wide-input') %]</div>
+      </li>
+
+      <li>
+        <label for="stgu.home_ou">
+          What do you want your home library to be?
+        </label>
+        <div>
+          [% INCLUDE build_org_selector 
+              name='stgu.home_ou' 
+              id='stgu.home_ou' 
+              value=value || ''
+              can_have_users_only=1
+              no_indent=1
+              no_root=1
+              valid_org_list=ctx.register.valid_orgs
+          %]
+          <span class="patron-reg-required">*</span>
+          [% IF ctx.register.invalid.bad_home_ou %]
+          <span class='patron-reg-required'>
+              [% l('Please select a valid library') %]
+          </span>
+          [% END %]
+        </div>
+      </li>
+
+      <li><h2>Contact Preferences</h2></li>
+      <hr/>
+
+      <li>
+        <label>May we contact you?</label>
+        <div>
+          <ul>
+            <li>
+              [% input_field('stgsc', '3', 'Events Mailing', 'checkbox') %]
+              <label for="stgsc.3">Tell me about KCLS news and events</label>
+            </li>
+            <li>
+              [% input_field('stgsc', '4', 'Foundation Mailing', 'checkbox') %]
+              <label for="stgsc.4">
+                Send me information about The King County Library System Foundation
+              </label>
+            </li>
+          </ul>
+        </div>
+      </li>
+
+      <li>
+        <h3>
+          Residential Address<span class="patron-reg-required">*</span>
+        </h3>
+      </li>
+
+      <li>
+        <div>[% input_field(
+          'stgba', 'street1', 'Street Address', 'text', 'wide-input') %]</div>
+        <label for="stgba.street1">
+          Street Address
+          <span class="patron-reg-required">*</span>
+        </label>
+      </li>
+      <li>
+        <div class="grid">
+          <div class="grid-cell" style="width:40%">
+            <div>[% input_field(
+              'stgba', 'street2', 'Address Line 2', 'text', 'wide-input') %]</div>
+            <div><label for="stgba.street2">Address Line 2</label></div>
+          </div>
+          <div class="grid-cell" style="width:40%">
+            <div>[% input_field('stgba', 'city', 'City') %]</div>
+            <div>
+              <label for="stgba.city">
+                City<span class="patron-reg-required">*</span>
+              </label>
+            </div>
+          </div>
+        </div>
+        <div class="grid-clear"></div>
+      </li>
+      <li>
+        <div class="grid">
+          <div class="grid-cell" style="width:40%">
+            <div>[% PROCESS state_selector name='stgba.state' id='stgba.state' %]</div>
+            <div>
+              <label for="stgba.state">
+                State<span class="patron-reg-required">*</span>
+              </label>
+            </div>
+          </div>
+          <div class="grid-cell" style="width:40%">
+            <div>[% input_field('stgba', 'post_code', 'Zip / Post Code') %]</div>
+            <div>
+              <label for="stgba.post_code">
+                Zip / Post Code<span class="patron-reg-required">*</span>
+              </label>
+            </div>
+          </div>
+        </div>
+        <div class="grid-clear"></div>
+        <input type='hidden' name='stgba.country' value='USA'/>
+      </li>
+      
+      <li>
+        <label>Mailing Address</label>
+        <div>
+          <input type="checkbox" onclick="show_hide_mailing(this.checked)"
+            name="mailing_matches_billing"
+            id="mail_addr_matches_billing" checked="checked">
+          <label for="mail_addr_matches_billing">Same as Residential Address</label>
+        </div>
+      </li>
+
+      <li id='mailing-address-1' style='display:none'>
+        <div>[% input_field(
+          'stgma', 'street1', 'Street Address', 'text', 'wide-input') %]</div>
+        <label for="stgma.street1">
+          Street Address
+          <span class="patron-reg-required">*</span>
+        </label>
+      </li>
+      <li id='mailing-address-2' style='display:none'>
+        <div class="grid">
+          <div class="grid-cell" style="width:40%">
+            <div>[% input_field(
+              'stgma', 'street2', 'Address Line 2', 'text', 'wide-input') %]</div>
+            <div><label for="stgma.street2">Address Line 2</label></div>
+          </div>
+          <div class="grid-cell" style="width:40%">
+            <div>[% input_field('stgma', 'city', 'City') %]</div>
+            <div>
+              <label for="stgma.city">
+                City<span class="patron-reg-required">*</span>
+              </label>
+            </div>
+          </div>
+        </div>
+        <div class="grid-clear"></div>
+      </li>
+      <li id='mailing-address-3' style='display:none'>
+        <div class="grid">
+          <div class="grid-cell" style="width:40%">
+            <div>[% PROCESS state_selector name='stgma.state' id='stgma.state' %]</div>
+            <div>
+              <label for="stgma.state">
+                State<span class="patron-reg-required">*</span>
+              </label>
+            </div>
+          </div>
+          <div class="grid-cell" style="width:40%">
+            <div>[% input_field('stgma', 'post_code', 'Zip / Post Code') %]</div>
+            <div>
+              <label for="stgma.post_code">
+                Zip / Post Code<span class="patron-reg-required">*</span>
+              </label>
+            </div>
+          </div>
+        </div>
+        <div class="grid-clear"></div>
+        <input type='hidden' name='stgma.country' value='USA'/>
+      </li>
+
+      <li>
+        <div class="grid">
+          <div class="grid-cell">
+            <input type="submit" value="[% l('Submit Registration') %]"/>
+          </div>
+          <div class="grid-cell">
+            <button onclick="location.href='https://kcls.bibliocms.com/'; return false">
+              Cancel and Return to KCLS
+            </button>
+          </div>
+        </div>
+        <div class="grid-clear"></div>
+      </li>
+    </ul>
+  </form>
+  </div>
+</div>
+
+[% END %]
+
diff --git a/Open-ILS/web/js/ui/default/opac/register.js b/Open-ILS/web/js/ui/default/opac/register.js
new file mode 100644 (file)
index 0000000..3f52174
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+    KCLS custom patron self-registration functions
+*/
+
+var invalid_fields = {};
+var is_juvenile = false;
+var required_fields = [
+    'stgu.card_style',
+    'stgu.first_given_name',
+    'stgu.family_name',
+    'stgu.dob',
+    'stgba.street1',
+    'stgba.city',
+    'stgba.state',
+    'stgba.post_code',
+];
+
+var copy_addr_fields = ['street1','street2','city','state','post_code'];
+
+/* show/hide card options depending on the selected type. */
+function show_card_types(type) {
+    var wal = document.getElementById('wallet-cards');
+    var key = document.getElementById('keychain-cards');
+    if (type == 'wallet') {
+        wal.style.display = 'block';
+        key.style.display = 'none';
+    } else {
+        wal.style.display = 'none';
+        key.style.display = 'block';
+    }
+
+    // Select the first card in each group.
+    // Cards are put into groups, but use the same form name.
+    document.getElementById('first-'+type+'-card').checked = 'checked';
+}
+
+function check_juvenile(dobString) {
+    var dobDate = Date.parse(dobString);
+    var ageDate = new Date(); // minimum age for non-juvenile
+    ageDate.setFullYear(ageDate.getFullYear() - 18);
+    is_juvenile = (dobDate > ageDate);
+}
+
+/*
+Show or hide the mailing address.
+*/
+function show_hide_mailing(hide) {
+    var display = hide ? 'none' : 'block';
+    document.getElementById('mailing-address-1').style.display = display;
+    document.getElementById('mailing-address-2').style.display = display;
+    document.getElementById('mailing-address-3').style.display = display;
+}
+
+function validate(dom_id) {
+    var element = document.getElementById(dom_id);
+    var value = element ? element.value : '';
+    var valid = true;
+
+    switch(dom_id) {
+        case 'stgu.first_given_name':
+            if (value) {
+                delete invalid_fields[dom_id];
+            } else {
+                invalid_fields[dom_id] = 'Please include first name';
+                valid = false;
+            }
+
+            break;
+
+        case 'stgu.family_name':
+            if (value) {
+                delete invalid_fields[dom_id];
+            } else {
+                invalid_fields[dom_id] = 'Please include last name';
+                valid = false;
+            }
+
+            break;
+
+        case 'stgu.dob':
+            // dob value is generated by compile_dob().
+            if (value) {
+                delete invalid_fields[dom_id];
+                check_juvenile(value);
+            } else {
+                valid = false;
+                is_juvenile = false;
+                invalid_fields[dom_id] = 
+                    "Please enter a valid date of birth.";
+            }
+            validate('stgu.ident_value2');
+            break;
+
+        case 'stgu.ident_value2': // parent/guardian
+            // A value is only required if is_juvenile is true.
+            valid = Boolean(value) || !is_juvenile;
+            if (valid) {
+                delete invalid_fields[dom_id];
+            } else {
+                invalid_fields[dom_id] = 
+                    "Please list all parents or guardians living at your address."
+            }
+
+            break;
+
+        case 'stgu.card_style': 
+            // be sure the user has selected a card style.
+            // In this case dom_id is really a radio selector name.
+            var styles = document.getElementsByName(dom_id);
+            var checked = false;
+            for (var i = 0; i < styles.length; i++) {
+                if (styles[i].checked) {
+                    checked = true;
+                    break;
+                }
+            }
+
+            if (checked) {
+                valid = false;
+                delete invalid_fields[dom_id];
+            } else {
+                invalid_fields[dom_id] = "Please select a card style."
+            }
+            break;
+
+        case 'stgba.street1':
+            if (value) {
+                delete invalid_fields[dom_id];
+            } else {
+                invalid_fields[dom_id] = "Please enter an address street";
+            }
+            break;
+
+        case 'stgba.city':
+            if (value) {
+                delete invalid_fields[dom_id];
+            } else {
+                invalid_fields[dom_id] = "Please enter a address city";
+            }
+            break;
+
+        case 'stgba.post_code':
+            if (value) {
+                delete invalid_fields[dom_id];
+            } else {
+                invalid_fields[dom_id] = "Please enter an address zip/post code";
+            }
+            break;
+    }
+
+    if (element) {
+        element.className = valid ? '' : 'patron-reg-invalid';
+    }
+}
+
+function compile_dob() {
+    var day = document.getElementById('dob.day').value || '';
+    var mon = document.getElementById('dob.month').value || '';
+    var year = document.getElementById('dob.year').value || '';
+    var dob = document.getElementById('stgu.dob');
+    var dob_display = document.getElementById('dob.display');
+
+    if (day && mon && year.length == 4 && Number(year) >= 1900) {
+        var dob_date = new Date(year, Number(mon) - 1, Number(day));
+
+        if (dob_date) {
+
+            // push the time forward by our timezone offset to force the
+            // stored (UTC) date to match the entered date.
+            dob_date.setTime(dob_date.getTime() + 
+                dob_date.getTimezoneOffset() * 60 * 1000);
+
+            dob.value = dob_date.toISOString().replace(/T.*/,'');
+            dob_display.innerHTML = '(' + dob_date.toDateString() + ')';
+            validate('stgu.dob');
+            return;
+        }
+    }
+
+    // date is incomplete or invalid.  clear the compiled dob value.
+    dob.value = '';
+    dob_display.innerHTML = '';
+    validate('stgu.dob');
+}
+
+/*
+If we have collected any invalid field messages, bundle 
+them into a single alert message, alert it, then prevent
+the form from submitting by returning false.
+*/
+function onsub() {
+
+    // force the validator to run on all required fields regardless
+    // of whether they have been changed.
+    for (var i = 0; i < required_fields.length; i++)
+        validate(required_fields[i]);
+
+    var msg = '';
+    for (var key in invalid_fields) {
+        msg += '\n*' + invalid_fields[key] + '\n';
+    }
+
+    if (msg) {
+        alert(msg);
+        return false;
+    }
+    return true;
+}
+
+