Monday, September 10, 2018

Using Github to help manage a large batch of Koha SQL reports

When I started managing the Next Kansas 51 library shared catalog in 2016 I inherited a collection of 1200+ SQL reports that had been created by seven or eight different employees over a seven or eight year period working with Koha.

Some reports have names that describe what the report does, but some have names like "pcm records," or "Problem hold," or (my personal favorite) "Not sure?"  Some reports have notes that describe what the report is intended to do, but many do not have notes ("Not sure?" does not have any notes).  Many are categorized into groups and sub-groups.  And sometimes the groups and sub-groups actually make sense.  But still, we had 1200+ reports.  Essentially, I inherited a SQL reports black hole.

For staff at our member libraries, finding a report went something like this:

1. A staff member from Library A would go to "Home > Reports > Guided reports wizard > Saved reports" and search for a report that does A, B, and C using the keyword search tools on the page to search for A, B, or C or some variations on that search.

2. If the searcher could not find a report that appeared to do what they wanted, they would phone or e-mail NEKLS saying "I'm looking for a report that does A, B, and C.  Do we have a report that does A, B, and C?"

3. NEKLS would respond with "Well, I think we have a report that does A, B, and C, but I can't find it, so I wrote a new report that does A, B, and C.  It's report #4656 and I named it "Report for Library A".  Let me know if it works."

4. A month later a staff member from a different library would go to "Home > Reports > Guided reports wizard > Saved reports" and search for and search for a report that does A, B, and C using the keyword search tools on the page to search for A, B, or C or some variations on that search.

5. If the new searcher could not find a report that appeared to do A, B, and C, they would phone or e-mail NEKLS saying "I'm looking for a report that does A, B, and C.  Do we have a report that does this?"

6. NEKLS would respond with "Well, I think we have a report that does that, but I can't find it, so I wrote a new report that does A, B, and C.  It's report #4799 and I named it "Report for Staff Member".  Let me know if it works."

One variation on this is when a staff member from a library calls and says "I found report 4656 that does A, B, and C, but I need a report that does A, B, C, and D.  Can you write me a report that does A, B, C, and D."  And then we end up with report "A, B, and C" and report "A, B, C, and D" and neither one of them has a name that says "This report does _____."

The result has been that, over time, we ended up with a lot of reports that were either duplicates, or minor variations on other reports written by many people over a long period of time and they are very difficult to navigate.

They are an SQL report black hole.

My solution to the too-many-reports problem was to start deleting old reports that didn't make any sense to me.

But this led to another problem.

About two months after my first pass at deleting reports that didn't make sense,  I got a phone call from one of our libraries saying "But I needed that report.  That was my special report that I only needed to run once every 163 days.  Liz wrote it for me in 2011."

Generally speaking, for every 10-15 reports I got rid of, I would get one phone call asking "Where's my super special report?"

Since re-creating these reports is a hassle, I started using an SQL report-of-reports, Microsoft Excel with a VBA macro, Atom, and a Github repository to make regular backups of our SQL library so that I could track the changes I was making to our reports.

This is the process:

I created a repository on Github.  In my case I called it nexpress.sql.  Then I cloned that repository to my local computer.  (https://github.com/will1410/nexpress.sql)


I created a folder on the computers I run this process from called C:\git

Then I run this SQL:

-----
SELECT
  Concat("R.", LPad(saved_sql.id, 6, 0)) AS FILE_NAME,
  Concat(
    Concat("R.", LPad(saved_sql.id, 6, 0)), Char(13), Char(10), Char(13), Char(10),
    Concat("----------"), Char(13), Char(10), Char(13), Char(10),
    Concat("Name: ", Coalesce(saved_sql.report_name, "-")), Char(13), Char(10),
    Concat("Created by: ", If(Coalesce(borrowers.borrowernumber, 0) = 0, "-", Concat(borrowers.firstname, " ", borrowers.surname))), Char(13), Char(10), Char(13), Char(10),
    Concat("----------"), Char(13), Char(10), Char(13), Char(10),
    Concat("Group: ", Coalesce(reportgroups.lib, "-")), Char(13), Char(10),
    Concat("     ", Coalesce(reportsubgroups.lib, "-")), Char(13), Char(10), Char(13), Char(10),
    Concat("Created on: ", Coalesce(saved_sql.date_created, "-")), Char(13), Char(10),
    Concat("Modified on: ", Coalesce(saved_sql.last_modified, "-")), Char(13), Char(10),
    Concat("Date last run: ", Coalesce(saved_sql.last_run, "-")), Char(13), Char(10), Char(13), Char(10),
    Concat("----------"), Char(13), Char(10), Char(13), Char(10),
    Concat("Public: ", Coalesce(saved_sql.public, "-")), Char(13), Char(10),
    Concat("Expiry: ", Coalesce(saved_sql.cache_expiry, "-")), Char(13), Char(10), Char(13), Char(10),
    Concat("----------"), Char(13), Char(10), Char(13), Char(10),
    Concat(Coalesce(saved_sql.notes, "-")), Char(13), Char(10), Char(13), Char(10),
    Concat("----------"), Char(13), Char(10), Char(13), Char(10),
    Concat(IF(Length(saved_sql.savedsql) > 32766, "Too large to process", saved_sql.savedsql)), Char(13), Char(10), Char(13), Char(10)
  ) AS CONTENTS
FROM
  saved_sql
  LEFT JOIN borrowers ON saved_sql.borrowernumber = borrowers.borrowernumber
  LEFT JOIN (SELECT
        authorised_values.id,
        authorised_values.category,
        authorised_values.authorised_value,
        authorised_values.lib,
        authorised_values.imageurl,
        authorised_values.lib_opac
      FROM
        authorised_values
      WHERE
        authorised_values.category = 'REPORT_GROUP') reportgroups ON
    saved_sql.report_group = reportgroups.authorised_value
  LEFT JOIN (SELECT
        authorised_values.id,
        authorised_values.category,
        authorised_values.authorised_value,
        authorised_values.lib,
        authorised_values.imageurl,
        authorised_values.lib_opac
      FROM
        authorised_values
      WHERE
        authorised_values.category = 'REPORT_SUBGROUP') reportsubgroups ON saved_sql.report_subgroup =
    reportsubgroups.authorised_value
GROUP BY
  saved_sql.id
ORDER BY
  saved_sql.id
-----

Once I run this report I download the results as a CSV file and open it in Excel.

I make sure that c:\git is empty then I run the following VBA macro:

-----
Sub WriteTotxtSQL()

Const forReading = 1, forAppending = 3, fsoForWriting = 2
Dim fs, objTextStream, sText As String
Dim lLastRow As Long, lRowLoop As Long, lLastCol As Long, lColLoop As Long

lLastRow = Cells(Rows.Count, 1).End(xlUp).Row

For lRowLoop = 1 To lLastRow

    Set fs = CreateObject("Scripting.FileSystemObject")
    Set objTextStream = fs.opentextfile("c:\git\" & Cells(lRowLoop, 1) & ".txt", fsoForWriting, True)

    sText = ""

    For lColLoop = 2 To 2
        sText = sText & Cells(lRowLoop, lColLoop) & Chr(10) & Chr(10)
    Next lColLoop

    objTextStream.writeline (Left(sText, Len(sText) - 1))


    objTextStream.Close
    Set objTextStream = Nothing
    Set fs = Nothing

Next lRowLoop

End Sub

-----

This will take each row in the CSV file and output it to a separate text file in the c:\git folder.  Each SQL report will have a filename formatted as "R.xxxxxx.txt" where the "Xs" represent the report number.  The exception will be any situations where the output is too big for Excel to process (more than 32,767 characters).  Since those reports can't be processed in Excel, placeholder data gets output into a file that starts with an X instead of an R.

Next I go to the nexpress.sql folder on my local computer and delete all of the files that start with an "R." or an "X."

Then I go to c:\git and cut all of these files and paste them to my nexpress.sql folder on my computer.

Next I run this report:

-----
 SELECT
  Concat(
    LPad(saved_sql.id, 5, 0),
    "<br /><br />",
    Coalesce(saved_sql.report_name, "-"),
    "<br /><br />",
    Concat(Coalesce(groups.lib, "-"),
    "<br />",
    Coalesce(subgroups.lib, "-")),
    "<br /><br />",
    Concat("Created by:<br />", If(borrowers.borrowernumber IS NULL, "-", Concat(borrowers.firstname, " ", borrowers.surname)))
  ) AS NAME,
  Coalesce(saved_sql.notes, "-") AS NOTES
FROM
  saved_sql
  LEFT JOIN borrowers ON saved_sql.borrowernumber = borrowers.borrowernumber
  LEFT JOIN (SELECT
        authorised_values.id,
        authorised_values.category,
        authorised_values.authorised_value,
        authorised_values.lib,
        authorised_values.imageurl,
        authorised_values.lib_opac
      FROM
        authorised_values
      WHERE
        authorised_values.category = 'REPORT_GROUP') groups ON saved_sql.report_group = groups.authorised_value
  LEFT JOIN (SELECT
        authorised_values.id,
        authorised_values.category,
        authorised_values.authorised_value,
        authorised_values.lib,
        authorised_values.imageurl,
        authorised_values.lib_opac
      FROM
        authorised_values) subgroups ON saved_sql.report_subgroup = subgroups.authorised_value
GROUP BY
  saved_sql.id
ORDER BY
  saved_sql.id

LIMIT 10000
-----

And let the results display on the screen.

Then I view the source of this page and cut out everything between the <table></table> tags and paste that into my "report_index.html" file using Atom where the old table used to be.

Next I look for any files that start with an X and I manually copy the SQL from the reports interface in Koha into the area for the SQL in that text file in Atom and rename the file to start with an "R."

Then I use Atom to push all of the updated files to my repository.

The end result is that I have a text file with the original SQL, name, and description for every saved SQL report in our system starting with an "R," I have an index of those files on Githup pages (https://will1410.github.io/nexpress.sql/report_index.html), and several other files related to my SQL reports where I can track changes to these reports over time.

Wednesday, October 11, 2017

Collapsible columns in circulation rules

This is the jQuery and CSS for the collapsible columns in the circulation fines and fees columns in Koha.

I used these three chunks of jQuery with the attendent CSS as examples of how to use jQuery in Koha at the 2017 Koha-US conference in Coeur d'Alene, Idaho, during the hack-fest sessions.  I decided that these three pieces of code were pretty good ones to look at when you're learning jQuery because each one gets more complex as you work through understanding them and adding them to Koha.

To use this code, cut and paste the jQuery between the two lines here into the IntranetUserJS system preference - between the $(document).ready(function() { and the closing }); brackets..

*************************

//Administration
  //Administration › Circulation and fine rules

    //BEGIN Click on circ rule to move it to the bottom of the table
      $("#default-circulation-rules tr:contains(Edit)").click(function() {
        $(this).insertBefore("#edit_row");
      });

    //BEGIN Create button to hide left hand navigation bar
      $("#admin_smart-rules #navmenu").parent().prepend("<button id='navhide' type='button'>Hide admin column</button>");
      $("#navhide").click( function() {
        $("#navhide").hide();
        $("#navmenu").hide();
        $(".yui-t1 #yui-main .yui-b").css("margin-left", "1em");
      });

    //BEGIN Hide unneeded columns in circulation rules by clicking on header (--requires corresponding css--)
      $("#default-circulation-rules thead th").append("<br /><br /><span>Hide<br />Column</span>");
      $('#default-circulation-rules thead th').click(function() {
        var index = (this.cellIndex + 1);
        var cells = $('#default-circulation-rules tr > :nth-child(' + index + ')');
        cells.toggleClass('hiderule');
        if ($(this).hasClass('hiderule')) {
          $(this).find('span').html('+');
        }
        else {
          $(this).find('span').html('Hide<br />Column');
        }
      });

*************************

This is the accompanying CSS for the third segment of the above jQuery.  To use it, cut and paste the CSS between the two lines of dashes into the IntranetUserCSS system preference.

-------------------------

/* -Administration › Circulation and fine rules- hides columns in circulation rules (requires accompanying jQuery) */
th.hiderule, td.hiderule {
    max-width: 10px;
    text-indent: -9999px;
}

-------------------------

Now here's an explanation of what each piece of this jQuery does:

  //Administration › Circulation and fine rules
    //BEGIN Click on circ rule to move it to the bottom of the table
      $("#default-circulation-rules tr:contains(Edit)").click(function() {
        $(this).insertBefore("#edit_row");
      });

Anything you put in jQuery that comes after a // or between a /* and a */ is considered a comment, so the first thing you see here is a set of comments that I use at the beginning of each bit of jQuery I add to Koha.  I have my IntranetUserJS system preference organized alphabetically by Koha breadcrumbs so that when I'm trying to find something I need to modify or test, I can find it more easily.  The second line describes what the actual jQuery does in Koha.

The "//BEGIN" part of this is something I started doing early on when I started adding jQuery to Koha so I could tell what it was I was doing and where the code started and where it ended.  The breadcrumbs are something I added when I started working for NExpress because when I started here, I inherited jQuery that other staff had written over an 8 year period and almost none of it had comments of any kind.  When we were upgrading to Koha 16.11 at the beginning of 2017 there was a piece of code that no longer worked - in fact it broke something in 16.11 and I spent quite a bit of time trying to figure out which thing caused the problem.  By organizing everything based on the breadcrumbs of the page that the jQuery affected, I was able to set myself up for an easier time testing during the next upgrade.
----------

    //BEGIN Click on circ rule to move it to the bottom of the table
      $("#default-circulation-rules tr:contains(Edit)").click(function() {
        $(this).insertBefore("#edit_row");
      });

Each piece of jQuery begins with a dollar sign and ends with a semicolon.  In this specific statement you will notice that there are two dollar signs and two semicolons.  That's because there is one statemnt nested inside another statement.  These indicate where the jQuery starts and where it ends.
----------

    //BEGIN Click on circ rule to move it to the bottom of the table
      $("#default-circulation-rules tr:contains(Edit)").click(function() {
        $(this).insertBefore("#edit_row");
      });

Next is a set of parentheses with some kind of selector usually inside of quotation marks.  The parentheses and the quotes identify what element on the page is going to be affected by this piece of jQuery.
----------

   //BEGIN Click on circ rule to move it to the bottom of the table
      $("#default-circulation-rules tr:contains(Edit)").click(function() {
        $(this).insertBefore("#edit_row");
      });

The thing that is inside of the quotes and parenthesis is the actual selector itself.  The selectors in this case tells jQuery to look for a DIV element on the page that has the ID "default-circulation-rules" and when it finds that to look for a "tr" (table row) that contains the word "edit".

If you look at W3 schools page at https://www.w3schools.com/jquery/jquery_ref_selectors.asp, they have a good list of html selectors.  A lot of coders knock W3 schools - and rightly so (please don't pay for one of their certificate of completion scams).  This is, however, is the most concise list of jQuery selectors I've found online.
----------

    //BEGIN Click on circ rule to move it to the bottom of the table
      $("#default-circulation-rules tr:contains(Edit)").click(function() {
        $(this).insertBefore("#edit_row");
      });

The next thing you see is a dot.  The dot essentially says "now that we have found what we were looking for, everything that comes next tells us what to do to that thing we just found."
----------

     //BEGIN Click on circ rule to move it to the bottom of the table
      $("#default-circulation-rules tr:contains(Edit)").click(function() {
        $(this).insertBefore("#edit_row");
      });

In this specific case, the thing that comes after the dot is a "click(function(){});" action.  This action is telling Koha that whenever it finds a table that contains a row that has the word "Edit" in it and that table is inside a DIV that has an ID of "default-circulation-rules", Koha is going to do something to that row when we click on it.
----------

    //BEGIN Click on circ rule to move it to the bottom of the table
      $("#default-circulation-rules tr:contains(Edit)").click(function() {
        $(this).insertBefore("#edit_row");
      });

And here inside of those brackets in the function part of the click action is another piece of jQuery with a beginning, a new selector, and a new action.  In this specific case this jQuery is saying "Now that we've found a row with the word "Edit" in it in a table with a DIV called "default-circulation-rules" and someone clicks on that row, we're going to take "this" row and move it so that it is just above this other thing on the page that has an ID called "edit_row."

The end result is that when you click on one of the circulation rules in the "Defining circulation and fine rules for all libraries" table, jQuery is going to move the row you've clicked on to the bottom of that table, just above the editing row.

I find that moving a row, or several rows, to the bottom of the table makes it easier for my eyes to track those rows across the table.
=====

The first one was pretty easy, so let's look at the next one which is slightly more complex.

    //BEGIN Create button to hide left hand navigation bar
      $("#admin_smart-rules #navmenu").parent().prepend("<button id='navhide' type='button'>Hide admin column</button>");
      $("#navhide").click( function() {
        $("#navhide").hide();
        $("#navmenu").hide();
        $(".yui-t1 #yui-main .yui-b").css("margin-left", "1em");
      });

A lot of the parts here are the same - the dollar signs, the semicolons, the parenthesis, the quotes, and the dots.  In this case, though, this group contains two statements.

The first line

      $("#admin_smart-rules #navmenu").parent().prepend("<button id='navhide' type='button'>Hide admin column</button>");
      $("#navhide").click( function() {
        $("#navhide").hide();
        $("#navmenu").hide();
        $(".yui-t1 #yui-main .yui-b").css("margin-left", "1em");
      });

is creating the button to hide the left hand navigation bar.  It's telling Koha that every time it sees a DIV called "navmenu" inside of the DIV called "admin_smart-rules" to find that DIV's parent (this is what the .parent() action does) and then to stick some other piece of HTML (i.e. "prepend") in front of that parent DIV.  In this case the HTML that Koha will prepend in front of the navmenu is a button called "navhide" and the text that will appear on that button is "Hide admin column."

That's all that this one line does is create a button and sticks it at the top of the left hand navigation column on the page with the breadcrumbs "Administration › Circulation and fine rules."

It's the next section that actually tells Koha what to do when someone clicks on the button.
----------

      $("#admin_smart-rules #navmenu").parent().prepend("<button id='navhide' type='button'>Hide admin column</button>");
      $("#navhide").click( function() {
        $("#navhide").hide();
        $("#navmenu").hide();
        $(".yui-t1 #yui-main .yui-b").css("margin-left", "1em");
      });

The first line is a lot like the first line in the earlier jQuery we looked at - it is telling Koha that every time the new button is clicked, do some different things.
----------

      $("#admin_smart-rules #navmenu").parent().prepend("<button id='navhide' type='button'>Hide admin column</button>");
      $("#navhide").click( function() {
        $("#navhide").hide();
        $("#navmenu").hide();
        $(".yui-t1 #yui-main .yui-b").css("margin-left", "1em");
      });

The next three lines actually tell Koha the three things jQuery is supposed to do when the button is clicked.
----------

      $("#admin_smart-rules #navmenu").parent().prepend("<button id='navhide' type='button'>Hide admin column</button>");
      $("#navhide").click( function() {
        $("#navhide").hide();
        $("#navmenu").hide();
        $(".yui-t1 #yui-main .yui-b").css("margin-left", "1em");
      });

The first jQuery says, essentially, "now that you've clicked on this new button you just created, make that very same button disappear."
----------

      $("#navhide").click( function() {
        $("#navhide").hide();
        $("#navmenu").hide();
        $(".yui-t1 #yui-main .yui-b").css("margin-left", "1em");
      });

The second jQuery says, "while you're at it, make that whole column of stuff underneath that button disappear too."
----------

      $("#navhide").click( function() {
        $("#navhide").hide();
        $("#navmenu").hide();
        $(".yui-t1 #yui-main .yui-b").css("margin-left", "1em"); 
      });

And the third line says, "now that we've made all of that stuff on the left hand side of the screen disapper, let's change the margins on this page so that the rules table can move to the left side of the screen."

Since the "Defining circulation and fine rules for all libraries" table is 7 miles wide, clearing the left hand side of the screen helps make that table easier to scroll that table by making some more room on the screen.
=====

Finally, there is this bigger piece that requires some CSS also:

    //BEGIN Hide unneeded columns in circulation rules by clicking on header (--requires corresponding css--)
      $("#default-circulation-rules thead th").append("<br /><br /><span>Hide<br />Column</span>");
      $('#default-circulation-rules thead th').click(function() {
        var index = (this.cellIndex + 1);
        var cells = $('#default-circulation-rules tr > :nth-child(' + index + ')');
        cells.toggleClass('hiderule');
        if ($(this).hasClass('hiderule')) {
          $(this).find('span').html('+');
        }
        else {
          $(this).find('span').html('Hide<br />Column');
        }
      });

----------

    //BEGIN Hide unneeded columns in circulation rules by clicking on header (--requires corresponding css--)
      $("#default-circulation-rules thead th").append("<br /><br /><span>Hide<br />Column</span>");
      $('#default-circulation-rules thead th').click(function() {
        var index = (this.cellIndex + 1);
        var cells = $('#default-circulation-rules tr > :nth-child(' + index + ')');
        cells.toggleClass('hiderule');
        if ($(this).hasClass('hiderule')) {
          $(this).find('span').html('+');
        }
        else {
          $(this).find('span').html('Hide<br />Column');
        }
      });

The first line here does kind of the same thing that the "prepend" part of the last example just instead of putting something before something else, it's adding something after the th elements in the rules table.  Once this is in place, it's going to add the words

Hide
Column

below the descripton of each column in the rules table.
----------

    //BEGIN Hide unneeded columns in circulation rules by clicking on header (--requires corresponding css--)
      $("#default-circulation-rules thead th").append("<br /><br /><span>Hide<br />Column</span>");
      $('#default-circulation-rules thead th').click(function() {
        var index = (this.cellIndex + 1);
        var cells = $('#default-circulation-rules tr > :nth-child(' + index + ')');
        cells.toggleClass('hiderule');
        if ($(this).hasClass('hiderule')) {
          $(this).find('span').html('+');
        }
        else {
          $(this).find('span').html('Hide<br />Column');
        }
       });

And this part should look farmilliar now too.  It's telling Koha that when you click on one of those columns, to do something.  But what it's telling Koha to do is much more complex this time.
----------

    //BEGIN Hide unneeded columns in circulation rules by clicking on header (--requires corresponding css--)
      $("#default-circulation-rules thead th").append("<br /><br /><span>Hide<br />Column</span>");
      $('#default-circulation-rules thead th').click(function() {
        var index = (this.cellIndex + 1);
        var cells = $('#default-circulation-rules tr > :nth-child(' + index + ')');
        cells.toggleClass('hiderule');
        if ($(this).hasClass('hiderule')) {
          $(this).find('span').html('+');
        }
        else {
          $(this).find('span').html('Hide<br />Column');
        }
      });

This line is telling Koha that when the column head is clicked to create a Java variable called "index" and that the value of this variable is the value of the "index number" of that cell plus one.  Essentially Koha is going to ask the question "Counting from left to right, what's the number of the cell in the table head that was clicked on?" and then it's adding one to that number.  (The + 1 will be explained in a just a moment.)
----------

    //BEGIN Hide unneeded columns in circulation rules by clicking on header (--requires corresponding css--)
      $("#default-circulation-rules thead th").append("<br /><br /><span>Hide<br />Column</span>");
      $('#default-circulation-rules thead th').click(function() {
        var index = (this.cellIndex + 1);
        var cells = $('#default-circulation-rules tr > :nth-child(' + index + ')');
        cells.toggleClass('hiderule');
        if ($(this).hasClass('hiderule')) {
          $(this).find('span').html('+');
        }
        else {
          $(this).find('span').html('Hide<br />Column');
        }
       });

This tells Koha to go through the table and look at all of the rows and to find the "nth-child" (i.e. cell number X in that row) where the cell number is the same as the "index" number we just got in the last line of this piece of code.  The reason that we don't add the "+1" here, though is that while "index" starts counting at 0, the "nth-child" selector starts counting at 1.  (Why does "index" start counting at 0 and nth-child start counting at 1?  Who knows?  Knowing why is probably not as important as knowing how to deal with the situation.)
----------

    //BEGIN Hide unneeded columns in circulation rules by clicking on header (--requires corresponding css--)
      $("#default-circulation-rules thead th").append("<br /><br /><span>Hide<br />Column</span>");
      $('#default-circulation-rules thead th').click(function() {
        var index = (this.cellIndex + 1);
        var cells = $('#default-circulation-rules tr > :nth-child(' + index + ')');
        cells.toggleClass('hiderule');
        if ($(this).hasClass('hiderule')) {
          $(this).find('span').html('+');
        }
        else {
          $(this).find('span').html('Hide<br />Column');
        }
      });

This line says, now that we've got got this "cells" variable figured out, we're going to add or remove a "class" element to each cell that is in that column and we're going to call that class "hiderule."  If the cell already has this class, the toggle will remove it.  If the cell doesn't have this class, the toggle will add it.  This is also where the CSS that corresponds with this whole set of commands comes into play.
----------

The corresponding CSS for this process is:

/* -Administration › Circulation and fine rules- hides columns in circulation rules (requires accompanying jQuery) */
th.hiderule, td.hiderule {
    max-width: 10px;
    text-indent: -9999px;
}

and this CSS is telling Koha that when an item has this "hiderule" class added to it to change the width to 10 pixels wide and to take the contents of the cell and move them 9999 pixels to the left.  Since 9999 pixels to the left is about 4 feet to the left of your monitor, adding this class to one of these columns in the circulation rules quickly renders that column invisible for all intents and purposes.
----------

    //BEGIN Hide unneeded columns in circulation rules by clicking on header (--requires corresponding css--)
      $("#default-circulation-rules thead th").append("<br /><br /><span>Hide<br />Column</span>");
      $('#default-circulation-rules thead th').click(function() {
        var index = (this.cellIndex + 1);
        var cells = $('#default-circulation-rules tr > :nth-child(' + index + ')');
        cells.toggleClass('hiderule');
        if ($(this).hasClass('hiderule')) {
          $(this).find('span').html('+');
        }
        else {
          $(this).find('span').html('Hide<br />Column');
        }
      });

The final section is a simple if-then statement.  It's saying, if the selector that this function is attached to (in this case the column header) has the "hiderule" class, to replace the "Hide Column" text with a "+" sign, but if it doesn't have that class, go ahead and keep the "Hide Column" in that space.
----------

The end result is that when you look at your circulation rules, the text "Hide Column" or a "+" sign will be added to the header row and when you click on that cell in the header, that column will collapse or expand as needed.

Between adding the ability to move the rows to the bottom of the table, the ability to make the navigation menu on the left disappear, and the ability to collapse the columns you don't need, all three of these pieces of jQuery and their corresponding piece of CSS work together to make the really wide "Defining circulation and fine rules for all libraries" table easier to manage.

Tuesday, August 8, 2017

Adding tabbed content to the Koha home page/Circulation home page/Reports home page



When I worked for the VALNet consortium we had a lot of content we needed to store somewhere easy for library staff to access.  There were some reports that were run monthly at various libraries and there were notices about school closures for the summer, and there were other things that seemed important to make available for staff.

The IntranetmainUserblock system preference seemed like a great place to store those things.  The problem was that we had so much content we wanted to share that, especially in the summer when the schools were closed, the Koha home page became about a mile tall.

One day I though, wouldn't it be cool to set that up as tabbed content.  School closures on one tab, reports on another, maybe a blog or calendar on a third.

This led to an investigation of tabbed content and I found a great, pre-built, tabbed content with only Java / CSS page template on GitHub.

The biggest modification I made to this code was to elements that the original author calls merely tab or tabs.  My fear is always that the next release of Koha will have an element with an ID called "tab" or "tabs" and that will screw up the operation of these pages.  Therefore in the IntranetmainUserblock the tabs are called mainxtabs, in IntranetCirculationHomeHTML they are called circxtabs, and in IntranetReportsHomeHTML they are called rptxtabs.

This is an example of this code written for IntranetCirculationHomeHTML with some added comments describing what the different lines are doing.

A second addition in this sample is that I have used inline CSS to hide Tabs 4 and 5.  What I have found is that 5 tabs on the screen for this space is enough.  If you put more tabs than that in a row in these areas, it's too much information, but the flip side of that is that I may not need 5 tabs all the time.  Rather than deleting the code that creates tabs 4 and 5 in this sample, I have them hidden so that all I have to do when I want to re-activate a tab is to remove the CSS.  This is particularly helpful when you want to show or hide a tab called "upcoming school closures" that you only need for a portion of the year.  Rather than deleting the content of that tab in the fall and recreating the tab again in the spring, hiding the tab takes it out of play

<!-- based on "Tabs pure javascript and css" by ShaunD2D at https://gist.github.com/Shaun2D2/6296191 -->

<style>

  ul#cirxtabs { list-style-type: none; margin: 30px 0 0 0; padding: 0 0 0.3em 0; }
    /*the above line controls the position of the tabs relative to the lines above them*/
  ul#cirxtabs li { display: inline; }
    /*the above line arranges the tabs horizontally across the top of the tabbed content*/
  ul#cirxtabs li a { color: #42454a; background-color: #dedbde; border: 1px solid #c9c3ba; border-bottom: none; padding: 0.3em; text-decoration: none; }
     /*the above line arranges controls the color of the tabs*/
  ul#cirxtabs li a:hover { background-color: #f1f0ee; }
    /*the above line changes the color of the selected tab and positions text on the tab*/
  ul#cirxtabs li a.selected { color: #000; background-color: #f1f0ee; font-weight: bold; padding: 0.7em 0.3em 0.38em 0.3em; }
    /*the above line controls the way that the content is positioned on the tab as well as the color of the tab*/
  div.cirxtabContent { border: 1px solid #c9c3ba; padding: 0.5em; background-color: #f1f0ee; }
  div.cirxtabContent.hide { display: none; }
    /*the above line hides the contents of the non-selected tabs*/

</style>

<script>

  var cirxtabLinks = new Array();
  var contentDivs = new Array();

  function init() {

    // Takes the content from the divs and puts them in the tabs
    var cirxtabListItems = document.getElementById('cirxtabs').childNodes;
    for ( var i = 0; i < cirxtabListItems.length; i++ ) {
      if ( cirxtabListItems[i].nodeName == "LI" ) {
        var cirxtabLink = getFirstChildWithTagName( cirxtabListItems[i], 'A' );
        var id = getHash( cirxtabLink.getAttribute('href') );
        cirxtabLinks[id] = cirxtabLink;
        contentDivs[id] = document.getElementById( id );
      }
    }

    // Tells the content what to do when the tabs are clicked
    // selects tab 1
    var i = 0;

    for ( var id in cirxtabLinks ) {
      cirxtabLinks[id].onclick = showcirxtab;
      cirxtabLinks[id].onfocus = function() { this.blur() };
      if ( i == 0 ) cirxtabLinks[id].className = 'selected';
      i++;
    }

    // Hide tabbed content except on tab 1
    var i = 0;

    for ( var id in contentDivs ) {
      if ( i != 0 ) contentDivs[id].className = 'cirxtabContent hide';
      i++;
    }
  }

  function showcirxtab() {
    var selectedId = getHash( this.getAttribute('href') );

    // Highlight the selected cirxtab, and dim all others.
    // Also show the selected content div, and hide all others.
    for ( var id in contentDivs ) {
      if ( id == selectedId ) {
        cirxtabLinks[id].className = 'selected';
        contentDivs[id].className = 'cirxtabContent';
      } else {
        cirxtabLinks[id].className = '';
        contentDivs[id].className = 'cirxtabContent hide';
      }
    }

    // Stop the browser following the link
    return false;
  }

  function getFirstChildWithTagName( element, tagName ) {
    for ( var i = 0; i < element.childNodes.length; i++ ) {
      if ( element.childNodes[i].nodeName == tagName ) return element.childNodes[i];
    }
  }

  function getHash( url ) {
    var hashPos = url.lastIndexOf ( '#' );
    return url.substring( hashPos + 1 );
  }

</script>

<body onload="init()">

<ul id="cirxtabs">
  <li><a href="#cirxtab01">Tab 01</a></li>
  <li><a href="#cirxtab02">Tab 02</a></li>
  <li><a href="#cirxtab03">Tab 03</a></li>
  <li style="display: none;"><a href="#cirxtab04">Tab 04</a></li>
  <li style="display: none;"><a href="#cirxtab05">Tab 05</a></li>
</ul>

<div class="cirxtabContent" id="cirxtab01">
  <h2>This is tab 01</h2>
  <div>
    <p>Tab 01 content goes here.</p>
  </div>
</div>

<div class="cirxtabContent" id="cirxtab02">
  <h2>This is tab 02</h2>
  <div>
    <p>Tab 02 content goes here.</p>
  </div>
</div>

<div class="cirxtabContent" id="cirxtab03">
  <h2>This is tab 03</h2>
  <div>
    <p>Tab 03 content goes here.</p>
  </div>
</div>

<div class="cirxtabContent" id="cirxtab04">
  <h2>This is tab 04</h2>
  <div>
    <p>Tab 04 content goes here.</p>
  </div>
</div>

<div class="cirxtabContent" id="cirxtab05">
  <h2>This is tab 05</h2>
  <div>
    <p>Tab 05 content goes here.</p>
  </div>
</div>

Monday, August 7, 2017

Using autorised values tables and wildcards in Koha reports

ByWater solutions did an example of what I'm about to demonstrate on their website while I was in the process of preparing this.  You can see their description of this same process at http://bywatersolutions.com/2017/06/08/koha-tutorial-branch-reporting/.

In the reporting module in Koha you can use runtime parameters when creating SQL reports.  Taking a simple query like:

SELECT
  branches.branchcode,
  branches.branchname
FROM
  branches
WHERE
  branches.branchcode = "DIGITAL"

And replacing

  branches.branchcode = "DIGITAL"

with

  branches.branchcode = <<Enter a branchcode>>

allows you to limit the results of the query to a different branch each time you run it.

You can go a step further though and, instead of using <<Enter a branchcode>> you can use <<Choose a branch from the drop-down|branches>>.  The text that comes after the pipe, "branches," tells Koha to give you that selection.  Currently the options you can use after a pipe include:


branches
itemtypes
date
categorycode

and the name of any authorised values table in your installation of Koha.

In my situation at my old job in VALNet and in my new job at NExpress, I have often had the need to re-write existing reports for libraries because the existing report worked fine for most libraries, but needed just one slight change in flexibility in order for it to work at other libraries.  One good example would be a simple circulation statistics report like:

SELECT
  statistics.branch,
  count(*) AS CIRC_PLUS_RENEW
FROM
  statistics
WHERE
  (statistics.type = 'issue' OR
  statistics.type = 'renew') AND
  statistics.datetime BETWEEN "2017-07-01" AND "2017-08-01"
GROUP BY
  statistics.branch

There are many times I will introduce a report like this into our consortium and someone will ask "Isn't there a way you could make that report so it only shows the results for my library?"  At that point it could be easily written as so:

SELECT
  statistics.branch,
  count(*) AS CIRC_PLUS_RENEW
FROM
  statistics
WHERE
  (statistics.type = 'issue' OR
  statistics.type = 'renew') AND
  statistics.datetime BETWEEN "2017-07-01" AND "2017-08-01" AND
  statistics.branch = <<Enter branch code>>
GROUP BY
  statistics.branch

My guess would be the immediate reaction to this report would be someone asking "Why do I have to type my library's code?" or "I put my library's name in there, but it didn't give me any results.  What happened?"  No one is going to be happy with having to type something in.  The simple solution there is to change <<Enter branch code>> to <<Choose your branch|branches>> so that the user gets a drop down menu to allow them to choose their branch.  Something like this:

SELECT
  statistics.branch,
  count(*) AS CIRC_PLUS_RENEW
FROM
  statistics
WHERE
  (statistics.type = 'issue' OR
  statistics.type = 'renew') AND
  statistics.datetime BETWEEN "2017-07-01" AND "2017-08-01" AND
  statistics.branch = <<Choose your branch|branches>>
GROUP BY
  statistics.branch

But this won't solve the problem because now some other librarian is going to ask "How can I run this report so that I see everyone's circulation statistics?  It used to show that.  Why doesn't it do that any more?"  Similarly I have two library districts in the NExpress consortium.  One school district and one public library district with four branches.  They often want results that encompass all of their libraries in one report and the "branches" parameter can't cover those circumstances.

My solution to this problem was to stop using the "branches" parameter after the pipe when I wanted a report with more flexibility.  I went ahead and created a new set of authorised values called "LBRANCH" that recreates all of the branch informaiton with a couple of changes.

First of all, the first value I created was "%" with a description called "All libraries."  Then, since all of the Doniphan county library district branches have a branchcode that starts with "DONI" and all of the school districts start with "PH," I created a value called "DON%" and a value called "PH%".

The second step was to re-write the SQL so that WHERE statistics.branch = <<Choose your branch|branches>> used the term "LIKE" instead of an equals sign.  The equals sign requires an exact match with no wildcards, but "LIKE" is a little more flexible because it can use wildcards.

The final step was to change "|branches" to "|LBRANCH".  The final result looks like this:

SELECT
  statistics.branch,
  count(*) AS CIRC_PLUS_RENEW
FROM
  statistics
WHERE
  (statistics.type = 'issue' OR
  statistics.type = 'renew') AND
  statistics.datetime BETWEEN "2017-07-01" AND "2017-08-01" AND
  statistics.branch LIKE <<Choose your branch|LBRANCH>>
GROUP BY
  statistics.branch

By doing this, I can select an individual branch, or I can select all branches.  I can also select the Doniphan county libraries one at a time, or I can select all of them at once.  I can also look at one of the schools in the district at a time, or I can see all of the school district libraries together.

I did discover a drawback, though.  If I used "|LBRANCH" on certain reports and chose "All branches," the report might shut down the system.  We have about 400,000 biblios, over 1,000,000 items, and about 125,000 patrons.  In some cases, asking Koha to deliver results for "All branches" could potentially slow down or crash the system.

So, I created a second alternative authorised values table called ZBRANCH that does the same things for Doniphan and the school district but omits the "All branches" option.  It looks like this in this sample query:

SELECT
  statistics.branch,
  count(*) AS CIRC_PLUS_RENEW
FROM
  statistics
WHERE
  (statistics.type = 'issue' OR
  statistics.type = 'renew') AND
  statistics.datetime BETWEEN "2017-07-01" AND "2017-08-01" AND
  statistics.branch LIKE <<Choose your branch|ZBRAN>>
GROUP BY
  statistics.branch

And I haven't stopped with branchcodes.  I have authorised values tables that cover collection codes, item types, patron categories and other variables that allow me to create flexible reports that can focus on one category of item or all categories of items.

The work-horse of these queries is the one called "Flexible weeding report."  It looks like this:

SELECT
  CONCAT( '<a href=\"/cgi-bin/koha/catalogue/detail.pl?biblionumber=', biblio.biblionumber,'\" target="_blank">', biblio.biblionumber, '</a>' ) AS LINK_TO_TITLE,
  items.itemnumber,
  items.homebranch,
  Concat("-",Coalesce(items.barcode, "-"),"-") AS BARCODE,
  items.location,
  items.itype,
  items.ccode,
  items.itemcallnumber,
  biblio.author,
  CONCAT_WS(' ', biblio.title, ExtractValue(biblioitems.marcxml, '//datafield[@tag="245"]/subfield[@code="b"]'), ExtractValue(biblioitems.marcxml, '//datafield[@tag="245"]/subfield[@code="p"]'), ExtractValue(biblioitems.marcxml, '//datafield[@tag="245"]/subfield[@code="n"]') ) AS FULL_TITLE,
  items.dateaccessioned,
  items.datelastborrowed,
  items.datelastseen,
  Sum((Coalesce(items.issues, 0)) + Coalesce(items.renewals, 0)) AS CHECKOUTS_PLUS_RENEWALS,
  IF(items.onloan IS NULL,'No','Yes') AS CHECKED_OUT,
  IF(SUM(coalesce(items.damaged,0) + coalesce(items.itemlost,0) + coalesce(items.withdrawn,0))=0,'No','Yes') AS STATUS_PROBLEMS,
  items.itemnotes,
  items.itemnotes_nonpublic
FROM
  items JOIN
  biblio
    ON items.biblionumber = biblio.biblionumber INNER JOIN
  biblioitems
    ON biblioitems.biblionumber = biblio.biblionumber AND
    items.biblioitemnumber = biblioitems.biblioitemnumber
WHERE
  items.homebranch LIKE <<Item home library|ZBRAN>> AND
  items.location LIKE <<Item shelving location|LLOC>> AND
  items.itype LIKE <<Item type|LITYPES>> AND
  items.ccode LIKE <<Item collection code|LCCODE>> AND
  items.dateaccessioned <= <<Item was added before|date>> AND
  Coalesce(items.datelastborrowed, "0") <= <<Date last borrowed was before|date>> AND
  items.datelastseen <= <<Date last seen was before|date>>
GROUP BY
  items.homebranch,
  BARCODE,
  items.location,
  items.itype,
  items.ccode,
  items.itemcallnumber,
  items.enumchron,
  biblio.author,
  biblio.title
HAVING CHECKOUTS_PLUS_RENEWALS <= <<With X or fewer checkouts|ZNUMBERS>> AND
  CHECKED_OUT LIKE <<Display checked out items|ZYES_NO>> AND
  STATUS_PROBLEMS LIKE <<Display lost, missing, and withdrawn items|ZYES_NO>>


A screenshot of it's parameter screen looks like this:


The creation of this report was, ultimately, a byproduct of laziness.  Within a month of arriving in Kansas I had people calling me saying "We normally use weeding report X, that Liz wrote in 2010, and I want that same report, I just want to be able to run it by collection code instead of by item type" or "I like weeding report Y but I want to limit it to items we added more than a year ago but will only let me limit based on the last seen date.  Can you make a copy that does that?" followed by Doniphan County staff asking if there could be a separate report for them that included all of their libraries and limited to things that had checked out fewer than 5 times.

This report was really a way for me to stop re-writing the same report every month for 4 different libraries.

Learning how to add new authorised values tables to Koha has the potential to give you a lot of flexibility in the reports you write.

Modifying the HOLD_SLIP receipt in Koha

When I first started working with the reciepts in Koha, I was particularly disappointed with the hold slip and the hold-transfer slip. I don't say this often, but this is one of two areas where I felt like the last proprietary ILS I used did a better job than Koha - at least "right out of the box," so to speak.

In the last proprietary ILS I used, if an item with a request on it was supposed to be held at our library, when it got to our library, it printed out a slip that said "Hold this for PATRON'S NAME" and if we checked in an item that had a request to be picked up somewhere else, the slip said "Send this somewhere else." They were actually two separate slips. The transit slip didn't print out any patron information (which we couldn't send through the courier system) and the "Hold at this library" slip didn't say "Transfer to/Hold in _____." It just said, "In transit to _____." In that respect, when I first started using Koha, I felt like the Hold/Transfer slip was a step backwards because it tries to use one slip to do two things.

Early on (and that was back in 2012) I had hoped I could create some Java or jQuery in Koha's IntranetSlipPrinterJS system preferences that could control what appears on an individual receipt. That turned out to be a big can of worms. I ended up fighting with the IntranetSlipPrinterJS settings and the JS Print plugin but I realized that using JS Print was going to be incredibly complicated.  At that time I worked for a library district that had 7 branches but our shared catalog consortium had 52 members so implementing JS Print at all of those libraries was going to be complicated, so I gave up on the receipts for a while and focused on other, more pressing things. Later on, though, I realized that since each receipt is 100% customizable, I could use HTML, CSS, Java, and jQuery to make any receipt for any library do whatever I wanted it to do.  I would actually have more flexibility than I would get from a one size fits all batch of jQuery that would be plugged into every receipt by a system preference if I sat down and wrote a new receipt for each library that I needed to write one for.

This is a walk-through of the steps I took to modify what is now called the "HOLD_SLIP" in Koha (used to be called "RESERVESLIP"). And I'm going to say at this stage that I am writing this explanation at a very basic level. If know HTML or work with CSS or Java or jQuery, I'm going to be going through things really slowly and that's because I'm absolutely sure that there are people in the audience who are reading this and even if I go slow, they will still have trouble with this -- not because they're not smart -- but because this work is outside of their primary area of experience. If you think I'm going slowly, it's because I don't want to lose the people that think I'm going too fast.

Also, in this post I'm going to be manipulating a receipt for the Digital Library branch in the NExpress consortium. There are things going on in NExpress that are going to be different than in your library. If you want to repeat this process at your library, you will need to know the branchcodes for the library whose slips you are manipulating and you're going to have to know which database fields are going to be important for your staff.

*************************************************

When first installed, the HOLD_SLIP data is:


<h5>Date: <<today>></h5>

<h3> Transfer to/Hold in <<branches.branchname>></h3>


<h3><<borrowers.surname>>, <<borrowers.firstname>></h3>


<ul>

    <li><<borrowers.cardnumber>></li>
    <li><<borrowers.phone>></li>
    <li> <<borrowers.address>><br />
         <<borrowers.address2>><br />
         <<borrowers.city>>  <<borrowers.zipcode>>
    </li>
    <li><<borrowers.email>></li>
</ul>
<br />
<h3>ITEM ON HOLD</h3>
<h4><<biblio.title>></h4>
<h5><<biblio.author>></h5>
<ul>
   <li><<items.barcode>></li>
   <li><<items.itemcallnumber>></li>
   <li><<reserves.waitingdate>></li>
</ul>
<p>Notes:
<<reserves.reservenotes>>



As a print-out this looks like:



*************************************************

This is the default slip that prints if the item needs to be shipped or if the item needs to go on the hold shelf at the location where you just checked it in.

As a transfer slip, this is not a good print-out because you can't stick it in the item for shipping because it has confidential patron contact information on it and this thing is going to be passing through the hands of a third party courier service.  As a slip to stick in a book on the hold shelf I found it inadequate because staff kept putting books that should have been shipped onto the hold shelf at our library because this hold and transfer slips were the same no matter if the item had reached its destination or not.

When I finally started working on modifying these slips, I started off by deleting the old slip altogether and putting all the tags for a web page into the slip:



<!DOCTYPE html>
<html>

  <head>



  </head>

  <body>



  </body>

</html>




*************************************************

Everything that is in the original receipt is important, but the borrower information is confidential and only really necessary when the item gets to its pickup location and the pickup location information is only really necessary if you're shipping the item to a different library.  So the next thing we're going to do is add some DIV tags to the body and create a space for the borrower information and the pickup location information to be stored in separate parts of the slip.  We'll give the first DIV an ID called "borrower" and the second one we'll call "loc_<<branches.branchcode>>".  The <<branches.branchcode>> portion of this is in double angle brackets because that part of this DIV ID is going to come from Koha.  Anything in double angles in the receipts is going to be something that Koha pulls out of the database, so, for NExpress, for example, if the pickup location is Paola Free Library, Koha is going to read this DIV ID as loc_PAOLA, but if the pickup location is the NEKLS office, the DIV ID will be loc_NEKLS.  In our example, the eventual result will be a DIV with the ID "loc_DIGITAL"


<!DOCTYPE html>
<html>

  <head>



  </head>

  <body>

    <div id="borrower">



    </div>

    <div id="loc_<<branches.branchcode>>">



    </div>

  </body>

</html>


*************************************************

And inside of the DIV for the borrower information we're going to add a second DIV and we're going to give this one an ID called "bor_<<branches.branchcode>>".  Having two DIVs may seem superflous, but it will become important when we add the code that controls what prints and when it prints.


<!DOCTYPE html>
<html>

  <head>



  </head>

  <body>

    <div id="borrower">
 
      <div id="bor_<<branches.branchcode>>">



      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
 
 
    </div>

  </body>

</html>


*************************************************

Next, we'll actually add the borrower's data to the "bor_<<branches.branchcode>>" DIV.  Here we'll be using some inline CSS to get the borrower's name on the left side of the page.  There is some code we'll add later to insert a pick-up date on the right hand side of the page at the top, so we'll need the CSS here to make sure the patron's name is at the top on the left.  You also may notice that there is a borrower attribute called "HOLD." That's a NExpress patron attribute we use to help libraries identify a patron's preferred contact method, so you probably won't see that attribute on your system.


<!DOCTYPE html>
<html>

  <head>



  </head>

  <body>

    <div id="borrower">
 
      <div id="bor_<<branches.branchcode>>">
   
        <h2 style="text-align: left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
 
 
    </div>

  </body>

</html>


*************************************************

Now we'll add the pickup location information to the "loc_<<branches.branchcode>>" DIV.  And here I'll point out that NExpress uses the "Notes" field in a library's name to indicate their library's code for the Kansas Library Express courier system.  That note does get prominence because it's a major part of how we ship things.  In another library system, you'd obviously want to figure out what order and what emphasis you'd want to give to various elements of the final receipt.


<!DOCTYPE html>
<html>

  <head>



  </head>

  <body>

    <div id="borrower">
 
      <div id="bor_<<branches.branchcode>>">
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

  </body>

</html>


*************************************************

When we're this far along, the receipt starts to look like this:



It still has the patron information and the pickup location on one slip, but they're broken up really well into two distinct areas.


*************************************************

Next we start to add some code so we can decide what appears on the final receipt and what stays hidden and when.  First we'll some inline CSS to make the borrower DIV invisible - style="display: none;"

This code hides everything in the borrower DIV but in just a few minutes we'll add some jQuery to unhide it if the item is at its destination library.


<!DOCTYPE html>
<html>

  <head>



  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

  </body>

</html>


*************************************************

Then we'll add some script tags and the jQuery "document ready function" to the header.  This will allow us to add some jQuery that will control what stays invisible and what gets hidden.


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){




      });


    </script>




  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

  </body>

</html>


*************************************************

Then we'll add jQuery that unhides the borrower information if the library where we are checking the item in at equals the library where the item is supposed to be picked up at.

This code is going to include #bor_THISSLIP'SBRANCHCODE as the selector.  The actual jQuery being used in this example will be |$("#bor_DIGITAL").parent().removeAttr('style');| and what this piece of code is saying is that "If <div id="bor_<<branches.branchcode>>"> has an ID equal to 'bor_DIGITAL' (because remember that <<branches.branchcode>> is going to change based on the request's pickup location), then remove the 'style="display: none;"' CSS from this section of the receipt."  So, if the pick-up library the patron has chosen is DIGITAL, then their borrower information will print on the final slip.


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
   
      });

  </script>


  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

  </body>

</html>


*************************************************

Then we'll add jQuery that hides the destination library section


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
          $("#loc_DIGITAL").hide();

      });


  </script>

  <style>



  </style>
 
  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

  </body>

</html>


*************************************************

The end result of all of this is a receipt that looks like this when the item needs to go on the hold shelf



and this when it needs to be shipped



*************************************************

The next thing I did when I first set this up was to add some CSS to control the location of the hold expiration date.  Later on I also learned how to use this to force the size of the page to certain parameters.  To do both of these things, we need to add a style tag into the head of the document.


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
          $("#loc_DIGITAL").hide();
   
      });

  </script>

  <style>



  </style>

 
  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

  </body>

</html>


*************************************************

The first piece of CSS we add will control the margins and the size of the printable area on the page.

This code also sets the left margin just a little inside of the left side of the page which I find helps the receipts come out better when you have the margins of Firefox or Chrome set to 0 all the way around.


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
          $("#loc_DIGITAL").hide();
   
      });

  </script>

  <style>

    body {width: 245px; margin-top: 0; margin-bottom: 0; margin-right: 0; margin-left: 20px}

  </style>
 
  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

  </body>

</html>


*************************************************

The second piece of CSS gives us way of right-justifying a "Hold till" date on the right hand side of the page opposite the patron's name.  To do this we'll use the HTML "article" tag and a class we'll call "right."


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
          $("#loc_DIGITAL").hide();
       
      });

  </script>

  <style>

    body {width: 245px; margin-top: 0; margin-bottom: 0; margin-right: 0; margin-left: 20px}
    article{display:inline-block}
    .right{float:right}

  </style>
 
  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

  </body>

</html>


*************************************************

Finally, we'll add the hold expiration date to the borrower information wrapped inside the article tag and I'll give the article tag a "right" class.  This will add the "Hold till" date information to the top of the receipt.


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
          $("#loc_DIGITAL").hide();
       
      });

  </script>

  <style>

    body {width: 245px; margin-top: 0; margin-bottom: 0; margin-right: 0; margin-left: 20px}
    article{display:inline-block}
    .right{float:right}

  </style>
 
  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <article class="right">
          <h2>Hold till: <<reserves.expirationdate>></h2>
        </article>    
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

  </body>

</html>


*************************************************

When I was at Latah County Library District, I also added a checklist for phone calls to the bottom of the receipt.

If the patron had an e-mail address in their account, we did not phone the patron when their requests came in.  The volume, particularly at the Moscow branch of LCLD was often over 150 items added to the hold shelf each day, so it was impractical to phone patrons if they had an e-mail address, but we did phone every patron who didn't have an e-mail address attached to their account.

During my time there, staff at the Moscow branch had an elaborate shorthand for marking the hold slips to indicate whether or not the patron had been phoned, whether staff had talked to a real live person, or left a message on their voice mail, or if we were unable to contact the patron because of a disconnected number, etc.

Because the shorthand was confusing and inconsistently applied, I added some more parameters to the LCLD HOLD_SLIP html in order to add a checklist to the bottom of the receipt if the patron did not have an e-mail address.

