From aec5e68df7f323da4190d198a997236a9d911c2f Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Tue, 17 Sep 2013 13:18:13 -0400 Subject: [PATCH] Experimenting w/ js stafff client * patron search * checkout Signed-off-by: Bill Erickson --- circ.css | 166 ++++++++++++++++++++ circ.html | 270 +++++++++++++++++++++++++++++++++ circ.js | 514 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ idl.js | 35 +++++ 4 files changed, 985 insertions(+) create mode 100644 circ.css create mode 100644 circ.html create mode 100644 circ.js create mode 100644 idl.js diff --git a/circ.css b/circ.css new file mode 100644 index 000000000..33ddac80c --- /dev/null +++ b/circ.css @@ -0,0 +1,166 @@ +body { padding: 0px; margin: 0px;} +#command-bar { + width: 100%; + height: 35px; + line-height: 35px; + background: #3F3F3F; + color: #FFF; +} +#command-bar-menu { + float: left; + padding-left: 10px; + height: 100%; +} +#command-bar-menu span { + padding: 0px 5px 0px 5px; +} +#command-bar-account { + float: right; + padding-right: 20px; + height: 100%; +} + +#top-main { + /* leave room for padding, etc. otherwise: horiz scroll */ + width: 99%; +} +#left-nav { + width: 12%; + + /* leave room for the fixed-height command bar. If this + * is set to 100%, the page always has a vertical scroll bar. */ + height: 90%; + + padding-top: 30px; + padding-left: 20px; + float:left; + border-right: 3px solid #EEF; +} + +.left-nav-div { + padding-bottom: 10px; +} + +#left-nav-patron { + display: none; +} + +#breadcrumbs { + width: 100%; + padding: 5px 0px 5px 10px; + border-bottom: 3px solid #EEF; +} + +a,a:visited { + color: #3F3F3F; +} +#breadcrumbs a,a:visited { + color: #3F3F3F; + font-weight: bold; + padding: 5px; +} + +#right-block { + /*height: 100%;*/ + width: 86%; /* leave some room for margin/padding */ + float:left; +} +#main { + padding-left: 20px; + padding-top: 20px; + visibility : hidden; +} + +#progwrap { + width: 100%; + text-align: center; + margin: 20px +} + +#search-div { visibility : hidden; width: 100%; } + +.main-table { border-collapse: collapse; width: 100%; } +.main-table td { padding: 5px; text-align: left;} +.main-table th { padding: 5px; text-align: left; border-bottom: 2px solid #EEF; } +.main-table tr:hover { background: #EEF; } +.main-table tr.selected { background: #DDE; } + +#checkout-table { visibility : hidden; } + +#search-form { + margin-bottom: 20px; + border-bottom: 2px solid #DDE; +} + +#search-left { + width: 75%; + float: left; +} +#search-right { + float: left; + width: 24%; +} +#search-detail { + visibility : hidden; + padding: 10px 5px 0px 10px; + border:2px solid #EEF; + border-radius:25px; + margin-top: 20px; + height: 80%; +} + +#search-detail div { + padding: 3px 0px 3px 0px; + border-bottom: 2px solid #EEF; +} + +#search-detail-name { + font-weight: bold; +} +#search-detail { +} + +#checkout { + display: none; +} +#checkout-input { + padding: 10px; + margin-bottom: 20px; + width: 75%; + /*background: #DDE;*/ + border-bottom: 2px solid #EEF; +} +#checkout-tbox { + margin-left: 15px; + width: 18em; +} + + +#search-bc { visibility: hidden; } +.user-search-label { + padding-right: 5px; +} + +#user-search-table td { + padding: 2px 5px 2px 5px; +} + +#user-search-table select,button,input[type="text"] { + width: 10em; +} + +#search-detail-table td:first-child { + white-space: nowrap; + width: 25%; +} + +.next-arrow { + text-decoration: none; + font-weight: bold; + font-size: 120%; +} + +.standing-penalty { + color: red; + font-weight: bold; +} diff --git a/circ.html b/circ.html new file mode 100644 index 000000000..5a49f990a --- /dev/null +++ b/circ.html @@ -0,0 +1,270 @@ + + + + + Web Fun + + +
+
+ Menu 1 + Menu 2 + Menu 3 + Menu 4 +
+
+ admin @ workstation123 +
+
+
+
+
+
+
Context Item 1
+
Context Item 2
+
Context Item 3
+
Context Item 4
+
+
+
+ Alerts +
+
+ Checkout +
+
+ Items Out +
+
+ Holds +
+
...
+
+
+
+ +
+

