Text call numbers from Aleph OPAC
Description
This feature allows patrons to send holdings data about items to their mobile phones. Users must simply navigate to an item’s holding page and click on the button titled “Send Info via Text.” They will then need to enter their phone number and cellular carrier to receive a text message with the title, location, and call number of the selected item. This is done WITHOUT a dedicated SMS server and relies, instead, on the Net::SMTP Perl module to send the messages. (You will need to know your institution’s mail server.)
State
Stable
Programming language(s)
HTML, CSS, JavaScript, Perl
Software requirements
Ex Libris Aleph (tested with v18 only)
Working example
Installation instructions
1. Add new CSS class (“sms”) to style the popup box
> wf > vi exlibris-xxx01.css ... #sms { width: 250px; font-family: arial, helvetica, sans-serif; font-size: 70%; border: 1px solid #36647B; position: absolute; left: 50%; top: 50%; z-index: 1000; padding: 10px; margin: 10px; background: #EFEFCF; }2. Add table ID (“bib-items”) so JavaScript will find table with holding info
> wf > vi item-global-body-head ... <table ... id="bib-items"> ...
3. Add JavaScript to hide “Send Info via Text” button if there is no item data available
> wf > vi item-global-body-tail ... <script> var itms = document.getElementById('bib-items'); var tr = itms.getElementsByTagName('tr'); for( var i = 1; i < tr.length; i++ ) { var td = tr[i].getElementsByTagName('td'); // our bib items table has 8 columns (if // item exists) -- yours may be different if( td.length !== 8 ) { document.getElementById('smslink').style.visibility='hidden'; } } </script> ...4. Add “Send Info via Text” button (span ID “smslink”) and create new span tag (ID “sms”) to display form
> wf > vi item-global-dropdown-menus-a ... <span id="smslink"> <a href="#" name="smslink" id="smslink" onClick="showSMS();return false;">Send Info via Text</a> </span> ... <span id="sms" style="visibility:hidden;display:none;"></span> ...
5. Include sms-js file in the holdings page header and add table ID (“bib-detail”) for JavaScript
> wf > vi item-global-head-1 ... <include>sms-js ... <table id="bib-detail"> ...
6. In cgi-bin, write Perl script (“sms.pl”), remembering to chmod 0755
> vi sms.pl #!/aleph/aleph/a18_1/product/bin/perl print "Content-type:text/html\n\n"; use Net::SMTP; use strict; use warnings; use CGI; use CGI::Carp qw( fatalsToBrowser ); my %providers = ( 'ATT' => '@txt.att.net', 'Boost' => '@myboostmobile.com', 'Metro' => '@mymetropcs.com', 'Nextel' => '@messaging.nextel.com', 'Sprint' => '@messaging.sprintpcs.com', 'T-Mobile' => '@tmomail.net', 'Verizon' => '@vtext.com', 'Virgin' => '@vmobl.com' ); # return address need not be an actual email address # (but should be easily identifiable to your users) my $from = "refdesk\@your.lib"; my $q = new CGI; my $num = $q->param( 'number' ); # remove all non-digit characters from number: $num =~ s/[^\d]//ig; # concatenate number with SMTP domain from %providers: my $to = $num . $providers{ $q->param( 'provider' ) }; # remove all parentheses and dashes: $to =~ s/[\(\)\-\s]//ig; my $title = $q->param( 'title' ); # remove extraneous whitespaces at beginning & end of line: $title =~ s/^\s+|\s+$//g; my $hold = $q->param( 'item' ); my $body = "'$title'\n$hold\n--DO NOT RESPOND"; # replace [mail-server] with your SMTP domain my $smtp = Net::SMTP->new( "[mail-server]", Debug => 1 ); $smtp->mail( $from ); $smtp->to( $to ); $smtp->data(); $smtp->datasend( $body ); $smtp->dataend(); $smtp->quit; print "clearSMS( );"; 7. In web directory, write JavaScript (“sms-js”) file
> wf > vi sms-js <!-- filename: sms-js --> <script> // ------ // sms.js: Scrapes OPAC screen for item title & holdings info, creates & displays a form that lets the user // choose his/her preferred item location & asks for his/her phone # number and celluluar carrier. // The actual "texting" functionality is handled by an external Perl script, which accepts title, // item info, cell #, and cell carrier as parameters and sends an email (number@carrier) to the user // (which is received as a text message) with the requested information. // ------ // Originally created for Innovative Interface OPAC by Adam Brin (Bryn Mawr College) in ~2005-2008 // Adapted for Ex Libris Aleph OPAC by Alevtina Verbovetskaya (City University of New York) in 2013 // ------ // set this to be the URL for the SMS Perl script var smsurl = "[ils-server-url]/[path-to-cgi-bin]/sms.pl?"; // This function shows the SMS layer and creates the form function showSMS( ) { try { // enable this to show debugging alerts var debug = 0; try { var bib = document.getElementById( "bib-detail" ); if( debug > 0 ) alert( "bib: " + bib ); // we have to iterate through every TR to get to the title var tr = bib.getElementsByTagName( "tr" ); if( debug > 0 ) alert( "tr: " + tr ); // for every TR in the document for( i = 0; i < tr.length; i++ ) { // get all of the columns var td = tr[i].getElementsByTagName( "td" ); // because of the way our OPAC is set up, it's impossible to know exactly // which row in the bib-detail table contains the title but we can capture // most titles because they end with " / <br>" for( j = 0; j < td.length; j++ ) { var str = td[j].innerHTML; var br = /\/ <br>/i; var slash = str.search( br ); if( slash !== -1 ) { var title = str.substring( 0, slash-1 ); // CUNY's e-books have "[electronic resource]" in title // and we're removing it here so as not to waste characters // while we remove non-alphanumeric characters (since it // will be passed as parameter in URL) title = title.replace( " ", " " ).replace( "[electronic resource]", "" ).replace( /[^a-zA-Z 0-9-':;\/]/g, "" ); unescape( title ); } if( debug > 0 ) alert( "tr[" + i + "]td[" + j + "] " + str ); } } if( !title ) { var sms = document.getElementById( "sms" ); var out = "<h3>Feature Unavailable</h3>"; out += "<p>That feature is currently unavailable for this item. We apologize for the inconvenience.</p>"; out += "<p style='text-align:center;'><input type='button' name='clearmessage' onClick='clearSMS();return false;' value='Close Window' /></p>"; var smshtml = sms.innerHTML.search( "Feature Unavailable" ); // some hackery to accommodate everyone's favorite browser (IE, of course!) // because it doesn't like adding HTML via the innerHTML property if( smshtml == -1 ) { var span = document.createElement( "span" ); span.innerHTML = out; sms.appendChild( span ); // now we make the div (currently hidden) visible sms.style.visibility = "visible"; sms.style.display = "block"; } } } catch( e ) {} if( debug > 0 ) alert( "Title: " + title ); // this is the DIV that we're going to put the text into var sms = document.getElementById( "sms" ); if( debug > 0 ) alert( "sms: " + sms.innerHTML ); // we'll load the 'out' variable with all the html and then put it into the sms div var out = "<h3>Send Item Info to Your Mobile Phone</h3>"; out += "<form name='sms_form' method=post>"; out += "<p><strong>Title</strong>:<br />" + title + "</p>"; // dump the title into a hidden form variable out += "<input type=hidden name=title value=\'" + title + "\' />"; out += "<p><strong>Choose an item near you:</strong><br />"; // get the ITEM table var itms = document.getElementById( "bib-items" ); if( debug > 0 ) alert( "itms: " + itms ); // get each row var tr = itms.getElementsByTagName( "tr" ); // setting i = 1 skips the first (heading) row for( i = 1; i < tr.length; i++ ) { // get each cell var td = tr[i].getElementsByTagName( "td" ); if( debug > 0 ) alert( "td.length: " + td.length ); // if there are 8 cells (like our ITEM table) if( td.length == 8 ) { // get the library (remove tags & non-alphanumeric chars) var college = td[1].innerHTML.replace( /<([^>]+)>/ig, "" ).replace( /[^a-zA-Z 0-9]/g, ""); // get the collection (remove tags) var collection = td[2].innerHTML.replace( /<([^>]+)>/ig, "" ); // get the call number (remove tags & convert ) var callnumber = td[3].innerHTML.replace( /<([^>]+)>/ig, "" ).replace( " ", " " ); unescape( college ); unescape( collection ); unescape( callnumber ); var chck = ""; // if we're on the first row, check it if( i == 1 ) chck = " checked "; out += "<input " + chck + " type=radio name=lib value='" + college + "\n" + collection + "\n" + callnumber + "' />" + college + " " + collection + " (" + callnumber + ")<br />"; if( debug > 0 ) { alert( "College: " + college ); alert( "Collection: " + collection ); alert( "Call #: " + callnumber ); } } } out += "</p>"; // input for the phone # out += "<p><strong>Enter your mobile phone #</strong>:<br />"; out += "<small>e.g., 2127945706 (10 digits, no spaces, no dashes)</small><br />"; out += "<input name=phone type=text style='width:175px;' /></p>"; // pull-down for each of phone carriers the values will be parsed by the perl script out += "<p><strong>Select your mobile carrier:</strong><br />"; out += "<select name=provider style='width:175px;'>"; out += "<option> </option>"; out += "<option value=ATT>AT&T/Cingular</option>"; out += "<option value=Boost>Boost Mobile</option>"; out += "<option value=Metro>Metro PCS</option>"; out += "<option value=Nextel>Nextel</option>"; out += "<option value=Sprint>Sprint PCS</option>"; out += "<option value=T-Mobile>T-Mobile</option>"; out += "<option value=Verizon>Verizon</option>"; out += "<option value=Virgin>Virgin Mobile USA</option>"; out += "</select></p>"; out += "<p><strong>NOTE</strong>: <em>Carrier charges may apply.</em></p>"; // add buttons at bottom. note the return false which stops the form from actually doing anything out += "<p style='text-align:center;'><input type='button' name='sendmessage' onClick='sendSMS( this.form );return false;' value='Send Message' /> <input type='button' name='clearmessage' onClick='clearSMS();return false;' value='Close Window' /></p>"; out += "</form>"; // check to see that sms div doesn't already exist on page or else it'll keep appending // content to the end of the div as long as the user keeps clicking "Send Info via Text" var smshtml = sms.innerHTML.search( "Send Item Info to Your Mobile Phone" ); // some hackery to accommodate everyone's favorite browser (IE, of course!) if( title && smshtml == -1 ) { // because it doesn't like adding HTML via the innerHTML property var span = document.createElement( "span" ); span.innerHTML = out; sms.appendChild( span ); // now we make the div visible sms.style.visibility = "visible"; sms.style.display = "block"; } if( debug > 0 ) alert( "sms: " + sms.innerHTML ); var smsbutton = document.getElementById( "smslink" ); // some fancy positioning findPos( smsbutton, sms, 150, -30 ); } catch( e ) { // doesn't work? hide the SMS button document.getElementById( "smslink" ).style.visibility = "hidden"; } return false; } function sendSMS( frm ) { // enable this to show debugging alerts var debug = 0; // get the phone # var phone = frm.phone.value; // ... & remove all non-digit characters phone = phone.replace( /[^\d]/ig, "" ); var prvdr = frm.provider.options[frm.provider.selectedIndex].value; // if 10 chars & provider selected, we're good if( ( phone.length == 10 ) && ( prvdr !== "" ) ) { // start creating the Perl script URL var url = smsurl; // truncate title & html escape field if( frm.title.value.length > 75 ) { url += "title=" + encodeURIComponent( frm.title.value ).substring( 0, 74 ) + "[...]"; } else { url += "title=" + encodeURIComponent( frm.title.value ); } // html escape phone # url += "&number=" + encodeURIComponent( frm.phone.value ); // html escape phone provider url += "&provider=" + encodeURIComponent( frm.provider.options[frm.provider.selectedIndex].value ); // for each item, get the checked one for( i = 0; i < frm.lib.length; i++ ) { // if checked, add it to the URL if( frm.lib[i].checked == true ) { url += "&item=" + encodeURIComponent( frm.lib[i].value ); } } // if just one -- should not come to this if( frm.lib.length == undefined ) { url += "&item=" + encodeURIComponent( frm.lib.value ); } // now we create a <SCRIPT> tag in the <HEAD> to get the response var head = document.getElementsByTagName( "head" )[0]; var script = document.createElement( "script" ); script.setAttribute( "type", "text/javascript" ); // the script is actually the PERL script script.setAttribute( "src", url ); // append the script head.appendChild( script ); if( debug > 0 ) alert( "Script URL: " + url ); } else { // invalid phone #, alert user to fix input alert( "Please make sure you've entered a valid phone number and chosen a carrier." ); } } // clear/hide the SMS DIV function clearSMS( ) { var sms = document.getElementById( "sms" ); sms.style.visibility = "hidden"; sms.style.display = "none"; sms.innerHTML = ""; } // get the position of an item, good for putting the SMS form in a useful place function findPos( obj1, obj2, loffset, toffset ) { var curleft = curtop = 0; if( obj1.offsetParent ) { curleft = obj1.offsetLeft; curtop = obj1.offsetTop; while( obj1 = obj1.offsetParent ) { curleft += obj1.offsetLeft; curtop += obj1.offsetTop; } } obj2.style.left = curleft + loffset; obj2.style.top = curtop + toffset; } </script> <!-- end file: sms-js --> 8.
- If your library is like CUNY, item titles on holdings pages will need to be somehow identified as titles. We added a trailing slash to the titles
> cd $xxx01_dev/xxx01/tab > vi edit_paragraph.eng !* Author-Title for Bib Inf 013 1#### D ^:^ 013 245## 9 ## ^/^ 013 250## D ## . 013 260## W ##^ . 013 300## D ##^ .
- Include small JavaScript in footer to remove slash (after SMS script has had a chance to run)
> wf > vi item-global-tail-1 ... <script> // some titles end with two slashes (//) and, because // this is not a desired behavior, we are replacing // the two slashes with just one slash var bib = document.getElementById( 'bib-detail' ); var x = bib.rows; for( var i = 0; i < x.length; i++ ) { var y = x[i].cells; for( var j = 0; j < y.length; j++ ) { var txt = y[j].innerHTML; y[j].innerHTML = txt.replace( /\/\//g, "/" ); } } </script>
