Bug 16033 - Quotes upload preview broken for 973 days
[koha.git] / koha-tmpl / intranet-tmpl / prog / en / modules / tools / quotes-upload.tt
1     [% INCLUDE 'doc-head-open.inc' %]
2     <title>Koha &rsaquo; Tools &rsaquo; 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/quotes.css" />
6     <link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" />
7     [% INCLUDE 'datatables.inc' %]
8     <script type="text/javascript" src="[% interface %]/lib/jquery/plugins/jquery.jeditable.mini.js"></script>
9     <script type="text/javascript">
10     //<![CDATA[
11     var oTable; //DataTable object
12     $(document).ready(function() {
13
14     // Credits:
15     // FileReader() code copied and hacked from:
16     // http://www.html5rocks.com/en/tutorials/file/dndfiles/
17     // fnCSVToArray() gratefully borrowed from:
18     // http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm
19
20     var reader;
21     var progress = document.querySelector('.percent');
22     $("#server_response").hide();
23
24     function yuiGetData() {
25         fnGetData(document.getElementById('quotes_editor'));
26     }
27
28     function fnAbortRead() {
29         reader.abort();
30     }
31
32     function fnErrorHandler(evt) {
33         switch(evt.target.error.code) {
34             case evt.target.error.NOT_FOUND_ERR:
35                 alert('File Not Found!');
36                 break;
37             case evt.target.error.NOT_READABLE_ERR:
38                 alert('File is not readable');
39                 break;
40             case evt.target.error.ABORT_ERR:
41                 break; // noop
42             default:
43                 alert('An error occurred reading this file.');
44         };
45     }
46
47     function fnUpdateProgress(evt) {
48         // evt is an ProgressEvent.
49         if (evt.lengthComputable) {
50             var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
51             // Increase the progress bar length.
52             if (percentLoaded < 100) {
53                 progress.style.width = percentLoaded + '%';
54                 progress.textContent = percentLoaded + '%';
55             }
56         }
57     }
58
59     function fnCSVToArray( strData, strDelimiter ){
60         // This will parse a delimited string into an array of
61         // arrays. The default delimiter is the comma, but this
62         // can be overriden in the second argument.
63
64         // Check to see if the delimiter is defined. If not,
65         // then default to comma.
66         strDelimiter = (strDelimiter || ",");
67
68         // Create a regular expression to parse the CSV values.
69         var objPattern = new RegExp(
70         (
71             // Delimiters.
72             "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
73             // Quoted fields.
74             "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
75             // Standard fields.
76             "([^\"\\" + strDelimiter + "\\r\\n]*))"
77         ),
78             "gi"
79         );
80
81         // Create an array to hold our data. Give the array
82         // a default empty first row.
83         var arrData = [[]];
84
85         // Create an array to hold our individual pattern
86         // matching groups.
87         var arrMatches = null;
88
89         // Keep looping over the regular expression matches
90         // until we can no longer find a match.
91         while (arrMatches = objPattern.exec( strData )){
92
93             // Get the delimiter that was found.
94             var strMatchedDelimiter = arrMatches[ 1 ];
95
96             // Check to see if the given delimiter has a length
97             // (is not the start of string) and if it matches
98             // field delimiter. If it does not, then we know
99             // that this delimiter is a row delimiter.
100             if ( strMatchedDelimiter.length && (strMatchedDelimiter != strDelimiter) ){
101                 // Since we have reached a new row of data,
102                 // add an empty row to our data array.
103                 // Note: if there is not more data, we will have to remove this row later
104                 arrData.push( [] );
105             }
106
107             // Now that we have our delimiter out of the way,
108             // let's check to see which kind of value we
109             // captured (quoted or unquoted).
110             if (arrMatches[ 2 ]){
111                 // We found a quoted value. When we capture
112                 // this value, unescape any double quotes.
113                 var strMatchedValue = arrMatches[ 2 ].replace(
114                 new RegExp( "\"\"", "g" ),
115                     "\""
116                 );
117             } else if (arrMatches[3]){
118                 // We found a non-quoted value.
119                 var strMatchedValue = arrMatches[ 3 ];
120             } else {
121                 // There is no more valid data so remove the row we added earlier
122                 // Is there a better way? Perhaps a look-ahead regexp?
123                 arrData.splice(arrData.length-1, 1);
124             }
125
126             // Now that we have our value string, let's add
127             // it to the data array.
128             arrData[ arrData.length - 1 ].push( strMatchedValue );
129         }
130
131         // Return the parsed data.
132         return( arrData );
133     }
134
135     function fnDataTable(aaData) {
136         for(var i=0; i<aaData.length; i++) {
137             aaData[i].unshift(i+1); // Add a column w/quote number
138         }
139
140
141         /* Transition from the quote file uploader to the quote file editor interface */
142         $('#toolbar').css("visibility","visible");
143         $('#toolbar').css("position","");
144         $('#file_editor_inst').css("visibility", "visible");
145         $('#file_editor_inst').css("position", "");
146         $('#file_uploader_inst').css("visibility", "hidden");
147         $('#save_quotes').css("visibility","visible");
148         $('#file_uploader').css("visibility","hidden");
149         $('#file_uploader').css("position","absolute");
150         $('#file_uploader').css("top","-150px");
151         $('#quotes_editor').css("visibility","visible");
152         $("#save_quotes").on("click", yuiGetData);
153         $("#delete_quote").on("click", fnClickDeleteRow);
154
155
156
157         oTable = $('#quotes_editor').dataTable( {
158             "bAutoWidth"        : false,
159             "bPaginate"         : true,
160             "bSort"             : false,
161             "sPaginationType"   : "full_numbers",
162             "sDom": '<"top pager"iflp>rt<"bottom pager"flp><"clear">',
163             "aaData"            : aaData,
164             "aoColumns"         : [
165                 {
166                     "sTitle"  : "Number",
167                     "sWidth"  : "2%",
168                 },
169                 {
170                     "sTitle"  : "Source",
171                     "sWidth"  : "15%",
172                 },
173                 {
174                     "sTitle"  : "Quote",
175                     "sWidth"  : "83%",
176                 },
177             ],
178            "fnPreDrawCallback": function(oSettings) {
179                 return true;
180             },
181             "fnRowCallback": function( nRow, aData, iDisplayIndex ) {
182                 /* do foo on various cells in the current row */
183                 var quoteNum = $('td', nRow)[0].innerHTML;
184                 $(nRow).attr("id", quoteNum); /* set row ids to quote number */
185                 $('td:eq(0)', nRow).click(function() {$(this.parentNode).toggleClass('selected',this.clicked);}); /* add row selectors */
186                 $('td:eq(0)', nRow).attr("title", _("Click ID to select/deselect quote"));
187                 /* apply no_edit id to noEditFields */
188                 noEditFields = [0]; /* number */
189                 for (i=0; i<noEditFields.length; i++) {
190                     $('td', nRow)[noEditFields[i]].setAttribute("id","no_edit");
191                 }
192                 return nRow;
193             },
194            "fnDrawCallback": function(oSettings) {
195                 /* Apply the jEditable handlers to the table on all fields w/o the no_edit id */
196                 $('#quotes_editor tbody td[id!="no_edit"]').editable( function(value, settings) {
197                         var cellPosition = oTable.fnGetPosition( this );
198                         oTable.fnUpdate(value, cellPosition[0], cellPosition[1], false, false);
199                         return(value);
200                     },
201                     {
202                     "callback"      : function( sValue, y ) {
203                                           oTable.fnDraw(false); /* no filter/sort or we lose our pagination */
204                                       },
205                     "height"        : "14px",
206                 });
207            },
208         });
209         $('#footer').css("visibility","visible");
210     }
211
212     function fnHandleFileSelect(evt) {
213         // Reset progress indicator on new file selection.
214         progress.style.width = '0%';
215         progress.textContent = '0%';
216
217         reader = new FileReader();
218         reader.onerror = fnErrorHandler;
219         reader.onprogress = fnUpdateProgress;
220         reader.onabort = function(e) {
221             alert('File read cancelled');
222             parent.location='quotes-upload.pl';
223         };
224         reader.onloadstart = function(e) {
225             $('#cancel_upload').css("visibility","visible");
226             $('#progress_bar').addClass("loading");
227         };
228         reader.onload = function(e) {
229             // Ensure that the progress bar displays 100% at the end.
230             progress.style.width = '100%';
231             progress.textContent = '100%';
232             $('#cancel_upload').css("visibility","hidden");
233             quotes = fnCSVToArray(e.target.result, ',');
234             fnDataTable(quotes);
235         }
236
237         // perform various sanity checks on the target file prior to uploading...
238         var fileType = evt.target.files[0].type || 'unknown';
239         var fileSizeInK = Math.round(evt.target.files[0].size/1024);
240
241         if (!fileType.match(/comma-separated-values|csv|excel/i)) {
242             alert(_("Uploads limited to csv. Incorrect filetype: %s").format(fileType));
243             parent.location='quotes-upload.pl';
244             return;
245         }
246         if (fileSizeInK > 512) {
247             if (!confirm(_("%s %s KB Do you really want to upload this file?").format(evt.target.files[0].name, fileSizeInK))) {
248                 parent.location='quotes-upload.pl';
249                 return;
250             }
251         }
252         // Read in the image file as a text string.
253         reader.readAsText(evt.target.files[0]);
254     }
255
256     $('#file_upload').one('change', fnHandleFileSelect);
257
258     });
259
260     function fnGetData(element) {
261         var jqXHR = $.ajax({
262             url         : "/cgi-bin/koha/tools/quotes/quotes-upload_ajax.pl",
263             type        : "POST",
264             contentType : "application/x-www-form-urlencoded", // we must claim this mimetype or CGI will not decode the URL encoding
265             dataType    : "json",
266             data        : {
267                             "quote"     : encodeURI ( JSON.stringify(oTable.fnGetData()) ),
268                             "action"    : "add",
269                           },
270             success     : function(){
271                             var response = JSON.parse(jqXHR.responseText);
272                             if (response.success) {
273                                 alert(_("%s quotes saved.").format(response.records));
274                                 window.location.reload(true);   // is this the best route?
275                             }
276                             else {
277                                 alert(_("%s quotes saved, but an error has occurred. Please ask your administrator to check the server log for more details.").format(response.records));
278                                 window.location.reload(true);   // is this the best route?
279                             }
280                           },
281         });
282     }
283
284     function fnClickDeleteRow() {
285         var idsToDelete = oTable.$('.selected').map(function() {
286               return this.id;
287         }).get().join(', ');
288         if (!idsToDelete) {
289             alert(_("Please select a quote(s) by clicking the quote id(s) you desire to delete."));
290         }
291         else if (confirm(_("Are you sure you wish to delete quote(s) %s?").format(idsToDelete))) {
292             oTable.$('.selected').each(function(){
293                 oTable.fnDeleteRow(this);
294             });
295         }
296     }
297
298     //]]>
299     </script>
300 </head>
301 <body id="tools_quotes" class="tools">
302 [% INCLUDE 'header.inc' %]
303 [% INCLUDE 'cat-search.inc' %]
304
305 <div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> &rsaquo; <a href="/cgi-bin/koha/tools/quotes.pl">Quote editor</a> &rsaquo; Quote uploader</div>
306
307 <div id="doc3" class="yui-t2">
308     <div id="bd">
309         <div id="yui-main">
310             <div class="yui-b">
311                 [% INCLUDE 'quotes-upload-toolbar.inc' %]
312                 <h2>Quote uploader</h2>
313                 <div id="instructions">
314                 <fieldset id="file_uploader_help" class="rows">
315                     <legend>Instructions</legend>
316                     <div id="file_uploader_inst">
317                         <ul>
318                         <li>The quote uploader accepts standard csv files with two columns: "source","text"</li>
319                         <li>Click the "Choose File" button and select the csv file to be uploaded.</li>
320                         <li>The file will be imported into an editable table for review prior to saving.</li>
321                         </ul>
322                     </div>
323                     <div id="file_editor_inst">
324                         <ul>
325                         <li>Click on any field to edit the contents; Press the &lt;Enter&gt; key to save edit.</li>
326                         <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>
327                         <li>Click the 'Save Quotes' button in the toolbar to save the entire batch of quotes.</li>
328                         </ul>
329                     </div>
330                 </fieldset>
331                 </div>
332                 <fieldset id="file_uploader" class="rows">
333                     <legend>Upload quotes</legend>
334                     <div id="file_upload">
335                         <input type="file" name="file" />
336                         <button id="cancel_upload" style="visibility:hidden;" onclick="fnAbortRead();">Cancel Upload</button>
337                         <div id="progress_bar"><div class="percent">0%</div></div>
338                     </div>
339                 </fieldset>
340                 <table id="quotes_editor" style="visibility: hidden;">
341                 <thead>
342                     <tr>
343                         <th>Source</th>
344                         <th>Text</th>
345                         <th>Actions</th>
346                     </tr>
347                 </thead>
348                 <tbody>
349                     <!-- tbody content is generated by DataTables -->
350                     <tr>
351                         <td></td>
352                         <td>Loading data...</td>
353                         <td></td>
354                     </tr>
355                 </tbody>
356                 </table>
357                 <fieldset id="footer" class="action" style="visibility: hidden;">
358                 </fieldset>
359             </div>
360         </div>
361     <div class="yui-b noprint">
362         [% INCLUDE 'tools-menu.inc' %]
363     </div>
364 </div>
365 [% INCLUDE 'intranet-bottom.inc' %]