+ Loading...

+ + +
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Include Inactive? + + + + + + + + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +
#IDNameCardProfileDoB +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + +
#BarcodeTitleAuthorDue DateStatus
+
+
+
+
+
+ + + + + + + + + + +
Card
Profile
Home Library
Create Date
Expire Date
Items Out
Items Overdue
Fines Owed $
Holds
+
+
+ Mailing Address + (map) +
+ +
+
+
+ Billing Address + (map) +
+ +
+
+
+
+ Navigation: + Up/Down Arrow key (or mouse) to navigate results; + Shift+RightArrow or click on right-arrow to open patron; + Shift+LeftArrow or click breadcrumb to go back. +
+
+
+
+ + + + + + + + + + + + + diff --git a/circ.js b/circ.js new file mode 100644 index 000000000..b53112cc5 --- /dev/null +++ b/circ.js @@ -0,0 +1,514 @@ +/* + * Author: Bill Erickson + * Web-based patron search and checkout interface. + * This code was rapidly put together and would + * benefit from some serious refinement. + */ + +dojo.require('dojo.cookie'); // for authtoken +dojo.require('dojo.keys'); +dojo.require('dojo.date'); +dojo.require('dojo.date.stamp'); +dojo.require('dojo.date.locale'); + +//var orgUnitTree; +var orgUnitMap = {}; +var permGroups = {}; +var authtoken; +var throbber = ''; + +var userSearchFields = [ + 'family_name', 'first_given_name', 'second_given_name', 'alias', + 'card', 'usrname', 'phone', 'email', 'ident', 'street1', 'street2', + 'city', 'state', 'post_code', 'profile']; + +function loadOrgTree() { + var ses = new OpenSRF.ClientSession('open-ils.pcrud'); + + ses.request({ + async : true, + method : 'open-ils.pcrud.search.aou', + params : [ + authtoken, + {parent_ou : null}, + { flesh : -1, + flesh_fields : {aou : ['children', 'ou_type']} + } + ], + oncomplete : function(r) { + orgUnitTree = r.recv().content(); + var sel = dojo.byId('org-selector'); + function d(node) { + orgUnitMap[node.id()] = node; + /* + if (node.ou_type().can_have_users() == 't') { + var opt = dojo.create('option', + { value : node.id(), innerHTML : node.shortname() }); + sel.appendChild(opt); + } + */ + dojo.forEach(node.children(), function(c) { d(c) }); + } + d(orgUnitTree); + } + }).send(); + + ses.request({ + async : true, + method : 'open-ils.pcrud.search.pgt.atomic', + params : [ + authtoken, + {id : {'!=' : null}}, // flat list + ], + oncomplete : function(r) { + var grps = r.recv().content(); + dojo.forEach(grps, function(grp) { + permGroups[grp.id()] = grp; + }); + /* + // turn grps into a tree, then shove into search selector + grps = grps.sort(function(a,b) { return a.name() < b.name() }); + dojo.forEach(grps, function(grp) { + dojo.place( + dojo.create('option', {value : grp.id(), innerHTML : grp.name()}), + dojo.byId('patron-search-profile') + ); + }); + */ + } + }).send(); +} + +dojo.addOnLoad(function() { + + loadIDL(); + + authtoken = dojo.cookie('ses'); + + if (!authtoken) { + // user needs to log in + dojo.style(dojo.byId('login-div'), 'visibility', 'visible'); + return; + } + + // may have an authtoken that expired + var ses = new OpenSRF.ClientSession('open-ils.auth'); + ses.request({ + method : 'open-ils.auth.session.retrieve', + params : [authtoken], + oncomplete : function(r) { + var user = r.recv(); + if (user && user.content() && user.content().classname) + return init2(); + + // user needs to log in + dojo.style(dojo.byId('login-div'), 'visibility', 'visible'); + } + }).send(); +}); + +function init2() { + + loadOrgTree(); + page = location.href.split(/#/)[1] || 'user_search'; + + // draw patron search page + dojo.style(dojo.byId('progwrap'), 'display', 'none'); + dojo.style(dojo.byId('main'), 'visibility', 'visible'); + + dojo.byId('patron-search-family_name').focus(); + + var searchkeyup = function(evt) { + if (evt.keyCode == dojo.keys.ENTER) { + doStuff(); + return false; + } + }; + + dojo.forEach(userSearchFields, function(field) { + dojo.byId('patron-search-' + field).onkeyup = searchkeyup; + }); +} + +function set_keyboard(page) { + /* this is becoming a generic draw-page function */ + + switch(page) { + case 'user_search' : + document.getElementsByTagName("title")[0].innerHTML = 'Patron Search'; + document.body.onkeyup = function(evt) { + var tbody = dojo.byId('result-tbody'); + var row = dojo.query('.selected', tbody)[0]; + if (!row) return; + + switch(evt.keyCode) { + case dojo.keys.DOWN_ARROW: + if (row.nextSibling && row.nextSibling.nodeName.match(/tr/i)) + row.nextSibling.onclick(); + break; + + case dojo.keys.UP_ARROW: + if (row.previousSibling && row.previousSibling.nodeName.match(/tr/i)) + row.previousSibling.onclick(); + break; + + case dojo.keys.RIGHT_ARROW: + if (evt.shiftKey) { + row.right_arrow.onclick(); + return false; + } + } + } + break; + + case 'user_detail' : + document.getElementsByTagName("title")[0].innerHTML = 'Patron Actions'; + document.body.onkeyup = function(evt) { + + if (evt.keyCode == dojo.keys.LEFT_ARROW) { + if (evt.shiftKey) { + // return to search + dojo.byId('search-bc-link').onclick(); + return false; + } + } + } + break; + + default: + document.body.onkeyup = function(evt) {}; + } +} + +var rowTemplate; +function doStuff() { + + var tbody = dojo.byId('result-tbody'); + dojo.style(dojo.byId('search-detail'), 'visibility', 'hidden'); + + set_keyboard('user_search'); + + dojo.byId('search-bc-link').onclick = function() { + dojo.style(dojo.byId('left-nav-sample'), 'display', 'block'); + dojo.style(dojo.byId('left-nav-patron'), 'display', 'none'); + dojo.style(dojo.byId('search-form'), 'display', 'block'); + dojo.style(dojo.byId('search-div'), 'display', 'block'); + dojo.style(dojo.byId('checkout'), 'display', 'none'); + dojo.style(dojo.byId('search-bc'), 'visibility', 'hidden'); + set_keyboard('user_search'); + dojo.byId('patron-search-family_name').focus(); + } + + if (rowTemplate) { + + while(tbody.childNodes[0]) + dojo.destroy(tbody.childNodes[0]); + + } else { + + rowTemplate = tbody.removeChild(dojo.byId('result-row')); + dojo.style(dojo.byId('search-div'), 'visibility', 'visible'); + } + + //var sel = dojo.byId('org-selector'); + //var org = sel.options[sel.selectedIndex].value; + + var args = {}; + dojo.forEach(userSearchFields, function(field) { + var val = dojo.byId('patron-search-' + field).value; + if (!val) return; + args[field] = {value : val, group : 0}; + + if (field.match(/phone|ident/)) + args[field].group = 2; + else { + if (field.match(/street|city|state|post_code/)) + args[field].group = 1; + else { + if (field == 'card') + args[field].group = 3; + } + } + }); + + var ses = new OpenSRF.ClientSession('open-ils.actor'); + ses.request({ + method : 'open-ils.actor.patron.search.advanced', + params : [ + authtoken, + args, + 15, // limit + [ // sort + "family_name ASC", + "first_given_name ASC", + "second_given_name ASC", + "dob DESC" + ], + /*ou*/ null, + Boolean(dojo.byId('patron-search-inactive').checked) + ], + oncomplete : function(r) { + var idlist = r.recv().content(); + dojo.forEach(idlist, function(id, idx) { + + var row = rowTemplate.cloneNode(true); + dojo.byId('result-tbody').appendChild(row); + dojo.query('[name=index]', row)[0].innerHTML = idx; + + // current evergreen + ses.request({ + method : 'open-ils.actor.user.fleshed.retrieve', + params : [authtoken, id], + onresponse : function(r) { + display(row, r.recv().content()); + } + }).send(); + }); + } + }).send(); +} + +function nodeByName(name, pnode) { + return dojo.query('[name=' + name + ']', pnode)[0]; +} + +function display(row, user) { + row.setAttribute('user', user.id()); + + row.onclick = function(evt) { + if (row.className.match(/selected/)) return; + + dojo.query('[user]', dojo.byId('result-tbody')).forEach( + function(r) { dojo.removeClass(r, 'selected') } ); + dojo.addClass(row, 'selected') + + displayDetail(user); + row.focus(); + + if (evt) { + // experimenting... + if (evt.shiftKey) console.log('shift-click'); + if (evt.ctrlKey) console.log('ctrl-click'); + } + } + + row.right_arrow = nodeByName('right-arrow', row); + row.right_arrow.onclick = function(evt) { + displayCheckout(user); + } + + nodeByName('id', row).innerHTML = user.id(); + + if (user.card()) + nodeByName('barcode', row).innerHTML = user.card().barcode(); + + nodeByName('profile', row).innerHTML = + permGroups[user.profile()].name(); + + if (user.dob()) { + var dob = dojo.date.stamp.fromISOString(user.dob()); + dob = dojo.date.locale.format(dob, {selector : 'date'}); + nodeByName('dob', row).innerHTML = dob; + } + + nodeByName('name', row).innerHTML = + user.first_given_name() + ' ' + user.family_name(); + + if (nodeByName('index', row).innerHTML == '0') + row.onclick(); +} + +function displayDetail(user) { + var container = dojo.byId('search-detail'); + dojo.style(container, 'visibility', 'visible'); + + if (user.card()) + dojo.byId('search-detail-barcode').innerHTML = + user.card().barcode(); + + dojo.byId('search-detail-name').innerHTML = + user.first_given_name() + ' ' + user.family_name(); + + dojo.byId('search-detail-profile').innerHTML = + permGroups[user.profile()].name(); + + dojo.byId('search-detail-home-ou').innerHTML = + orgUnitMap[user.home_ou()].shortname(); + + var cd = dojo.date.stamp.fromISOString(user.create_date()); + dojo.byId('search-detail-create-date').innerHTML = + dojo.date.locale.format(cd, {selector : 'date'}); + + var ed = dojo.date.stamp.fromISOString(user.expire_date()); + dojo.byId('search-detail-expire-date').innerHTML = + dojo.date.locale.format(ed, {selector : 'date'}); + + dojo.forEach(['mailing', 'billing'], function(type) { + var addr = user[type + '_address'](); + + if (addr) { + var str = addr.street1() + '
' + addr.city() + ' ' + + addr.state() + ' ' + addr.post_code(); + dojo.byId('search-detail-'+type+'-address').innerHTML = str; + + var maplink = dojo.byId('search-detail-'+type+'-address-map'); + maplink.setAttribute('href', + 'https://maps.google.com/maps?q=' + + escape(str.replace('
',' ')) + ); + } + }); + + if (user.standing_penalties().length) { + dojo.forEach(user.standing_penalties(), function(pen) { + dojo.place( + dojo.create('div', { + 'class' : 'standing-penalty', + innerHTML : pen.standing_penalty().label() + }), + dojo.byId('search-detail') + ); + }); + + } else { + + dojo.query('.standing-penalty', dojo.byId('search-detail')).forEach( + function(node) { dojo.byId('search-detail').removeChild(node) } + ); + } + + + dojo.byId('search-detail-items-out').innerHTML = throbber; + dojo.byId('search-detail-items-od').innerHTML = throbber; + dojo.byId('search-detail-money-owed').innerHTML = throbber; + dojo.byId('search-detail-holds').innerHTML = throbber; + + // NOTE; settimeout before launch to prevent up/down arrow spewing + var ses = new OpenSRF.ClientSession('open-ils.actor'); + ses.request({ + async : true, + method : 'open-ils.actor.user.checked_out.count.authoritative', + params : [authtoken, user.id()], + oncomplete : function(r) { + var blob = r.recv().content(); + dojo.byId('search-detail-items-out').innerHTML = + blob.out + blob.overdue; + dojo.byId('search-detail-items-od').innerHTML = blob.overdue; + } + }).send(); + + ses.request({ + async : true, + method : 'open-ils.actor.user.fines.summary.authoritative', + params : [authtoken, user.id()], + oncomplete : function(r) { + var resp = r.recv(); + var owed = 0; + if (resp) { + var summary = resp.content(); + var owed = (summary) ? summary.balance_owed() : 0; + } + dojo.byId('search-detail-money-owed').innerHTML = + Number(owed).toFixed(2); + } + }).send(); + + ses.request({ + async : true, + method : 'open-ils.actor.user.hold_requests.count.authoritative', + params : [authtoken, user.id()], + oncomplete : function(r) { + var resp = r.recv(); + var count = '0/0'; + if (resp) { + var summary = resp.content(); + count = summary.ready + '/' + summary.total; + } + dojo.byId('search-detail-holds').innerHTML = count; + } + }).send(); +} + + +var checkoutTemplate; +var checkoutTbody; +function displayCheckout(user) { + dojo.style(dojo.byId('left-nav-sample'), 'display', 'none'); + dojo.style(dojo.byId('left-nav-patron'), 'display', 'block'); + dojo.style(dojo.byId('search-form'), 'display', 'none'); + dojo.style(dojo.byId('search-div'), 'display', 'none'); + dojo.style(dojo.byId('checkout'), 'display', 'block'); + dojo.style(dojo.byId('checkout-table'), 'visibility', 'hidden'); + + set_keyboard('user_detail'); + + if (checkoutTbody) { + + while(checkoutTbody.childNodes[0]) + dojo.destroy(checkoutTbody.childNodes[0]); + + } else { + checkoutTbody = dojo.byId('checkout-tbody'); + checkoutTemplate = checkoutTbody.removeChild( + dojo.byId('checkout-template')); + } + + + dojo.style(dojo.byId('search-bc'), 'visibility', 'visible'); + dojo.byId('checkout-tbox').focus(); + dojo.byId('checkout-tbox').onchange = function() { + performCheckout(user); + return false; + } +} + +function performCheckout(user, row) { + var barcode = dojo.byId('checkout-tbox').value; + if (!barcode) return; + dojo.style(dojo.byId('checkout-table'), 'visibility', 'visible'); + + dojo.byId('checkout-tbox').value = ''; + + var row = checkoutTemplate.cloneNode(true); + checkoutTbody.insertBefore(row, checkoutTbody.childNodes[0]); + nodeByName('index', row).innerHTML = + checkoutTbody.getElementsByTagName('tr').length; + + var ses = new OpenSRF.ClientSession('open-ils.circ'); + ses.request({ + async : true, + method : 'open-ils.circ.checkout.full', + params : [authtoken, {patron_id : user.id(), copy_barcode : barcode}], + oncomplete : function(r) { + var blob = r.recv().content(); + if (dojo.isArray(blob)) blob = blob[0]; + + var payload = blob.payload; + + nodeByName('status', row).innerHTML = blob.textcode; + nodeByName('barcode', row).innerHTML = barcode; + + if (!payload) return; + + if (payload.record) { + // mvr.. wheee. display attrs, anyone? + nodeByName('title', row).innerHTML = payload.record.title(); + nodeByName('author', row).innerHTML = payload.record.author(); + } + if (payload.circ) { + var due = dojo.date.stamp.fromISOString(payload.circ.due_date()); + nodeByName('due_date', row).innerHTML = dojo.date.locale.format(due); + } + } + }).send(); + +} + +function clearSearchForm() { + dojo.query('input', dojo.byId('user-search-table')).forEach( + function(node) { node.value = '' } + ); + dojo.query('select', dojo.byId('user-search-table')).forEach( + function(node) { node.selectedIndex = 0; } + ); +} diff --git a/idl.js b/idl.js new file mode 100644 index 000000000..943a04d4d --- /dev/null +++ b/idl.js @@ -0,0 +1,35 @@ +// Requires