Bugzilla – Attachment 9327 Details for
Bug 7977
Add a "Quote-of-the-day" feature to the OPAC homepage
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
QOTD-uploader
0006-Beginning-work-on-QOTD-uploader.patch (text/plain), 21.36 KB, created by
Chris Nighswonger
on 2012-04-27 19:30:41 UTC
(
hide
)
Description:
QOTD-uploader
Filename:
MIME Type:
Creator:
Chris Nighswonger
Created:
2012-04-27 19:30:41 UTC
Size:
21.36 KB
patch
obsolete
>From bc5e8a88b863f2fef7c656415211bed3681075dc Mon Sep 17 00:00:00 2001 >From: Chris Nighswonger <cnighswonger@foundations.edu> >Date: Fri, 20 Apr 2012 16:12:26 -0400 >Subject: [PATCH 6/7] Beginning work on QOTD uploader >Content-Type: text/plain; charset="utf-8" > >This series will add a DataTable's based upload/editor with which >to upload csv files containing quotes to be used by the QOTD >feature. > >The file should be formatted thusly: > >"source","text-of-quote" >"source","text-of-quote" >... > >Note: This work serves as a good example of potential improvements >in all other "editor" and file upload areas of Koha. > >This patch is a squash of the following work: > >Adding code to parse CSV file contents and push it into a DataTable > >Adding in jEditable to enable table editing > >Adding ajax to post data back to the server to be saved > >Fixing edit and adding delete functionality > >Adding some missing css as well as server feedback on save > >Fixing a bug which limited the number of quotes which could be uploaded > >Also fixing a minor bug with fnCSVToArray and doing some style cleanup. >--- > koha-tmpl/intranet-tmpl/prog/en/css/uploader.css | 38 +++ > .../prog/en/modules/tools/quotes-upload.tt | 300 ++++++++++++++++++++ > .../intranet-tmpl/prog/en/modules/tools/quotes.tt | 10 +- > koha-tmpl/intranet-tmpl/prog/img/x_alt_16x16.png | Bin 0 -> 215 bytes > tools/quotes-upload.pl | 44 +++ > tools/quotes/quotes-upload_ajax.pl | 68 +++++ > 6 files changed, 455 insertions(+), 5 deletions(-) > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/css/uploader.css > create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes-upload.tt > create mode 100644 koha-tmpl/intranet-tmpl/prog/img/x_alt_16x16.png > create mode 100755 tools/quotes-upload.pl > create mode 100755 tools/quotes/quotes-upload_ajax.pl > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/css/uploader.css b/koha-tmpl/intranet-tmpl/prog/en/css/uploader.css >new file mode 100644 >index 0000000..eca87b9 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/css/uploader.css >@@ -0,0 +1,38 @@ >+#progress_bar { >+ margin: 10px 0; >+ padding: 3px; >+ border: 1px solid #000; >+ font-size: 14px; >+ clear: both; >+ opacity: 0; >+ -moz-transition: opacity 1s linear; >+ -o-transition: opacity 1s linear; >+ -webkit-transition: opacity 1s linear; >+} >+#progress_bar.loading { >+ opacity: 1.0; >+} >+#progress_bar .percent { >+ background-color: #99ccff; >+ height: auto; >+ width: 0; >+} >+#server_response { >+ background-color: white; >+ background-image: url("../../img/x_alt_16x16.png"); >+ background-repeat: no-repeat; >+ background-origin: padding-box; >+ background-position: right top; >+ border: 1px solid #DDDDDD; >+ color: #999999; >+ font-size: 14px; >+ height: 30px; >+ left: 50%; >+ margin-left: -125px; >+ margin-top: -15px; >+ padding: 14px 0 2px; >+ position: fixed; >+ text-align: center; >+ top: 50%; >+ width: 250px; >+} >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes-upload.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes-upload.tt >new file mode 100644 >index 0000000..05fac8b >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes-upload.tt >@@ -0,0 +1,300 @@ >+ [% INCLUDE 'doc-head-open.inc' %] >+ <title>Koha › Tools › Quote Uploader</title> >+ [% INCLUDE 'doc-head-close.inc' %] >+ <link rel="stylesheet" type="text/css" href="/intranet-tmpl/prog/en/css/uploader.css" /> >+ <link rel="stylesheet" type="text/css" href="/intranet-tmpl/prog/en/css/datatables.css" /> >+ <script type="text/javascript" src="/intranet-tmpl/prog/en/lib/jquery/plugins/jquery.dataTables.min.js"></script> >+ [% INCLUDE 'datatables-strings.inc' %] >+ </script> >+ <script type="text/javascript" src="/intranet-tmpl/prog/en/js/datatables.js"></script> >+ <script type="text/javascript" src="/intranet-tmpl/prog/en/js/jquery.jeditable.mini.js"></script> >+ <script type="text/javascript"> >+ //<![CDATA[ >+ var oTable; //DataTable object >+ $(document).ready(function() { >+ >+ // Credits: >+ // FileReader() code copied and hacked from: >+ // http://www.html5rocks.com/en/tutorials/file/dndfiles/ >+ // fnCSVToArray() gratefully borrowed from: >+ // http://www.bennadel.com/blog/1504-Ask-Ben-Parsing-CSV-Strings-With-Javascript-Exec-Regular-Expression-Command.htm >+ >+ var reader; >+ var progress = document.querySelector('.percent'); >+ $("#server_response").hide(); >+ >+ function fnAbortRead() { >+ reader.abort(); >+ } >+ >+ function fnErrorHandler(evt) { >+ switch(evt.target.error.code) { >+ case evt.target.error.NOT_FOUND_ERR: >+ alert('File Not Found!'); >+ break; >+ case evt.target.error.NOT_READABLE_ERR: >+ alert('File is not readable'); >+ break; >+ case evt.target.error.ABORT_ERR: >+ break; // noop >+ default: >+ alert('An error occurred reading this file.'); >+ }; >+ } >+ >+ function fnUpdateProgress(evt) { >+ // evt is an ProgressEvent. >+ if (evt.lengthComputable) { >+ var percentLoaded = Math.round((evt.loaded / evt.total) * 100); >+ // Increase the progress bar length. >+ if (percentLoaded < 100) { >+ progress.style.width = percentLoaded + '%'; >+ progress.textContent = percentLoaded + '%'; >+ } >+ } >+ } >+ >+ function fnCSVToArray( strData, strDelimiter ){ >+ // This will parse a delimited string into an array of >+ // arrays. The default delimiter is the comma, but this >+ // can be overriden in the second argument. >+ >+ // Check to see if the delimiter is defined. If not, >+ // then default to comma. >+ strDelimiter = (strDelimiter || ","); >+ >+ // Create a regular expression to parse the CSV values. >+ var objPattern = new RegExp( >+ ( >+ // Delimiters. >+ "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" + >+ // Quoted fields. >+ "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" + >+ // Standard fields. >+ "([^\"\\" + strDelimiter + "\\r\\n]*))" >+ ), >+ "gi" >+ ); >+ >+ // Create an array to hold our data. Give the array >+ // a default empty first row. >+ var arrData = [[]]; >+ >+ // Create an array to hold our individual pattern >+ // matching groups. >+ var arrMatches = null; >+ >+ // Keep looping over the regular expression matches >+ // until we can no longer find a match. >+ while (arrMatches = objPattern.exec( strData )){ >+ >+ // Get the delimiter that was found. >+ var strMatchedDelimiter = arrMatches[ 1 ]; >+ >+ // Check to see if the given delimiter has a length >+ // (is not the start of string) and if it matches >+ // field delimiter. If it does not, then we know >+ // that this delimiter is a row delimiter. >+ if ( strMatchedDelimiter.length && (strMatchedDelimiter != strDelimiter) ){ >+ // Since we have reached a new row of data, >+ // add an empty row to our data array. >+ // Note: if there is not more data, we will have to remove this row later >+ arrData.push( [] ); >+ } >+ >+ // Now that we have our delimiter out of the way, >+ // let's check to see which kind of value we >+ // captured (quoted or unquoted). >+ if (arrMatches[ 2 ]){ >+ // We found a quoted value. When we capture >+ // this value, unescape any double quotes. >+ var strMatchedValue = arrMatches[ 2 ].replace( >+ new RegExp( "\"\"", "g" ), >+ "\"" >+ ); >+ } else if (arrMatches[3]){ >+ // We found a non-quoted value. >+ var strMatchedValue = arrMatches[ 3 ]; >+ } else { >+ // There is no more valid data so remove the row we added earlier >+ // Is there a better way? Perhaps a look-ahead regexp? >+ arrData.splice(arrData.length-1, 1); >+ } >+ >+ // Now that we have our value string, let's add >+ // it to the data array. >+ arrData[ arrData.length - 1 ].push( strMatchedValue ); >+ } >+ >+ // Return the parsed data. >+ return( arrData ); >+ } >+ >+ function fnDataTable(aaData) { >+ for(var i=0; i<aaData.length; i++) { >+ aaData[i].push('Delete'); //this is hackish FIXME >+ } >+ document.getElementById('quotes_editor').style.visibility="visible"; >+ document.getElementById('file_uploader').style.visibility="hidden"; >+ oTable = $('#quotes_editor').dataTable( { >+ "bAutoWidth" : false, >+ "bPaginate" : true, >+ "bSort" : false, >+ "sPaginationType" : "full_numbers", >+ "sDom" : '<"save_quotes">frtip', >+ "aaData" : aaData, >+ "aoColumns" : [ >+ { >+ "sTitle" : "Source", >+ "sWidth" : "15%", >+ }, >+ { >+ "sTitle" : "Quote", >+ "sWidth" : "75%", >+ }, >+ { >+ "sTitle" : "Actions", >+ "sWidth" : "10%", >+ }, >+ ], >+ "fnPreDrawCallback": function(oSettings) { >+ return true; >+ }, >+ "fnRowCallback": function( nRow, aData, iDisplayIndex ) { >+ noEditFields = [2]; /* action */ >+ /* console.log('Quote ID: '+quoteID); */ >+ /* do foo on various cells in the current row */ >+ $('td:eq(2)', nRow).html('<input type="button" class="delete" value="Delete" onclick="fnClickDeleteRow(this.parentNode);" />'); >+ /* apply no_edit id to noEditFields */ >+ for (i=0; i<noEditFields.length; i++) { >+ $('td', nRow)[noEditFields[i]].setAttribute("id","no_edit"); >+ } >+ return nRow; >+ }, >+ "fnDrawCallback": function(oSettings) { >+ /* Apply the jEditable handlers to the table on all fields w/o the no_edit id */ >+ $('#quotes_editor tbody td[id!="no_edit"]').editable( function(value, settings) { >+ var cellPosition = oTable.fnGetPosition( this ); >+ oTable.fnUpdate(value, cellPosition[0], cellPosition[1], false, false); >+ return(value); >+ }, >+ { >+ "callback" : function( sValue, y ) { >+ oTable.fnDraw(false); /* no filter/sort or we lose our pagination */ >+ }, >+ "height" : "14px", >+ }); >+ $("div.save_quotes").html('<input type="button" class="add_quote_button" value="Save Quotes" style="float: right;" onclick="fnGetData(document.getElementById(\'quotes_editor\'));"/>'); >+ }, >+ }); >+ } >+ >+ function fnHandleFileSelect(evt) { >+ // Reset progress indicator on new file selection. >+ progress.style.width = '0%'; >+ progress.textContent = '0%'; >+ >+ reader = new FileReader(); >+ reader.onerror = fnErrorHandler; >+ reader.onprogress = fnUpdateProgress; >+ reader.onabort = function(e) { >+ alert('File read cancelled'); >+ }; >+ reader.onloadstart = function(e) { >+ document.getElementById('progress_bar').className = 'loading'; >+ }; >+ reader.onload = function(e) { >+ // Ensure that the progress bar displays 100% at the end. >+ progress.style.width = '100%'; >+ progress.textContent = '100%'; >+ setTimeout("document.getElementById('progress_bar').className='';", 2000); >+ quotes = fnCSVToArray(e.target.result, ','); >+ fnDataTable(quotes); >+ } >+ // Read in the image file as a text string. >+ reader.readAsText(evt.target.files[0]); >+ } >+ >+ document.getElementById('files').addEventListener('change', fnHandleFileSelect, false); >+ >+ }); >+ >+ function fnGetData(element) { >+ var jqXHR = $.ajax({ >+ url : "/cgi-bin/koha/tools/quotes/quotes-upload_ajax.pl", >+ type : "POST", >+ contentType : "application/x-www-form-urlencoded", // we must claim this mimetype or CGI will not decode the URL encoding >+ dataType : "json", >+ data : { >+ "quote" : JSON.stringify(oTable.fnGetData()), >+ "action" : "add", >+ }, >+ success : function(){ >+ var response = JSON.parse(jqXHR.responseText); >+ if (response.success) { >+ $("#server_response").text(response.records+' quotes saved.'); >+ } >+ else { >+ $("#server_response").text('An error has occurred. '+response.records+' quotes saved. Please ask your administrator to check the server log for more details.'); >+ } >+ $("#server_response").fadeIn(200); >+ }, >+ }); >+ } >+ >+ function fnClickDeleteRow(td) { >+ oTable.fnDeleteRow(oTable.fnGetPosition(td)[0]); >+ } >+ >+ function fnResetUpload() { >+ $('#server_response').fadeOut(200); >+ window.location.reload(true); // is this the best route? >+ } >+ >+ //]]> >+ </script> >+</head> >+<body id="tools_quotes" class="tools"> >+[% INCLUDE 'header.inc' %] >+[% INCLUDE 'cat-search.inc' %] >+ >+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> › <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> › Quote Uploader</div> >+ >+<div id="doc3" class="yui-t2"> >+ <div id="bd"> >+ <div id="yui-main"> >+ <div class="yui-b"> >+ >+ <div id="file_uploader" style="float: left; width: 100%; visibility:visible;"> >+ <input type="file" id="files" name="file" /> >+ <button onclick="fnAbortRead();">Cancel Upload</button> >+ <div id="progress_bar"><div class="percent">0%</div></div> >+ </div> >+ <div id="server_response" onclick='fnResetUpload()'>Server Response</div> >+ <table id="quotes_editor" style="float: left; width: 100%; visibility:hidden;"> >+ <thead> >+ <tr> >+ <th>Source</th> >+ <th>Text</th> >+ <th>Actions</th> >+ </tr> >+ </thead> >+ <tbody> >+ <!-- tbody content is generated by DataTables --> >+ <tr> >+ <td></td> >+ <td>Loading data...</td> >+ <td></td> >+ </tr> >+ </tbody> >+ </table> >+ >+ >+ >+ </div> >+ </div> >+ <div class="yui-b noprint"> >+ [% INCLUDE 'tools-menu.inc' %] >+ </div> >+</div> >+[% INCLUDE 'intranet-bottom.inc' %] >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes.tt >index 49df31d..a5c87e4 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/quotes.tt >@@ -1,12 +1,12 @@ > [% INCLUDE 'doc-head-open.inc' %] > <title>Koha › Tools › Quote Editor</title> > [% INCLUDE 'doc-head-close.inc' %] >- <link rel="stylesheet" type="text/css" href="/intranet-tmpl/prog/en/css/datatables.css" /> >- <script type="text/javascript" src="/intranet-tmpl/prog/en/lib/jquery/plugins/jquery.dataTables.min.js"></script> >+ <link rel="stylesheet" type="text/css" href="[% themelang %]/css/datatables.css" /> >+ <script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.dataTables.min.js"></script> > [% INCLUDE 'datatables-strings.inc' %] > </script> >- <script type="text/javascript" src="/intranet-tmpl/prog/en/js/datatables.js"></script> >- <script type="text/javascript" src="/intranet-tmpl/prog/en/js/jquery.jeditable.mini.js"></script> >+ <script type="text/javascript" src="[% themelang %]/js/datatables.js"></script> >+ <script type="text/javascript" src="[% themelang %]/js/jquery.jeditable.mini.js"></script> > <script type="text/javascript"> > //<![CDATA[ > var oTable; /* oTable needs to be global */ >@@ -64,7 +64,7 @@ > }); > }, > }); >- $("div.add_quote").html('<input type="button" class="add_quote_button" value="Add Quote" style="float: right;" onclick="fnClickAddRow();"/>'); >+ $("div.add_quote").html('<input type="button" class="add_quote_button" value="Add Quote" style="float: right;" onclick="fnClickAddRow();"/><input type="button" class="import_quote_button" value="Import Quotes" style="float: right;" onclick="parent.location=\'quotes-upload.pl\'"/>'); > }); > > function fnClickAddQuote() { >diff --git a/koha-tmpl/intranet-tmpl/prog/img/x_alt_16x16.png b/koha-tmpl/intranet-tmpl/prog/img/x_alt_16x16.png >new file mode 100644 >index 0000000000000000000000000000000000000000..a99310e42eb61377703e8a2564fb7c88191ccb3e >GIT binary patch >literal 215 >zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? >zp1FzXsX?iUDV2pMQ*D5XDm`5sLn>}1B^+Skbl4-`@qh8dzX6tW92h&!CtvL7JMVlo >zDWlH6B~7BPKkr1PkcskcmUrnTm!&skWE^<pRWRet@kIi66Cxg%CU8cqIp^(Qq{7(l >zohY$fUiiWyvDH>;Y+4LmdmRcDf@FF{)Kylgq;?q#-@GZQaDc&4dBx&a%xew<9l_w~ >L>gTe~DWM4fy}3+! > >literal 0 >HcmV?d00001 > >diff --git a/tools/quotes-upload.pl b/tools/quotes-upload.pl >new file mode 100755 >index 0000000..7db61ce >--- /dev/null >+++ b/tools/quotes-upload.pl >@@ -0,0 +1,44 @@ >+#!/usr/bin/perl >+ >+# Copyright 2012 Foundations Bible College Inc. >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it under the >+# terms of the GNU General Public License as published by the Free Software >+# Foundation; either version 2 of the License, or (at your option) any later >+# version. >+# >+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY >+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR >+# A PARTICULAR PURPOSE. See the GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License along >+# with Koha; if not, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use strict; >+use warnings; >+ >+use CGI; >+use autouse 'Data::Dumper' => qw(Dumper); >+ >+use C4::Auth; >+use C4::Koha; >+use C4::Context; >+use C4::Output; >+ >+my $cgi = new CGI; >+ >+my ( $template, $borrowernumber, $cookie ) = get_template_and_user( >+ { >+ template_name => "tools/quotes-upload.tt", >+ query => $cgi, >+ type => "intranet", >+ authnotrequired => 0, >+ flagsrequired => { tools => 'edit_quotes' }, >+ debug => 1, >+ } >+); >+ >+output_html_with_http_headers $cgi, $cookie, $template->output; >diff --git a/tools/quotes/quotes-upload_ajax.pl b/tools/quotes/quotes-upload_ajax.pl >new file mode 100755 >index 0000000..f1c3746 >--- /dev/null >+++ b/tools/quotes/quotes-upload_ajax.pl >@@ -0,0 +1,68 @@ >+#!/usr/bin/perl >+ >+# Copyright 2012 Foundations Bible College Inc. >+# >+# This file is part of Koha. >+# >+# Koha is free software; you can redistribute it and/or modify it under the >+# terms of the GNU General Public License as published by the Free Software >+# Foundation; either version 2 of the License, or (at your option) any later >+# version. >+# >+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY >+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR >+# A PARTICULAR PURPOSE. See the GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License along >+# with Koha; if not, write to the Free Software Foundation, Inc., >+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. >+ >+use strict; >+use warnings; >+ >+use CGI; >+use JSON; >+use autouse 'Data::Dumper' => qw(Dumper); >+ >+use C4::Auth; >+use C4::Koha; >+use C4::Context; >+use C4::Output; >+ >+my $cgi = new CGI; >+my $dbh = C4::Context->dbh; >+ >+my ( $template, $borrowernumber, $cookie ) = get_template_and_user( >+ { >+ template_name => "", >+ query => $cgi, >+ type => "intranet", >+ authnotrequired => 0, >+ flagsrequired => { tools => 'edit_quotes' }, >+ debug => 1, >+ } >+); >+ >+my $success = 'true'; >+ >+my $quotes = decode_json($cgi->param('quote')); >+my $action = $cgi->param('action'); >+ >+my $sth = $dbh->prepare('INSERT INTO quotes (source, text) VALUES (?, ?);'); >+ >+my $insert_count = 0; >+ >+foreach my $quote (@$quotes) { >+ $insert_count++ if $sth->execute($quote->[0], $quote->[1]); >+ if ($sth->err) { >+ warn sprintf('Database returned the following error: %s', $sth->errstr); >+ $success = 'false'; >+ } >+} >+ >+print $cgi->header('application/json'); >+ >+print to_json({ >+ success => $success, >+ records => $insert_count, >+}); >-- >1.7.0.4 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 7977
:
9310
|
9321
|
9323
|
9324
|
9325
|
9326
|
9327
|
9328
|
9338
|
9343
|
9373
|
9376
|
9377
|
9378
|
9392
|
9405
|
9406
|
9407
|
9408
|
9409
|
9433
|
9434
|
9435
|
9441
|
9442
|
9443
|
9447
|
9473
|
9474
|
9475
|
9476
|
9477
|
9478
|
9479
|
9480
|
9481
|
9482
|
9483
|
9484
|
9485
|
9486
|
9503
|
9504
|
9505
|
9510
|
9517
|
9521
|
9529
|
9530
|
9531
|
9682
|
9683
|
9711
|
9712
|
9713
|
9714
|
9715
|
9716
|
9717
|
9718
|
9719
|
9720
|
9721
|
9722
|
9723
|
9724
|
9725
|
9726
|
9727
|
9728
|
9729
|
9730
|
9731
|
9732