Bug 14242: Use ISBN-field to automatically fill out purchase suggestions
Add new jQuery plugin "autofill" which transforms selected element(s) into search fields for Google Books API and automatically fills requested fields with search result. Use in OPAC purchase suggestions to automatically fill out fields when entering ISBN-number. Test plan: 1. Run updatedatabase.pl 2. Enable system precference "OPACSuggestionAutoFill" 3. Log into OPAC and go to purchase suggestions page 4. Write a valid ISBN (such as 0-9690745-2-2 or 978-1-78416-110-1) into the ISBN field and click the 'search google books' button, or unfocus the field. 5. Title, Author, Publisher, publication year and Item Type fields should now be filled automatically. 6. Press "clear form" to undo - should restore all fields that the API pull filled to empty Signed-off-by: Owen Leonard <oleonard@myacpl.org> Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de> Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de> Signed-off-by: Fridolin Somers <fridolin.somers@biblibre.com>
This commit is contained in:
parent
7e85abde99
commit
bdcf9a8a3f
|
|
@ -0,0 +1,6 @@
|
|||
$DBversion = 'XXX';
|
||||
if( CheckVersion( $DBversion ) ) {
|
||||
$dbh->do(q{ INSERT IGNORE INTO systempreferences (`variable`, `value`, `options`, `explanation`,`type`) VALUES ('OPACSuggestionAutoFill', '0', NULL, 'Automatically fill OPAC suggestion form with data from Google Books API','YesNo') });
|
||||
|
||||
NewVersion( $DBversion, 14242, "Add OPACSuggestionAutoFill system preference");
|
||||
}
|
||||
|
|
@ -389,6 +389,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `
|
|||
('OAI-PMH:DeletedRecord','persistent','Koha\'s deletedbiblio table will never be deleted (persistent), might be deleted (transient), or will never have any data in it (no)','transient|persistent|no','Choice'),
|
||||
('OAI-PMH:MaxCount','50',NULL,'OAI-PMH maximum number of records by answer to ListRecords and ListIdentifiers queries','Integer'),
|
||||
('OPACAcquisitionDetails','0','','Show the acquisition details at the OPAC','YesNo'),
|
||||
('OPACSuggestionAutoFill','0',NULL,'Automatically fill OPAC suggestion form with data from Google Books API','YesNo'),
|
||||
('OpacAddMastheadLibraryPulldown','0','','Adds a pulldown menu to select the library to search on the opac masthead.','YesNo'),
|
||||
('OpacAdvSearchMoreOptions','pubdate,itemtype,language,subtype,sorting,location','Show search options for the expanded view (More options)','pubdate|itemtype|language|subtype|sorting|location','multiple'),
|
||||
('OpacAdvSearchOptions','pubdate,itemtype,language,sorting,location','Show search options','pubdate|itemtype|language|subtype|sorting|location','multiple'),
|
||||
|
|
|
|||
|
|
@ -514,6 +514,13 @@ OPAC:
|
|||
1: Display
|
||||
0: Don't display
|
||||
- a library selection list for news items in the OPAC.
|
||||
-
|
||||
- pref: OPACSuggestionAutoFill
|
||||
default: 0
|
||||
choices:
|
||||
yes: Enable
|
||||
no: Disable
|
||||
- OPAC suggestions form automatically filling with data from Google Books API.
|
||||
-
|
||||
- "Use the following as the OPAC ISBD template:"
|
||||
- pref: OPACISBD
|
||||
|
|
|
|||
|
|
@ -75,6 +75,12 @@
|
|||
<form action="/cgi-bin/koha/opac-suggestions.pl" method="post" id="add_suggestion_form">
|
||||
<fieldset class="rows">
|
||||
<ol>
|
||||
[% IF Koha.Preference( 'OPACSuggestionAutoFill' ) %]
|
||||
<li>
|
||||
<label for="isbn">Standard number (ISBN, ISSN or other):</label>
|
||||
<input type="text" id="isbn" name="isbn" maxlength="80" style="margin-right:10px;margin-top:10px;" size="15"/>
|
||||
</li>
|
||||
[% END %]
|
||||
<li>
|
||||
[% IF ( title_required ) %]
|
||||
<label for="title" class="required">Title:</label>
|
||||
|
|
@ -101,17 +107,18 @@
|
|||
<li>
|
||||
<div title="Copyright or publication year, for example: 2016">
|
||||
[% IF ( copyrightdate_required ) %]
|
||||
<label for="copyrightdate" class="required">Copyright date:</label>
|
||||
<input type="text" id="copyrightdate" name="copyrightdate" pattern="[12]\d{3}" size="4" maxlength="4" value="[% copyrightdate | html %]" required="required" />
|
||||
<div class="required_label required">Required</div>
|
||||
<label for="copyrightdate" class="required">Publication year:</label>
|
||||
<input type="text" id="copyrightdate" name="copyrightdate" pattern="[12]\d{3}" size="10" maxlength="4" value="[% copyrightdate | html %]" required="required" />
|
||||
<div class="required_label required">Required</div>
|
||||
[% ELSE %]
|
||||
<label for="copyrightdate">Copyright date:</label>
|
||||
<input type="text" id="copyrightdate" name="copyrightdate" pattern="[12]\d{3}" size="4" maxlength="4" value="[% copyrightdate | html %]" />
|
||||
<label for="copyrightdate">Publication year:</label>
|
||||
<input type="text" id="copyrightdate" name="copyrightdate" pattern="[12]\d{3}" size="10" maxlength="4" value="[% copyrightdate | html %]" />
|
||||
[% END %]
|
||||
<span class="hint">Copyright or publication year, for example: 2016</span>
|
||||
</div>
|
||||
</li>
|
||||
[% END %]
|
||||
[% UNLESS ( isbn_hidden )%]
|
||||
[% UNLESS ( isbn_hidden or Koha.Preference('OPACSuggestionAutoFill') ) %]
|
||||
<li>
|
||||
[% IF ( isbn_required ) %]
|
||||
<label for="isbn" class="required">Standard number (ISBN, ISSN or other):</label>
|
||||
|
|
@ -490,6 +497,9 @@
|
|||
|
||||
[% INCLUDE 'opac-bottom.inc' %]
|
||||
[% BLOCK jsinclude %]
|
||||
[% IF ( Koha.Preference("OPACSuggestionAutoFill") ) %]
|
||||
[% Asset.js("js/autofill.js") | $raw %]
|
||||
[% END %]
|
||||
[% INCLUDE 'datatables.inc' %]
|
||||
<script>
|
||||
[% IF ( loggedinusername ) %]
|
||||
|
|
@ -506,6 +516,20 @@
|
|||
}
|
||||
[% END %]
|
||||
|
||||
[% IF Koha.Preference( 'OPACSuggestionAutoFill' ) %]
|
||||
$(function() {
|
||||
$('#isbn').autofill({
|
||||
'volumeInfo.title': {target: 'title', effect: 'flash'},
|
||||
'volumeInfo.authors': {target: 'author', effect: 'flash'},
|
||||
'volumeInfo.publisher': {target: 'publishercode', effect: 'flash'},
|
||||
'volumeInfo.publishedDate': {target: 'copyrightdate', effect: 'flash'},
|
||||
/* google books api seem to only have books, so if we got a result
|
||||
* item, type will be a book, so set to BK (book). */
|
||||
'kind': {target: 'itemtype', handle: function(t,v) { t.val('BK'); },},
|
||||
});
|
||||
});
|
||||
[% END %]
|
||||
|
||||
$(function() {
|
||||
$("#suggestt").dataTable($.extend(true, {}, dataTablesDefaults, {
|
||||
"order": [[ 1, "asc" ]],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* Plugin dealing with the Google API based on jQuery autofill plugin
|
||||
* https://plugins.jquery.com/autofill/
|
||||
*
|
||||
* Automatically fills form inputs with relevant data based on search result
|
||||
* Modified for OPAC Suggestion form
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
function typeString(o) {
|
||||
if (typeof o != 'object')
|
||||
return typeof o;
|
||||
|
||||
if (o === null)
|
||||
return "null";
|
||||
|
||||
//object, array, function, date, regexp, string, number, boolean, error
|
||||
var internalClass = Object.prototype.toString.call(o)
|
||||
.match(/\[object\s(\w+)\]/)[1];
|
||||
|
||||
return internalClass.toLowerCase();
|
||||
}
|
||||
|
||||
var AutoFiller = function (elm, fields, type) {
|
||||
var self = this;
|
||||
self.type = type;
|
||||
self.$elm = $(elm);
|
||||
self.fields = fields;
|
||||
|
||||
var MSG_UNDO_AUTOFILL_SUGGESTION = __("Clear form");
|
||||
var MSG_SEARCH_GOOGLE_BOOKS = __("Search Google Books");
|
||||
|
||||
/* decorate element as autofiller */
|
||||
self.$undo = $('<input type="button" class="btn btn-info btn-sm" style="display:none;margin-left:5px;" value="' + MSG_UNDO_AUTOFILL_SUGGESTION + '" />');
|
||||
self.$fillbtn = $('<input type="button" class="btn btn-primary btn-sm" value="' + MSG_SEARCH_GOOGLE_BOOKS + '" />');
|
||||
self.$error = $('<span class="add-on" style="display:none;padding-left:5px;"></span>');
|
||||
self.$elm.after(self.$error);
|
||||
self.$elm.after(self.$undo);
|
||||
self.$elm.after(self.$fillbtn);
|
||||
|
||||
for(var key in fields) {
|
||||
if( fields.hasOwnProperty(key) && typeof fields[key] === 'object' ) {
|
||||
var $target = $('#' + self.fields[key].target);
|
||||
self.fields[key].$target = $target;
|
||||
}
|
||||
}
|
||||
|
||||
self.$fillbtn.click(function(){
|
||||
/* clear fields first */
|
||||
for(var key in self.fields) {
|
||||
var field = self.fields[key];
|
||||
field.$target.trigger('autofill-undo');
|
||||
}
|
||||
/* only allow forced update once every second */
|
||||
if(Date.now() - self.lastupdate > 1000) {
|
||||
self.$elm.trigger('change');
|
||||
}
|
||||
});
|
||||
|
||||
self.$undo.click(function(){
|
||||
for(var key in self.fields) {
|
||||
var field = self.fields[key];
|
||||
//field.$target.val("");
|
||||
field.$target.trigger('autofill-undo');
|
||||
}
|
||||
self.$undo.hide();
|
||||
});
|
||||
|
||||
self.$elm.change(function() {
|
||||
self.lastupdate = Date.now();
|
||||
self.$error.html('');
|
||||
self.$error.hide();
|
||||
/* give user some feedback that the request is in progress */
|
||||
self.$fillbtn.fadeOut(1000).fadeIn(1000);
|
||||
if ( self.$elm.val()) {
|
||||
var gAPI = 'https://www.googleapis.com/books/v1/volumes?q=';
|
||||
gAPI += self.$elm.val().replace(/\-/g, '');
|
||||
gAPI += '&maxResults=1';
|
||||
$.getJSON(gAPI, function (response) {
|
||||
if(response.totalItems == 0) {
|
||||
self.$error.html(__('Sorry, nothing found.'));
|
||||
self.$error.show();
|
||||
return;
|
||||
}
|
||||
|
||||
var undos = 0;
|
||||
var item = response.items[0];
|
||||
for(var key in self.fields) {
|
||||
var filled = false;
|
||||
var value = eval('item.'+key);
|
||||
var field = self.fields[key];
|
||||
|
||||
/* field handled by caller */
|
||||
if('handle' in field) {
|
||||
if(typeof field.handle === 'function')
|
||||
field.handle(field.$target, value);
|
||||
|
||||
continue; /* next please */
|
||||
}
|
||||
|
||||
/* wouldn't know what to do with result unless we have a
|
||||
* target */
|
||||
if( ! field.$target )
|
||||
continue;
|
||||
|
||||
/* handle differently depending on datatype */
|
||||
switch(typeString(value)) {
|
||||
case 'array':
|
||||
switch(field.$target.prop('nodeName').toUpperCase()) {
|
||||
case 'TEXTAREA':
|
||||
undos++;
|
||||
field.$target.bind('autofill-undo', field.$target.text(), function(e){$(this).text(e.data);});
|
||||
field.$target.text(value.join(', '));
|
||||
break;
|
||||
case 'INPUT':
|
||||
default:
|
||||
undos++;
|
||||
field.$target.bind('autofill-undo', field.$target.val(), function(e){$(this).val(e.data);});
|
||||
field.$target.val(value.join(', '));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
switch(field.$target.prop('nodeName').toUpperCase()) {
|
||||
case 'TEXTAREA':
|
||||
undos++;
|
||||
field.$target.bind('autofill-undo', field.$target.text(), function(e){$(this).text(e.data);});
|
||||
field.$target.text(value);
|
||||
break;
|
||||
case 'SELECT':
|
||||
case 'INPUT':
|
||||
default:
|
||||
undos++;
|
||||
field.$target.bind('autofill-undo', field.$target.val(), function(e){$(this).val(e.data);});
|
||||
field.$target.val(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch(field.effect) {
|
||||
case 'flash':
|
||||
field.$target.fadeOut(500).fadeIn(500);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(undos > 0)
|
||||
self.$undo.show();
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* @fields object: Google Books API item propreties map for
|
||||
* mapping against a target element. Expected
|
||||
* type:
|
||||
* {
|
||||
* GoogleBooksItem.property: {target: ELEM,
|
||||
* handle: function(target, value),
|
||||
* effect: jQuery effects,
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* "target" is optional and if specified alone (i.e no
|
||||
* handle proprety) autofill will automaticly fill this
|
||||
* target element with returned data.
|
||||
*
|
||||
* "handle" is optional and will be called when ajax request
|
||||
* has finished and target is matched. Function specifies
|
||||
* two arguments: target and value. Target is the target
|
||||
* element specified by "target" and value is the value
|
||||
* returned by Google Books API for the matched property.
|
||||
*
|
||||
* If a handle function is given, full control of result data
|
||||
* is given to the handle function.
|
||||
*
|
||||
* "effect" is optional and specifies effect name of effect
|
||||
* to use for the target once value has been set. Can be one of:
|
||||
*
|
||||
* - 'flash'
|
||||
*
|
||||
* @type string: defines the query type, default to input name
|
||||
* For example <input type="text" name="isbn"></input>
|
||||
* will search for isbn by default
|
||||
*
|
||||
* @EXAMPLE
|
||||
*
|
||||
* $('#isbn').autofill({
|
||||
* 'volumeInfo.title': {target: 'title', effect: 'flash'},
|
||||
* 'volumeInfo.authors': {target: 'author'},
|
||||
* 'volumeInfo.publisher': {target: 'publishercode'},
|
||||
* 'selfLink': {handle: function(t,v){window.location=v;}}
|
||||
* });
|
||||
* */
|
||||
$.fn.autofill = function(fields, type) {
|
||||
if(type === undefined) // default to input name
|
||||
type = this.attr('name');
|
||||
|
||||
return this.each(function(i){
|
||||
var plugin = $.data(this, "plugin_autofill");
|
||||
if (plugin)
|
||||
plugin.destroy();
|
||||
|
||||
$.data(this, "plugin_autofill", new AutoFiller(this, fields, type));
|
||||
});
|
||||
};
|
||||
}(jQuery));
|
||||
Loading…
Reference in New Issue