--- /dev/null
+ [% INCLUDE 'doc-head-open.inc' %]
+ <title>Koha › Tools › Quote uploader</title>
+ [% INCLUDE 'doc-head-close.inc' %]
+ <link rel="stylesheet" type="text/css" href="/intranet-tmpl/prog/en/css/uploader.css" />
+ <link rel="stylesheet" type="text/css" href="/intranet-tmpl/prog/en/css/datatables.css" />
+ <script type="text/javascript" src="/intranet-tmpl/prog/en/lib/jquery/plugins/jquery.dataTables.min.js"></script>
+ [% INCLUDE 'datatables-strings.inc' %]
+ </script>
+ <script type="text/javascript" src="/intranet-tmpl/prog/en/js/datatables.js"></script>
+ <script type="text/javascript" src="/intranet-tmpl/prog/en/js/jquery.jeditable.mini.js"></script>
+ <script type="text/javascript">
+ //<![CDATA[
+ var oTable; //DataTable object
+ $(document).ready(function() {
+
+ // 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 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.
+ arrData[ arrData.length - 1 ].push( strMatchedValue );
+ }
+
+ // 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
+ }
+ $('#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");
+ oSaveButton.on("click", yuiGetData);
+ oDeleteButton.on("click", fnClickDeleteRow);
+ oTable = $('#quotes_editor').dataTable( {
+ "bAutoWidth" : false,
+ "bPaginate" : true,
+ "bSort" : false,
+ "sPaginationType" : "full_numbers",
+ "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 */
+ $('td:eq(0)', nRow).click(function() {$(this.parentNode).toggleClass('selected',this.clicked);}); /* add row selectors */
+ $('td:eq(0)', nRow).attr("title", "Click ID to select/deselect quote");
+ /* 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').css("visibility","visible");
+ $('#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').css("visibility","hidden");
+ 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/i)) {
+ alert('Incorrect filetype: '+fileType+'. Uploads limited to csv.');
+ parent.location='quotes-upload.pl';
+ return;
+ }
+ if (fileSizeInK > 512) {
+ if (!confirm(evt.target.files[0].name+' is '+fileSizeInK+' K in size. Do you really want to upload this file?')) {
+ 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);
+
+ });
+
+ function fnGetData(element) {
+ var jqXHR = $.ajax({
+ url : "/cgi-bin/koha/tools/quotes/quotes-upload_ajax.pl",
+ type : "POST",
+ contentType : "application/x-www-form-urlencoded", // we must claim this mimetype or CGI will not decode the URL encoding
+ dataType : "json",
+ data : {
+ "quote" : JSON.stringify(oTable.fnGetData()),
+ "action" : "add",
+ },
+ success : function(){
+ var response = JSON.parse(jqXHR.responseText);
+ if (response.success) {
+ $("#server_response").text(response.records+' quotes saved.');
+ }
+ else {
+ $("#server_response").text('An error has occurred. '+response.records+' quotes saved. Please ask your administrator to check the server log for more details.');
+ }
+ $("#server_response").fadeIn(200);
+ },
+ });
+ }
+
+ function fnClickDeleteRow() {
+ var idsToDelete = oTable.$('.selected').map(function() {
+ return this.id;
+ }).get().join(', ');
+ if (!idsToDelete) {
+ alert('Please select a quote(s) by clicking the quote id(s) you desire to delete.');
+ }
+ else if (confirm('Are you sure you wish to delete quote(s) '+idsToDelete+'?')) {
+ oTable.$('.selected').each(function(){
+ oTable.fnDeleteRow(this);
+ });
+ }
+ }
+
+ function fnResetUpload() {
+ $('#server_response').fadeOut(200);
+ window.location.reload(true); // is this the best route?
+ }
+
+ //]]>
+ </script>
+</head>
+<body id="tools_quotes" class="tools">
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'cat-search.inc' %]
+
+<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>
+
+<div id="doc3" class="yui-t2">
+ <div id="bd">
+ <div id="yui-main">
+ <div class="yui-b">
+ [% INCLUDE 'quotes-upload-toolbar.inc' %]
+ <h2>Quote uploader</h2>
+ <fieldset id="file_uploader" class="rows" style="visibility:visible;">
+ <legend>Upload quotes</legend>
+ <div id="file_upload" style="margin-left: 10px;">
+ <input type="file" name="file" />
+ <button id="cancel_upload" style="visibility:hidden;" onclick="fnAbortRead();">Cancel Upload</button>
+ <div id="progress_bar"><div class="percent">0%</div></div>
+ </div>
+ </fieldset>
+ <div id="server_response" onclick='fnResetUpload()'>Server Response</div>
+ <table id="quotes_editor" style="float: left; width: 100%; visibility:hidden;">
+ <thead>
+ <tr>
+ <th>Source</th>
+ <th>Text</th>
+ <th>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; height:25px">
+ </fieldset>
+ </div>
+ </div>
+ <div class="yui-b noprint">
+ [% INCLUDE 'tools-menu.inc' %]
+ </div>
+</div>
+[% INCLUDE 'intranet-bottom.inc' %]