From 802a86a276784601e0a952611bfcfb823831608f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Sloth=20T=C3=B8nnesen?= Date: Fri, 10 Aug 2012 21:14:52 +0000 Subject: implement search support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Asbjørn Sloth Tønnesen --- web/js/labitrack.d/.gitignore | 1 + web/js/labitrack.d/31-handlebars-helpers.js | 3 ++ web/js/labitrack.d/39-pagination.coffee | 50 ++++++++++-------- web/js/labitrack.d/43-browse.coffee | 2 +- web/js/labitrack.d/55-search.coffee | 81 +++++++++++++++++++++++++++++ web/labitrackd.lua | 61 +++++++++++++++++++++- web/pub/index.html | 7 ++- web/templates/labitrack.d/search | 2 + web/templates/labitrack.d/searchtable | 15 ++++++ 9 files changed, 195 insertions(+), 27 deletions(-) create mode 100644 web/js/labitrack.d/55-search.coffee create mode 100644 web/templates/labitrack.d/search create mode 100644 web/templates/labitrack.d/searchtable 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: '|←' classes: 'prev' } - prev: (pg) -> @pages.push { - id: 'prev' - link: link pg - label: '←' - } - next: (pg) -> @pages.push { - id: 'next' - link: link pg - label: '→' - } + prev: (pg) -> + i = + id: 'prev' + link: @link pg + label: '←' + if @pg is 1 + i.link = undefined + i.classes = 'disabled' + @pages.push i + next: (pg) -> + i = + id: 'next' + link: @link pg + label: '→' + 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: '→|' 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 @@
  • Recent
  • About
  • - + 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 @@ +
    Search
    +
    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}} +

    Found {{meta.count}} results for »{{meta.query}}«

    + {{objecttable}} +{{else}} + {{#if meta.query}} +
    + No results for »{{meta.query}}« +
    + {{else}} +
    + Use the search box! + It is the located in the top right corner. +
    + {{/if}} +{{/if}} -- cgit v1.2.1