Owen Leonard
a682c620ee
I think the "breadcrumbs" ID is worth saving for past and future CSS customization reasons. Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com> Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
407 lines
17 KiB
Text
407 lines
17 KiB
Text
[% USE raw %]
|
|
[% USE Asset %]
|
|
[% SET footerjs = 1 %]
|
|
[% INCLUDE 'doc-head-open.inc' %]
|
|
<title>Koha › Tools › Quote uploader</title>
|
|
[% INCLUDE 'doc-head-close.inc' %]
|
|
[% Asset.css("css/uploader.css") | $raw %]
|
|
[% Asset.css("css/quotes.css") | $raw %]
|
|
</head>
|
|
|
|
<body id="tools_quotes" class="tools">
|
|
[% INCLUDE 'header.inc' %]
|
|
[% INCLUDE 'cat-search.inc' %]
|
|
|
|
<nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumb">
|
|
<ol>
|
|
<li>
|
|
<a href="/cgi-bin/koha/mainpage.pl">Home</a>
|
|
</li>
|
|
<li>
|
|
<a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a>
|
|
</li>
|
|
<li>
|
|
<a href="/cgi-bin/koha/tools/quotes.pl">Quote editor</a>
|
|
</li>
|
|
<li>
|
|
<a href="#" aria-current="page">
|
|
Quote uploader
|
|
</a>
|
|
</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<div class="main container-fluid">
|
|
<div class="row">
|
|
<div class="col-sm-10 col-sm-push-2">
|
|
<main>
|
|
<div id="toolbar" class="btn-toolbar" style="visibility: hidden; position: absolute">
|
|
<div class="btn-group"><a class="btn btn-default" id="save_quotes" href="#"><i class="fa fa-save"></i> Save quotes</a></div>
|
|
<div class="btn-group"><a href="/cgi-bin/koha/tools/quotes-upload.pl" id="cancel_quotes" class="btn btn-default"><i class="fa fa-remove"></i> Cancel import</a></div>
|
|
</div>
|
|
|
|
<h2>Quote uploader</h2>
|
|
|
|
<div id="messages" style="display: none;">
|
|
<div class="import_success dialog message" style="display: none;"></div>
|
|
<div class="import_errors dialog alert" style="display: none;"></div>
|
|
</div>
|
|
|
|
<div id="csv_error" class="alert" style="visibility: hidden;">Something went wrong, check your CSV file.</div>
|
|
<div id="instructions">
|
|
<fieldset id="file_uploader_help" class="rows">
|
|
<legend>Instructions</legend>
|
|
<div id="file_uploader_inst">
|
|
<ul>
|
|
<li>The quote uploader accepts standard csv files with two columns: "source","text"</li>
|
|
<li>Click the "Choose File" button and select the csv file to be uploaded.</li>
|
|
<li>The file will be imported into an editable table for review prior to saving.</li>
|
|
</ul>
|
|
</div>
|
|
<div id="file_editor_inst">
|
|
<ul>
|
|
<li>Click on any field to edit the contents; Press the <Enter> key to save edit.</li>
|
|
<li>Click the 'Save Quotes' button in the toolbar to save the entire batch of quotes.</li>
|
|
</ul>
|
|
</div>
|
|
</fieldset>
|
|
</div>
|
|
|
|
<fieldset id="file_uploader" class="rows">
|
|
<legend>Upload quotes</legend>
|
|
<div id="file_upload">
|
|
<input type="file" name="file" />
|
|
<button id="cancel_upload" style="display:none">Cancel upload</button>
|
|
<div id="progress_bar"><div class="percent">0%</div></div>
|
|
</div>
|
|
</fieldset>
|
|
<table id="quotes_editor" style="visibility: hidden;">
|
|
<thead>
|
|
<tr>
|
|
<th>Source</th>
|
|
<th>Text</th>
|
|
<th class="noExport">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- tbody content is generated by DataTables -->
|
|
<tr>
|
|
<td></td>
|
|
<td>Loading data...</td>
|
|
<td></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<fieldset id="footer" class="action" style="visibility: hidden;">
|
|
</fieldset>
|
|
|
|
</main>
|
|
</div> <!-- /.col-sm-10.col-sm-push-2 -->
|
|
|
|
<div class="col-sm-2 col-sm-pull-10">
|
|
<aside>
|
|
[% INCLUDE 'tools-menu.inc' %]
|
|
</aside>
|
|
</div> <!-- /.col-sm-2.col-sm-pull-10 -->
|
|
</div> <!-- /.row -->
|
|
|
|
[% MACRO jsinclude BLOCK %]
|
|
[% Asset.js("js/tools-menu.js") | $raw %]
|
|
[% INCLUDE 'datatables.inc' %]
|
|
[% Asset.js("lib/jquery/plugins/jquery.jeditable.mini.js") | $raw %]
|
|
<script>
|
|
var oTable; //DataTable object
|
|
$(document).ready(function() {
|
|
|
|
$("#cancel_upload").on("click",function(e){
|
|
e.preventDefault();
|
|
fnAbortRead();
|
|
});
|
|
$("#cancel_quotes").on("click",function(){
|
|
return confirm( _("Are you sure you want to cancel this import?") );
|
|
});
|
|
|
|
// Credits:
|
|
// FileReader() code copied and hacked from:
|
|
// http://www.html5rocks.com/en/tutorials/file/dndfiles/
|
|
// fnCSVToArray() gratefully borrowed from:
|
|
// http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm
|
|
|
|
var reader;
|
|
var progress = document.querySelector('.percent');
|
|
$("#server_response").hide();
|
|
|
|
function yuiGetData() {
|
|
fnGetData(document.getElementById('quotes_editor'));
|
|
}
|
|
|
|
function fnAbortRead() {
|
|
reader.abort();
|
|
}
|
|
|
|
function fnErrorHandler(evt) {
|
|
switch(evt.target.error.code) {
|
|
case evt.target.error.NOT_FOUND_ERR:
|
|
alert(_("File Not Found!"));
|
|
break;
|
|
case evt.target.error.NOT_READABLE_ERR:
|
|
alert(_("File is not readable"));
|
|
break;
|
|
case evt.target.error.ABORT_ERR:
|
|
break; // noop
|
|
default:
|
|
alert(_("An error occurred reading this file."));
|
|
};
|
|
}
|
|
|
|
function fnUpdateProgress(evt) {
|
|
// evt is an ProgressEvent.
|
|
if (evt.lengthComputable) {
|
|
var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
|
|
// Increase the progress bar length.
|
|
if (percentLoaded < 100) {
|
|
progress.style.width = percentLoaded + '%';
|
|
progress.textContent = percentLoaded + '%';
|
|
}
|
|
}
|
|
}
|
|
|
|
function fnCSVToArray( strData, strDelimiter ){
|
|
// This will parse a delimited string into an array of
|
|
// arrays. The default delimiter is the comma, but this
|
|
// can be overriden in the second argument.
|
|
|
|
// Check to see if the delimiter is defined. If not,
|
|
// then default to comma.
|
|
strDelimiter = (strDelimiter || ",");
|
|
|
|
// Create a regular expression to parse the CSV values.
|
|
var objPattern = new RegExp(
|
|
(
|
|
// Delimiters.
|
|
"(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
|
|
// Quoted fields.
|
|
"(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
|
|
// Standard fields.
|
|
"([^\"\\" + strDelimiter + "\\r\\n]*))"
|
|
),
|
|
"gi"
|
|
);
|
|
|
|
// Create an array to hold our data. Give the array
|
|
// a default empty first row.
|
|
var arrData = [[]];
|
|
|
|
// Create an array to hold our individual pattern
|
|
// matching groups.
|
|
var arrMatches = null;
|
|
|
|
// Keep looping over the regular expression matches
|
|
// until we can no longer find a match.
|
|
while (arrMatches = objPattern.exec( strData )){
|
|
|
|
// Get the delimiter that was found.
|
|
var strMatchedDelimiter = arrMatches[ 1 ];
|
|
|
|
// Check to see if the given delimiter has a length
|
|
// (is not the start of string) and if it matches
|
|
// field delimiter. If it does not, then we know
|
|
// that this delimiter is a row delimiter.
|
|
if ( strMatchedDelimiter.length && (strMatchedDelimiter != strDelimiter) ){
|
|
// Since we have reached a new row of data,
|
|
// add an empty row to our data array.
|
|
// Note: if there is not more data, we will have to remove this row later
|
|
arrData.push( [] );
|
|
}
|
|
|
|
// Now that we have our delimiter out of the way,
|
|
// let's check to see which kind of value we
|
|
// captured (quoted or unquoted).
|
|
if (arrMatches[ 2 ]){
|
|
// We found a quoted value. When we capture
|
|
// this value, unescape any double quotes.
|
|
var strMatchedValue = arrMatches[ 2 ].replace(
|
|
new RegExp( "\"\"", "g" ),
|
|
"\""
|
|
);
|
|
} else if (arrMatches[3]){
|
|
// We found a non-quoted value.
|
|
var strMatchedValue = arrMatches[ 3 ];
|
|
} else {
|
|
// There is no more valid data so remove the row we added earlier
|
|
// Is there a better way? Perhaps a look-ahead regexp?
|
|
arrData.splice(arrData.length-1, 1);
|
|
}
|
|
|
|
// Now that we have our value string, let's add
|
|
// it to the data array.
|
|
if ( arrData[ arrData.length - 1 ] ) {
|
|
arrData[ arrData.length - 1 ].push( strMatchedValue );
|
|
} else {
|
|
$('#csv_error').css( 'visibility' , 'visible' );
|
|
}
|
|
}
|
|
|
|
// Return the parsed data.
|
|
return( arrData );
|
|
}
|
|
|
|
function fnDataTable(aaData) {
|
|
for(var i=0; i<aaData.length; i++) {
|
|
aaData[i].unshift(i+1); // Add a column w/quote number
|
|
}
|
|
|
|
/* Transition from the quote file uploader to the quote file editor interface */
|
|
$('#toolbar').css("visibility","visible");
|
|
$('#toolbar').css("position","");
|
|
$('#file_editor_inst').css("visibility", "visible");
|
|
$('#file_editor_inst').css("position", "");
|
|
$('#file_uploader_inst').css("visibility", "hidden");
|
|
$('#save_quotes').css("visibility","visible");
|
|
$('#file_uploader').css("visibility","hidden");
|
|
$('#file_uploader').css("position","absolute");
|
|
$('#file_uploader').css("top","-150px");
|
|
$('#quotes_editor').css("visibility","visible");
|
|
$("#save_quotes").on("click", yuiGetData);
|
|
|
|
oTable = $('#quotes_editor').dataTable( {
|
|
"bAutoWidth" : false,
|
|
"bPaginate" : true,
|
|
"bSort" : false,
|
|
"sPaginationType" : "full_numbers",
|
|
"sDom": '<"top pager"iflp>rt<"bottom pager"flp><"clear">',
|
|
"aaData" : aaData,
|
|
"aoColumns" : [
|
|
{
|
|
"sTitle" : _("Number"),
|
|
"sWidth" : "2%",
|
|
},
|
|
{
|
|
"sTitle" : _("Source"),
|
|
"sWidth" : "15%",
|
|
},
|
|
{
|
|
"sTitle" : _("Quote"),
|
|
"sWidth" : "83%",
|
|
},
|
|
],
|
|
"fnPreDrawCallback": function(oSettings) {
|
|
return true;
|
|
},
|
|
"fnRowCallback": function( nRow, aData, iDisplayIndex ) {
|
|
/* do foo on various cells in the current row */
|
|
var quoteNum = $('td', nRow)[0].innerHTML;
|
|
$(nRow).attr("id", quoteNum); /* set row ids to quote number */
|
|
/* apply no_edit id to noEditFields */
|
|
noEditFields = [0]; /* number */
|
|
for (i=0; i<noEditFields.length; i++) {
|
|
$('td', nRow)[noEditFields[i]].setAttribute("id","no_edit");
|
|
}
|
|
return nRow;
|
|
},
|
|
"fnDrawCallback": function(oSettings) {
|
|
/* Apply the jEditable handlers to the table on all fields w/o the no_edit id */
|
|
$('#quotes_editor tbody td[id!="no_edit"]').editable( function(value, settings) {
|
|
var cellPosition = oTable.fnGetPosition( this );
|
|
oTable.fnUpdate(value, cellPosition[0], cellPosition[1], false, false);
|
|
return(value);
|
|
},
|
|
{
|
|
"callback" : function( sValue, y ) {
|
|
oTable.fnDraw(false); /* no filter/sort or we lose our pagination */
|
|
},
|
|
"height" : "14px",
|
|
});
|
|
},
|
|
});
|
|
$('#footer').css("visibility","visible");
|
|
}
|
|
|
|
function fnHandleFileSelect(evt) {
|
|
// Reset progress indicator on new file selection.
|
|
progress.style.width = '0%';
|
|
progress.textContent = '0%';
|
|
|
|
reader = new FileReader();
|
|
reader.onerror = fnErrorHandler;
|
|
reader.onprogress = fnUpdateProgress;
|
|
reader.onabort = function(e) {
|
|
alert(_("File read cancelled"));
|
|
parent.location='quotes-upload.pl';
|
|
};
|
|
reader.onloadstart = function(e) {
|
|
$('#cancel_upload').show();
|
|
$('#progress_bar').addClass("loading");
|
|
};
|
|
reader.onload = function(e) {
|
|
// Ensure that the progress bar displays 100% at the end.
|
|
progress.style.width = '100%';
|
|
progress.textContent = '100%';
|
|
$('#cancel_upload').hide();
|
|
quotes = fnCSVToArray(e.target.result, ',');
|
|
fnDataTable(quotes);
|
|
}
|
|
|
|
// perform various sanity checks on the target file prior to uploading...
|
|
var fileType = evt.target.files[0].type || 'unknown';
|
|
var fileSizeInK = Math.round(evt.target.files[0].size/1024);
|
|
|
|
if (!fileType.match(/comma-separated-values|csv|excel|calc/i)) {
|
|
alert(_("Uploads limited to csv. Incorrect filetype: %s").format(fileType));
|
|
parent.location='quotes-upload.pl';
|
|
return;
|
|
}
|
|
if (fileSizeInK > 512) {
|
|
if (!confirm(_("%s %s KB Do you really want to upload this file?").format(evt.target.files[0].name, fileSizeInK))) {
|
|
parent.location='quotes-upload.pl';
|
|
return;
|
|
}
|
|
}
|
|
// Read in the image file as a text string.
|
|
reader.readAsText(evt.target.files[0]);
|
|
}
|
|
|
|
$('#file_upload').one('change', fnHandleFileSelect);
|
|
|
|
var MSG_IMPORT_SUCCESS = _("%s quotes imported successfully");
|
|
var MSG_IMPORT_ERROR = _("%s quotes have not been imported. An error occurred");
|
|
function fnGetData(element) {
|
|
var lines = oTable.fnGetData();
|
|
$(lines).each(function(line){
|
|
var data = {source: this[1], text: this[2]};
|
|
var success = 0; var error = 0;
|
|
$.ajax({
|
|
url : "/api/v1/quotes",
|
|
method : "POST",
|
|
data : JSON.stringify(data),
|
|
dataType : "application/json",
|
|
success : function(data) {
|
|
$("#messages").show();
|
|
var import_success = $("#messages .import_success");
|
|
import_success.show();
|
|
import_success.data("nb")
|
|
nb_success = import_success.data("nb") || 0;
|
|
nb_success++;
|
|
$("#messages .import_success").text(MSG_IMPORT_SUCCESS.format(nb_success));
|
|
import_success.data("nb", nb_success);
|
|
},
|
|
error : function(xhr) {
|
|
if (xhr.status==201) { this.success(null, "Created", xhr); return; }
|
|
$("#messages").show();
|
|
var import_error = $("#messages .import_error");
|
|
import_error.show();
|
|
import_error.data("nb")
|
|
nb_error = import_error.data("nb") || 0;
|
|
nb_error++;
|
|
$("#messages .import_error").text(MSG_IMPORT_ERROR.format(nb_error));
|
|
import_error.data("nb", nb_error);
|
|
},
|
|
});
|
|
});
|
|
}
|
|
|
|
}); // $(document).ready
|
|
</script>
|
|
[% END %]
|
|
|
|
[% INCLUDE 'intranet-bottom.inc' %]
|