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