From: Chris Jaekl Date: Sun, 5 Nov 2017 13:39:58 +0000 (+0900) Subject: Add basic html/js to search for and display some books. X-Git-Url: https://jaekl.net/gitweb/?a=commitdiff_plain;h=ca96e0b6276f6efe56b102ad8286a2534d6e264b;p=quanweb.git Add basic html/js to search for and display some books. --- diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..322ac7e --- /dev/null +++ b/app/index.html @@ -0,0 +1,32 @@ + + + eBook Library + + + + + + + +
 eBook Library
+ +
+ + Author: + Title: + Series: +
+ +

Books

+ +
(No books found)
+ + + + + + diff --git a/app/lib.css b/app/lib.css new file mode 100644 index 0000000..be1e367 --- /dev/null +++ b/app/lib.css @@ -0,0 +1,93 @@ +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; +} diff --git a/app/lib.js b/app/lib.js new file mode 100644 index 0000000..7175471 --- /dev/null +++ b/app/lib.js @@ -0,0 +1,125 @@ +// 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 = '
' + + '' + + '
' + + '' + book.Description + '
'; + return result; +} + diff --git a/main/db.go b/main/db.go index cd2c79c..fa34828 100644 --- a/main/db.go +++ b/main/db.go @@ -162,6 +162,8 @@ func queryBooksByIds(ids []int) []Book { } 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" diff --git a/main/handler.go b/main/handler.go index 255f193..8686f2b 100644 --- a/main/handler.go +++ b/main/handler.go @@ -48,6 +48,28 @@ func handler(w http.ResponseWriter, r *http.Request) { } } +/* +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, "/") @@ -90,8 +112,8 @@ func handleInfo(w http.ResponseWriter, r *http.Request) { 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 @@ -101,7 +123,6 @@ func handleInfo(w http.ResponseWriter, r *http.Request) { books := queryBooksByIds(ids) var jsonValue []byte - var err error jsonValue, err = json.Marshal(books) if nil != err { fmt.Fprintln(w, "ERROR!", err) @@ -111,6 +132,9 @@ func handleInfo(w http.ResponseWriter, r *http.Request) { } 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)) @@ -120,8 +144,9 @@ func handleSearch(w http.ResponseWriter, r *http.Request) { 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} @@ -140,3 +165,4 @@ func handleSearch(w http.ResponseWriter, r *http.Request) { w.Write(jsonValue) } } + diff --git a/main/main.go b/main/main.go index 99c09a0..d30f84b 100644 --- a/main/main.go +++ b/main/main.go @@ -12,6 +12,10 @@ func main() { 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) }