Ready to Learn?Ex Libris products all provide open APIs

Tech Blog

 

Report a Broken Link in PrimoVE

We set up a link that only appears under "View It" that opens a dialog screen for patrons to report broken links.
The form automatically gathers all data (except abstract) from addata in the PNX, additional information for creating the permalink, and asks the user what problem was encountered.
When the patron sends the report, the data is captured in a MySQL databases and emailed to librarians, who can then perform additional research to see if the article or journal is available and to investigate the cause of the broken link.

We use an open source plugin called Tingle, written in pure JavaScript, for the dialog box.

First, we created a templateUrl for the broken link. This will appear in the page only when the View It section is visible.

In <VIEW_CODE>/html/homepage, create a new file called report_broken_link.html with the following code:

<div class="tcc-broken-link">
   <a target="_blank" class="arrow-link md-primoExplore-theme" onclick="tcc_modal.open()">
      $ctrl.getText()
      <prm-icon external-link="" icon-type="svg" svg-icon-set="primo-ui" icon-definition="open-in-new">
         <md-icon md-svg-icon="primo-ui:open-in-new" alt="" class="md-primoExplore-theme" aria-hidden="true">
            <svg id="open-in-new" width="100%" height="100%" viewBox="0 0 24 24" y="504" xmlns="http://www.w3.org/2000/svg" fit="" preserveAspectRatio="xMidYMid meet" focusable="false">
               <path d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"></path>
            </svg>
         </md-icon>
         <prm-icon-after parent-ctrl="$ctrl"></prm-icon-after>
      </prm-icon>
   </a>
</div>

In custom1.css, add this line for the modal dialog:

.tingle-modal *{box-sizing:border-box}.tingle-modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:10000;display:-webkit-box;display:-ms-flexbox;display:flex;visibility:hidden;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;overflow:hidden;-webkit-overflow-scrolling:touch;background:rgba(0,0,0,.8);opacity:0;cursor:pointer;-webkit-transition:-webkit-transform .2s ease;transition:-webkit-transform .2s ease;transition:transform .2s ease;transition:transform .2s ease,-webkit-transform .2s ease}.tingle-modal--noClose .tingle-modal__close,.tingle-modal__closeLabel{display:none}.tingle-modal--confirm .tingle-modal-box{text-align:center}.tingle-modal--noOverlayClose{cursor:default}.tingle-modal__close{position:fixed;top:10px;right:28px;z-index:1000;padding:0;width:5rem;height:5rem;border:none;background-color:transparent;color:#f0f0f0;font-size:6rem;font-family:monospace;line-height:1;cursor:pointer;-webkit-transition:color .3s ease;transition:color .3s ease}.tingle-modal__close:hover{color:#fff}.tingle-modal-box{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:auto;margin-bottom:auto;width:60%;border-radius:4px;background:#fff;opacity:1;cursor:auto;-webkit-transition:-webkit-transform .3s cubic-bezier(.175,.885,.32,1.275);transition:-webkit-transform .3s cubic-bezier(.175,.885,.32,1.275);transition:transform .3s cubic-bezier(.175,.885,.32,1.275);transition:transform .3s cubic-bezier(.175,.885,.32,1.275),-webkit-transform .3s cubic-bezier(.175,.885,.32,1.275);-webkit-transform:scale(.8);-ms-transform:scale(.8);transform:scale(.8)}.tingle-modal-box__content{padding:3rem}.tingle-modal-box__footer{padding:1.5rem 2rem;width:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px;background-color:#f5f5f5;cursor:auto}.tingle-modal-box__footer::after{display:table;clear:both;content:""}.tingle-modal-box__footer--sticky{position:fixed;bottom:-200px;z-index:10001;opacity:1;-webkit-transition:bottom .3s ease-in-out .3s;transition:bottom .3s ease-in-out .3s}.tingle-enabled{position:fixed;overflow:hidden;left:0;right:0}.tingle-modal--visible .tingle-modal-box__footer{bottom:0}.tingle-enabled .tingle-content-wrapper{-webkit-filter:blur(8px);filter:blur(8px)}.tingle-modal--visible{visibility:visible;opacity:1}.tingle-modal--visible .tingle-modal-box{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}.tingle-modal--overflow{overflow-y:scroll;padding-top:8vh}.tingle-btn{display:inline-block;margin:0 .5rem;padding:1rem 2rem;border:none;background-color:grey;box-shadow:none;color:#fff;vertical-align:middle;text-decoration:none;font-size:inherit;font-family:inherit;line-height:normal;cursor:pointer;-webkit-transition:background-color .4s ease;transition:background-color .4s ease}.tingle-btn--primary{background-color:#3498db}.tingle-btn--danger{background-color:#e74c3c}.tingle-btn--default{background-color:#34495e}.tingle-btn--pull-left{float:left}.tingle-btn--pull-right{float:right}@media (max-width :540px){.tingle-modal{top:0;display:block;padding-top:60px;width:100%}.tingle-modal-box{width:auto;border-radius:0}.tingle-modal-box__content{overflow-y:scroll}.tingle-modal--noClose{top:0}.tingle-modal--noOverlayClose{padding-top:0}.tingle-modal-box__footer .tingle-btn{display:block;float:none;margin-bottom:1rem;width:100%}.tingle-modal__close{top:0;right:0;left:0;display:block;width:100%;height:60px;border:none;background-color:#2c3e50;box-shadow:none;color:#fff;line-height:55px}.tingle-modal__closeLabel{display:inline-block;vertical-align:middle;font-size:1.5rem;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",sans-serif}.tingle-modal__closeIcon{display:inline-block;margin-right:.5rem;vertical-align:middle;font-size:4rem}}@supports ((-webkit-backdrop-filter:blur(12px)) or (backdrop-filter:blur(12px))){.tingle-modal{-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px)}@media (max-width :540px){.tingle-modal{-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}}.tingle-enabled .tingle-content-wrapper{-webkit-filter:none;filter:none}}

In custom.js, add:

(function () {
   "use strict";
   'use strict';

   var app = angular.module('viewCustom', ['angularLoad']);

/* ...other code you may already have in custom.js... */

app.component('prmAlmaViewitAfter', {
   bindings: { parentCtrl: '<' },
   controller: 'prmAlmaViewitAfterController',
   templateUrl: '/discovery/custom/<VIEW_CODE>/html/homepage/report_broken_link.html'
});

app.controller('prmAlmaViewitAfterController', [ function() {
   var vm = this;
   vm.tcc_dialog_content = "<form id='tcc_bad_link_form' method='post'>";

   try {
      /* add all data except abstract to the form in hidden fields */
      vm.tcc_info = vm.parentCtrl.item.pnx.addata;
      for(var property_name in vm.tcc_info) {
         if(property_name!='abstract') {
            vm.tcc_dialog_content += "<input type='hidden' id='" + property_name + "' name='" + property_name + "' value='" + vm.tcc_info[property_name] + "'>";
         }
      }

      /* put the article title, the book title, or the journal title in the dialog (whichever comes first) */
      if(undefined!=vm.parentCtrl.item.pnx.addata.atitle) {
         vm.tcc_dialog_content += "<div class='tcc-dialog-title'><div style='color:green;font-weight:bold;'>" + vm.parentCtrl.item.pnx.addata.atitle + "</div></div>";
      } else if(undefined!=vm.parentCtrl.item.pnx.addata.btitle) {
         vm.tcc_dialog_content += "<div class='tcc-dialog-title'><div style='color:green;font-weight:bold;'>" + vm.parentCtrl.item.pnx.addata.btitle + "</div></div>";
      } else if(undefined!=vm.parentCtrl.item.pnx.addata.jtitle) {
         vm.tcc_dialog_content += "<div class='tcc-dialog-title'><div style='color:green;font-weight:bold;'>" + vm.parentCtrl.item.pnx.addata.jtitle + "</div></div>";
      }

      /* put the author data in the dialog (full name or last name/first name) */
      if(undefined!=vm.parentCtrl.item.pnx.addata.au) {
         vm.tcc_dialog_content += "<div class='tcc-dialog-au'>by " + vm.parentCtrl.item.pnx.addata.au + "</div>";
      } else {
         if(undefined!=vm.parentCtrl.item.pnx.addata.aulast) {
            vm.tcc_dialog_content += "<div class='tcc-dialog-aulast'>by " + vm.parentCtrl.item.pnx.addata.aulast+"</div>";

            if(undefined!=vm.parentCtrl.item.pnx.addata.aufirst) {
               vm.tcc_dialog_content += "<div class='tcc-dialog-aufirst'>" + vm.parentCtrl.item.pnx.addata.aufirst+"</div>";
            }
         }
      }

      /* put the journal title in the dialog */
      if(undefined!=vm.parentCtrl.item.pnx.addata.jtitle) {
         vm.tcc_dialog_content += "<div class='tcc-dialog-jtitle' style='font-weight:bold;'>" + vm.parentCtrl.item.pnx.addata.jtitle + "</div>";
      }

      /* put the issn in the dialog */
      if(undefined!=vm.parentCtrl.item.pnx.addata.eissn) {
         vm.tcc_dialog_content += "<div class='tcc-dialog-eissn'>ISSN: " + vm.parentCtrl.item.pnx.addata.eissn + "</div>";
      } else if(undefined!=vm.parentCtrl.item.pnx.addata.issn) {
         vm.tcc_dialog_content += "<div class='tcc-dialog-issn'>ISSN: " + vm.parentCtrl.item.pnx.addata.issn + "</div>";
      }

      /* put the volume in the dialog */
      if(undefined!=vm.parentCtrl.item.pnx.addata.volume) {
         vm.tcc_dialog_content += "<div class='tcc-dialog-volume'>Vol: " + vm.parentCtrl.item.pnx.addata.volume + "</div>";
      }

      /* put the issue in the dialog */
      if(undefined!=vm.parentCtrl.item.pnx.addata.issue) {
         vm.tcc_dialog_content += "<div class='tcc-dialog-issue'>Issue: " + vm.parentCtrl.item.pnx.addata.issue + "</div>";
      }

      /* put the pages in the dialog */
      if(undefined!=vm.parentCtrl.item.pnx.addata.pages) {
         vm.tcc_dialog_content += "<div class='tcc-dialog-pages'>Pages: " + vm.parentCtrl.item.pnx.addata.pages + "</div>";
      } else if(undefined!=vm.parentCtrl.item.pnx.addata.spage) {
         vm.tcc_dialog_content += "<div class='tcc-dialog-pages'>Pages: " + vm.parentCtrl.item.pnx.addata.spage;
         if(undefined!=vm.parentCtrl.item.pnx.addata.epage) {
            vm.tcc_dialog_content += "- " + vm.parentCtrl.item.pnx.addata.epage;
         }
         vm.tcc_dialog_content += "</div>";
      }

      /* put additional questions in the dialog */
      vm.tcc_dialog_content += "<div style='border:1px solid #CECECE;padding:8px;'><div><strong>Which of following best describes the problem with this article?</strong></div><div class='tcc-dialog-problem'><select name='badlink_report_option_id' id='badlink_report_option_id' class='' size='1'><option value=''>-- select one --</option><option value='1' >The PDF is blank/missing pages</option><option value='2' >I received a 404/page not found error</option><option value='3' >The website prompted me to pay to access the article</option><option value='4' >The link went to another website other than the selected article</option><option value='5' >Full text for the article was not available, only the abstract or citation</option><option value='6' >Something else went wrong, explain in the comments below</option></select></div></div>";
      vm.tcc_dialog_content += "<div style='border:1px solid #CECECE;padding:8px;'><div><strong>If we locate this item, at what <span style='font-style:italic;color:blue;'>email address</span> may we contact you? (Optional)</strong><label for='cc_email'><input type='checkbox' id='cc_email' name='cc_email' value='1'><span style='font-weight:.8em;'>(Cc report here)</span></label></div><div class='tcc-dialog-email'><input type='text' id='badlink_report_email' name='badlink_report_email' value='' style='width:98%;border:1px solid #ECECEC;'></div></div>";
      vm.tcc_dialog_content += "<div style='border:1px solid #CECECE;padding:8px;'><div><strong>Comments (Optional)</strong></div><div class='tcc-dialog-comment'><textarea id='badlink_report_comments' name='badlink_report_comments' style='width:98%;'></textarea></div></div>";

      /* this information is used to build the permalink on our local server */
      /* there is a permalink available, but I had trouble getting it to transfer to the server without being malfomed */
      vm.tcc_dialog_content += "<input type='hidden' id='vid' name='vid' value='" + vm.parentCtrl.fullViewService.configurationUtil.searchFieldsService._searchParams.vid + "'>";
      vm.tcc_dialog_content += "<input type='hidden' id='tab' name='tab' value='" + vm.parentCtrl.fullViewService.configurationUtil.searchFieldsService._searchParams.tab + "'>";
      vm.tcc_dialog_content += "<input type='hidden' id='docid' name='docid' value='" + vm.parentCtrl.item.pnx.control.recordid + "'>";
      vm.tcc_dialog_content += "<input type='hidden' id='context' name='context' value='" + vm.parentCtrl.item.context + "'>";
      vm.tcc_dialog_content += "<input type='hidden' id='search_scope' name='search_scope' value='" + vm.parentCtrl.fullViewService.configurationUtil.searchFieldsService._searchParams.search_scope + "'>";
      vm.tcc_dialog_content += "<input type='hidden' id='lang' name='lang' value='en'></form>";

      /* create and open the dialog */
      document.getElementsByClassName("tingle-modal-box__content")[0].innerHTML = vm.tcc_dialog_content;
   } catch(err){
      console.log(err);
   }

   vm.getText = getText;

   function getText() {
      return "Report a broken link";
   }

}]);

/* ... other code you may have ...*/

})(); /* This is the end of your angular code block */

In custom.js, add this code at the very bottom (underneath your angular code block):

/* Tingle JavaScript plugin */
!function(t,o){"function"==typeof define&&define.amd?define(o):"object"==typeof exports?module.exports=o():t.tingle=o()}(this,function(){function t(t){var o={onClose:null,onOpen:null,beforeOpen:null,beforeClose:null,stickyFooter:!1,footer:!1,cssClass:[],closeLabel:"Close",closeMethods:["overlay","button","escape"]};this.opts=r({},o,t),this.init()}function o(){this.modalBoxFooter&&(this.modalBoxFooter.style.width=this.modalBox.clientWidth+"px",this.modalBoxFooter.style.left=this.modalBox.offsetLeft+"px")}function e(){this.modal=document.createElement("div"),this.modal.classList.add("tingle-modal"),0!==this.opts.closeMethods.length&&-1!==this.opts.closeMethods.indexOf("overlay")||this.modal.classList.add("tingle-modal--noOverlayClose"),this.modal.style.display="none",this.opts.cssClass.forEach(function(t){"string"==typeof t&&this.modal.classList.add(t)},this),-1!==this.opts.closeMethods.indexOf("button")&&(this.modalCloseBtn=document.createElement("button"),this.modalCloseBtn.classList.add("tingle-modal__close"),this.modalCloseBtnIcon=document.createElement("span"),this.modalCloseBtnIcon.classList.add("tingle-modal__closeIcon"),this.modalCloseBtnIcon.innerHTML="×",this.modalCloseBtnLabel=document.createElement("span"),this.modalCloseBtnLabel.classList.add("tingle-modal__closeLabel"),this.modalCloseBtnLabel.innerHTML=this.opts.closeLabel,this.modalCloseBtn.appendChild(this.modalCloseBtnIcon),this.modalCloseBtn.appendChild(this.modalCloseBtnLabel)),this.modalBox=document.createElement("div"),this.modalBox.classList.add("tingle-modal-box"),this.modalBoxContent=document.createElement("div"),this.modalBoxContent.classList.add("tingle-modal-box__content"),this.modalBox.appendChild(this.modalBoxContent),-1!==this.opts.closeMethods.indexOf("button")&&this.modal.appendChild(this.modalCloseBtn),this.modal.appendChild(this.modalBox)}function s(){this.modalBoxFooter=document.createElement("div"),this.modalBoxFooter.classList.add("tingle-modal-box__footer"),this.modalBox.appendChild(this.modalBoxFooter)}function i(){this._events={clickCloseBtn:this.close.bind(this),clickOverlay:l.bind(this),resize:this.checkOverflow.bind(this),keyboardNav:n.bind(this)},-1!==this.opts.closeMethods.indexOf("button")&&this.modalCloseBtn.addEventListener("click",this._events.clickCloseBtn),this.modal.addEventListener("mousedown",this._events.clickOverlay),window.addEventListener("resize",this._events.resize),document.addEventListener("keydown",this._events.keyboardNav)}function n(t){-1!==this.opts.closeMethods.indexOf("escape")&&27===t.which&&this.isOpen()&&this.close()}function l(t){-1!==this.opts.closeMethods.indexOf("overlay")&&!d(t.target,"tingle-modal")&&t.clientX=t},t.prototype.checkOverflow=function(){this.modal.classList.contains("tingle-modal--visible")&&(this.isOverflow()?this.modal.classList.add("tingle-modal--overflow"):this.modal.classList.remove("tingle-modal--overflow"),!this.isOverflow()&&this.opts.stickyFooter?this.setStickyFooter(!1):this.isOverflow()&&this.opts.stickyFooter&&(o.call(this),this.setStickyFooter(!0)))},{modal:t);

/* create a tingle modal dialog */ var tcc_modal = new tingle.modal({
   footer: true,
   stickyFooter: false,
   closeMethods: ['overlay', 'button', 'escape'],
   closeLabel: "Close",
   cssClass: ['custom-class-1', 'custom-class-2'],
   onOpen: function() {
      //console.log('modal open');
   },
   onClose: function() {
      //console.log('modal closed');
   },
   beforeClose: function() {
      return true; // close the modal
   }
});

tcc_modal.addFooterBtn('Send Report', 'tingle-btn tingle-btn--primary', function() {
   var form_data = new FormData(document.getElementById("tcc_bad_link_form"));

   var xhttp = new XMLHttpRequest();
   xhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
         alert(this.responseText);
         return true;
      }
   };
   xhttp.open("POST", "https://your.domain.edu/path_to_code/", true);
   //xhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
   xhttp.send(form_data);

   tcc_modal.close();
});

 

Here is a sample of the data received by the server:

/* This is from the addata in the PNX */
[date] => 20180308
[aulast] => Marks,Gottlieb
[issue] => 10
[cop] => Boston
[format] => journal
[ristype] => JOUR
[spage] => 954
[atitle] => Balancing Safety and Innovation for Cell-Based Regenerative Medicine
[url] => http://search.proquest.com/docview/2019525043/
[aufirst] => Peter,Scott
[volume] => 378
[pages] => 954-959
[issn] => 00284793
[au] => Marks, Peter,Gottlieb, Scott
[epage] => 959
[genre] => article
[jtitle] => The New England Journal of Medicine
[risdate] => 20180308
[pub] => Massachusetts Medical Society
[doi] => 10.1056/NEJMsr1715626

/* This is from the drop down menu */
[badlink_report_option_id] => 1

/* This is from the checkbox */
[cc_email] => 1

/* This is from the text field */
[badlink_report_email] => jims_test@email.com

/* This is from the textarea */
[badlink_report_comments] => sample comments

/* This is for the permalink */
[vid] => 01TARRANT_INST:TCCD2017
[tab] => Everything
[docid] => proquest2019525043
[context] => PC
[search_scope] => MyInst_and_CI
[lang] => en


Here is a permalink to a record with Report a broken link.
Click Report a broken link to view the form, but please don't report a broken link if it's not broken.
(Don't click the Send Report button unless you really wish to report a broken link.)
If you don't have a server to process code, you may be able to send it as an email with one these free email services (I have not tried this, so I cannot answer questions about it...:-):