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