This requires a new "checklist" DIV down at the bottom of the page that we will call "check_<<branches.branchcode>>" and which we will hide by default.  We'll also add a horizontal line to delineate this section from the other sections.


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
          $("#loc_DIGITAL").hide();
       
      });

  </script>

  <style>

    body {width: 245px; margin-top: 0; margin-bottom: 0; margin-right: 0; margin-left: 20px}
    article{display:inline-block}
    .right{float:right}

  </style>
 
  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <article class="right">
          <h2>Hold till: <<reserves.expirationdate>></h2>
        </article>    
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

    <div id="check_<<branches.branchcode>>" style="display:none">

      <hr />




    </div>


  </body>

</html>


*************************************************

Then we're going to add another DIV to the checklist DIV we just created called "list" and we're going to hide it too.


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
          $("#loc_DIGITAL").hide();
       
      });

  </script>

  <style>

    body {width: 245px; margin-top: 0; margin-bottom: 0; margin-right: 0; margin-left: 20px}
    article{display:inline-block}
    .right{float:right}

  </style>
 
  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <article class="right">
          <h2>Hold till: <<reserves.expirationdate>></h2>
        </article>    
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

    <div id="check_<<branches.branchcode>>" style="display:none">

      <hr />

        <div id="list" style="display:none">



        </div>

    </div>

  </body>

