Index table books

This commit is contained in:
Meutel 2017-08-05 11:16:19 +02:00
parent 14764d931f
commit 634d10c63e
7 changed files with 127 additions and 174 deletions

View File

@ -1,90 +1,50 @@
var app = new Vue({
el: '#app',
// TODO ref components tables
var index = new Vue({
el: '#index',
data: {
},
methods: {
showSeries: function() {
console.log("Series");
},
showAuthors: function() {
console.log("Authors");
},
showBooks: function() {
console.log("Books");
}
}
})
var books = new Vue({
el: '#books',
data: {
books: [],
authors: [],
series: [],
booksCount: 0,
page: 1,
perpage: 20,
sort_by: null,
order_desc: false
order_desc: false,
books: [ {
id: 456,
title: "Test title",
authors: [ {
id: 123,
name: "Test author"
} ],
series: {
id: 789,
name: "Test series",
idx: 1
}
} ]
},
methods: {
sendQuery: function(url, error, success) {
var xmh = new XMLHttpRequest();
var v;
xmh.onreadystatechange = function() {
v = xmh.responseText;
if (xmh.readyState === 4 && xmh.status === 200) {
var res;
try {
res = JSON.parse(v);
} catch (err) {
if (null !== error)
error(err.name, err.message);
}
if (null !== success)
success(res);
} else if (xmh.readyState === 4) {
if (null !== error)
error(xmh.status, v);
}
};
xmh.open('GET', url, true);
xmh.send(null);
sortBy: function(sort) {
console.log("Sort by" + sort);
},
stdError: function(code, resp) {
console.log('ERROR ' + code + ': ' + resp);
},
indexSuccess: function(resp) {
this.booksCount = resp.count;
},
loadIndex: function() {
this.sendQuery('cgi-bin/bouquins/index', this.stdError, this.indexSuccess);
},
authorsSuccess: function(resp) {
this.authors = resp;
loadBooks: function() {
this.sendQuery(this.params('/books/'), this.stdError, this.booksSuccess);
},
booksSuccess: function(resp) {
this.books = resp;
},
seriesSuccess: function(resp) {
this.series = resp;
},
sortBy: function(col) {
if (this.sort_by == col) {
if (this.order_desc) {
this.order_desc = false;
this.sort_by = null;
} else {
this.order_desc = true;
}
} else {
this.order_desc = false;
this.sort_by = col;
}
this.reload();
},
reload: function() {
if (this.books.length > 0)
this.loadBooks();
if (this.authors.length > 0)
this.loadAuthors();
if (this.series.length > 0)
this.loadSeries();
},
prevPage: function() {
if (this.page > 1) {
this.page--;
this.reload();
}
},
nextPage: function() {
this.page++;
this.reload();
this.books = resp.books;
},
order: function(query) {
if (this.order_desc)
@ -102,44 +62,35 @@ var app = new Vue({
params: function(url) {
return this.order(this.sort(this.paginate(url)));
},
loadAuthors: function() {
this.sendQuery(this.params('cgi-bin/bouquins/authors'), this.stdError, this.authorsSuccess);
sendQuery: function(url, error, success) {
var xmh = new XMLHttpRequest();
var v;
xmh.onreadystatechange = function() {
v = xmh.responseText;
if (xmh.readyState === 4 && xmh.status === 200) {
var res;
try {
res = JSON.parse(v);
} catch (err) {
if (null !== error)
error(err.name, err.message);
}
if (null !== success)
success(res);
} else if (xmh.readyState === 4) {
if (null !== error)
error(xmh.status, v);
}
};
xmh.open('GET', url, true);
xmh.setRequestHeader('Accept','application/json');
xmh.send(null);
},
loadBooks: function() {
this.sendQuery(this.params('cgi-bin/bouquins/books'), this.stdError, this.booksSuccess);
},
loadSeries: function() {
this.sendQuery(this.params('cgi-bin/bouquins/series'), this.stdError, this.seriesSuccess);
},
showSeries: function() {
this.books = [];
this.authors = [];
this.page = 1;
this.perpage = 20;
this.order_desc = false;
this.sort_by = null;
this.loadSeries();
},
showAuthors: function() {
this.books = [];
this.series = [];
this.page = 1;
this.perpage = 20;
this.order_desc = false;
this.sort_by = null;
this.loadAuthors();
},
showBooks: function() {
this.authors = [];
this.series = [];
this.page = 1;
this.perpage = 20;
this.order_desc = false;
this.sort_by = null;
this.loadBooks();
stdError: function(code, resp) {
console.log('ERROR ' + code + ': ' + resp);
}
},
mounted: function() {
this.loadIndex();
this.loadBooks();
}
})

View File

@ -154,9 +154,9 @@ type IndexModel struct {
}
// Constructor IndexModel
func NewIndexModel(title string, count int64) *IndexModel {
func NewIndexModel(title, js string, count int64) *IndexModel {
return &IndexModel{
*NewBouquinsModel(title),
*NewBouquinsModelJs(title, js),
count,
nil,
nil,
@ -206,7 +206,7 @@ func paramInt(name string, req *http.Request) int {
val := req.URL.Query().Get(name)
valInt, err := strconv.Atoi(val)
if err != nil {
log.Print("Invalid value for", name, ":", val)
log.Println("Invalid value for", name, ":", val)
return 0
}
return valInt
@ -226,7 +226,7 @@ func (app *Bouquins) IndexPage(res http.ResponseWriter, req *http.Request) {
if err != nil {
log.Print(err)
}
model := NewIndexModel("", count)
model := NewIndexModel("", "index.js", count)
order, sort := paramOrder(req), req.URL.Query().Get(PARAM_SORT)
limit, offset := paramInt(PARAM_LIMIT, req), paramInt(PARAM_OFFSET, req)
switch req.URL.Query().Get(PARAM_LIST) {
@ -259,7 +259,24 @@ func (app *Bouquins) BooksPage(res http.ResponseWriter, req *http.Request) {
// FIXME 404
log.Fatalln("Invalid URL")
}
id, err := strconv.Atoi(req.URL.Path[len(URL_BOOKS):])
idParam := req.URL.Path[len(URL_BOOKS):]
if len(idParam) == 0 {
// books list
if req.Header.Get("Accept") == "application/json" {
model := NewIndexModel("", "", 0) // FIXME model books/paginate
var err error
model.Books, err = app.BooksAdv(10, 0, "", "") // FIXME params
res.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(res)
err = enc.Encode(model)
if err != nil {
log.Println(err)
http.Error(res, err.Error(), 500)
}
}
} else {
// book page
id, err := strconv.Atoi(idParam)
if err != nil {
// FIXME 404
log.Fatalln(err)
@ -275,6 +292,7 @@ func (app *Bouquins) BooksPage(res http.ResponseWriter, req *http.Request) {
}
app.render(res, TPL_BOOKS, model)
}
}
func (app *Bouquins) AuthorsPage(res http.ResponseWriter, req *http.Request) {
if !strings.HasPrefix(req.URL.Path, URL_AUTHORS) {
// FIXME 404

View File

@ -1,4 +1,4 @@
<script src="/js/vue.min.js"></script>
<script src="/js/vue.js"></script>
{{ if .PageJs }}
<script src="/js/{{ .PageJs }}"></script>
{{ end }}

View File

@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charset="utf-8" />
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="preload" href="/js/vue.min.js" as="script">
<link rel="prefetch" href="/js/vue.min.js">
<link rel="preload" href="/js/vue.js" as="script">
<link rel="prefetch" href="/js/vue.js">
{{ if .PageJs }}
<link rel="preload" href="/js/{{ .PageJs }}" as="script">
<link rel="prefetch" href="/js/{{ .PageJs }}">

View File

@ -1,6 +1,6 @@
{{ template "header.html" . }}
<div class="container" id="app">
<div class="jumbotron">
<div class="container">
<div class="jumbotron" id="index">
<h1>Bouquins</h1>
<p>Cette bibliothèque contient actuellement <strong>{{ .BooksCount }}</strong> livres et BD en format papier ou électronique.</p>
<button class="btn btn-primary" type="button" @click="showBooks">Livres</button>
@ -8,31 +8,11 @@
<button class="btn btn-primary" type="button" @click="showSeries">Series</button>
</div>
<div class="table-responsive">
{{ if (ne (len .Books) 0) or (ne (len .Authors) 0) or (ne (len .Series) 0) }}
<nav aria-label="Pages">
<ul class="pager">
<li class="previous" v-bind:class="{ disabled: page <= 1 }"><a href="#" @click="prevPage"><span aria-hidden="true">&larr;</span> Précédents</a></li>
<li class="next"><a href="#" @click="nextPage">Suivants <span aria-hidden="true">&rarr;</span></a></li>
</ul>
</nav>
{{ end }}
{{ if ne (len .Series) 0 }}
{{ template "index_series.html" .Series }}
{{ end }}
{{ if ne (len .Authors) 0 }}
{{ template "index_authors.html" .Authors }}
{{ end }}
{{ if ne (len .Books) 0 }}
{{ template "index_books.html" .Books }}
{{ end }}
{{ if (ne (len .Books) 0) or (ne (len .Authors) 0) or (ne (len .Series) 0) }}
<nav aria-label="Pages">
<ul class="pager">
<li class="previous" v-bind:class="{ disabled: page <= 1 }"><a href="#" @click="prevPage"><span aria-hidden="true">&larr;</span> Précédents</a></li>
<li class="next"><a href="#" @click="nextPage">Suivants <span aria-hidden="true">&rarr;</span></a></li>
</ul>
</nav>
{{ end }}
{{ template "index_paginate.html" }}
{{/* template "index_series.html" */}}
{{/* template "index_authors.html" */}}
{{ template "index_books.html" }}
{{ template "index_paginate.html" }}
</div>
</div>
{{ template "footer.html" . }}

View File

@ -1,4 +1,4 @@
<table class="table table-striped">
<table id="books" class="table table-striped" v-if="books.length > 0">
<tr>
<th>
<a href="#" @click="sortBy('title')">Nom</a>
@ -7,23 +7,21 @@
<th>Auteur(s)</th>
<th>Serie</th>
</tr>
{{ range . }}
<tr>
<tr v-for="book in books">
<td><span class="glyphicon glyphicon-book"></span>
<a href="/books/{{ .Id }}">{{ .Title }}</a></td>
<a :href="'/books/'+book.id">{{ "{{" }} book.title {{ "}}" }}</a></td>
<td>
{{ range .Authors }}
<template v-for="author in book.authors">
<span class="glyphicon glyphicon-user"></span>
<a href="/authors/{{ .Id }}">{{ .Name }}</a>
{{ end }}
<a :href="'/authors/'+author.id">{{ "{{" }} author.name {{ "}}" }}</a>
</template>
</td>
<td>
{{ if .Series }}
<template v-if="book.series">
<span class="glyphicon glyphicon-list"></span>
<a href="/series/{{ .Series.Id }}">{{ .Series.Name }}</a>
<span class="badge">{{ .Book.SeriesIndex }}</span>
{{ end }}
<a :href="'/series/'+book.series.id">{{ "{{" }} book.series.name {{ "}}" }}</a>
<span class="badge">{{ "{{" }} book.series ? book.series.idx : '' {{ "}}" }}</span>
</template>
</td>
</tr>
{{ end }}
</table>

View File

@ -0,0 +1,6 @@
<nav aria-label="Pages">
<ul class="pager">
<li class="previous" v-bind:class="{ disabled: page <= 1 }"><a href="#" @click="prevPage"><span aria-hidden="true">&larr;</span> Précédents</a></li>
<li class="next"><a href="#" @click="nextPage">Suivants <span aria-hidden="true">&rarr;</span></a></li>
</ul>
</nav>