Created
May 4, 2012 21:59
-
-
Save jkeck/2598002 to your computer and use it in GitHub Desktop.
Nearby on shelf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
These are the flat files for Nearby on Shelf as implemented in SearchWorks. | |
I would like to turn this into an installable gem, however as a stop-gap measure for those interested in implementing nearby for themselves (or wants to run w/ this code an refactoring into a gem). | |
Some major areas of consideration are: | |
1) The current code gets sort keys and call numbers out of a delimited item_display field. These should be single configurable fields for each key. | |
2) jQuery for scrolling the nearby UI. We should really find a reliable 3rd party library for this kind of carrousel behavior. | |
3) There is some code revolving around checking barcodes. This should be refactored out in some way but I can't think of how. | |
4) The part of the NearbyOnShelf class initializer that takes action based on an "ajax" parameter seems wrong to me, but I can't think of a proper solution off the top of my head. | |
5) Several pieces of this will need to have a way of injecting HTML into the Blacklight UI. Changes to both Blacklight and this Nearby on Shelf code will be necessary to gem-ify this codebase. | |
Files: | |
lib/nearby_on_shelf.rb Needs to be taken out of the Stanford module. You may need to include a solr_helper here. | |
app/controllers/browse_controller.rb This file has to take into account our grid and list layouts, which won't be necessary in core Blacklight. | |
app/views/browse/nearby.html.erb Simple wrapper view | |
app/views/browse/_nearby.html.erb Main container for nearby sidebar. This file also includes the jQuery for scrolling. | |
app/views/browse/_nearby_item.html.erb The partial for individual items in the nearby UI. There are some helpers/constants here for processing the data that I'm going to omit, so you should just add your own fields/parsing here. (e.g. nearby_author helper method or Constants::LIB_TRANSLATIONS constant) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%- nearby_response ||= @nearby_response -%> | |
<%- unless nearby_response.items.nil? -%> | |
<div id="nearby_wrapper"> | |
<ol id="nearby_objects" class="nrby_obj"> | |
<%- nearby_response.items.each do |item| -%> | |
<%= render "browse/nearby_item", :item => item %> | |
<%- end -%> | |
</ol> | |
</div> | |
<div id="browse_controls"> | |
<div id="previous"> | |
<a href="" onclick="return false;"> | |
<span class="page_prev">Previous</span> | |
</a> | |
</div> | |
<div id="full_view"> | |
<%= link_to("#{image_tag("fullpage.png")} Show full page".html_safe, browse_index_path(:start => params[:id] ? params[:id] : params[:original_id])) %> | |
</div> | |
<div id="next"> | |
<a href="" onclick="return false;"> | |
<span class="page_next">Next</span> | |
</a> | |
</div> | |
</div> | |
<script type="text/javascript"> | |
$("div#next").click(function(){ | |
var text = $("ol#nearby_objects").children("li:last").children().children("div.callnum_sort").attr("title"); | |
$.get("<%= browse_nearby_path %>?original_id=<%= params[:id] ? params[:id] : params[:original_id] %>&start=" + text + "&field=shelfkey&num=5", function(data){ | |
var $resp = $(data); | |
$resp.filter("div#nearby_wrapper").children("ol").each(function(){ | |
$("ol#nearby_objects").after($(this)); | |
$("ol.nrby_obj:last").attr("style","position:absolute;"); | |
$("ol.nrby_obj:last").animate({top:"0"},500,function(){ | |
$("ol.nrby_obj:first").remove(); | |
$("ol.nrby_obj").each(function(){ | |
$(this).attr("style","position:relative;"); | |
}); | |
}); | |
}); | |
}); | |
return false; | |
}); | |
$("div#previous").click(function(){ | |
var text = $("ol#nearby_objects").children("li:first").children().children("div.callnum_reverse_sort").attr("title"); | |
$.get("<%= browse_nearby_path %>?original_id=<%= params[:id] ? params[:id] : params[:original_id] %>&start=" + text + "&field=reverse_shelfkey&num=5", function(data){ | |
var $resp = $(data); | |
$resp.filter("div#nearby_wrapper").children("ol").each(function(){ | |
$(this).attr("style","position:absolute;top:-500px;z-index:1;"); | |
$("ol#nearby_objects:first").attr("style","position:relative;"); | |
$("ol#nearby_objects").before($(this)); | |
$(this).animate({top:"0"},500,function(){ | |
$("ol.nrby_obj:last").remove(); | |
$("ol.nrby_obj").each(function(){ | |
$(this).attr("style","position:relative;"); | |
}); | |
}); | |
}); | |
}); | |
return false; | |
}); | |
</script> | |
<%- end -%> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<li <%= (item[:doc][:id] == params[:id] or item[:doc][:id] == params[:original_id]) ? "class='current_item'" : nil %>> | |
<div class="nearby_item"> | |
<div class="title"> | |
<%- if (item[:doc][:id] == params[:id] or item[:doc][:id] == params[:original_id]) -%> | |
<%= h(truncate(nearby_title(item[:doc]),:length=>38)) %> | |
<%- else -%> | |
<%= link_to_document(item[:doc], :label => truncate(nearby_title(item[:doc]),:length=>38).to_s) %> | |
<%- end -%> | |
</div> | |
<div class="year"><%= item[:doc][:pub_date] unless item[:doc][:pub_date].nil? %></div> | |
<div class="author"> | |
<%- if nearby_author(item[:doc]).nil? -%> | |
<br/> | |
<%- else -%> | |
<%= nearby_author(item[:doc]) %> | |
<%- end -%> | |
</div> | |
<div class="callnum"> | |
<%- unless item[:holding].nil? -%> | |
<%= h(Constants::LIB_TRANSLATIONS[item[:holding][:library]]) %> » <%= item[:holding][:callnumber] %> | |
<%- else -%> | |
<br/> | |
<%- end -%> | |
</div> | |
<div style="display:none;" class="callnum_sort" title="<%= URI.encode(item[:holding][:shelfkey]) %>"></div> | |
<div style="display:none;" class="callnum_reverse_sort" title="<%= URI.encode(item[:holding][:reverse_shelfkey]) %>"></div> | |
</div> | |
</li> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class BrowseController < ApplicationController | |
include Blacklight::Catalog | |
def index | |
@response, @original_doc = get_solr_response_for_doc_id(params[:start]) | |
if params[:barcode] | |
preferred_barcode = params[:barcode] | |
else | |
preferred_barcode = @original_doc[:preferred_barcode] | |
end | |
save_current_search_params | |
if params[:view] and params[:view] == "list" | |
@document_list = Stanford::NearbyOnShelf.new("static",{:item_display => @original_doc[:item_display],:preferred_barcode=>preferred_barcode, :before => 19, :after => 20, :page => params[:page]}).items | |
render "catalog/_list_list" | |
else | |
@document_list = Stanford::NearbyOnShelf.new("static",{:item_display => @original_doc[:item_display],:preferred_barcode=>preferred_barcode, :before => 9, :after => 10, :page => params[:page]}).items | |
render "catalog/_gallery_list" | |
end | |
end | |
def nearby | |
if params[:preferred_barcode].to_s.empty? | |
@nearby_response = Stanford::NearbyOnShelf.new("ajax",{:start => params[:start], :field => params[:field], :num => params[:num]}) | |
else | |
@nearby_response = Stanford::NearbyOnShelf.new("bcode",{:item_display=>params[:item_display],:preferred_barcode=>params[:preferred_barcode], :before => 2, :after => 2}) | |
end | |
render :layout => false | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%- nearby_response ||= @nearby_response -%> | |
<%= render "browse/nearby", :nearby_response => nearby_response %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Stanford | |
class NearbyOnShelf | |
attr_reader :items | |
def initialize(type,options) | |
if type == "ajax" | |
@items = get_next_spines_from_field(options[:start],options[:field],options[:num],nil) | |
else | |
@items = get_nearby_items(options[:item_display],options[:preferred_barcode],options[:before],options[:after],options[:page]) | |
end | |
end | |
protected | |
def get_nearby_items(itm_display, barcode, before, after, page) | |
items=[] | |
item_display = get_item_display(itm_display,barcode) | |
if !item_display.nil? | |
my_shelfkey = get_shelfkey(item_display) | |
my_reverse_shelfkey = get_reverse_shelfkey(item_display) | |
if page.nil? or page.to_i == 0 | |
# get preceding bookspines | |
items << get_next_spines_from_field(my_reverse_shelfkey, "reverse_shelfkey", before, nil) | |
# TODO: can we avoid this extra call to Solr but keep the code this clean? | |
# What is the purpose of this call? To just return the original document? | |
items << get_spines_from_field_values([my_shelfkey], "shelfkey").uniq | |
# get following bookspines | |
items << get_next_spines_from_field(my_shelfkey, "shelfkey", after, nil) | |
else | |
if page.to_i < 0 # page is negative so we need to get the preceding docs | |
items << get_next_spines_from_field(my_reverse_shelfkey, "reverse_shelfkey", (before.to_i+1)*2, page.to_i) | |
elsif page.to_i > 0 # page is possitive, so we need to get the following bookspines | |
items << get_next_spines_from_field(my_shelfkey, "shelfkey", after.to_i*2, page.to_i) | |
end | |
end | |
items.flatten | |
end | |
end # get_nearby_items | |
# given a shelfkey or reverse shelfkey (for a lopped call number), get the | |
# text for the next "n" nearby items | |
def get_next_spines_from_field(starting_value, field_name, how_many, page) | |
number_of_items = how_many | |
unless page.nil? | |
if page < 0 | |
page = page.to_s[1,page.to_s.length] | |
end | |
number_of_items = how_many.to_i * page.to_i+1 | |
end | |
desired_values = get_next_terms_for_field(starting_value, field_name, number_of_items) | |
unless page.nil? or page.to_i == 0 | |
desired_values = desired_values.values_at((desired_values.length-how_many.to_i)..desired_values.length) | |
end | |
get_spines_from_field_values(desired_values, field_name) | |
end | |
# return an array of the next terms in the index for the indicated field and | |
# starting term. Returned array does NOT include starting term. Queries Solr (duh). | |
def get_next_terms_for_field(starting_term, field_name, how_many=3) | |
result = [] | |
# terms is array of one element hashes with key=term and value=count | |
terms_array = get_next_terms(starting_term, field_name, how_many.to_i+1) | |
terms_array.each { |term_hash| | |
result << term_hash.keys[0] unless term_hash.keys[0] == starting_term | |
} | |
result | |
end | |
# create an array of sorted html list items containing the appropriate display text | |
# (analogous to what would be visible if you were looking at the spine of | |
# a book on a shelf) from relevant solr docs, given a particular solr | |
# field and value for which to retrieve spine info. | |
# Each html list item must match a desired value | |
def get_spines_from_field_values(desired_values, field) | |
spines_hash = {} | |
docs = get_docs_for_field_values(desired_values, field) | |
docs.each do |doc| | |
hsh = get_spine_hash_from_doc(doc, desired_values, field) | |
spines_hash.merge!(hsh) | |
end | |
result = [] | |
spines_hash.keys.sort.each { |sortkey| | |
result << spines_hash[sortkey] | |
} | |
result | |
end | |
# create a hash with | |
# key = sorting key for the spine, | |
# value = the html list item containing appropriate display text | |
# (analogous to what would be visible if you were looking at the spine of | |
# a book on a shelf) from a solr doc. | |
# spine is: <li> title [(pub year)] [<br/> author] <br/> callnum </li> | |
# Each element of the hash must match a desired value in the | |
# desired_values array for the indicated piece (shelfkey or reverse shelfkey) | |
def get_spine_hash_from_doc(doc, desired_values, field) | |
result_hash = {} | |
return if doc[:item_display].nil? | |
# This winnows down the holdings hashs on only ones where the desired values includes the shelfkey or reverse shelfkey using a very quick select statment | |
# The resulting array looke like [[:"36105123456789",{:barcode=>"36105123456789",:callnumber=>"PS3156 .A53"}]] | |
item_array = doc.holdings_from_solr.select{|k,v| ( (field == "shelfkey" and desired_values.include?(v[:shelfkey]) ) or ( field == "reverse_shelfkey" and desired_values.include?(v[:reverse_shelfkey]) ) ) } | |
temp_holdings_hash = {} | |
unless item_array.empty? | |
# putting items back into a hash for readibility | |
item_array.each do |value| | |
temp_holdings_hash[value.first] = value.last | |
end | |
# looping through the resulting temp hash of holdings to build proper sort keys and then return a hash that conains a solr document for every item in the hash | |
temp_holdings_hash.each do |key,value| | |
# create sorting key for spine | |
# shelfkey asc, then by sorting title asc, then by pub date desc | |
# notice that shelfkey and sort_title need to be a constant length | |
# separator of " -|- " is for human readability only | |
sort_key = "#{value[:shelfkey][0,100].ljust(100)} -|- " | |
sort_key << "#{doc[:title_sort][0,100].ljust(100)} -|- " unless doc[:title_sort].nil? | |
# pub_year must be inverted for descending sort | |
if doc[:pub_date].nil? || doc[:pub_date].length == 0 | |
sort_key << '9999' | |
else | |
sort_key << doc[:pub_date].tr('0123456789', '9876543210') | |
end | |
# Adding ckey to sort to make sure we collapse things that have the same callnumber, title, pub date, AND ckey | |
sort_key << " -|- #{doc[:id][0,20].ljust(20)}" | |
# We were adding the library to the sortkey. However; if we don't add the library we can easily collapse items that have the same | |
# call number (shelfkey), title, pub date, and ckey but are housed in different libraries. | |
#sort_key << " -|- #{value[:library][0,40].ljust(40)}" | |
result_hash[sort_key] = {:doc=>doc,:holding=>value} | |
end # end each item display | |
end | |
return result_hash | |
end | |
# given a document and the barcode of an item in the document, return the | |
# item_display field corresponding to the barcode, or nil if there is no | |
# such item | |
def get_item_display(item_display, barcode) | |
item = "" | |
if barcode.nil? || barcode.length == 0 | |
return nil | |
end | |
item_display.each do |item_disp| | |
item = item_disp if item_disp =~ /^#{CGI::escape(barcode)}/ | |
end | |
return item unless item == "" | |
end | |
# return the shelfkey (lopped) piece of the item_display field | |
def get_shelfkey(item_display) | |
get_item_display_piece(item_display, 6) | |
end | |
# return the reverse shelfkey (lopped) piece of the item_display field | |
def get_reverse_shelfkey(item_display) | |
get_item_display_piece(item_display, 7) | |
end | |
def get_item_display_piece(item_display, index) | |
if (item_display) | |
item_array = item_display.split('-|-') | |
return item_array[index].strip unless item_array[index].nil? | |
end | |
nil | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment