From: Bill Erickson Date: Mon, 4 May 2015 01:19:51 +0000 (-0400) Subject: LP#1452950 angularize patron registration phase I X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=6841e682eb6003177109b7ec5e723ed1841002ad;p=working%2FEvergreen.git LP#1452950 angularize patron registration phase I Replace legacy Dojo patron registration / edit UI's in the browser client with an initial cut of an Angular version. For this commit, the UI is basically a wireframe, but the selectors display values and most fields display the correct values set on the patron. No save or clone etc. operations or data validation are functional. Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/templates/staff/circ/patron/index.tt2 b/Open-ILS/src/templates/staff/circ/patron/index.tt2 index bb0d2f8798..3234b95f1c 100644 --- a/Open-ILS/src/templates/staff/circ/patron/index.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/index.tt2 @@ -17,6 +17,7 @@ [% INCLUDE 'staff/circ/share/hold_strings.tt2' %] + diff --git a/Open-ILS/src/templates/staff/circ/patron/register.tt2 b/Open-ILS/src/templates/staff/circ/patron/register.tt2 index 4a3c9ce3df..a0b2af49fa 100644 --- a/Open-ILS/src/templates/staff/circ/patron/register.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/register.tt2 @@ -5,8 +5,9 @@ %] [% BLOCK APP_JS %] - + + [% END %] diff --git a/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2 index 3f85366f4e..17dde5e58f 100644 --- a/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2 @@ -1,2 +1,423 @@ - +[% DOC_IMG = '/images/question-mark.png' %] + +
[% l('Patron Edit') %]
+ + +
+
+ + +
+ + + +
+ + {{idl_fields[selected_field_doc.fm_class()][selected_field_doc.field()].label}} + +
{{selected_field_doc.string()}}
+
+
+ +[% 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 %] + +
+ +
+ + + + + +
+ +
+ + [% + model = path ? 'patron.' _ path _ '.' _ field : 'patron.' _ field; + IF input_type == 'checkbox' %] + +
+ +
+ + [% ELSE %] + + + + [% END %] +
+ +
+ + [% IF field == 'barcode' %] + + + + + [% ELSIF field == 'password' %] + + + + [% ELSE %] + + + + [% set_str = "org_settings['ui.patron.edit." _ + cls _ "." _ field _ ".example']"; %] + + + [% l('Example: [_1]', "{{" _ set_str _ "}}") %] + + + [% IF field.match('phone') %] + + + [% l('Example: [_1]', + "{{org_settings['ui.patron.edit.phone.example']}}") %] + + [% END %] + [% END %] + +
+
+[% END %] + +[% 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') %] + +
+
+ + +
+
+ +
+
+ +[% formfield('au', 'juvenile', '', 'checkbox') %] + + + +
+
+ + +
+
+
+ + +
+
+
+ + +[% formfield('au', 'ident_value') %] +[% formfield('au', 'ident_value2') %] +[% formfield('au', 'email', '', 'email') %] +[% formfield('au', 'day_phone') %] +[% formfield('au', 'evening_phone') %] +[% formfield('au', 'other_phone') %] + + + +
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+
+ + +
+
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + + +
+
+ + +
+
+
+ + +
+
+
+ +[% 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') %] + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+ + [% l('Phone') %] +
+
+ + [% l('Email') %] +
+
+ + [% l('SMS') %] +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+ + + + + +[% formfield('aua', 'address_type', 'mailing_address') %] +[% formfield('aua', 'post_code', 'mailing_address') %] +[% formfield('aua', 'street1', 'mailing_address') %] +[% formfield('aua', 'street2', 'mailing_address') %] +[% formfield('aua', 'city', 'mailing_address') %] +[% formfield('aua', 'county', 'mailing_address') %] +[% formfield('aua', 'state', 'mailing_address') %] +[% formfield('aua', 'country', 'mailing_address') %] +[% formfield('aua', 'valid', 'mailing_address', 'checkbox') %] +[% formfield('aua', 'within_city_limits', 'mailing_address', 'checkbox') %] + +
+ +
+ + + + + +
+
+ +
+
+
+ +
+
+
+ + +
+
+
+
+ + + + + +
+
+ +
+
+
+ + +
+
+
+ + diff --git a/Open-ILS/src/templates/staff/css/circ.css.tt2 b/Open-ILS/src/templates/staff/css/circ.css.tt2 index 8d6c139d88..bf9d8341cf 100644 --- a/Open-ILS/src/templates/staff/css/circ.css.tt2 +++ b/Open-ILS/src/templates/staff/css/circ.css.tt2 @@ -54,6 +54,54 @@ but the ones I'm finding aren't quite cutting it..*/ border-bottom: 1px solid #CCC; } +/* -- patron registration -- */ + +/* make all input widgets the same width, i.e. fill their column */ + +.reg-field-input input:not([type="checkbox"]) { width: 100%; } + +/* selector contents float left to allow depth-based left-padding */ +.reg-field-input .eg-org-selector, +.reg-field-input .btn-group { + width: 100%; + text-align: left; +} + +/* selector button labels float right */ +.reg-field-input .eg-org-selector button, +.reg-field-input .btn-group > button { + width: 100%; + text-align: right; +} + + +/* floating div along top-right with field documentation */ +#reg-control-actions { + position: fixed; + top:160px; + right:30px; + width:300px; + border:2px dashed #d9e8f9; + -moz-border-radius: 10px; + font-weight: bold; + padding: 20px; + margin-top: 20px; +} + +#reg-field-doc { + border:2px dashed #d9e8f9; + -moz-border-radius: 10px; + font-weight: bold; + padding: 20px; + margin-top: 20px; +} + +#reg-field-doc legend { + /* otherwise the font size is quite large */ + font-size: 100%; +} + +/* -- end patron registration -- */ [%# vim: ft=css diff --git a/Open-ILS/src/templates/staff/css/style.css.tt2 b/Open-ILS/src/templates/staff/css/style.css.tt2 index 0c21913168..5c367b8b29 100644 --- a/Open-ILS/src/templates/staff/css/style.css.tt2 +++ b/Open-ILS/src/templates/staff/css/style.css.tt2 @@ -116,6 +116,7 @@ table.list tr.selected td { /* deprecated? */ .pad-left {padding-left: 10px;} .pad-right {padding-right: 10px;} .pad-all-min {padding : 5px; } +.pad-all-min2 {padding : 2px; } .pad-all {padding : 10px; } #print-div { display: none; } diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js index d5fb95d5c1..46aab7c6e5 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/patron/app.js +++ b/Open-ILS/web/js/ui/default/staff/circ/patron/app.js @@ -159,7 +159,7 @@ angular.module('egPatronApp', ['ngRoute', 'ui.bootstrap', $routeProvider.when('/circ/patron/:id/edit', { templateUrl: './circ/patron/t_edit', - controller: 'PatronEditCtrl', + controller: 'PatronRegCtrl', resolve : resolver }); @@ -1167,26 +1167,6 @@ function($scope , $q , $routeParams, egCore , $modal , patronSvc , egCirc) { /** - * Link to patron edit UI - */ -.controller('PatronEditCtrl', - ['$scope','$routeParams','$location','egCore','patronSvc', -function($scope, $routeParams, $location , egCore , patronSvc) { - $scope.initTab('edit', $routeParams.id); - - var url = $location.absUrl().replace(/\/staff.*/, '/actor/user/register'); - url += '?usr=' + encodeURIComponent($routeParams.id); - - $scope.funcs = { - on_save : function() { - patronSvc.refreshPrimary(); - } - } - - $scope.patron_edit_url = url; -}]) - -/** * Credentials tester */ .controller('PatronVerifyCredentialsCtrl', diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js new file mode 100644 index 0000000000..b940514707 --- /dev/null +++ b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js @@ -0,0 +1,364 @@ + +angular.module('egCoreMod') +// toss tihs onto egCoreMod since the page app may vary + +.factory('patronRegSvc', ['$q', 'egCore', function($q, egCore) { + + var service = { + field_doc : {}, // config.idl_field_doc + profiles : [], // permission groups + sms_carriers : [], + user_settings : {}, // applied user settings + user_setting_types : {}, // config.usr_setting_type + modified_user_settings : {} // settings modifed this session + }; + + // launch a series of parallel data retrieval calls + service.init = function(scope) { + return $q.all([ + service.get_field_doc(), + service.get_perm_groups(), + service.get_ident_types(), + service.get_user_settings(), + service.get_org_settings(), + service.get_stat_cats(), + service.get_surveys(), + service.get_net_access_levels() + ]); + }; + + service.get_surveys = function() { + var org_ids = egCore.org.ancestors(egCore.auth.user().ws_ou(), true); + + return egCore.pcrud.search('asv', + {owner : org_ids}, + {flesh : 1, flesh_fields : {asv : ['questions']}}, + {atomic : true} + ).then(function(surveys) { + service.surveys = surveys; + }); + } + + service.get_stat_cats = function() { + return egCore.net.request( + 'open-ils.circ', + 'open-ils.circ.stat_cat.actor.retrieve.all', + egCore.auth.token(), egCore.auth.user().ws_ou() + ).then(function(cats) { + service.stat_cats = cats; + }); + }; + + service.get_org_settings = function() { + return egCore.org.settings([ + 'global.password_regex', + 'global.juvenile_age_threshold', + 'patron.password.use_phone', + 'ui.patron.default_inet_access_level', + 'ui.patron.default_ident_type', + 'ui.patron.default_country', + 'ui.patron.registration.require_address', + 'circ.holds.behind_desk_pickup_supported', + 'circ.patron_edit.clone.copy_address', + 'ui.patron.edit.au.prefix.require', + 'ui.patron.edit.au.prefix.show', + 'ui.patron.edit.au.prefix.suggest', + 'ui.patron.edit.ac.barcode.regex', + 'ui.patron.edit.au.second_given_name.show', + 'ui.patron.edit.au.second_given_name.suggest', + 'ui.patron.edit.au.suffix.show', + 'ui.patron.edit.au.suffix.suggest', + 'ui.patron.edit.au.alias.show', + 'ui.patron.edit.au.alias.suggest', + 'ui.patron.edit.au.dob.require', + 'ui.patron.edit.au.dob.show', + 'ui.patron.edit.au.dob.suggest', + 'ui.patron.edit.au.dob.calendar', + 'ui.patron.edit.au.juvenile.show', + 'ui.patron.edit.au.juvenile.suggest', + 'ui.patron.edit.au.ident_value.show', + 'ui.patron.edit.au.ident_value.suggest', + 'ui.patron.edit.au.ident_value2.show', + 'ui.patron.edit.au.ident_value2.suggest', + 'ui.patron.edit.au.email.require', + 'ui.patron.edit.au.email.show', + 'ui.patron.edit.au.email.suggest', + 'ui.patron.edit.au.email.regex', + 'ui.patron.edit.au.email.example', + 'ui.patron.edit.au.day_phone.require', + 'ui.patron.edit.au.day_phone.show', + 'ui.patron.edit.au.day_phone.suggest', + 'ui.patron.edit.au.day_phone.regex', + 'ui.patron.edit.au.day_phone.example', + 'ui.patron.edit.au.evening_phone.require', + 'ui.patron.edit.au.evening_phone.show', + 'ui.patron.edit.au.evening_phone.suggest', + 'ui.patron.edit.au.evening_phone.regex', + 'ui.patron.edit.au.evening_phone.example', + 'ui.patron.edit.au.other_phone.require', + 'ui.patron.edit.au.other_phone.show', + 'ui.patron.edit.au.other_phone.suggest', + 'ui.patron.edit.au.other_phone.regex', + 'ui.patron.edit.au.other_phone.example', + 'ui.patron.edit.phone.regex', + 'ui.patron.edit.phone.example', + 'ui.patron.edit.au.active.show', + 'ui.patron.edit.au.active.suggest', + 'ui.patron.edit.au.barred.show', + 'ui.patron.edit.au.barred.suggest', + 'ui.patron.edit.au.master_account.show', + 'ui.patron.edit.au.master_account.suggest', + 'ui.patron.edit.au.claims_returned_count.show', + 'ui.patron.edit.au.claims_returned_count.suggest', + 'ui.patron.edit.au.claims_never_checked_out_count.show', + 'ui.patron.edit.au.claims_never_checked_out_count.suggest', + 'ui.patron.edit.au.alert_message.show', + 'ui.patron.edit.au.alert_message.suggest', + 'ui.patron.edit.aua.post_code.regex', + 'ui.patron.edit.aua.post_code.example', + 'ui.patron.edit.aua.county.require', + 'format.date', + 'ui.patron.edit.default_suggested', + 'opac.barcode_regex', + 'opac.username_regex', + 'sms.enable', + 'ui.patron.edit.aua.state.require', + 'ui.patron.edit.aua.state.suggest', + 'ui.patron.edit.aua.state.show' + ]).then(function(settings) { + service.org_settings = settings; + return service.process_org_settings(settings); + }); + }; + + // some org settings require the retrieval of additional data + service.process_org_settings = function(settings) { + + if (!settings['sms.enable']) { + return $q.when(); + } + + return egCore.pcrud.search('csc', + {active: 'true'}, + {'order_by':[ + {'class':'csc', 'field':'name'}, + {'class':'csc', 'field':'region'} + ]}, + {atomic : true} + ).then(function(carriers) { + service.sms_carriers = carriers; + }); + }; + + service.get_ident_types = function() { + return egCore.pcrud.retrieveAll('cit', {}, {atomic : true}) + .then(function(types) { service.ident_types = types }); + }; + + service.get_net_access_levels = function() { + return egCore.pcrud.retrieveAll('cnal', {}, {atomic : true}) + .then(function(levels) { service.net_access_levels = levels }); + } + + service.get_perm_groups = function() { + if (egCore.env.pgt) { + service.profiles = egCore.env.pgt.list; + return $q.when(); + } else { + return egCore.pcrud.search('pgt', {parent : null}, + {flesh : -1, flesh_fields : {pgt : ['children']}} + ).then( + function(tree) { + egCore.env.absorbTree(tree, 'pgt') + service.profiles = egCore.env.pgt.list; + } + ); + } + } + + service.get_field_doc = function() { + + return egCore.pcrud.search('fdoc', { + fm_class: ['au', 'ac', 'aua', 'actsc', 'asv', 'asvq', 'asva']}) + .then(null, null, function(doc) { + if (!service.field_doc[doc.fm_class()]) { + service.field_doc[doc.fm_class()] = {}; + } + service.field_doc[doc.fm_class()][doc.field()] = doc; + }); + }; + + service.get_user_settings = function() { + var org_ids = egCore.org.ancestors(egCore.auth.user().ws_ou(), true); + + return egCore.pcrud.search('cust', { + '-or' : [ + {name : [ // common user settings + 'circ.holds_behind_desk', + 'circ.collections.exempt', + 'opac.hold_notify', + 'opac.default_phone', + 'opac.default_pickup_location', + 'opac.default_sms_carrier', + 'opac.default_sms_notify']}, + {name : { // opt-in notification user settings + 'in': { + select : {atevdef : ['opt_in_setting']}, + from : 'atevdef', + // we only care about opt-in settings for + // event_defs our users encounter + where : {'+atevdef' : {owner : org_ids}} + } + }} + ] + }, {}, {atomic : true}).then(function(setting_types) { + + angular.forEach(setting_types, function(stype) { + service.user_setting_types[stype.name()] = stype; + }); + + if(service.patron_id) { + // retrieve applied values for the current user + // for the setting types we care about. + + var setting_names = + setting_types.map(function(obj) { return obj.name() }); + + return egCore.net.request( + 'open-ils.actor', + 'open-ils.actor.patron.settings.retrieve.authoritative', + egCore.auth.token(), + service.patron_id, + setting_names + ).then(function(settings) { + service.user_settings = settings; + }); + } + + // apply default user setting values + angular.forEach(setting_types, function(stype, index) { + if (stype.reg_default() != undefined) { + service.modified_user_settings[setting.name()] = + service.user_settings[setting.name()] = + setting.reg_default(); + } + }); + }); + } + + service.init_patron = function(current) { + + if (!current) + return service.init_new_patron(); + + service.patron = current; + return service.init_existing_patron(current) + } + + /* + * Existing patron objects reqire some data munging before insertion + * into the scope. + * + * 1. Turn everything into a hash + * 2. ... Except certain fields (selectors) whose widgets require objects + * 3. Bools must be Boolean, not t/f. + */ + service.init_existing_patron = function(current) { + + var patron = egCore.idl.toHash(current); + + patron.home_ou = egCore.org.get(patron.home_ou.id); + patron.expire_date = new Date(Date.parse(patron.expire_date)); + patron.dob = new Date(Date.parse(patron.dob)); + patron.profile = current.profile(); // pre-hash version + patron.net_access_level = current.net_access_level(); + patron.ident_type = current.ident_type(); + + angular.forEach( + ['juvenile', 'barred', 'active', 'master_account'], + function(field) { patron[field] = patron[field] == 't'; } + ); + + angular.forEach(patron.addresses, function(addr) { + addr.valid = addr.valid == 't'; + addr.within_city_limits = addr.within_city_limits == 't'; + }); + + return patron; + } + + service.init_new_patron = function() { + + var addr = { + valid : true, + within_city_limits : true + // default state, etc. + }; + + return { + isnew : true, + active : true, + card : {}, + home_ou : egCore.org.get(egCore.auth.user().ws_ou()), + // TODO default profile group? + mailing_address : addr, + addresses : [addr] + }; + } + + return service; +}]); + + +function PatronRegCtrl($scope, $routeParams, + $q, egCore, patronSvc, patronRegSvc) { + + $scope.clone_id = $routeParams.clone_id; + $scope.stage_username = $routeParams.stage_username; + $scope.patron_id = + patronRegSvc.patron_id = $routeParams.edit_id || $routeParams.id; + + $q.all([ + + $scope.initTab ? // initTab comes from patron app + $scope.initTab('edit', $routeParams.id) : $q.when(), + + patronRegSvc.init() + + ]).then(function() { + // called after initTab and patronRegSvc.init have completed + + var prs = patronRegSvc; // brevity + // in standalone mode, we have no patronSvc + $scope.patron = prs.init_patron(patronSvc ? patronSvc.current : null); + $scope.field_doc = prs.field_doc; + $scope.profiles = prs.profiles; + $scope.ident_types = prs.ident_types; + $scope.net_access_levels = prs.net_access_levels; + $scope.user_settings = prs.user_settings; + $scope.user_setting_types = prs.user_setting_types; + $scope.modified_user_settings = prs.modified_user_settings; + $scope.org_settings = prs.org_settings; + $scope.sms_carriers = prs.sms_carriers; + $scope.stat_cats = prs.stat_cats; + $scope.surveys = prs.surveys; + }); + + // returns the tree depth of the selected profile group tree node. + $scope.pgt_depth = function(grp) { + var d = 0; + while (grp = egCore.env.pgt.map[grp.parent()]) d++; + return d; + } + + // IDL fields used for labels in the UI. + $scope.idl_fields = { + au : egCore.idl.classes.au.field_map, + ac : egCore.idl.classes.ac.field_map, + aua : egCore.idl.classes.aua.field_map + }; + +} + + +// TODO: $inject controller params diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/register.js b/Open-ILS/web/js/ui/default/staff/circ/patron/register.js index 13b4a41ed2..49306f015e 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/patron/register.js +++ b/Open-ILS/web/js/ui/default/staff/circ/patron/register.js @@ -4,7 +4,7 @@ * Search, checkout, items out, holds, bills, edit, etc. */ -angular.module('egPatronRegApp', ['ui.bootstrap','ngRoute','egCoreMod']) +angular.module('egPatronRegApp', ['ui.bootstrap','ngRoute','egCoreMod', 'egUiMod']) .config(function($routeProvider, $locationProvider, $compileProvider) { @@ -15,25 +15,25 @@ angular.module('egPatronRegApp', ['ui.bootstrap','ngRoute','egCoreMod']) ['egStartup', function(egStartup) {return egStartup.go()}]} $routeProvider.when('/circ/patron/register', { - template: '', + templateUrl: './circ/patron/t_edit', controller: 'PatronRegCtrl', resolve : resolver }); $routeProvider.when('/circ/patron/register/stage/:stage_username', { - template: '', + templateUrl: './circ/patron/t_edit', controller: 'PatronRegCtrl', resolve : resolver }); $routeProvider.when('/circ/patron/register/edit/:edit_id', { - template: '', + templateUrl: './circ/patron/t_edit', controller: 'PatronRegCtrl', resolve : resolver }); $routeProvider.when('/circ/patron/register/clone/:clone_id', { - template: '', + templateUrl: './circ/patron/t_edit', controller: 'PatronRegCtrl', resolve : resolver }); @@ -41,32 +41,7 @@ angular.module('egPatronRegApp', ['ui.bootstrap','ngRoute','egCoreMod']) $routeProvider.otherwise({redirectTo : '/circ/patron/register'}); }) +// dummy service so standalone patron editor can reference it +.factory('patronSvc', function() {}); -/** - * */ -.controller('PatronRegCtrl', - ['$scope','$routeParams','$location','egCore', -function($scope , $routeParams , $location , egCore) { - - - var url = $location.absUrl().replace(/\/staff.*/, '/actor/user/register'); - - // since we don't store auth cookies, pass the cookie via URL - url += '?ses=' + egCore.auth.token(); - - if ($routeParams.stage_username) { - url += '&stage=' + encodeURIComponent($routeParams.stage_username); - } - - if ($routeParams.edit_id) { - url += '&usr=' + encodeURIComponent($routeParams.edit_id); - } - - if ($routeParams.clone_id) { - url += '&clone=' + encodeURIComponent($routeParams.clone_id); - } - // pass the reg URL into the scope, thus into the - $scope.reg_url = url; -}]) -