</html>


*************************************************

Then we're going to hide the patron's e-mail address inside this DIV.  This gives us something we can use later in the code as part of a logical operation.


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
          $("#loc_DIGITAL").hide();
       
      });

  </script>

  <style>

    body {width: 245px; margin-top: 0; margin-bottom: 0; margin-right: 0; margin-left: 20px}
    article{display:inline-block}
    .right{float:right}

  </style>
 
  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <article class="right">
          <h2>Hold till: <<reserves.expirationdate>></h2>
        </article>    
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

    <div id="check_<<branches.branchcode>>" style="display:none">

      <hr />

        <div id="list" style="display:none">
     
          <h5 style="display:none"><<borrowers.email>></h5>
     
        </div>

    </div>

  </body>

</html>


*************************************************

Then we will throw in the rest of this checklist.


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
          $("#loc_DIGITAL").hide();
       
      });

  </script>

  <style>

    body {width: 245px; margin-top: 0; margin-bottom: 0; margin-right: 0; margin-left: 20px}
    article{display:inline-block}
    .right{float:right}

  </style>
 
  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <article class="right">
          <h2>Hold till: <<reserves.expirationdate>></h2>
        </article>    
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

    <div id="check_<<branches.branchcode>>" style="display:none">

      <hr />

      <div id="list" style="display:none">

        <h5 style="display:none"><<borrowers.email>></h5>

        <h2>Phone call checklist</h2>

        <h3>Circle one:</h3>


        <p>Spoke directly with the patron.</p>


        <p>Left message with person who answered the phone.</p>


        <p>Left message on answering machine / voice mail.</p>


        <p>Please send a postcard to the patron if you cannot contact them by other means within 24 hours of the requested item's arrival.</p>


        <br />


        <hr />


      </div>

    </div>

  </body>

