1 [% INCLUDE 'doc-head-open.inc' %]
2 <title>Koha › Tools › Quote uploader</title>
3 [% INCLUDE 'doc-head-close.inc' %]
4 <link rel="stylesheet" type="text/css" href="[% themelang %]/css/uploader.css" />
5 <link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
6 <link rel="stylesheet" type="text/css" href="[% themelang %]/css/quotes.css" />
7 <script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.dataTables.min.js"></script>
8 [% INCLUDE 'datatables-strings.inc' %]
10 <script type="text/javascript" src="[% themelang %]/js/datatables.js"></script>
11 <script type="text/javascript" src="[% themelang %]/js/jquery.jeditable.mini.js"></script>
12 <script type="text/javascript">
14 var oTable; //DataTable object
15 $(document).ready(function() {
18 // FileReader() code copied and hacked from:
19 // http://www.html5rocks.com/en/tutorials/file/dndfiles/
20 // fnCSVToArray() gratefully borrowed from:
21 // http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm
24 var progress = document.querySelector('.percent');
25 $("#server_response").hide();
27 function yuiGetData() {
28 fnGetData(document.getElementById('quotes_editor'));
31 function fnAbortRead() {
35 function fnErrorHandler(evt) {
36 switch(evt.target.error.code) {
37 case evt.target.error.NOT_FOUND_ERR:
38 alert('File Not Found!');
40 case evt.target.error.NOT_READABLE_ERR:
41 alert('File is not readable');
43 case evt.target.error.ABORT_ERR:
46 alert('An error occurred reading this file.');
50 function fnUpdateProgress(evt) {
51 // evt is an ProgressEvent.
52 if (evt.lengthComputable) {
53 var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
54 // Increase the progress bar length.
55 if (percentLoaded < 100) {
56 progress.style.width = percentLoaded + '%';
57 progress.textContent = percentLoaded + '%';
62 function fnCSVToArray( strData, strDelimiter ){
63 // This will parse a delimited string into an array of
64 // arrays. The default delimiter is the comma, but this
65 // can be overriden in the second argument.
67 // Check to see if the delimiter is defined. If not,
68 // then default to comma.
69 strDelimiter = (strDelimiter || ",");
71 // Create a regular expression to parse the CSV values.
72 var objPattern = new RegExp(
75 "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
77 "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
79 "([^\"\\" + strDelimiter + "\\r\\n]*))"
84 // Create an array to hold our data. Give the array
85 // a default empty first row.
88 // Create an array to hold our individual pattern
90 var arrMatches = null;
92 // Keep looping over the regular expression matches
93 // until we can no longer find a match.
94 while (arrMatches = objPattern.exec( strData )){
96 // Get the delimiter that was found.
97 var strMatchedDelimiter = arrMatches[ 1 ];
99 // Check to see if the given delimiter has a length
100 // (is not the start of string) and if it matches
101 // field delimiter. If it does not, then we know
102 // that this delimiter is a row delimiter.
103 if ( strMatchedDelimiter.length && (strMatchedDelimiter != strDelimiter) ){
104 // Since we have reached a new row of data,
105 // add an empty row to our data array.
106 // Note: if there is not more data, we will have to remove this row later
110 // Now that we have our delimiter out of the way,
111 // let's check to see which kind of value we
112 // captured (quoted or unquoted).
113 if (arrMatches[ 2 ]){
114 // We found a quoted value. When we capture
115 // this value, unescape any double quotes.
116 var strMatchedValue = arrMatches[ 2 ].replace(
117 new RegExp( "\"\"", "g" ),
120 } else if (arrMatches[3]){
121 // We found a non-quoted value.
122 var strMatchedValue = arrMatches[ 3 ];
124 // There is no more valid data so remove the row we added earlier
125 // Is there a better way? Perhaps a look-ahead regexp?
126 arrData.splice(arrData.length-1, 1);
129 // Now that we have our value string, let's add
130 // it to the data array.
131 arrData[ arrData.length - 1 ].push( strMatchedValue );
134 // Return the parsed data.
138 function fnDataTable(aaData) {
139 for(var i=0; i<aaData.length; i++) {
140 aaData[i].unshift(i+1); // Add a column w/quote number
144 /* Transition from the quote file uploader to the quote file editor interface */
145 $('#toolbar').css("visibility","visible");
146 $('#toolbar').css("position","");
147 $('#file_editor_inst').css("visibility", "visible");
148 $('#file_editor_inst').css("position", "");
149 $('#file_uploader_inst').css("visibility", "hidden");
150 $('#save_quotes').css("visibility","visible");
151 $('#file_uploader').css("visibility","hidden");
152 $('#file_uploader').css("position","absolute");
153 $('#file_uploader').css("top","-150px");
154 $('#quotes_editor').css("visibility","visible");
155 $("#save_quotes").on("click", yuiGetData);
156 $("#delete_quote").on("click", fnClickDeleteRow);
160 oTable = $('#quotes_editor').dataTable( {
161 "bAutoWidth" : false,
164 "sPaginationType" : "full_numbers",
180 "fnPreDrawCallback": function(oSettings) {
183 "fnRowCallback": function( nRow, aData, iDisplayIndex ) {
184 /* do foo on various cells in the current row */
185 var quoteNum = $('td', nRow)[0].innerHTML;
186 $(nRow).attr("id", quoteNum); /* set row ids to quote number */
187 $('td:eq(0)', nRow).click(function() {$(this.parentNode).toggleClass('selected',this.clicked);}); /* add row selectors */
188 $('td:eq(0)', nRow).attr("title", _("Click ID to select/deselect quote"));
189 /* apply no_edit id to noEditFields */
190 noEditFields = [0]; /* number */
191 for (i=0; i<noEditFields.length; i++) {
192 $('td', nRow)[noEditFields[i]].setAttribute("id","no_edit");
196 "fnDrawCallback": function(oSettings) {
197 /* Apply the jEditable handlers to the table on all fields w/o the no_edit id */
198 $('#quotes_editor tbody td[id!="no_edit"]').editable( function(value, settings) {
199 var cellPosition = oTable.fnGetPosition( this );
200 oTable.fnUpdate(value, cellPosition[0], cellPosition[1], false, false);
204 "callback" : function( sValue, y ) {
205 oTable.fnDraw(false); /* no filter/sort or we lose our pagination */
211 $('#footer').css("visibility","visible");
214 function fnHandleFileSelect(evt) {
215 // Reset progress indicator on new file selection.
216 progress.style.width = '0%';
217 progress.textContent = '0%';
219 reader = new FileReader();
220 reader.onerror = fnErrorHandler;
221 reader.onprogress = fnUpdateProgress;
222 reader.onabort = function(e) {
223 alert('File read cancelled');
224 parent.location='quotes-upload.pl';
226 reader.onloadstart = function(e) {
227 $('#cancel_upload').css("visibility","visible");
228 $('#progress_bar').addClass("loading");
230 reader.onload = function(e) {
231 // Ensure that the progress bar displays 100% at the end.
232 progress.style.width = '100%';
233 progress.textContent = '100%';
234 $('#cancel_upload').css("visibility","hidden");
235 quotes = fnCSVToArray(e.target.result, ',');
239 // perform various sanity checks on the target file prior to uploading...
240 var fileType = evt.target.files[0].type || 'unknown';
241 var fileSizeInK = Math.round(evt.target.files[0].size/1024);
243 if (!fileType.match(/comma-separated-values|csv|excel/i)) {
244 alert(_("Uploads limited to csv. Incorrect filetype: ")+fileType);
245 parent.location='quotes-upload.pl';
248 if (fileSizeInK > 512) {
249 if (!confirm(evt.target.files[0].name+' '+fileSizeInK+_(" KB Do you really want to upload this file?"))) {
250 parent.location='quotes-upload.pl';
254 // Read in the image file as a text string.
255 reader.readAsText(evt.target.files[0]);
258 $('#file_upload').one('change', fnHandleFileSelect);
262 function fnGetData(element) {
264 url : "/cgi-bin/koha/tools/quotes/quotes-upload_ajax.pl",
266 contentType : "application/x-www-form-urlencoded", // we must claim this mimetype or CGI will not decode the URL encoding
269 "quote" : JSON.stringify(oTable.fnGetData()),
272 success : function(){
273 var response = JSON.parse(jqXHR.responseText);
274 if (response.success) {
275 alert(response.records+_(" quotes saved."));
276 window.location.reload(true); // is this the best route?
279 alert(response.records+_(" quotes saved, but an error has occurred. Please ask your administrator to check the server log for more details."));
280 window.location.reload(true); // is this the best route?
286 function fnClickDeleteRow() {
287 var idsToDelete = oTable.$('.selected').map(function() {
291 alert(_("Please select a quote(s) by clicking the quote id(s) you desire to delete."));
293 else if (confirm(_("Are you sure you wish to delete quote(s) ")+idsToDelete+"?")) {
294 oTable.$('.selected').each(function(){
295 oTable.fnDeleteRow(this);
303 <body id="tools_quotes" class="tools">
304 [% INCLUDE 'header.inc' %]
305 [% INCLUDE 'cat-search.inc' %]
307 <div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> › <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> › <a href="/cgi-bin/koha/tools/quotes.pl">Quote editor</a> › Quote uploader</div>
309 <div id="doc3" class="yui-t2">
313 [% INCLUDE 'quotes-upload-toolbar.inc' %]
314 <h2>Quote uploader</h2>
315 <div id="instructions">
316 <fieldset id="file_uploader_help" class="rows">
317 <legend>Instructions</legend>
318 <div id="file_uploader_inst">
320 <li>The quote uploader accepts standard csv files with two columns: "source","text"</li>
321 <li>Click the "Choose File" button and select the csv file to be uploaded.</li>
322 <li>The file will be imported into an editable table for review prior to saving.</li>
325 <div id="file_editor_inst">
327 <li>Click on any field to edit the contents; Press the <Enter> key to save edit.</li>
328 <li>Click on one or more quote numbers to select entire quotes for deletion; Click the 'Delete Quote(s)' button to delete selected quotes.</li>
329 <li>Click the 'Save Quotes' button in the toolbar to save the entire batch of quotes.</li>
334 <fieldset id="file_uploader" class="rows">
335 <legend>Upload quotes</legend>
336 <div id="file_upload">
337 <input type="file" name="file" />
338 <button id="cancel_upload" style="visibility:hidden;" onclick="fnAbortRead();">Cancel Upload</button>
339 <div id="progress_bar"><div class="percent">0%</div></div>
342 <table id="quotes_editor" style="visibility: hidden;">
351 <!-- tbody content is generated by DataTables -->
354 <td>Loading data...</td>
359 <fieldset id="footer" class="action" style="visibility: hidden;">
363 <div class="yui-b noprint">
364 [% INCLUDE 'tools-menu.inc' %]
367 [% INCLUDE 'intranet-bottom.inc' %]