Skip to content

Instantly share code, notes, and snippets.

@gmarik
Last active February 17, 2016 23:14
Show Gist options
  • Save gmarik/5186b7a29ec48936dc10 to your computer and use it in GitHub Desktop.
Save gmarik/5186b7a29ec48936dc10 to your computer and use it in GitHub Desktop.
<html>
<head> <script src="https://code.jquery.com/jquery-2.2.0.min.js" type="text/javascript"></script>
</head>
<body>
<form action="/search">
<input type="text" name="q" autocomplete="off" placeholder="Search" />
</form>
<pre id="result"> </pre>
<script>
// basic implementation
var AutoComplete = function(searcher) {
var search = {}; // state
this.value = function() { return search.value; }
this.search = function(value) {
// abort ongoing request if any
if (search.promise) { search.promise.abort() }
var promise = searcher(value)
search = {promise: promise, value: value}
promise.always(function() { search = {} })
return promise;
}
}
// throttles `fn` calls for at least `timeout`
var defer = function(timeout, fn) {
var id; // state; if set then throttle is in progress
return function(e) {
if (id) { clearTimeout(id) }
id = setTimeout(function() { dfr = null; return fn(e) }, timeout)
}
}
var $results = $('#result'),
$form = $('form'),
$q = $form.find('input[name=q]'),
$searcher = function(value) { return $.get($form.attr('action'), {q: value}) },
ac = new AutoComplete($searcher);
// abort has to capture search value being aborted
var abort = function(value) {
return function(_, kind) {
if ("abort" != kind) {return};
$results.text($results.text() + "\naborting search for:" + value)
}
}
// app logic
var doSearch = function(value) {
// do not search for blank value
if ("" == value) { return }
// also skip if input value hasn't changed
if (ac.value() == value) { return }
$results.text("\nsearching for:" + value)
ac.search(value)
.then(function(responseText) { $results.text(responseText) })
.fail(abort(ac.value()))
}
// entry point
$(function() {
$q.focus()
$q.on('keyup', defer(400, function(e) { doSearch(e.target.value) }))
$('form').on('submit', function(e) { e.preventDefault(); doSearch($q.val()) })
})
</script>
</body>
</html>
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"time"
)
// Running example:
// $ go run searver.go
// $ open http://localhost:3030
func main() {
http.HandleFunc("/search", handleSearch)
http.HandleFunc("/", handleIndex)
log.Fatal(http.ListenAndServe(":3030", nil))
}
// handleSearch handles search/ search cancellation requests
func handleSearch(w http.ResponseWriter, r *http.Request) {
var (
// abort signals other goroutine that any work needs not to be completed
abort = make(chan struct{})
result = make(chan error)
q = r.FormValue("q")
log = func(str string) { fmt.Printf("[%s] %s\n", q, str) }
)
log("starting")
// search goroutine
go func() {
log("searching...")
result <- pseudoSearch(w, q, abort)
log("complete")
}()
select {
// assuming successful type assertion
//client diconnected
case <-w.(http.CloseNotifier).CloseNotify():
close(abort) // signal disconnection
case <-result:
// search completed
}
log("done")
}
// pseudoSearch is an example pseudo search implementation
// it renders 40 lines prefixed with a query string
// rendering of each line is slowed down with 100ms delay to resemble some latency
func pseudoSearch(w http.ResponseWriter, q string, abort <-chan struct{}) error {
for i := 0; i < 40; i += 1 {
select {
case <-time.After(100 * time.Millisecond):
fmt.Fprintln(w, q, "result", i)
case <-abort:
fmt.Printf("[%s] abort!\n", q)
return nil
}
}
return nil
}
// handleIndex reads `client.html` and renders it to a user-agent/browser
func handleIndex(w http.ResponseWriter, r *http.Request) {
html, err := ioutil.ReadFile("client.html")
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
}
fmt.Fprint(w, string(html))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment