View | Details | Raw Unified | Return to bug 7977
Collapse All | Expand All

(-)a/koha-tmpl/intranet-tmpl/prog/en/css/datatables.css (+12 lines)
Lines 191-196 div.dataTables_paginate.paging_four_button { Link Here
191
    width: 250px;
191
    width: 250px;
192
}
192
}
193
193
194
input {
195
    border-radius: 5px;
196
}
197
198
tr.odd.selected td {
199
    background-color: #D3D3D3;
200
}
201
202
tr.even.selected td {
203
    background-color: #D3D3D3;
204
}
205
194
/*
206
/*
195
table.display {
207
table.display {
196
    width: 100%;
208
    width: 100%;
(-)a/koha-tmpl/intranet-tmpl/prog/en/css/uploader.css (+49 lines)
Line 0 Link Here
1
#progress_bar {
2
  margin: 10px 0;
3
  padding: 3px;
4
  border: 1px solid #000;
5
  font-size: 14px;
6
  clear: both;
7
  opacity: 0;
8
  -moz-transition: opacity 1s linear;
9
  -o-transition: opacity 1s linear;
10
  -webkit-transition: opacity 1s linear;
11
}
12
#progress_bar.loading {
13
    opacity: 1.0;
14
}
15
#progress_bar .percent {
16
    color: #E6F0F2;
17
    background-color: #004D99;
18
    height: auto;
19
    width: 0;
20
}
21
#server_response {
22
    background-color: white;
23
    background-image: url("../../img/x_alt_16x16.png");
24
    background-repeat: no-repeat;
25
    background-origin: padding-box;
26
    background-position: right top;
27
    border: 1px solid #DDDDDD;
28
    color: #999999;
29
    font-size: 14px;
30
    height: 30px;
31
    left: 50%;
32
    margin-left: -125px;
33
    margin-top: -15px;
34
    padding: 14px 0 2px;
35
    position: fixed;
36
    text-align: center;
37
    top: 50%;
38
    width: 250px;
39
}
40
41
#file_uploader {
42
    position: relative;
43
    top: -24px;
44
}
45
46
#file_upload {
47
    position: relative;
48
    top: -1px;
49
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/quotes-upload-toolbar.inc (+42 lines)
Line 0 Link Here
1
<script type="text/javascript">
2
    var oSaveButton = '';
3
    var oDeleteButton = '';
4
    //<![CDATA[
5
    // prepare DOM for YUI Toolbar
6
    $(document).ready(function() {
7
        $("#save_quotes").empty();
8
        $("#delete_quote").empty();
9
        yuiToolbar();
10
     });
11
12
    // YUI Toolbar Functions
13
    function yuiGetData() {
14
        fnGetData(document.getElementById('quotes_editor'));
15
    }
16
17
    function yuiToolbar() {
18
19
        oSaveButton = new YAHOO.widget.Button({
20
            type: "button",
21
            label: _("Save quotes"),
22
            name: "save",
23
            container: "save_quotes",
24
        });
25
26
        oDeleteButton = new YAHOO.widget.Button({
27
            type: "button",
28
            label: _("Delete quote(s)"),
29
            name: "delete",
30
            container: "delete_quote",
31
        });
32
    }
33
34
//]]>
35
</script>
36
<div id="toolbar">
37
    <ul class="toolbar">
38
        <li id="save_quotes"><a id="save" href="#">Save quotes</a></li>
39
        <li id="delete_quote"><a id="delete" href="#">Delete quote(s)</a></li>
40
        <span class="hint" style="">Click Source or Text field to edit contents. Press &lt;Enter&gt; to save changes.</span>
41
    </ul>
42
</div>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes-upload.tt (+337 lines)
Line 0 Link Here
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
    <script type="text/javascript" src="/intranet-tmpl/prog/en/lib/jquery/plugins/jquery.dataTables.min.js"></script>
7
    [% INCLUDE 'datatables-strings.inc' %]
8
    </script>
9
    <script type="text/javascript" src="/intranet-tmpl/prog/en/js/datatables.js"></script>
10
    <script type="text/javascript" src="/intranet-tmpl/prog/en/js/jquery.jeditable.mini.js"></script>
11
    <script type="text/javascript">
12
    //<![CDATA[
13
    var oTable; //DataTable object
14
    $(document).ready(function() {
15
16
    // Credits:
17
    // FileReader() code copied and hacked from:
18
    // http://www.html5rocks.com/en/tutorials/file/dndfiles/
19
    // fnCSVToArray() gratefully borrowed from:
20
    // http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm
21
22
    var reader;
23
    var progress = document.querySelector('.percent');
24
    $("#server_response").hide();
25
26
    function fnAbortRead() {
27
        reader.abort();
28
    }
29
30
    function fnErrorHandler(evt) {
31
        switch(evt.target.error.code) {
32
            case evt.target.error.NOT_FOUND_ERR:
33
                alert('File Not Found!');
34
                break;
35
            case evt.target.error.NOT_READABLE_ERR:
36
                alert('File is not readable');
37
                break;
38
            case evt.target.error.ABORT_ERR:
39
                break; // noop
40
            default:
41
                alert('An error occurred reading this file.');
42
        };
43
    }
44
45
    function fnUpdateProgress(evt) {
46
        // evt is an ProgressEvent.
47
        if (evt.lengthComputable) {
48
            var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
49
            // Increase the progress bar length.
50
            if (percentLoaded < 100) {
51
                progress.style.width = percentLoaded + '%';
52
                progress.textContent = percentLoaded + '%';
53
            }
54
        }
55
    }
56
57
    function fnCSVToArray( strData, strDelimiter ){
58
        // This will parse a delimited string into an array of
59
        // arrays. The default delimiter is the comma, but this
60
        // can be overriden in the second argument.
61
62
        // Check to see if the delimiter is defined. If not,
63
        // then default to comma.
64
        strDelimiter = (strDelimiter || ",");
65
66
        // Create a regular expression to parse the CSV values.
67
        var objPattern = new RegExp(
68
        (
69
            // Delimiters.
70
            "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +
71
            // Quoted fields.
72
            "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +
73
            // Standard fields.
74
            "([^\"\\" + strDelimiter + "\\r\\n]*))"
75
        ),
76
            "gi"
77
        );
78
79
        // Create an array to hold our data. Give the array
80
        // a default empty first row.
81
        var arrData = [[]];
82
83
        // Create an array to hold our individual pattern
84
        // matching groups.
85
        var arrMatches = null;
86
87
        // Keep looping over the regular expression matches
88
        // until we can no longer find a match.
89
        while (arrMatches = objPattern.exec( strData )){
90
91
            // Get the delimiter that was found.
92
            var strMatchedDelimiter = arrMatches[ 1 ];
93
94
            // Check to see if the given delimiter has a length
95
            // (is not the start of string) and if it matches
96
            // field delimiter. If it does not, then we know
97
            // that this delimiter is a row delimiter.
98
            if ( strMatchedDelimiter.length && (strMatchedDelimiter != strDelimiter) ){
99
                // Since we have reached a new row of data,
100
                // add an empty row to our data array.
101
                // Note: if there is not more data, we will have to remove this row later
102
                arrData.push( [] );
103
            }
104
105
            // Now that we have our delimiter out of the way,
106
            // let's check to see which kind of value we
107
            // captured (quoted or unquoted).
108
            if (arrMatches[ 2 ]){
109
                // We found a quoted value. When we capture
110
                // this value, unescape any double quotes.
111
                var strMatchedValue = arrMatches[ 2 ].replace(
112
                new RegExp( "\"\"", "g" ),
113
                    "\""
114
                );
115
            } else if (arrMatches[3]){
116
                // We found a non-quoted value.
117
                var strMatchedValue = arrMatches[ 3 ];
118
            } else {
119
                // There is no more valid data so remove the row we added earlier
120
                // Is there a better way? Perhaps a look-ahead regexp?
121
                arrData.splice(arrData.length-1, 1);
122
            }
123
124
            // Now that we have our value string, let's add
125
            // it to the data array.
126
            arrData[ arrData.length - 1 ].push( strMatchedValue );
127
        }
128
129
        // Return the parsed data.
130
        return( arrData );
131
    }
132
133
    function fnDataTable(aaData) {
134
        for(var i=0; i<aaData.length; i++) {
135
            aaData[i].unshift(i+1); // Add a column w/quote number
136
        }
137
        $('#save_quotes').css("visibility","visible");
138
        $('#file_uploader').css("visibility","hidden");
139
        $('#file_uploader').css("position","absolute");
140
        $('#file_uploader').css("top","-150px");
141
        $('#quotes_editor').css("visibility","visible");
142
        oSaveButton.on("click", yuiGetData);
143
        oDeleteButton.on("click", fnClickDeleteRow);
144
        oTable = $('#quotes_editor').dataTable( {
145
            "bAutoWidth"        : false,
146
            "bPaginate"         : true,
147
            "bSort"             : false,
148
            "sPaginationType"   : "full_numbers",
149
            "aaData"            : aaData,
150
            "aoColumns"         : [
151
                {
152
                    "sTitle"  : "Number",
153
                    "sWidth"  : "2%",
154
                },
155
                {
156
                    "sTitle"  : "Source",
157
                    "sWidth"  : "15%",
158
                },
159
                {
160
                    "sTitle"  : "Quote",
161
                    "sWidth"  : "83%",
162
                },
163
            ],
164
           "fnPreDrawCallback": function(oSettings) {
165
                return true;
166
            },
167
            "fnRowCallback": function( nRow, aData, iDisplayIndex ) {
168
                /* do foo on various cells in the current row */
169
                var quoteNum = $('td', nRow)[0].innerHTML;
170
                $(nRow).attr("id", quoteNum); /* set row ids to quote number */
171
                $('td:eq(0)', nRow).click(function() {$(this.parentNode).toggleClass('selected',this.clicked);}); /* add row selectors */
172
                $('td:eq(0)', nRow).attr("title", "Click ID to select/deselect quote");
173
                /* apply no_edit id to noEditFields */
174
                noEditFields = [0]; /* number */
175
                for (i=0; i<noEditFields.length; i++) {
176
                    $('td', nRow)[noEditFields[i]].setAttribute("id","no_edit");
177
                }
178
                return nRow;
179
            },
180
           "fnDrawCallback": function(oSettings) {
181
                /* Apply the jEditable handlers to the table on all fields w/o the no_edit id */
182
                $('#quotes_editor tbody td[id!="no_edit"]').editable( function(value, settings) {
183
                        var cellPosition = oTable.fnGetPosition( this );
184
                        oTable.fnUpdate(value, cellPosition[0], cellPosition[1], false, false);
185
                        return(value);
186
                    },
187
                    {
188
                    "callback"      : function( sValue, y ) {
189
                                          oTable.fnDraw(false); /* no filter/sort or we lose our pagination */
190
                                      },
191
                    "height"        : "14px",
192
                });
193
           },
194
        });
195
        $('#footer').css("visibility","visible");
196
    }
197
198
    function fnHandleFileSelect(evt) {
199
        // Reset progress indicator on new file selection.
200
        progress.style.width = '0%';
201
        progress.textContent = '0%';
202
203
        reader = new FileReader();
204
        reader.onerror = fnErrorHandler;
205
        reader.onprogress = fnUpdateProgress;
206
        reader.onabort = function(e) {
207
            alert('File read cancelled');
208
            parent.location='quotes-upload.pl';
209
        };
210
        reader.onloadstart = function(e) {
211
            $('#cancel_upload').css("visibility","visible");
212
            $('#progress_bar').addClass("loading");
213
        };
214
        reader.onload = function(e) {
215
            // Ensure that the progress bar displays 100% at the end.
216
            progress.style.width = '100%';
217
            progress.textContent = '100%';
218
            $('#cancel_upload').css("visibility","hidden");
219
            quotes = fnCSVToArray(e.target.result, ',');
220
            fnDataTable(quotes);
221
        }
222
223
        // perform various sanity checks on the target file prior to uploading...
224
        var fileType = evt.target.files[0].type || 'unknown';
225
        var fileSizeInK = Math.round(evt.target.files[0].size/1024);
226
227
        if (!fileType.match(/comma-separated-values|csv|excel/i)) {
228
            alert('Incorrect filetype: '+fileType+'. Uploads limited to csv.');
229
            parent.location='quotes-upload.pl';
230
            return;
231
        }
232
        if (fileSizeInK > 512) {
233
            if (!confirm(evt.target.files[0].name+' is '+fileSizeInK+' K in size. Do you really want to upload this file?')) {
234
                parent.location='quotes-upload.pl';
235
                return;
236
            }
237
        }
238
        // Read in the image file as a text string.
239
        reader.readAsText(evt.target.files[0]);
240
    }
241
242
    $('#file_upload').one('change', fnHandleFileSelect);
243
244
    });
245
246
    function fnGetData(element) {
247
        var jqXHR = $.ajax({
248
            url         : "/cgi-bin/koha/tools/quotes/quotes-upload_ajax.pl",
249
            type        : "POST",
250
            contentType : "application/x-www-form-urlencoded", // we must claim this mimetype or CGI will not decode the URL encoding
251
            dataType    : "json",
252
            data        : {
253
                            "quote"     : JSON.stringify(oTable.fnGetData()),
254
                            "action"    : "add",
255
                          },
256
            success     : function(){
257
                            var response = JSON.parse(jqXHR.responseText);
258
                            if (response.success) {
259
                                $("#server_response").text(response.records+' quotes saved.');
260
                            }
261
                            else {
262
                                $("#server_response").text('An error has occurred. '+response.records+' quotes saved. Please ask your administrator to check the server log for more details.');
263
                            }
264
                            $("#server_response").fadeIn(200);
265
                          },
266
        });
267
    }
268
269
    function fnClickDeleteRow() {
270
        var idsToDelete = oTable.$('.selected').map(function() {
271
              return this.id;
272
        }).get().join(', ');
273
        if (!idsToDelete) {
274
            alert('Please select a quote(s) by clicking the quote id(s) you desire to delete.');
275
        }
276
        else if (confirm('Are you sure you wish to delete quote(s) '+idsToDelete+'?')) {
277
            oTable.$('.selected').each(function(){
278
                oTable.fnDeleteRow(this);
279
            });
280
        }
281
    }
282
283
    function fnResetUpload() {
284
        $('#server_response').fadeOut(200);
285
        window.location.reload(true);   // is this the best route?
286
    }
287
288
    //]]>
289
    </script>
290
</head>
291
<body id="tools_quotes" class="tools">
292
[% INCLUDE 'header.inc' %]
293
[% INCLUDE 'cat-search.inc' %]
294
295
<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>
296
297
<div id="doc3" class="yui-t2">
298
    <div id="bd">
299
        <div id="yui-main">
300
            <div class="yui-b">
301
                [% INCLUDE 'quotes-upload-toolbar.inc' %]
302
                <h2>Quote uploader</h2>
303
                <fieldset id="file_uploader" class="rows" style="visibility:visible;">
304
                    <legend>Upload quotes</legend>
305
                    <div id="file_upload" style="margin-left: 10px;">
306
                        <input type="file" name="file" />
307
                        <button id="cancel_upload" style="visibility:hidden;" onclick="fnAbortRead();">Cancel Upload</button>
308
                        <div id="progress_bar"><div class="percent">0%</div></div>
309
                    </div>
310
                </fieldset>
311
                <div id="server_response" onclick='fnResetUpload()'>Server Response</div>
312
                <table id="quotes_editor" style="float: left; width: 100%; visibility:hidden;">
313
                <thead>
314
                    <tr>
315
                        <th>Source</th>
316
                        <th>Text</th>
317
                        <th>Actions</th>
318
                    </tr>
319
                </thead>
320
                <tbody>
321
                    <!-- tbody content is generated by DataTables -->
322
                    <tr>
323
                        <td></td>
324
                        <td>Loading data...</td>
325
                        <td></td>
326
                    </tr>
327
                </tbody>
328
                </table>
329
                <fieldset id="footer" class="action" style="visibility:hidden; height:25px">
330
                </fieldset>
331
            </div>
332
        </div>
333
    <div class="yui-b noprint">
334
        [% INCLUDE 'tools-menu.inc' %]
335
    </div>
336
</div>
337
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes.tt (-2 / +2 lines)
Lines 6-13 Link Here
6
    <script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/dataTables.fnReloadAjax.js"></script>
6
    <script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/dataTables.fnReloadAjax.js"></script>
7
    [% INCLUDE 'datatables-strings.inc' %]
7
    [% INCLUDE 'datatables-strings.inc' %]
8
    </script>
8
    </script>
9
    <script type="text/javascript" src="/intranet-tmpl/prog/en/js/datatables.js"></script>
9
    <script type="text/javascript" src="[% themelang %]/js/datatables.js"></script>
10
    <script type="text/javascript" src="/intranet-tmpl/prog/en/js/jquery.jeditable.mini.js"></script>
10
    <script type="text/javascript" src="[% themelang %]/js/jquery.jeditable.mini.js"></script>
11
    <script type="text/javascript">
11
    <script type="text/javascript">
12
    //<![CDATA[
12
    //<![CDATA[
13
    var oTable; /* oTable needs to be global */
13
    var oTable; /* oTable needs to be global */
(-)a/tools/quotes-upload.pl (+44 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2012 Foundations Bible College Inc.
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 2 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use strict;
21
use warnings;
22
23
use CGI;
24
use autouse 'Data::Dumper' => qw(Dumper);
25
26
use C4::Auth;
27
use C4::Koha;
28
use C4::Context;
29
use C4::Output;
30
31
my $cgi = new CGI;
32
33
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
34
    {
35
        template_name   => "tools/quotes-upload.tt",
36
        query           => $cgi,
37
        type            => "intranet",
38
        authnotrequired => 0,
39
        flagsrequired   => { tools => 'edit_quotes' },
40
        debug           => 1,
41
    }
42
);
43
44
output_html_with_http_headers $cgi, $cookie, $template->output;
(-)a/tools/quotes/quotes-upload_ajax.pl (-1 / +68 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# Copyright 2012 Foundations Bible College Inc.
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU General Public License as published by the Free Software
9
# Foundation; either version 2 of the License, or (at your option) any later
10
# version.
11
#
12
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License along
17
# with Koha; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20
use strict;
21
use warnings;
22
23
use CGI;
24
use JSON;
25
use autouse 'Data::Dumper' => qw(Dumper);
26
27
use C4::Auth;
28
use C4::Koha;
29
use C4::Context;
30
use C4::Output;
31
32
my $cgi = new CGI;
33
my $dbh = C4::Context->dbh;
34
35
my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
36
    {
37
        template_name   => "",
38
        query           => $cgi,
39
        type            => "intranet",
40
        authnotrequired => 0,
41
        flagsrequired   => { tools => 'edit_quotes' },
42
        debug           => 1,
43
    }
44
);
45
46
my $success = 'true';
47
48
my $quotes = decode_json($cgi->param('quote'));
49
my $action = $cgi->param('action');
50
51
my $sth = $dbh->prepare('INSERT INTO quotes (source, text) VALUES (?, ?);');
52
53
my $insert_count = 0;
54
55
foreach my $quote (@$quotes) {
56
    $insert_count++ if $sth->execute($quote->[0], $quote->[1]);
57
    if ($sth->err) {
58
        warn sprintf('Database returned the following error: %s', $sth->errstr);
59
        $success = 'false';
60
    }
61
}
62
63
print $cgi->header('application/json');
64
65
print to_json({
66
                success => $success,
67
                records => $insert_count,
68
});

Return to bug 7977