Add basic html/js to search for and display some books.
authorChris Jaekl <cejaekl@yahoo.com>
Sun, 5 Nov 2017 13:39:58 +0000 (22:39 +0900)
committerChris Jaekl <cejaekl@yahoo.com>
Sun, 5 Nov 2017 13:39:58 +0000 (22:39 +0900)
app/index.html [new file with mode: 0644]
app/lib.css [new file with mode: 0644]
app/lib.js [new file with mode: 0644]
main/db.go
main/handler.go
main/main.go

diff --git a/app/index.html b/app/index.html
new file mode 100644 (file)
index 0000000..322ac7e
--- /dev/null
@@ -0,0 +1,32 @@
+<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">&nbsp;</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>
diff --git a/app/lib.css b/app/lib.css
new file mode 100644 (file)
index 0000000..be1e367
--- /dev/null
@@ -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 (file)
index 0000000..7175471
--- /dev/null
@@ -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 = '<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;
+}
+
index cd2c79c..fa34828 100644 (file)
@@ -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"
index 255f193..8686f2b 100644 (file)
@@ -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)
   }
 }
+
index 99c09a0..d30f84b 100644 (file)
@@ -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)
 }