summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAsbjørn Sloth Tønnesen <asbjorn@asbjorn.biz>2012-08-10 21:14:52 +0000
committerAsbjørn Sloth Tønnesen <asbjorn@asbjorn.biz>2012-08-10 21:14:52 +0000
commit802a86a276784601e0a952611bfcfb823831608f (patch)
tree38a45e0a43b2cfd7e757f3dfae49257269443d71
parent2a029fb35f22ddf03326ec1f9c6b7ef413c2c293 (diff)
downloadlabitrack-802a86a276784601e0a952611bfcfb823831608f.tar.gz
labitrack-802a86a276784601e0a952611bfcfb823831608f.tar.xz
labitrack-802a86a276784601e0a952611bfcfb823831608f.zip
implement search support
Signed-off-by: Asbjørn Sloth Tønnesen <asbjorn@asbjorn.biz>
-rw-r--r--web/js/labitrack.d/.gitignore1
-rw-r--r--web/js/labitrack.d/31-handlebars-helpers.js3
-rw-r--r--web/js/labitrack.d/39-pagination.coffee50
-rw-r--r--web/js/labitrack.d/43-browse.coffee2
-rw-r--r--web/js/labitrack.d/55-search.coffee81
-rwxr-xr-xweb/labitrackd.lua61
-rw-r--r--web/pub/index.html7
-rw-r--r--web/templates/labitrack.d/search2
-rw-r--r--web/templates/labitrack.d/searchtable15
9 files changed, 195 insertions, 27 deletions
diff --git a/web/js/labitrack.d/.gitignore b/web/js/labitrack.d/.gitignore
index fc7aaf7..515beb9 100644
--- a/web/js/labitrack.d/.gitignore
+++ b/web/js/labitrack.d/.gitignore
@@ -1,2 +1,3 @@
39-pagination.js
43-browse.js
+55-search.js
diff --git a/web/js/labitrack.d/31-handlebars-helpers.js b/web/js/labitrack.d/31-handlebars-helpers.js
index 6fa462a..6bd222f 100644
--- a/web/js/labitrack.d/31-handlebars-helpers.js
+++ b/web/js/labitrack.d/31-handlebars-helpers.js
@@ -5,6 +5,9 @@
Handlebars.registerHelper('pagination', function(){
return new Handlebars.SafeString(λ.template('pagination', this));
});
+ Handlebars.registerHelper('objecttable', function(){
+ return new Handlebars.SafeString(λ.template('objecttable', this));
+ });
Handlebars.registerHelper('dump_ctx', function(){
console.log({'ctx': this});
});
diff --git a/web/js/labitrack.d/39-pagination.coffee b/web/js/labitrack.d/39-pagination.coffee
index ca50b9b..98311f5 100644
--- a/web/js/labitrack.d/39-pagination.coffee
+++ b/web/js/labitrack.d/39-pagination.coffee
@@ -1,43 +1,51 @@
λ.pagination = (->
- link = (pg) -> '/browse' + (if pg > 1 then '/page/' + pg else '')
class pgs
- constructor: -> @pages = []
+ constructor: (@prefix, @pg, @cnt) -> @pages = []
+ link: (pg) -> @prefix + (if pg > 1 then '/page/' + pg else '')
dots: -> @pages.push { id: 'dots', label: '…', classes: 'disabled' }
page: (pgno) -> @pages.push {
id: pgno
- link: link pgno
+ link: @link pgno
label: pgno
}
first: -> @pages.push {
id: 'first'
- link: link 1
+ link: @link 1
label: '|&larr;'
classes: 'prev'
}
- prev: (pg) -> @pages.push {
- id: 'prev'
- link: link pg
- label: '&larr;'
- }
- next: (pg) -> @pages.push {
- id: 'next'
- link: link pg
- label: '&rarr;'
- }
+ prev: (pg) ->
+ i =
+ id: 'prev'
+ link: @link pg
+ label: '&larr;'
+ if @pg is 1
+ i.link = undefined
+ i.classes = 'disabled'
+ @pages.push i
+ next: (pg) ->
+ i =
+ id: 'next'
+ link: @link pg
+ label: '&rarr;'
+ if @pg is @cnt
+ i.link = undefined
+ i.classes = 'disabled'
+ @pages.push i
last: (pg) -> @pages.push {
id: 'last'
- link: link pg
+ link: @link pg
label: '&rarr;|'
classes: 'next'
}
- return (page, cnt) ->
- p = new pgs
- first = page != 1
- prev = page > 2
- next = page + 1 < cnt
- last = page < cnt
+ return (prefix, page, cnt) ->
+ p = new pgs prefix, page, cnt
+ first = page != 1 and cnt >= 10
+ prev = page > 2 or cnt < 10
+ next = page + 1 < cnt or cnt < 10
+ last = page < cnt and cnt >= 10
slots = 11
left = right = Math.floor(slots / 2)
diff --git a/web/js/labitrack.d/43-browse.coffee b/web/js/labitrack.d/43-browse.coffee
index b0d3358..f630d90 100644
--- a/web/js/labitrack.d/43-browse.coffee
+++ b/web/js/labitrack.d/43-browse.coffee
@@ -35,7 +35,7 @@ browse = Backbone.View.extend {
pages = []
if stats != undefined
pgcnt = Math.ceil(stats.count / 10)
- pages = λ.pagination page, pgcnt
+ pages = λ.pagination '/browse', page, pgcnt
data = {
rows: @collection.toJSON(),
diff --git a/web/js/labitrack.d/55-search.coffee b/web/js/labitrack.d/55-search.coffee
new file mode 100644
index 0000000..38569af
--- /dev/null
+++ b/web/js/labitrack.d/55-search.coffee
@@ -0,0 +1,81 @@
+hdl_add = ->
+ console.log 'add'
+
+hdl_remove = ->
+ console.log 'remove'
+
+hdl_reset = ->
+ console.log 'reset'
+ @render()
+
+$ ->
+ form = $('.navbar-search')
+ q = form.find('input')[0]
+ form.bind 'submit', (e) ->
+ e.preventDefault()
+ url = '/search/' + encodeURIComponent q.value
+ Backbone.history.navigate(url, true)
+ return false
+
+collection = Backbone.Collection.extend {
+ model: λ.o,
+ url: -> '/search.json?offset='+(10*((@nextpage++)-1))+'&q='+encodeURIComponent(@q)
+ comparator: (object) -> object.id
+ fetchpage: (@q, page) ->
+ if page
+ @nextpage = page
+ @fetch()
+ parse: (data) ->
+ @meta = {count: data.count, query: data.query}
+ console.log data
+ return data.objects
+}
+
+search = Backbone.View.extend {
+ initialize: () ->
+ messages = @collection
+ messages.bind "reset", hdl_reset, @
+ messages.bind "add", hdl_add, @
+ messages.bind "remove", hdl_remove, @
+ render: (q, page) ->
+ if page then @page = page
+ if q then @q = q
+ page = @page
+ q = @q
+ meta = @collection.meta
+ pages = []
+ if meta != undefined
+ pgcnt = Math.ceil(meta.count / 10)
+ pages = λ.pagination '/search/'+q, page, pgcnt
+
+ data = {
+ rows: @collection.toJSON(),
+ pages
+ meta
+ }
+ console.log data, meta
+ $(@el).html λ.template 'searchtable', data
+}
+
+collection = new collection()
+
+view = λ.routableview.extend {
+ initialize: () ->
+ λ.routableview.prototype.initialize.call(@)
+ @search = new search({collection: collection})
+ render: (q, page) ->
+ console.log 'render', q, page
+ q = decodeURIComponent q
+ page || (page = 1)
+ page = parseInt page, 10
+ console.log 'page', page
+ λ.setcontent 'search', {page}
+ @search.el = $(@el).find('#objecttable_ph')[0]
+ console.log @search.render
+ @search.render q, page
+ @search.collection.fetchpage q, page
+}
+
+view.route 'search'
+view.route 'search/:q'
+view.route 'search/:q/page/:page'
diff --git a/web/labitrackd.lua b/web/labitrackd.lua
index 1d208df..e345dd4 100755
--- a/web/labitrackd.lua
+++ b/web/labitrackd.lua
@@ -101,7 +101,8 @@ local function add_json_row(res, values, i)
for j = 1, clen do
local k = values[0][j]
local v = point[j]
- if k == 'id' then
+ if k == nil then
+ elseif k == 'id' then
d[k] = tonumber(v)
elseif k == 'created' then
d[k] = tonumber(v)
@@ -154,6 +155,8 @@ assert(db:prepare('since', 'SELECT '..cols..' FROM objects ORDER BY id LIMIT 10
assert(db:prepare('insert', 'INSERT INTO objects (name, "desc", tags) VALUES ($1, $2, string_to_array($3, \',\')::text[]) RETURNING id;'))
assert(db:prepare('update', 'UPDATE objects SET name=$2, "desc"=$3, tags=string_to_array($4, \',\')::text[], updated=now() WHERE id = $1;'))
assert(db:prepare('count', 'SELECT COUNT(id) FROM objects;'))
+assert(db:prepare('search', 'SELECT * FROM (SELECT row_number() over (order by rank desc, updated desc) as rn, * FROM (SELECT count(*) over () as cnt, '..cols..', ts_rank_cd(textsearch, query) AS rank FROM objects, to_tsquery($1) query WHERE query @@ textsearch) ss1) ss2 where rn between $2+1 and $2+10;'))
+assert(db:prepare('search_plain', 'SELECT plainto_tsquery($1);'))
local function count()
return assert(db:run('count'))[1][1]
@@ -173,6 +176,8 @@ GET('/recent', htmlpage)
GET('/about', htmlpage)
GETM('^/view/(%d+)$', htmlpage)
GETM('^/edit/(%d+)$', htmlpage)
+GET('/search', htmlpage)
+GETM('^/search/', htmlpage)
GET('/js/corelibs.min.js', sendfile_js('js/dist/corelibs.min.js'))
GET('/js/corelibs.src.js', sendfile_js('js/dist/corelibs.src.js'))
@@ -200,6 +205,60 @@ GETM('^/browse/(%d+).json$', function(req, res, since)
res:add('}');
end)
+local function urldecode(str)
+ return str:gsub('+', ' '):gsub('%%(%x%x)', function (str)
+ return string.char(tonumber(str, 16))
+ end)
+end
+local function parse_qs(str)
+ local t = {}
+ for k, v in str:gmatch('([^&]+)=([^&]*)') do
+ t[urldecode(k)] = urldecode(v)
+ end
+ return t
+end
+
+GETM('^/search.json%??(.*)$', function(req, res, rawqs)
+ set_json_nocache_headers(res)
+
+ qs = parse_qs(rawqs)
+
+ q = qs['q']
+
+ if q == nil then
+ q = ''
+ end
+
+ offset = qs['offset']
+ if offset == nil then
+ offset = 0
+ end
+
+ _, result = pcall(db.run, db, 'search', q, offset)
+
+ if result == nil then
+ q = assert(db:run('search_plain', q))[1][1]
+ result = assert(db:run('search', q, offset))
+ end
+
+ local n = #result
+ if n > 0 then
+ cols = #result[0]
+ setmetatable(result[0], { __len = function(op)
+ return cols
+ end })
+ result[0][1] = nil
+ result[0][2] = nil
+ cnt = result[1][2]
+
+ res:add('{"count": %d, "offset": %d, "query": %s, "objects":', cnt, offset, json.encode(q));
+ add_json(res, result)
+ res:add('}');
+ else
+ res:add('{"count": 0, "offset": 0, "query": %s}', json.encode(q));
+ end
+end)
+
GET('/queue.json', function(req, res)
set_json_nocache_headers(res)
res:add('%s', json.encode(queue:stat()))
diff --git a/web/pub/index.html b/web/pub/index.html
index 80f6555..c2146ee 100644
--- a/web/pub/index.html
+++ b/web/pub/index.html
@@ -18,10 +18,9 @@
<li><a href="/recent">Recent</a></li>
<li><a href="/about">About</a></li>
</ul>
- <!--<form action="/search" class="pull-right">
- <input class="input-small" type="text" name="q" placeholder="#, name, desc">
- <button class="btn" type="submit">Search</button>
- </form>-->
+ <form class="navbar-search pull-right">
+ <input class="search-query input-medium" type="text" placeholder="#, name, desc">
+ </form>
</div>
</div>
</div>
diff --git a/web/templates/labitrack.d/search b/web/templates/labitrack.d/search
new file mode 100644
index 0000000..c811a31
--- /dev/null
+++ b/web/templates/labitrack.d/search
@@ -0,0 +1,2 @@
+<div class="hide tmplheader">Search</div>
+<div id="objecttable_ph"/>
diff --git a/web/templates/labitrack.d/searchtable b/web/templates/labitrack.d/searchtable
new file mode 100644
index 0000000..bfba433
--- /dev/null
+++ b/web/templates/labitrack.d/searchtable
@@ -0,0 +1,15 @@
+{{#if meta.count}}
+ <p>Found <strong>{{meta.count}}</strong> results for »<strong>{{meta.query}}</strong>«</p>
+ {{objecttable}}
+{{else}}
+ {{#if meta.query}}
+ <div class="alert alert-info">
+ No results for »<strong>{{meta.query}}</strong>«
+ </div>
+ {{else}}
+ <div class="alert alert-notice">
+ <strong>Use the search box!</strong>
+ It is the located in the top right corner.
+ </div>
+ {{/if}}
+{{/if}}