--- /dev/null
+<html>
+ <head>
+ <title>eBook Library</title>
+ <link href="lib.css" rel="stylesheet" type="text/css"/>
+ <meta charset="utf-8"/>
+ </head>
+
+ <body>
+ <table class="header">
+ <tr><td class="box"> </td><td class="title">eBook Library</td></tr>
+ </table>
+
+ <form>
+ <input id="search" onclick="onSearch();" type="button" value="Search"/>
+ Author: <input id="aut" type="text"/>
+ Title: <input id="tit" type="text"/>
+ Series: <input id="ser" type="text"/>
+ </form>
+
+ <h1>Books</h1>
+
+ <div id="books">(No books found)</div>
+
+ <div class="footer">
+ <input id="back" value="Back" type="button"/>
+ <input id="forward" value="Forward" type="button"/>
+ Showing <span id="first">0</span> through <span id="last">0</span> out of <span id="count">0</span> matching books.
+ </div>
+
+ <script src="lib.js"></script>
+ </body>
+</html>
--- /dev/null
+a:link {
+ color: #008000;
+}
+
+a:visited {
+ color: #008080;
+}
+
+a:active {
+ color: #ff0000;
+}
+
+body {
+ background-color: #e0e0ff;
+ color: black;
+}
+
+div.book {
+ display: inline-block;
+ width: 400px;
+ margin: 10px;
+ border 3px solid #73ad21;
+}
+
+div.footer {
+ background-color: #004080;
+ border-color: #004080;
+ border-style: solid;
+ border-width: 3px;
+ color: #ffffff;
+ font-size: 0.8em;
+ padding: 2;
+ text-align: left;
+}
+
+h1 {
+ background-color: #004080;
+ border-color: #004080;
+ border-style: solid;
+ border-width: 5px;
+ color: #ffffff;
+ font-size: 1.2em;
+ font-weight: normal;
+ margin: 2px 0px 0px 2px;
+}
+
+img.cover-thumb { max-height: 200px; max-width: 200px; }
+
+p.navigator { }
+
+p.quote {
+ margin-left: 50px;
+ margin-right: 50px;
+ text-align: justify;
+}
+
+span.popup { }
+
+span.popup:hover { text-decoration: none; background: #cfffff; z-index: 6; }
+
+span.popup span.pop-inner {
+ border-color:black;
+ border-style:solid;
+ border-width:1px;
+ display: none;
+ margin: 4px 0 0 0px;
+ padding: 3px 3px 3px 3px;
+ position: absolute;
+}
+
+span.popup:hover span.pop-inner {
+ background: #ffffaf;
+ display: block;
+ margin: 20px 0 0 0px;
+ z-index:6;
+}
+
+table.header {
+ width: 100%;
+ line-height: 1.8;
+}
+
+td.box {
+ background-color: #0000ff;
+ width: 15%;
+}
+
+td.title {
+ background-color: #004080;
+ color: #ffffff;
+ font-size: 2.0em;
+ text-align: right;
+}
--- /dev/null
+// QuanLib: eBook Library
+// (C) 2017 by Christian Jaekl (cejaekl@yahoo.com)
+
+g_state = {
+ cache: {},
+ count: 0,
+ first: 0,
+ ids: [],
+ last: 0,
+}
+
+function constructSearchUrl() {
+ var url = window.location.protocol + '//' + window.location.host + '/search/';
+
+ var firstTime = true;
+ var terms = ['aut', 'tit', 'ser'];
+
+ for (idx in terms) {
+ var term = terms[idx];
+ var elem = document.getElementById(term);
+ if (null === elem) {
+ console.log('Error: could not find form element for search term "' + term + '".');
+ continue;
+ }
+
+ var value = elem.value;
+ if (value.length > 0) {
+ if (firstTime) {
+ url += '?';
+ firstTime = false;
+ }
+ else {
+ url += '&';
+ }
+ url += term + '=' + encodeURIComponent('%' + value + '%');
+ }
+ }
+
+ return url;
+}
+
+function onNext() {
+}
+
+function onPrev() {
+}
+
+function onSearch() {
+ console.log('onSearch()');
+
+ var url = constructSearchUrl();
+
+ report('Loading data from server, please wait...')
+ console.log('Fetching: "' + url + '"...')
+
+ fetch(url, {method:'GET', cache:'default'})
+ .then(response => response.json())
+ .then((jsonValue) => {
+ console.log('JSON response: ', jsonValue);
+ g_state.ids = jsonValue
+ g_state.first = 0
+ g_state.last = (g_state.ids.length) - 1;
+ if (g_state.last > 100) {
+ g_state.last = 100;
+ }
+ refreshData()
+ })
+ .catch(err => {
+ var msg = 'Error fetching JSON from URL: ' + url + ': ' + err + ':' + err.stack;
+ console.log(msg);
+ report(msg);
+ });
+}
+
+function refreshData() {
+ report('Loading details for books ' + g_state.first + ' through ' + g_state.last + ', please wait...');
+
+ var i;
+ var url = '/info/?ids=';
+ for (i = g_state.first; i <= g_state.last; ++i) {
+ if (i > 0) {
+ url += ',';
+ }
+ url += g_state.ids[i];
+ }
+
+ fetch(url, {method:'GET', cache:'default'})
+ .then(response => response.json())
+ .then((jsonValue) => {
+ console.log('JSON response for info: ', jsonValue);
+ report('');
+ g_state.cache = jsonValue;
+ refreshLayout();
+ })
+ .catch(err => {
+ var msg = 'Error fetching book details via URL: ' + url + ': ' + err;
+ console.log(msg, err.stack);
+ report(msg);
+ });
+}
+
+function refreshLayout() {
+ var i;
+ var html = '';
+ for (i = g_state.first; i <= g_state.last; ++i) {
+ var book = g_state.cache[i];
+ html += bookHtml(book);
+ }
+
+ document.getElementById('books').innerHTML = html;
+}
+
+function report(message) {
+ document.getElementById('books').innerHTML = message;
+}
+
+function bookHtml(book) {
+ console.log('bookHtml(): ', book);
+ var result = '<div class="book"><table><tr><td><a href="/book/' + book.Id + '">'
+ + '<img class="cover-thumb" src="/download/' + book.CoverId + '"/></a></td>'
+ + '<td><span class="popup">' + book.Description + '</span></td>'
+ + '</tr></table></div>';
+ return result;
+}
+
}
func queryIds(criteria []SearchTerm) []int {
+ fmt.Println("queryIds():", criteria)
+
query := "SELECT b.id FROM Books b" +
" INNER JOIN Authors a ON a.id=b.author" +
" LEFT OUTER JOIN Series s ON s.id=b.series"
}
}
+/*
+func handleApp(w http.ResponseWriter, r *http.Request) {
+ fmt.Println("handleApp():", r.URL.Path)
+
+ // Security check: prevent walking up the directory
+ pos := strings.Index(r.Url.Path, "../")
+ if (-1) == pos {
+ fmt.Fprintln(w, "Paths containing \"../\" are not permitted:", r.URL.Path)
+ return
+ }
+
+ fileName := "../app" + r.URL.Path
+ _, err := os.Stat(fileName)
+ if nil != err {
+ fmt.Fprintln(w, "Failed to find file:", fileName, err)
+ return
+ }
+
+ http.ServeFile(w, r, "../app/" + r.URL.Path[1:])
+}
+*/
+
func handleDownload(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path[1:]
tok := strings.Split(path, "/")
idParam := idParams[0]
idStrings := strings.Split(idParam, ",")
ids := make([]int, len(idStrings))
+ var err error
for i, v := range(idStrings) {
- var err error
ids[i], err = strconv.Atoi(v)
if nil != err {
ids[i] = 0
books := queryBooksByIds(ids)
var jsonValue []byte
- var err error
jsonValue, err = json.Marshal(books)
if nil != err {
fmt.Fprintln(w, "ERROR!", err)
}
func handleSearch(w http.ResponseWriter, r *http.Request) {
+ var err error
+ fmt.Println("DEBUG: handleSearch(): " + r.URL.Path)
+
fields := []Field{Author, Title, Series}
terms := make([]SearchTerm, len(fields))
paramName := fv.String()
paramValues := r.Form[paramName]
for _, pv := range(paramValues) {
+ fmt.Println("DEBUG: handleSearch(): ", paramName, "=", pv)
if count >= len(terms) {
- fmt.Printf("WARNING: limit of %v search terms exceeded. One or more terms ignored.")
+ fmt.Printf("WARNING: limit of %d search terms exceeded. One or more terms ignored.", len(terms))
break
}
terms[count] = SearchTerm{Attribute:fv, Text:pv}
w.Write(jsonValue)
}
}
+
var b sql.NullString
nsVal(b)
+ fs := http.FileServer(http.Dir("../app"))
+ http.Handle("/app/", http.StripPrefix("/app", fs))
+
http.HandleFunc("/", handler)
+
http.ListenAndServe(":8001", nil)
}