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