3 [% PROCESS 'i18n.inc' %]
5 [% INCLUDE 'doc-head-open.inc' %]
6 <title>[% FILTER collapse %]
7 [% t("Quote uploader") | html %] ›
8 [% t("Quote editor") | html %] ›
9 [% t("Tools") | html %] ›
10 [% t("Koha") | html %]
12 [% INCLUDE 'doc-head-close.inc' %]
13 [% Asset.css("css/uploader.css") | $raw %]
25 <body id="tools_quotes" class="tools">
26 [% WRAPPER 'header.inc' %]
27 [% INCLUDE 'cat-search.inc' %]
30 [% WRAPPER 'sub-header.inc' %]
31 [% WRAPPER breadcrumbs %]
32 [% WRAPPER breadcrumb_item %]
33 <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a>
35 [% WRAPPER breadcrumb_item %]
36 <a href="/cgi-bin/koha/tools/quotes.pl">Quote editor</a>
38 [% WRAPPER breadcrumb_item bc_active= 1 %]
39 <span>Quote uploader</span>
41 [% END #/ WRAPPER breadcrumbs %]
42 [% END #/ WRAPPER sub-header.inc %]
44 <div class="main container-fluid">
46 <div class="col-sm-10 col-sm-push-2">
48 <div id="toolbar" class="btn-toolbar" style="display:none">
49 <div class="btn-group"><a class="btn btn-primary" id="save_quotes" href="#"><i class="fa fa-save"></i> Save quotes</a></div>
50 <div class="btn-group"><a href="/cgi-bin/koha/tools/quotes-upload.pl" id="cancel_quotes" class="btn btn-default"><i class="fa fa-times"></i> Cancel import</a></div>
53 <h1>Quote uploader</h1>
55 <div id="messages" style="display: none;">
56 <div class="import_success dialog message" style="display: none;"></div>
57 <div class="import_error dialog alert" style="display: none;"></div>
60 <div id="instructions" class="page-section">
62 <div id="file_uploader_inst">
64 <li>The quote uploader accepts standard csv files with two columns: "source","text"</li>
65 <li>Click the "Choose file" button and select the csv file to be uploaded.</li>
66 <li>The file will be imported into an editable table for review prior to saving.</li>
69 <div id="file_editor_inst" style="display:none">
71 <li>Click on any field to edit the contents; Press the <Enter> key to save edit.</li>
72 <li>Click the 'Save quotes' button in the toolbar to save the entire batch of quotes.</li>
77 <fieldset id="file_uploader" class="rows">
78 <legend>Upload quotes</legend>
81 <label for="uploadfile">Select the file to upload: </label>
82 <div id="file_upload">
83 <input type="file" name="file" />
84 <button id="cancel_upload" style="display:none">Cancel upload</button>
85 <div id="progress_bar"><div class="percent">0%</div></div>
91 <div class="page-section">
92 <table id="quotes_editor" style="display:none">
96 <fieldset id="footer" class="action" style="display:none">
100 </div> <!-- /.col-sm-10.col-sm-push-2 -->
102 <div class="col-sm-2 col-sm-pull-10">
104 [% INCLUDE 'tools-menu.inc' %]
106 </div> <!-- /.col-sm-2.col-sm-pull-10 -->
107 </div> <!-- /.row -->
109 [% MACRO jsinclude BLOCK %]
110 [% Asset.js("js/tools-menu.js") | $raw %]
111 [% INCLUDE 'datatables.inc' %]
112 [% Asset.js("lib/jquery/plugins/jquery.jeditable.mini.js") | $raw %]
114 var oTable; //DataTable object
115 $(document).ready(function() {
117 $("#cancel_upload").on("click",function(e){
121 $("#cancel_quotes").on("click",function(){
122 return confirm( _("Are you sure you want to cancel this import?") );
126 // FileReader() code copied and hacked from:
127 // http://www.html5rocks.com/en/tutorials/file/dndfiles/
128 // fnCSVToArray() gratefully borrowed from:
129 // http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm
132 var progress = document.querySelector('.percent');
133 $("#server_response").hide();
135 function yuiGetData() {
136 fnGetData(document.getElementById('quotes_editor'));
139 function fnAbortRead() {
143 function fnErrorHandler(evt) {
144 switch(evt.target.error.code) {
145 case evt.target.error.NOT_FOUND_ERR:
146 alert(_("File not found!"));
148 case evt.target.error.NOT_READABLE_ERR:
149 alert(_("File is not readable"));
151 case evt.target.error.ABORT_ERR:
154 alert(_("An error occurred reading this file."));
158 function fnUpdateProgress(evt) {
159 // evt is an ProgressEvent.
160 if (evt.lengthComputable) {
161 var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
162 // Increase the progress bar length.
163 if (percentLoaded < 100) {
164 progress.style.width = percentLoaded + '%';
165 progress.textContent = percentLoaded + '%';
170 function fnCSVToArray( strData, strDelimiter ){
171 // This will parse a delimited string into an array of
172 // arrays. The default delimiter is the comma, but this
173 // can be overriden in the second argument.
175 // Check to see if the delimiter is defined. If not,
176 // then default to comma.
177 strDelimiter = (strDelimiter || ",");
179 strData = escape_str(strData);
181 // Create a regular expression to parse the CSV values.
182 var objPattern = new RegExp(
185 "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
187 "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
189 "([^\"\\" + strDelimiter + "\\r\\n]*))"
194 // Create an array to hold our data. Give the array
195 // a default empty first row.
198 // Create an array to hold our individual pattern
200 var arrMatches = null;
202 // Keep looping over the regular expression matches
203 // until we can no longer find a match.
204 while (arrMatches = objPattern.exec( strData )){
206 // Get the delimiter that was found.
207 var strMatchedDelimiter = arrMatches[ 1 ];
209 // Check to see if the given delimiter has a length
210 // (is not the start of string) and if it matches
211 // field delimiter. If it does not, then we know
212 // that this delimiter is a row delimiter.
213 if ( strMatchedDelimiter.length && (strMatchedDelimiter != strDelimiter) ){
214 // Since we have reached a new row of data,
215 // add an empty row to our data array.
216 // Note: if there is not more data, we will have to remove this row later
220 // Now that we have our delimiter out of the way,
221 // let's check to see which kind of value we
222 // captured (quoted or unquoted).
223 if (arrMatches[ 2 ]){
224 // We found a quoted value. When we capture
225 // this value, unescape any double quotes.
226 var strMatchedValue = arrMatches[ 2 ].replace(
227 new RegExp( "\"\"", "g" ),
230 } else if (arrMatches[3]){
231 // We found a non-quoted value.
232 var strMatchedValue = arrMatches[ 3 ];
234 // There is no more valid data so remove the row we added earlier
235 // Is there a better way? Perhaps a look-ahead regexp?
236 arrData.splice(arrData.length-1, 1);
239 // Now that we have our value string, let's add
240 // it to the data array.
241 if ( arrData[ arrData.length - 1 ] ) {
242 arrData[ arrData.length - 1 ].push( strMatchedValue );
244 $("#messages .import_error").text(_("Something went wrong, check your CSV file."));
248 // Return the parsed data.
252 function fnDataTable(aaData) {
253 for(var i=0; i<aaData.length; i++) {
254 aaData[i].unshift(i+1); // Add a column w/quote number
257 /* Transition from the quote file uploader to the quote file editor interface */
258 $('#toolbar').show();
259 $('#file_editor_inst').show();
260 $('#file_uploader_inst').hide();
261 $('#save_quotes').show();
262 $('#file_uploader').hide();
263 $('#quotes_editor').show();
264 $("#save_quotes").on("click", yuiGetData);
266 oTable = $('#quotes_editor').dataTable( {
267 "bAutoWidth" : false,
270 "sPaginationType" : "full_numbers",
271 "sDom": '<"top pager"iflp>rt<"bottom pager"flp><"clear">',
277 "sTitle" : _("Number"),
281 "sTitle" : _("Source"),
285 "sTitle" : _("Quote"),
289 "fnPreDrawCallback": function(oSettings) {
292 "fnRowCallback": function( nRow, aData, iDisplayIndex ) {
293 /* do foo on various cells in the current row */
294 var quoteNum = $('td', nRow)[0].innerHTML;
295 $(nRow).attr("id", quoteNum); /* set row ids to quote number */
296 /* apply no_edit id to noEditFields */
297 noEditFields = [0]; /* number */
298 for (i=0; i<noEditFields.length; i++) {
299 $('td', nRow)[noEditFields[i]].setAttribute("id","no_edit");
303 "fnDrawCallback": function(oSettings) {
304 /* Apply the jEditable handlers to the table on all fields w/o the no_edit id */
305 $('#quotes_editor tbody td[id!="no_edit"]').editable( function(value, settings) {
306 value = escape_str(value);
307 var cellPosition = oTable.fnGetPosition( this );
308 oTable.fnUpdate(value, cellPosition[0], cellPosition[1], false, false);
312 "callback" : function( sValue, y ) {
313 oTable.fnDraw(false); /* no filter/sort or we lose our pagination */
315 "cancel" : _("Cancel"),
317 "inputcssclass": "jeditable-text",
320 "submit" : _("Save"),
330 function fnHandleFileSelect(evt) {
331 // Reset progress indicator on new file selection.
332 progress.style.width = '0%';
333 progress.textContent = '0%';
335 reader = new FileReader();
336 reader.onerror = fnErrorHandler;
337 reader.onprogress = fnUpdateProgress;
338 reader.onabort = function(e) {
339 alert(_("File read cancelled"));
340 parent.location='quotes-upload.pl';
342 reader.onloadstart = function(e) {
343 $('#cancel_upload').show();
344 $('#progress_bar').addClass("loading");
346 reader.onload = function(e) {
347 // Ensure that the progress bar displays 100% at the end.
348 progress.style.width = '100%';
349 progress.textContent = '100%';
350 $('#cancel_upload').hide();
351 quotes = fnCSVToArray(e.target.result, ',');
355 // perform various sanity checks on the target file prior to uploading...
356 var fileType = evt.target.files[0].type || 'unknown';
357 var fileSizeInK = Math.round(evt.target.files[0].size/1024);
359 if (!fileType.match(/comma-separated-values|csv|excel|calc/i)) {
360 alert(_("Uploads limited to csv. Incorrect filetype: %s").format(fileType));
361 parent.location='quotes-upload.pl';
364 if (fileSizeInK > 512) {
365 if (!confirm(_("%s %s KB Do you really want to upload this file?").format(evt.target.files[0].name, fileSizeInK))) {
366 parent.location='quotes-upload.pl';
370 // Read in the image file as a text string.
371 reader.readAsText(evt.target.files[0]);
374 $('#file_upload').on('change', fnHandleFileSelect);
376 var MSG_IMPORT_SUCCESS = _("%s quotes imported successfully");
377 var MSG_IMPORT_ERROR = _("%s quotes have not been imported. An error occurred");
378 function fnGetData(element) {
379 var lines = oTable.fnGetData();
380 $(lines).each(function(line){
381 var s = this[1].replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
382 var t = this[2].replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
383 var data = {source: s, text: t};
384 var success = 0; var error = 0;
386 url : "/api/v1/quotes",
388 data : JSON.stringify(data),
389 dataType : "application/json",
390 success : function(data) {
391 $("#messages").addClass("message").show();
392 var import_success = $("#messages .import_success");
393 import_success.show();
394 import_success.data("nb")
395 nb_success = import_success.data("nb") || 0;
397 $("#messages .import_success").text(MSG_IMPORT_SUCCESS.format(nb_success));
398 import_success.data("nb", nb_success);
400 error : function(xhr) {
401 if (xhr.status==201) { this.success(null, "Created", xhr); return; }
402 $("#messages").addClass("alert").show();
403 var import_error = $("#messages .import_error");
405 import_error.data("nb")
406 nb_error = import_error.data("nb") || 0;
408 $("#messages .import_error").text(MSG_IMPORT_ERROR.format(nb_error));
409 import_error.data("nb", nb_error);
415 }); // $(document).ready
419 [% INCLUDE 'intranet-bottom.inc' %]