</html>


*************************************************

Next we're going to go back and add an ID to the "Hold till" date.  We're going to give it the ID "pudate" so that we can use jQuery to modify the text that appears if the patron needs to be phoned.  And then we'll hide a span inside that h2 tag that includes the patron's e-mail address.


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
          $("#loc_DIGITAL").hide();
       
      });

  </script>

  <style>

    body {width: 245px; margin-top: 0; margin-bottom: 0; margin-right: 0; margin-left: 20px}
    article{display:inline-block}
    .right{float:right}

  </style>
 
  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <article class="right">
          <h2 id="pudate">Hold till: <<reserves.expirationdate>> <span style="display: none;"><<borrowers.email>></span></h2>
        </article>    
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

    <div id="check_<<branches.branchcode>>" style="display:none">

      <hr />

      <div id="list" style="display:none">

        <h5 style="display:none"><<borrowers.email>></h5>

        <h2>Phone call checklist</h2>

        <h3>Circle one:</h3>

        <p>Spoke directly with the patron.</p>

        <p>Left message with person who answered the phone.</p>

        <p>Left message on answering machine / voice mail.</p>

        <p>Please send a postcard to the patron if you cannot contact them by other means within 24 hours of the requested item's arrival.</p>

        <br />

        <hr />

      </div>

    </div>

  </body>

</html>


*************************************************

Now we're going to start adding the additional jQuery.

First we'll add the jQuery to work with the ID we just added to the pickup date.  When I was at Latah County Library District we held the items for patrons we phoned for 7 days from the date we called them rather than 7 days from the day that the item arrived.  So this jQuery takes the "Hold till MM/YY/DD" text and replaces it with the phrase "Phone the patron."  The idea was that staff would see this and put the item in a pile next to the phone so the patron could be called.


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
          $("#loc_DIGITAL").hide();
          $('#pudate').not(':contains("@")').html("Phone<br />the<br />Patron");
       
      });

  </script>

  <style>

    body {width: 245px; margin-top: 0; margin-bottom: 0; margin-right: 0; margin-left: 20px}
    article{display:inline-block}
    .right{float:right}

  </style>
 
  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <article class="right">
          <h2 id="pudate">Hold till: <<reserves.expirationdate>> <span style="display: none;"><<borrowers.email>></h2>
        </article>    
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

    <div id="check_<<branches.branchcode>>" style="display:none">

      <hr />

      <div id="list" style="display:none">

        <h5 style="display:none"><<borrowers.email>></h5>

        <h2>Phone call checklist</h2>

        <h3>Circle one:</h3>

        <p>Spoke directly with the patron.</p>

        <p>Left message with person who answered the phone.</p>

        <p>Left message on answering machine / voice mail.</p>

        <p>Please send a postcard to the patron if you cannot contact them by other means within 24 hours of the requested item's arrival.</p>

        <br />

        <hr />

      </div>

    </div>

  </body>

</html>

*************************************************

Then we'll add the jQuery that un-hides the checklist DIV if the item is supposed to be picked up at this location.  There's no point in printing the checklist if the patron is going to pick up the item somewhere else.

<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
          $("#loc_DIGITAL").hide();
          $('#pudate').not(':contains("@")').html("Phone<br />the<br />Patron");
          $('#check_DIGITAL').removeAttr("style");
       
      });

  </script>

  <style>

    body {width: 245px; margin-top: 0; margin-bottom: 0; margin-right: 0; margin-left: 20px}
    article{display:inline-block}
    .right{float:right}

  </style>
 
  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <article class="right">
          <h2 id="pudate">Hold till: <<reserves.expirationdate>> <span style="display: none;"><<borrowers.email>></h2>
        </article>    
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

    <div id="check_<<branches.branchcode>>" style="display:none">

      <hr />

      <div id="list" style="display:none">

        <h5 style="display:none"><<borrowers.email>></h5>

        <h2>Phone call checklist</h2>

        <h3>Circle one:</h3>

        <p>Spoke directly with the patron.</p>

        <p>Left message with person who answered the phone.</p>

        <p>Left message on answering machine / voice mail.</p>

        <p>Please send a postcard to the patron if you cannot contact them by other means within 24 hours of the requested item's arrival.</p>

        <br />

        <hr />

      </div>

    </div>

  </body>

</html>


*************************************************

Finally we'll add the jQuery that un-hides the actual checklist if the patron does not have an e-mail address.


<!DOCTYPE html>
<html>

  <head>

    <script type="text/javascript">

      $(document).ready(function(){

          $("#bor_DIGITAL").parent().removeAttr('style');
          $("#loc_DIGITAL").hide();
          $('#pudate').not(':contains("@")').html("Phone<br />the<br />Patron");
          $('#check_DIGITAL').removeAttr("style");
          $('#list').not(':contains("@")').removeAttr("style");
       
      });

  </script>

  <style>

    body {width: 245px; margin-top: 0; margin-bottom: 0; margin-right: 0; margin-left: 20px}
    article{display:inline-block}
    .right{float:right}

  </style>
 
  </head>

  <body>

    <div id="borrower" style="display: none;">
 
      <div id="bor_<<branches.branchcode>>">
   
        <article class="right">
          <h2 id="pudate">Hold till: <<reserves.expirationdate>> <span style="display: none;"><<borrowers.email>></h2>
        </article>    
   
        <h2 style="text-align:left"><<borrowers.surname>>,<br>
        <<borrowers.firstname>></h2><br>
        <br>
        <br>
        <h3>Hold at <<branches.branchname>></h3>
        <ul>
          <li><<borrowers.cardnumber>></li>
          <li><<borrowers.phone>></li>
          <li><<borrowers.email>></li>
          <li><<borrower-attribute:HOLD>></li>
        </ul><br>
        <h3>ITEM ON HOLD</h3>
        <ul>
          <li><<items.itemcallnumber>> // <<biblio.title>> // <<biblio.author>></li>
          <li><<items.barcode>></li>
          <li>Available since: <<reserves.waitingdate>></li>
        </ul>
        <p>Notes:<br>
        <<reserves.reservenotes>></p><br>
       
      </div>
 
    </div>
 
    <div id="loc_<<branches.branchcode>>">
 
      <h1 style="font-size: 48px;"><<branches.branchnotes>></h1><br>
      <br>
      <br>
      <p>Ship to:</p>
      <h1><<branches.branchname>></h1>
      <h1><<branches.branchaddress1>></h1>
      <h1><<branches.branchcity>>, <<branches.branchstate>> <<branches.branchzip>></h1>
      <p> </p>
      <p><<items.itemcallnumber>></p>
      <p><<biblio.title>></p>
      <p><<biblio.author>></p>
      <p><<items.barcode>></p>
      <p> </p>
      <p>Shipped on: <<today>></p>
      <p> </p>
      <p>Shipped to: <<branches.branchcode>></p>
      <p> </p>
      <p>This slip generated at DIGITAL</p>
      <p> </p>
 
    </div>

    <div id="check_<<branches.branchcode>>" style="display:none">

      <hr />

      <div id="list" style="display:none">

        <h5 style="display:none"><<borrowers.email>></h5>

        <h2>Phone call checklist</h2>

        <h3>Circle one:</h3>

        <p>Spoke directly with the patron.</p>

        <p>Left message with person who answered the phone.</p>

        <p>Left message on answering machine / voice mail.</p>

        <p>Please send a postcard to the patron if you cannot contact them by other means within 24 hours of the requested item's arrival.</p>

        <br />

        <hr />

      </div>

    </div>

  </body>

</html>


**************************************************

The end result is a receipt that looks like this if the requesting patron does not have an e-mail address:



**************************************************

These are just some of the things I've done with receipts so far.

It is also possible to modify the HOLD_SLIP receipts to include scannable barcodes for patrons and items.  It is also possible to modify the receipts to print sideways.  It is also possible to modify the receipts to only display portions of a patron's name or barcode number in order to protect their privacy when items are stored on a self-service hold shelf, for example.

There are many possibilities.