From c519f16fc60ddc1e02be3c89b51c40b64e79fefc Mon Sep 17 00:00:00 2001 From: Fergal Moran Date: Sun, 30 Jun 2013 15:42:47 +0100 Subject: [PATCH] Added search bar --- spa/ajax.py | 16 + spa/api/v1/MixResource.py | 37 +- spa/models/_basemodel.py | 2 +- static/css/deepsouthsounds.css | 110 +- static/data/repos.json | 1093 ++++++++++++++++ static/js/app/appv2.coffee | 13 +- static/js/app/appv2.js | 16 +- static/js/app/lib/controller.coffee | 7 +- static/js/app/lib/controller.js | 19 +- static/js/app/lib/router.coffee | 9 +- static/js/app/lib/router.js | 18 +- static/js/app/site.js | 2 - .../headerView.coffee} | 13 +- .../{header.js => widgets/headerView.js} | 12 +- static/js/app/views/widgets/searchView.coffee | 37 + static/js/app/views/widgets/searchView.js | 68 + ...trap.datagrid.js => bootstrap-datagrid.js} | 0 .../js/libs/bootstrap/bootstrap-typeahead.js | 1159 +++++++++++++++++ templates/views/HeaderView.html | 6 +- templates/views/SearchResultView.html | 10 + templates/views/SearchView.html | 4 + 21 files changed, 2585 insertions(+), 66 deletions(-) create mode 100644 static/data/repos.json rename static/js/app/views/{header.coffee => widgets/headerView.coffee} (82%) rename static/js/app/views/{header.js => widgets/headerView.js} (88%) mode change 100755 => 100644 create mode 100644 static/js/app/views/widgets/searchView.coffee create mode 100644 static/js/app/views/widgets/searchView.js rename static/js/libs/bootstrap/{bootstrap.datagrid.js => bootstrap-datagrid.js} (100%) create mode 100644 static/js/libs/bootstrap/bootstrap-typeahead.js create mode 100644 templates/views/SearchResultView.html create mode 100644 templates/views/SearchView.html diff --git a/spa/ajax.py b/spa/ajax.py index 6177b1a..48e20b4 100755 --- a/spa/ajax.py +++ b/spa/ajax.py @@ -3,6 +3,7 @@ import logging from django.conf.urls import url from django.contrib.auth.decorators import login_required +from django.core import serializers from django.core.exceptions import ObjectDoesNotExist from django.db.models import get_model from django.http import HttpResponse, HttpResponseNotFound @@ -52,6 +53,7 @@ class AjaxHandler(object): name='ajax_upload_release_image'), url(r'^upload_avatar_image/$', 'spa.ajax.upload_avatar_image', name='ajax_upload_avatar_image'), url(r'^upload_mix_file_handler/$', 'spa.ajax.upload_mix_file_handler', name='ajax_upload_mix_file_handler'), + url(r'^lookup/search/$', 'spa.ajax.lookup_search', name='ajax_lookup'), url(r'^lookup/(?P\w+)/$', 'spa.ajax.lookup', name='ajax_lookup'), ] return pattern_list @@ -138,6 +140,7 @@ def live_now_playing(request): return None + @render_to('inc/release_player.html') def release_player(request, release_id): return HttpResponse('Hello Sailor') @@ -311,6 +314,19 @@ def upload_mix_file_handler(request): return HttpResponse(_get_json("Failed"), mimetype='application/json') +@csrf_exempt +def lookup_search(request): + query = request.GET['query'] if 'query' in request.GET else request.GET['q'] if 'q' in request.GET else '' + if query != '': + filter_field = Mix.get_lookup_filter_field() + kwargs = { + '{0}__{1}'.format(filter_field, 'icontains'): query, + } + rows = Mix.objects.values("title").filter(**kwargs) + #results = serializers.serialize("json", rows, fields="title",) + results = json.dumps(rows) + return HttpResponse(results, mimetype='application/json') + @csrf_exempt def lookup(request, source): query = request.GET['query'] if 'query' in request.GET else request.GET['q'] if 'q' in request.GET else '' diff --git a/spa/api/v1/MixResource.py b/spa/api/v1/MixResource.py index 2f5098e..775e85c 100755 --- a/spa/api/v1/MixResource.py +++ b/spa/api/v1/MixResource.py @@ -1,6 +1,8 @@ from django.conf.urls import url from django.core.exceptions import ObjectDoesNotExist +from django.core.paginator import Paginator, InvalidPage from django.db.models import Count +from django.http import Http404 from django.template.loader import render_to_string from tastypie import fields from tastypie.authorization import Authorization @@ -52,14 +54,17 @@ class MixResource(BackboneCompatibleResource): def prepend_urls(self): return [ + url(r"^(?P%s)/search%s$" % (self._meta.resource_name, trailing_slash()), + self.wrap_view('get_search'), + name="api_get_search"), url(r"^(?P%s)/(?P[\d]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"), url(r"^(?P%s)/(?P[\w\d-]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"), url(r"^(?P%s)/(?P\w[\w/-]*)/comments%s$" % ( - self._meta.resource_name, trailing_slash()), self.wrap_view('get_comments'), name="api_get_comments"), + self._meta.resource_name, trailing_slash()), self.wrap_view('get_comments'), name="api_get_comments"), url(r"^(?P%s)/(?P\w[\w/-]*)/activity%s$" % ( - self._meta.resource_name, trailing_slash()), self.wrap_view('get_activity'), name="api_get_activity"), + self._meta.resource_name, trailing_slash()), self.wrap_view('get_activity'), name="api_get_activity"), ] def get_comments(self, request, **kwargs): @@ -137,7 +142,6 @@ class MixResource(BackboneCompatibleResource): if user is not None: semi_filtered = semi_filtered.filter(user__slug=user) - return semi_filtered def hydrate_favourited(self, bundle): @@ -166,3 +170,30 @@ class MixResource(BackboneCompatibleResource): return bundle + def get_search(self, request, **kwargs): + self.method_check(request, allowed=['get']) + self.is_authenticated(request) + self.throttle_check(request) + + # Do the query. + sqs = Mix.objects.filter(title__icontains=request.GET.get('q', '')) + paginator = Paginator(sqs, 20) + + try: + page = paginator.page(int(request.GET.get('page', 1))) + except InvalidPage: + raise Http404("Sorry, no results on that page.") + + objects = [] + + for result in page.object_list: + bundle = self.build_bundle(obj=result, request=request) + bundle = self.full_dehydrate(bundle) + objects.append(bundle) + + object_list = { + 'objects': objects, + } + + self.log_throttled_access(request) + return self.create_response(request, object_list) diff --git a/spa/models/_basemodel.py b/spa/models/_basemodel.py index 773cf6a..e1d07b5 100755 --- a/spa/models/_basemodel.py +++ b/spa/models/_basemodel.py @@ -41,7 +41,7 @@ class _BaseModel(models.Model): def get_lookup_filter_field(cls): field_list = cls._meta.get_all_field_names() for field in field_list: - if field.endswith("name") or field.endswith("description"): + if field.endswith("title") or field.endswith("name") or field.endswith("description"): return field return "description" diff --git a/static/css/deepsouthsounds.css b/static/css/deepsouthsounds.css index ef061e4..f0f7097 100755 --- a/static/css/deepsouthsounds.css +++ b/static/css/deepsouthsounds.css @@ -451,15 +451,17 @@ div.event-content td { .now-playing-play { background-position: -90px 0px; } -.now-playing-pause{ + +.now-playing-pause { background-position: -210px 0px; } + .now-playing-bio p { display: inline-block; margin-left: 5px; } -#aaaa{ +#aaaa { border: 1px solid #802c59; -webkit-border-radius: 15px; @@ -476,13 +478,111 @@ div.event-content td { padding: 8px; } -.div-small-heading{ +.div-small-heading { width: 98%; text-align: center; margin-bottom: 8px; } -dss-datatable{ + +dss-datatable { cellpadding: 0; cellspacing: 0; border: 0; -} \ No newline at end of file +} + +img.flag { + height: 10px; + width: 15px; + padding-right: 10px; +} + +.search-result td { + vertical-align: top +} + +.search-result-image img { + height: 48px; + width: 48px; +} + +.search-result-info { + padding-left: 10px; + vertical-align: top; +} + +.search-result-synopsis { + font-size: .8em; + color: #888; +} + +.navbar-search .search-query { + padding-left: 29px !important; +} + +.twitter-typeahead .tt-query, +.twitter-typeahead .tt-hint { + margin-bottom: 0; +} + +.tt-dropdown-menu { + width: 280px; + margin-top: 2px; + background-color: #fff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + *border-right-width: 2px; + *border-bottom-width: 2px; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; +} + +.tt-suggestion { + display: block; + padding: 3px 20px; +} + +.tt-suggestion.tt-is-under-cursor { + color: #fff; + background-color: #0081c2; + background-image: -moz-linear-gradient(top, #0088cc, #0077b3); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); + background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); + background-image: -o-linear-gradient(top, #0088cc, #0077b3); + background-image: linear-gradient(to bottom, #0088cc, #0077b3); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0) +} + +.tt-suggestion.tt-is-under-cursor a { + color: #fff; +} + +.tt-suggestion p { + margin: 0; +} + +.navbar-search { + position: relative; +} + +.navbar-search .search-query { + padding-left: 29px; +} + +.navbar-search .icon-search { + position: absolute; + top: 7px; + left: 11px; + background-image: url("../img/glyphicons-halflings.png"); +} + +.tt-hint { + font-size: 0 !important; +} diff --git a/static/data/repos.json b/static/data/repos.json new file mode 100644 index 0000000..a69824b --- /dev/null +++ b/static/data/repos.json @@ -0,0 +1,1093 @@ +[ + { + "name": "typeahead.js", + "description": "A fast and fully-featured autocomplete library", + "language": "JavaScript", + "value": "typeahead.js", + "tokens": [ + "typeahead.js", + "JavaScript" + ] + }, + { + "name": "cassandra", + "description": "A Ruby client for the Cassandra distributed database", + "language": "Ruby", + "value": "cassandra", + "tokens": [ + "cassandra", + "Ruby" + ] + }, + { + "name": "hadoop-lzo", + "description": "Refactored version of code.google.com/hadoop-gpl-compression for hadoop 0.20", + "language": "Shell", + "value": "hadoop-lzo", + "tokens": [ + "hadoop", + "lzo", + "Shell", + "hadoop-lzo" + ] + }, + { + "name": "scribe", + "description": "A Ruby client library for Scribe", + "language": "Ruby", + "value": "scribe", + "tokens": [ + "scribe", + "Ruby" + ] + }, + { + "name": "thrift_client", + "description": "A Thrift client wrapper that encapsulates some common failover behavior", + "language": "Ruby", + "value": "thrift_client", + "tokens": [ + "thrift", + "client", + "Ruby", + "thrift_client" + ] + }, + { + "name": "mustache.js", + "description": "Minimal templating with {{mustaches}} in JavaScript", + "language": "JavaScript", + "value": "mustache.js", + "tokens": [ + "mustache.js", + "JavaScript" + ] + }, + { + "name": "grabby-hands", + "description": "A JVM Kestrel client that aggregates queues from multiple servers. Implemented in Scala with Java bindings. In use at Twitter for all JVM Search and Streaming Kestrel interactions.", + "language": "Scala", + "value": "grabby-hands", + "tokens": [ + "grabby", + "hands", + "Scala", + "grabby-hands" + ] + }, + { + "name": "gizzard", + "description": "A flexible sharding framework for creating eventually-consistent distributed datastores", + "language": "Scala", + "value": "gizzard", + "tokens": [ + "gizzard", + "Scala" + ] + }, + { + "name": "thrift", + "description": "Twitter's out-of-date, forked thrift", + "language": "C++", + "value": "thrift", + "tokens": [ + "thrift", + "C++" + ] + }, + { + "name": "flockdb", + "description": "A distributed, fault-tolerant graph database", + "language": "Scala", + "value": "flockdb", + "tokens": [ + "flockdb", + "Scala" + ] + }, + { + "name": "flockdb-client", + "description": "A Ruby client library for FlockDB", + "language": "Ruby", + "value": "flockdb-client", + "tokens": [ + "flockdb", + "client", + "Ruby", + "flockdb-client" + ] + }, + { + "name": "standard-project", + "description": "A slightly more standard sbt project plugin library ", + "language": "Scala", + "value": "standard-project", + "tokens": [ + "standard", + "project", + "Scala", + "standard-project" + ] + }, + { + "name": "snowflake", + "description": "Snowflake is a network service for generating unique ID numbers at high scale with some simple guarantees.", + "language": "Scala", + "value": "snowflake", + "tokens": [ + "snowflake", + "Scala" + ] + }, + { + "name": "haplocheirus", + "description": "A Redis-backed storage engine for timelines", + "language": "Scala", + "value": "haplocheirus", + "tokens": [ + "haplocheirus", + "Scala" + ] + }, + { + "name": "gizzmo", + "description": "A command-line client for Gizzard", + "language": "Ruby", + "value": "gizzmo", + "tokens": [ + "gizzmo", + "Ruby" + ] + }, + { + "name": "scala-zookeeper-client", + "description": "A Scala client library for ZooKeeper", + "language": "Scala", + "value": "scala-zookeeper-client", + "tokens": [ + "scala", + "zookeeper", + "client", + "Scala", + "scala-zookeeper-client" + ] + }, + { + "name": "rpc-client", + "description": "A scala library that encapsulates RPC communications.", + "language": "Scala", + "value": "rpc-client", + "tokens": [ + "rpc", + "client", + "Scala", + "rpc-client" + ] + }, + { + "name": "twitcher", + "description": "A tool for executing scripts when ZooKeeper nodes change.", + "language": "Python", + "value": "twitcher", + "tokens": [ + "twitcher", + "Python" + ] + }, + { + "name": "killdeer", + "description": "Killdeer is a simple server for replaying a sample of responses to sythentically recreate production response characteristics.", + "language": "Scala", + "value": "killdeer", + "tokens": [ + "killdeer", + "Scala" + ] + }, + { + "name": "ostrich", + "description": "A stats collector & reporter for Scala servers", + "language": "Scala", + "value": "ostrich", + "tokens": [ + "ostrich", + "Scala" + ] + }, + { + "name": "rubyenterpriseedition187-248", + "description": "Twitter's updates to Ruby Enterprise Edition, itself based on MRI 1.8.7-p248", + "language": "Ruby", + "value": "rubyenterpriseedition187-248", + "tokens": [ + "rubyenterpriseedition187", + "248", + "Ruby", + "rubyenterpriseedition187-248" + ] + }, + { + "name": "scala_school", + "description": "Lessons in the Fundamentals of Scala", + "language": "Scala", + "value": "scala_school", + "tokens": [ + "scala", + "school", + "Scala", + "scala_school" + ] + }, + { + "name": "querulous", + "description": "An agreeable way to talk to your database", + "language": "Scala", + "value": "querulous", + "tokens": [ + "querulous", + "Scala" + ] + }, + { + "name": "xrayspecs", + "description": "extensions to scala specs", + "language": "Scala", + "value": "xrayspecs", + "tokens": [ + "xrayspecs", + "Scala" + ] + }, + { + "name": "kestrel-client", + "description": "A Kestrel client library for Ruby", + "language": "Ruby", + "value": "kestrel-client", + "tokens": [ + "kestrel", + "client", + "Ruby", + "kestrel-client" + ] + }, + { + "name": "finagle", + "description": "A fault tolerant, protocol-agnostic RPC system", + "language": "Scala", + "value": "finagle", + "tokens": [ + "finagle", + "Scala" + ] + }, + { + "name": "naggati2", + "description": "Protocol builder for netty using scala", + "language": "Scala", + "value": "naggati2", + "tokens": [ + "naggati2", + "Scala" + ] + }, + { + "name": "twitter-text-conformance", + "description": "Conformance testing data for the twitter-text-* repositories", + "language": null, + "value": "twitter-text-conformance", + "tokens": [ + "twitter", + "text", + "conformance", + "twitter-text-conformance" + ] + }, + { + "name": "twitter-text-rb", + "description": "A library that does auto linking and extraction of usernames, lists and hashtags in tweets", + "language": "Ruby", + "value": "twitter-text-rb", + "tokens": [ + "twitter", + "text", + "rb", + "Ruby", + "twitter-text-rb" + ] + }, + { + "name": "twitter-text-java", + "description": "A Java implementation of Twitter's text processing library", + "language": "Java", + "value": "twitter-text-java", + "tokens": [ + "twitter", + "text", + "java", + "Java", + "twitter-text-java" + ] + }, + { + "name": "twitter-text-js", + "description": "A JavaScript implementation of Twitter's text processing library", + "language": "JavaScript", + "value": "twitter-text-js", + "tokens": [ + "twitter", + "text", + "js", + "JavaScript", + "twitter-text-js" + ] + }, + { + "name": "joauth", + "description": "A Scala library for authenticating HTTP Requests using OAuth", + "language": "Scala", + "value": "joauth", + "tokens": [ + "joauth", + "Scala" + ] + }, + { + "name": "schmemcached", + "description": "A prototype implementation of a Memcached client & server in Scala using Finagle", + "language": "Scala", + "value": "schmemcached", + "tokens": [ + "schmemcached", + "Scala" + ] + }, + { + "name": "chainsaw", + "description": "A thin Scala wrapper for SLF4J", + "language": "Scala", + "value": "chainsaw", + "tokens": [ + "chainsaw", + "Scala" + ] + }, + { + "name": "streamyj", + "description": "Scala sugar for the Jackson JSON parser", + "language": "Scala", + "value": "streamyj", + "tokens": [ + "streamyj", + "Scala" + ] + }, + { + "name": "cloudhopper-commons-util", + "description": "The ch-commons-util package contains common utility classes for Cloudhopper-based Java projects.", + "language": "Java", + "value": "cloudhopper-commons-util", + "tokens": [ + "cloudhopper", + "commons", + "util", + "Java", + "cloudhopper-commons-util" + ] + }, + { + "name": "twitter.github.com", + "description": "A listing of open source efforts at Twitter on GitHub", + "language": "JavaScript", + "value": "twitter.github.com", + "tokens": [ + "twitter.github.com", + "JavaScript" + ] + }, + { + "name": "time_constants", + "description": "Time constants, in seconds, so you don't have to use slow ActiveSupport helpers", + "language": "Ruby", + "value": "time_constants", + "tokens": [ + "time", + "constants", + "Ruby", + "time_constants" + ] + }, + { + "name": "commons", + "description": "Twitter common libraries for python and the JVM", + "language": "Java", + "value": "commons", + "tokens": [ + "commons", + "Java" + ] + }, + { + "name": "scala-bootstrapper", + "description": "initial setup for a scala library or server, using sbt", + "language": "Ruby", + "value": "scala-bootstrapper", + "tokens": [ + "scala", + "bootstrapper", + "Ruby", + "scala-bootstrapper" + ] + }, + { + "name": "sbt-thrift", + "description": "sbt rules for generating source stubs out of thrift IDLs, for java & scala", + "language": "Ruby", + "value": "sbt-thrift", + "tokens": [ + "sbt", + "thrift", + "Ruby", + "sbt-thrift" + ] + }, + { + "name": "cloudhopper-smpp", + "description": "Efficient, scalable, and flexible Java implementation of the Short Messaging Peer to Peer Protocol (SMPP)", + "language": "Java", + "value": "cloudhopper-smpp", + "tokens": [ + "cloudhopper", + "smpp", + "Java", + "cloudhopper-smpp" + ] + }, + { + "name": "cloudhopper-commons-charset", + "description": "Java utility classes for converting between charsets (mostly \"mobile\" in nature) such as Unicode to GSM-7/GSM-8 and vice versa.", + "language": "Java", + "value": "cloudhopper-commons-charset", + "tokens": [ + "cloudhopper", + "commons", + "charset", + "Java", + "cloudhopper-commons-charset" + ] + }, + { + "name": "cloudhopper-commons-gsm", + "description": "Java utility classes for working with GSM mobile technologies such as SMS and MMS.", + "language": "Java", + "value": "cloudhopper-commons-gsm", + "tokens": [ + "cloudhopper", + "commons", + "gsm", + "Java", + "cloudhopper-commons-gsm" + ] + }, + { + "name": "util", + "description": "Wonderful reusable code from Twitter", + "language": "Scala", + "value": "util", + "tokens": [ + "util", + "Scala" + ] + }, + { + "name": "Rowz", + "description": "A sample gizzard application", + "language": "Scala", + "value": "Rowz", + "tokens": [ + "rowz", + "Scala" + ] + }, + { + "name": "scala-json", + "description": "Scala JSON toolkit. Originally from the Odersky \"Stairway\" Book, tightened up and tests added by Twitter", + "language": "Scala", + "value": "scala-json", + "tokens": [ + "scala", + "json", + "Scala", + "scala-json" + ] + }, + { + "name": "twui", + "description": "A UI framework for Mac based on Core Animation", + "language": "Objective-C", + "value": "twui", + "tokens": [ + "twui", + "Objective-C" + ] + }, + { + "name": "bootstrap", + "description": "Sleek, intuitive, and powerful front-end framework for faster and easier web development.", + "language": "JavaScript", + "value": "bootstrap", + "tokens": [ + "bootstrap", + "JavaScript" + ] + }, + { + "name": "scrooge-runtime", + "description": "Runtime classes for scrooge-generated Thrift code", + "language": "Scala", + "value": "scrooge-runtime", + "tokens": [ + "scrooge", + "runtime", + "Scala", + "scrooge-runtime" + ] + }, + { + "name": "sbt-scrooge", + "description": "An SBT plugin that adds a mixin for doing Thrift code auto-generation during your compile phase", + "language": "Scala", + "value": "sbt-scrooge", + "tokens": [ + "sbt", + "scrooge", + "Scala", + "sbt-scrooge" + ] + }, + { + "name": "scrooge", + "description": "A Thrift generator for Scala", + "language": "Scala", + "value": "scrooge", + "tokens": [ + "scrooge", + "Scala" + ] + }, + { + "name": "webrat", + "description": "Webrat - Ruby Acceptance Testing for Web applications", + "language": "Ruby", + "value": "webrat", + "tokens": [ + "webrat", + "Ruby" + ] + }, + { + "name": "twemperf", + "description": "A tool for measuring memcached server performance", + "language": "C", + "value": "twemperf", + "tokens": [ + "twemperf", + "C" + ] + }, + { + "name": "pycascading", + "description": "A Python wrapper for Cascading", + "language": "Python", + "value": "pycascading", + "tokens": [ + "pycascading", + "Python" + ] + }, + { + "name": "hogan.js", + "description": "A compiler for the Mustache templating language", + "language": "JavaScript", + "value": "hogan.js", + "tokens": [ + "hogan.js", + "JavaScript" + ] + }, + { + "name": "mysql", + "description": "MySQL fork maintained and used at Twitter", + "language": "C", + "value": "mysql", + "tokens": [ + "mysql", + "C" + ] + }, + { + "name": "scalding", + "description": "A Scala API for Cascading", + "language": "Scala", + "value": "scalding", + "tokens": [ + "scalding", + "Scala" + ] + }, + { + "name": "cassie", + "description": "A Scala client for Cassandra", + "language": "Scala", + "value": "cassie", + "tokens": [ + "cassie", + "Scala" + ] + }, + { + "name": "effectivescala", + "description": "Twitter's Effective Scala Guide", + "language": "Shell", + "value": "effectivescala", + "tokens": [ + "effectivescala", + "Shell" + ] + }, + { + "name": "twitterActors", + "description": "Improved Scala actors library; used internally at Twitter", + "language": "Scala", + "value": "twitterActors", + "tokens": [ + "twitteractors", + "Scala" + ] + }, + { + "name": "mahout", + "description": "Twitter's fork of Apache Mahout (we intend to push changes upstream)", + "language": "Java", + "value": "mahout", + "tokens": [ + "mahout", + "Java" + ] + }, + { + "name": "cassovary", + "description": "Cassovary is a simple big graph processing library for the JVM", + "language": "Scala", + "value": "cassovary", + "tokens": [ + "cassovary", + "Scala" + ] + }, + { + "name": "twemproxy", + "description": "A fast, light-weight proxy for memcached and redis", + "language": "C", + "value": "twemproxy", + "tokens": [ + "twemproxy", + "C" + ] + }, + { + "name": "jvmgcprof", + "description": "A simple utility for profile allocation and garbage collection activity in the JVM", + "language": "C", + "value": "jvmgcprof", + "tokens": [ + "jvmgcprof", + "C" + ] + }, + { + "name": "twitter-cldr-rb", + "description": "Ruby implementation of the ICU (International Components for Unicode) that uses the Common Locale Data Repository to format dates, plurals, and more.", + "language": "Ruby", + "value": "twitter-cldr-rb", + "tokens": [ + "twitter", + "cldr", + "rb", + "Ruby", + "twitter-cldr-rb" + ] + }, + { + "name": "bootstrap-server", + "description": "The node server that powers the bootstrap customize page", + "language": "JavaScript", + "value": "bootstrap-server", + "tokens": [ + "bootstrap", + "server", + "JavaScript", + "bootstrap-server" + ] + }, + { + "name": "sbt-package-dist", + "description": "sbt 11 plugin codifying best practices for building, packaging, and publishing", + "language": "Scala", + "value": "sbt-package-dist", + "tokens": [ + "sbt", + "package", + "dist", + "Scala", + "sbt-package-dist" + ] + }, + { + "name": "ospriet", + "description": "An example audience moderation app built on Twitter", + "language": "JavaScript", + "value": "ospriet", + "tokens": [ + "ospriet", + "JavaScript" + ] + }, + { + "name": "innovators-patent-agreement", + "description": "Innovators Patent Agreement (IPA)", + "language": null, + "value": "innovators-patent-agreement", + "tokens": [ + "innovators", + "patent", + "agreement", + "innovators-patent-agreement" + ] + }, + { + "name": "recess", + "description": "A simple and attractive code quality tool for CSS built on top of LESS", + "language": "JavaScript", + "value": "recess", + "tokens": [ + "recess", + "JavaScript" + ] + }, + { + "name": "ambrose", + "description": "A platform for visualization and real-time monitoring of data workflows", + "language": "JavaScript", + "value": "ambrose", + "tokens": [ + "ambrose", + "JavaScript" + ] + }, + { + "name": "twitter-text-objc", + "description": "An Objective-C implementation of Twitter's text processing library", + "language": "Objective-C", + "value": "twitter-text-objc", + "tokens": [ + "twitter", + "text", + "objc", + "Objective-C", + "twitter-text-objc" + ] + }, + { + "name": "activerecord-reputation-system", + "description": "An Active Record Reputation System for Rails", + "language": "Ruby", + "value": "activerecord-reputation-system", + "tokens": [ + "activerecord", + "reputation", + "system", + "Ruby", + "activerecord-reputation-system" + ] + }, + { + "name": "twitter4j", + "description": "Twitter4J is an open-sourced, mavenized and Google App Engine safe Java library for the Twitter API which is released under the APL 2.0.", + "language": "Java", + "value": "twitter4j", + "tokens": [ + "twitter4j", + "Java" + ] + }, + { + "name": "zipkin", + "description": "Zipkin is a distributed tracing system", + "language": "Scala", + "value": "zipkin", + "tokens": [ + "zipkin", + "Scala" + ] + }, + { + "name": "elephant-twin", + "description": "Elephant Twin is a framework for creating indexes in Hadoop", + "language": "Java", + "value": "elephant-twin", + "tokens": [ + "elephant", + "twin", + "Java", + "elephant-twin" + ] + }, + { + "name": "elephant-twin-lzo", + "description": "Elephant Twin LZO uses Elephant Twin to create LZO block indexes", + "language": "Java", + "value": "elephant-twin-lzo", + "tokens": [ + "elephant", + "twin", + "lzo", + "Java", + "elephant-twin-lzo" + ] + }, + { + "name": "iago", + "description": "A load generator, built for engineers", + "language": "Scala", + "value": "iago", + "tokens": [ + "iago", + "Scala" + ] + }, + { + "name": "twemcache", + "description": "Twemcache is the Twitter Memcached", + "language": "C", + "value": "twemcache", + "tokens": [ + "twemcache", + "C" + ] + }, + { + "name": "twitter-cldr-js", + "description": "JavaScript implementation of the ICU (International Components for Unicode) that uses the Common Locale Data Repository to format dates, plurals, and more. Based on twitter-cldr-rb.", + "language": "JavaScript", + "value": "twitter-cldr-js", + "tokens": [ + "twitter", + "cldr", + "js", + "JavaScript", + "twitter-cldr-js" + ] + }, + { + "name": "algebird", + "description": "Abstract Algebra for Scala", + "language": "Scala", + "value": "algebird", + "tokens": [ + "algebird", + "Scala" + ] + }, + { + "name": "hdfs-du", + "description": "Visualize your HDFS cluster usage", + "language": "JavaScript", + "value": "hdfs-du", + "tokens": [ + "hdfs", + "du", + "JavaScript", + "hdfs-du" + ] + }, + { + "name": "clockworkraven", + "description": "Human-Powered Data Analysis with Mechanical Turk", + "language": "Ruby", + "value": "clockworkraven", + "tokens": [ + "clockworkraven", + "Ruby" + ] + }, + { + "name": "jerkson", + "description": "The Scala applewood bacon to Jackson's chicken breast: JSON cordon bleu.", + "language": "Scala", + "value": "jerkson", + "tokens": [ + "jerkson", + "Scala" + ] + }, + { + "name": "bower-server", + "description": "The Bower Server", + "language": "Ruby", + "value": "bower-server", + "tokens": [ + "bower", + "server", + "Ruby", + "bower-server" + ] + }, + { + "name": "bower", + "description": "A package manager for the web", + "language": "JavaScript", + "value": "bower", + "tokens": [ + "bower", + "JavaScript" + ] + }, + { + "name": "twitter-cldr-npm", + "description": "TwitterCldr npm package", + "language": "JavaScript", + "value": "twitter-cldr-npm", + "tokens": [ + "twitter", + "cldr", + "npm", + "JavaScript", + "twitter-cldr-npm" + ] + }, + { + "name": "tormenta", + "description": "Scala extensions for Storm", + "language": "Scala", + "value": "tormenta", + "tokens": [ + "tormenta", + "Scala" + ] + }, + { + "name": "sprockets-commonjs-twitter", + "description": "Adds CommonJS support to Sprockets", + "language": "JavaScript", + "value": "sprockets-commonjs-twitter", + "tokens": [ + "sprockets", + "commonjs", + "twitter", + "JavaScript", + "sprockets-commonjs-twitter" + ] + }, + { + "name": "scalding-commons", + "description": "Common extensions to the Scalding MapReduce DSL.", + "language": "Scala", + "value": "scalding-commons", + "tokens": [ + "scalding", + "commons", + "Scala", + "scalding-commons" + ] + }, + { + "name": "captured", + "description": "Quick screen capture sharing utility for Mac OS X.", + "language": "Ruby", + "value": "captured", + "tokens": [ + "captured", + "Ruby" + ] + }, + { + "name": "chill", + "description": "Scala extensions for the Kryo serialization library", + "language": "Scala", + "value": "chill", + "tokens": [ + "chill", + "Scala" + ] + }, + { + "name": "bookkeeper", + "description": "Twitter's fork of Apache BookKeeper (will push changes upstream eventually)", + "language": "Java", + "value": "bookkeeper", + "tokens": [ + "bookkeeper", + "Java" + ] + }, + { + "name": "secureheaders", + "description": "Security related headers all in one gem", + "language": "Ruby", + "value": "secureheaders", + "tokens": [ + "secureheaders", + "Ruby" + ] + }, + { + "name": "RTLtextarea", + "description": "Automatically detects RTL and configures a text input", + "language": "JavaScript", + "value": "RTLtextarea", + "tokens": [ + "rtltextarea", + "JavaScript" + ] + }, + { + "name": "bijection", + "description": "Reversible conversions between types", + "language": "Scala", + "value": "bijection", + "tokens": [ + "bijection", + "Scala" + ] + }, + { + "name": "fatcache", + "description": "Memcache on SSD", + "language": "C", + "value": "fatcache", + "tokens": [ + "fatcache", + "C" + ] + }, + { + "name": "rails", + "description": "Ruby on Rails", + "language": "Ruby", + "value": "rails", + "tokens": [ + "rails", + "Ruby" + ] + }, + { + "name": "flight", + "description": "A lightweight, component-based JavaScript framework", + "language": "JavaScript", + "value": "flight", + "tokens": [ + "flight", + "JavaScript" + ] + } +] diff --git a/static/js/app/appv2.coffee b/static/js/app/appv2.coffee index a2973e7..1941cdf 100755 --- a/static/js/app/appv2.coffee +++ b/static/js/app/appv2.coffee @@ -1,14 +1,8 @@ define ['backbone', 'marionette', 'vent', - 'app.lib/router', 'app.lib/panningRegion', 'app.lib/realtimeController', 'app.lib/audioController', 'views/header', + 'app.lib/router', 'app.lib/panningRegion', 'app.lib/realtimeController', 'app.lib/audioController', 'views/widgets/headerView', 'views/sidebar/sidebarView', 'models/mix/mixCollection'], (Backbone, Marionette, vent, DssRouter, PanningRegion, RealtimeController, AudioController, HeaderView, SidebarView, MixCollection) -> - Marionette.Region.prototype.open = (view) -> - @.$el.hide(); - @.$el.html(view.el); - @.$el.slideDown("fast"); - true - App = new Marionette.Application(); App.audioController = new AudioController(); App.realtimeController = new RealtimeController(); @@ -32,7 +26,6 @@ define ['backbone', 'marionette', 'vent', footerRegion: "#footer" sidebarRegion: "#sidebar" - App.addInitializer -> console.log("App: routing starting"); App.Router = new DssRouter(); @@ -69,7 +62,7 @@ define ['backbone', 'marionette', 'vent', true App.headerRegion.show(new HeaderView()); - sidebarView = new SidebarView(); - App.sidebarRegion.show(sidebarView) + #sidebarView = new SidebarView(); + #App.sidebarRegion.show(sidebarView) return App; diff --git a/static/js/app/appv2.js b/static/js/app/appv2.js index 2cc9708..f525550 100755 --- a/static/js/app/appv2.js +++ b/static/js/app/appv2.js @@ -1,21 +1,14 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.3.3 (function() { - define(['backbone', 'marionette', 'vent', 'app.lib/router', 'app.lib/panningRegion', 'app.lib/realtimeController', 'app.lib/audioController', 'views/header', 'views/sidebar/sidebarView', 'models/mix/mixCollection'], function(Backbone, Marionette, vent, DssRouter, PanningRegion, RealtimeController, AudioController, HeaderView, SidebarView, MixCollection) { - var App, sidebarView; - Marionette.Region.prototype.open = function(view) { - this.$el.hide(); - this.$el.html(view.el); - this.$el.slideDown("fast"); - return true; - }; + define(['backbone', 'marionette', 'vent', 'app.lib/router', 'app.lib/panningRegion', 'app.lib/realtimeController', 'app.lib/audioController', 'views/widgets/headerView', 'views/sidebar/sidebarView', 'models/mix/mixCollection'], function(Backbone, Marionette, vent, DssRouter, PanningRegion, RealtimeController, AudioController, HeaderView, SidebarView, MixCollection) { + var App; App = new Marionette.Application(); App.audioController = new AudioController(); App.realtimeController = new RealtimeController(); App.realtimeController.startSocketIO(); App.vent.on("routing:started", function() { var enablePushState, pushState; - console.log("App(vent): routing:started"); enablePushState = true; pushState = !!(enablePushState && window.history && window.history.pushState); @@ -41,7 +34,6 @@ App.addInitializer(function() { $(document).on("click", "a[href]:not([data-bypass])", function(evt) { var href, root; - href = { prop: $(this).prop("href"), attr: $(this).attr("href") @@ -81,8 +73,6 @@ }); }); App.headerRegion.show(new HeaderView()); - sidebarView = new SidebarView(); - App.sidebarRegion.show(sidebarView); return App; }); diff --git a/static/js/app/lib/controller.coffee b/static/js/app/lib/controller.coffee index f7544fd..1dd1ecb 100755 --- a/static/js/app/lib/controller.coffee +++ b/static/js/app/lib/controller.coffee @@ -1,12 +1,13 @@ -define ['app', 'marionette', +define ['app', 'marionette', 'vent', 'views/chat/chatView', 'models/mix/mixItem', 'views/mix/mixListView', 'views/mix/mixDetailView', 'views/mix/mixEditView', 'models/user/userItem', 'views/user/userListView', 'views/user/userEditView'], -(App, Marionette, ChatView, MixItem, MixListView, MixDetailView, MixEditView, UserItem, UserListView, UserEditView)-> +(App, Marionette, vent, ChatView, MixItem, MixListView, MixDetailView, MixEditView, UserItem, UserListView, UserEditView)-> class DssController extends Marionette.Controller + home: -> console.log "Controller: home" - @showMixList() + #@showMixList() true _showMixList: (options) -> diff --git a/static/js/app/lib/controller.js b/static/js/app/lib/controller.js index 474b931..1535b02 100755 --- a/static/js/app/lib/controller.js +++ b/static/js/app/lib/controller.js @@ -1,28 +1,25 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.3.3 (function() { var __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; - define(['app', 'marionette', 'views/chat/chatView', 'models/mix/mixItem', 'views/mix/mixListView', 'views/mix/mixDetailView', 'views/mix/mixEditView', 'models/user/userItem', 'views/user/userListView', 'views/user/userEditView'], function(App, Marionette, ChatView, MixItem, MixListView, MixDetailView, MixEditView, UserItem, UserListView, UserEditView) { - var DssController, _ref; - + define(['app', 'marionette', 'vent', 'views/chat/chatView', 'models/mix/mixItem', 'views/mix/mixListView', 'views/mix/mixDetailView', 'views/mix/mixEditView', 'models/user/userItem', 'views/user/userListView', 'views/user/userEditView'], function(App, Marionette, vent, ChatView, MixItem, MixListView, MixDetailView, MixEditView, UserItem, UserListView, UserEditView) { + var DssController; DssController = (function(_super) { + __extends(DssController, _super); function DssController() { - _ref = DssController.__super__.constructor.apply(this, arguments); - return _ref; + return DssController.__super__.constructor.apply(this, arguments); } DssController.prototype.home = function() { console.log("Controller: home"); - this.showMixList(); return true; }; DssController.prototype._showMixList = function(options) { var app; - console.log("Controller: _showMixList"); app = require('app'); app.contentRegion.show(new MixListView(options)); @@ -38,7 +35,6 @@ DssController.prototype.showMix = function(slug) { var app, mix; - console.log("Controller: showMix"); app = require('app'); mix = new MixItem({ @@ -57,7 +53,6 @@ DssController.prototype.uploadMix = function() { var app, mix; - console.log("Controller: mixUpload"); app = require('app'); mix = new MixItem({ @@ -74,7 +69,6 @@ DssController.prototype.editMix = function(slug) { var app, mix; - console.log("Controller: mixEdit"); app = require('app'); mix = new MixItem({ @@ -92,7 +86,6 @@ DssController.prototype.showChat = function() { var app; - console.log("Controller: showChat"); app = require('app'); return app.contentRegion.show(new ChatView()); @@ -100,7 +93,6 @@ DssController.prototype.showUserList = function(type) { var app; - console.log("Controller: showUserList"); app = require('app'); return app.contentRegion.show(new UserListView()); @@ -152,7 +144,6 @@ DssController.prototype.editUser = function() { var app, user; - console.log("Controller: editUser"); app = require('app'); user = new UserItem({ diff --git a/static/js/app/lib/router.coffee b/static/js/app/lib/router.coffee index d89b2e0..8d7752d 100755 --- a/static/js/app/lib/router.coffee +++ b/static/js/app/lib/router.coffee @@ -1,5 +1,5 @@ -define ['marionette', 'app.lib/controller'], -(Marionette, Controller) -> +define ['marionette', 'vent', 'app.lib/controller'], +(Marionette, vent, Controller) -> class DssRouter extends Marionette.AppRouter controller: new Controller, appRoutes: @@ -22,5 +22,10 @@ define ['marionette', 'app.lib/controller'], "user/:slug": "showUserDetail" "me": "editUser" + initialize: -> + console.log "Router: initializing" + @listenTo vent, "navigate:mix", (slug)-> + @navigate 'mix/' + slug, true + diff --git a/static/js/app/lib/router.js b/static/js/app/lib/router.js index 3c5c1e5..a51c86f 100755 --- a/static/js/app/lib/router.js +++ b/static/js/app/lib/router.js @@ -1,17 +1,16 @@ -// Generated by CoffeeScript 1.6.2 +// Generated by CoffeeScript 1.3.3 (function() { var __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; - define(['marionette', 'app.lib/controller'], function(Marionette, Controller) { - var DssRouter, _ref; - + define(['marionette', 'vent', 'app.lib/controller'], function(Marionette, vent, Controller) { + var DssRouter; return DssRouter = (function(_super) { + __extends(DssRouter, _super); function DssRouter() { - _ref = DssRouter.__super__.constructor.apply(this, arguments); - return _ref; + return DssRouter.__super__.constructor.apply(this, arguments); } DssRouter.prototype.controller = new Controller; @@ -34,6 +33,13 @@ "me": "editUser" }; + DssRouter.prototype.initialize = function() { + console.log("Router: initializing"); + return this.listenTo(vent, "navigate:mix", function(slug) { + return this.navigate('mix/' + slug, true); + }); + }; + return DssRouter; })(Marionette.AppRouter); diff --git a/static/js/app/site.js b/static/js/app/site.js index c9fd1e9..1cf8a5f 100755 --- a/static/js/app/site.js +++ b/static/js/app/site.js @@ -58,10 +58,8 @@ $(document).ajaxSend(function (event, xhr, settings) { if (com.podnoms.settings.isDebug) { $(document).on({ ajaxStart: function () { - console.log("Site: ajax request starting"); }, ajaxStop: function () { - console.log("Site: ajax request finished"); } }); } else { diff --git a/static/js/app/views/header.coffee b/static/js/app/views/widgets/headerView.coffee similarity index 82% rename from static/js/app/views/header.coffee rename to static/js/app/views/widgets/headerView.coffee index a4b4695..cf6ae8d 100755 --- a/static/js/app/views/header.coffee +++ b/static/js/app/views/widgets/headerView.coffee @@ -6,9 +6,9 @@ Copyright (c) 2012, Fergal Moran. All rights reserved. Code provided under the BSD License: ### -define ["underscore", "backbone", "vent", "utils", "text!/tpl/HeaderView"], -(_, Backbone, vent, utils, Template) -> - class HeaderView extends Backbone.View +define ["underscore", "marionette", "vent", "utils", "views/widgets/searchView", "text!/tpl/HeaderView"], +(_, Marionette, vent, utils, SearchView, Template) -> + class HeaderView extends Marionette.Layout template: _.template(Template) events: "click #header-play-pause-button": "togglePlayState" @@ -18,11 +18,18 @@ define ["underscore", "backbone", "vent", "utils", "text!/tpl/HeaderView"], ui: liveButton: "#header-live-button" + regions: + searchRegion: "#header-search" + initialize: -> @render() + @listenTo vent, "mix:play", @trackPlaying @listenTo vent, "mix:pause", @trackPaused + onShow: -> + @searchRegion.show(new SearchView()) + login: -> utils.modal "/dlg/LoginView" diff --git a/static/js/app/views/header.js b/static/js/app/views/widgets/headerView.js old mode 100755 new mode 100644 similarity index 88% rename from static/js/app/views/header.js rename to static/js/app/views/widgets/headerView.js index 28f6b64..be2b982 --- a/static/js/app/views/header.js +++ b/static/js/app/views/widgets/headerView.js @@ -14,7 +14,7 @@ Code provided under the BSD License: var __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; - define(["underscore", "backbone", "vent", "utils", "text!/tpl/HeaderView"], function(_, Backbone, vent, utils, Template) { + define(["underscore", "marionette", "vent", "utils", "views/widgets/searchView", "text!/tpl/HeaderView"], function(_, Marionette, vent, utils, SearchView, Template) { var HeaderView; HeaderView = (function(_super) { @@ -37,12 +37,20 @@ Code provided under the BSD License: liveButton: "#header-live-button" }; + HeaderView.prototype.regions = { + searchRegion: "#header-search" + }; + HeaderView.prototype.initialize = function() { this.render(); this.listenTo(vent, "mix:play", this.trackPlaying); return this.listenTo(vent, "mix:pause", this.trackPaused); }; + HeaderView.prototype.onShow = function() { + return this.searchRegion.show(new SearchView()); + }; + HeaderView.prototype.login = function() { return utils.modal("/dlg/LoginView"); }; @@ -85,7 +93,7 @@ Code provided under the BSD License: return HeaderView; - })(Backbone.View); + })(Marionette.Layout); return HeaderView; }); diff --git a/static/js/app/views/widgets/searchView.coffee b/static/js/app/views/widgets/searchView.coffee new file mode 100644 index 0000000..4d64f71 --- /dev/null +++ b/static/js/app/views/widgets/searchView.coffee @@ -0,0 +1,37 @@ +define ['jquery', 'underscore', 'libs/bootstrap/bootstrap-typeahead', 'marionette', 'vent', 'text!/tpl/SearchView', + 'text!/tpl/SearchResultView'], +($, _, Typeahead, Marionette, vent, Template, SearchResultView) -> + class SearchView extends Marionette.CompositeView + template: _.template(Template) + + ui: + searchText: '#search-text' + + engine: + compile: (template) -> + compiled = _.template(template) + render: (context) -> + compiled context + + onShow: -> + console.log("SearchView: onShow") + t = $('#search-text', @el).typeahead + name: "search" + engine: @engine + valueKey: "title" + template: SearchResultView + remote: + url: "/api/v1/mix/search?q=%QUERY" + dataType: "json" + filter: (parsedResponse)-> + parsedResponse.objects + + $('.tt-hint', @el).addClass('search-query'); + $('.tt-hint', @el).addClass('span3'); + + t.on 'typeahead:selected': (event, datum, dataset_name) -> + console.log("SearchView: Selected") + vent.trigger 'navigate:mix', datum.slug + $('#search-text', @el).blur() + $('.tt-hint', @el).blur() + SearchView \ No newline at end of file diff --git a/static/js/app/views/widgets/searchView.js b/static/js/app/views/widgets/searchView.js new file mode 100644 index 0000000..ef919ec --- /dev/null +++ b/static/js/app/views/widgets/searchView.js @@ -0,0 +1,68 @@ +// Generated by CoffeeScript 1.3.3 +(function() { + var __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + define(['jquery', 'underscore', 'libs/bootstrap/bootstrap-typeahead', 'marionette', 'vent', 'text!/tpl/SearchView', 'text!/tpl/SearchResultView'], function($, _, Typeahead, Marionette, vent, Template, SearchResultView) { + var SearchView; + SearchView = (function(_super) { + + __extends(SearchView, _super); + + function SearchView() { + return SearchView.__super__.constructor.apply(this, arguments); + } + + SearchView.prototype.template = _.template(Template); + + SearchView.prototype.ui = { + searchText: '#search-text' + }; + + SearchView.prototype.engine = { + compile: function(template) { + var compiled; + compiled = _.template(template); + return { + render: function(context) { + return compiled(context); + } + }; + } + }; + + SearchView.prototype.onShow = function() { + var t; + console.log("SearchView: onShow"); + t = $('#search-text', this.el).typeahead({ + name: "search", + engine: this.engine, + valueKey: "title", + template: SearchResultView, + remote: { + url: "/api/v1/mix/search?q=%QUERY", + dataType: "json", + filter: function(parsedResponse) { + return parsedResponse.objects; + } + } + }); + $('.tt-hint', this.el).addClass('search-query'); + $('.tt-hint', this.el).addClass('span3'); + return t.on({ + 'typeahead:selected': function(event, datum, dataset_name) { + console.log("SearchView: Selected"); + vent.trigger('navigate:mix', datum.slug); + $('#search-text', this.el).blur(); + return $('.tt-hint', this.el).blur(); + } + }); + }; + + return SearchView; + + })(Marionette.CompositeView); + return SearchView; + }); + +}).call(this); diff --git a/static/js/libs/bootstrap/bootstrap.datagrid.js b/static/js/libs/bootstrap/bootstrap-datagrid.js similarity index 100% rename from static/js/libs/bootstrap/bootstrap.datagrid.js rename to static/js/libs/bootstrap/bootstrap-datagrid.js diff --git a/static/js/libs/bootstrap/bootstrap-typeahead.js b/static/js/libs/bootstrap/bootstrap-typeahead.js new file mode 100644 index 0000000..c0aa837 --- /dev/null +++ b/static/js/libs/bootstrap/bootstrap-typeahead.js @@ -0,0 +1,1159 @@ +/*! + * typeahead.js 0.9.3 + * https://github.com/twitter/typeahead + * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT + */ + +(function ($) { + var VERSION = "0.9.3"; + var utils = { + isMsie: function () { + var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent); + return match ? parseInt(match[2], 10) : false; + }, + isBlankString: function (str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function (str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function (obj) { + return typeof obj === "string"; + }, + isNumber: function (obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function (obj) { + return typeof obj === "undefined"; + }, + bind: $.proxy, + bindAll: function (obj) { + var val; + for (var key in obj) { + $.isFunction(val = obj[key]) && (obj[key] = $.proxy(val, obj)); + } + }, + indexOf: function (haystack, needle) { + for (var i = 0; i < haystack.length; i++) { + if (haystack[i] === needle) { + return i; + } + } + return -1; + }, + each: $.each, + map: $.map, + filter: $.grep, + every: function (obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function (key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function (obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function (key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + getUniqueId: function () { + var counter = 0; + return function () { + return counter++; + }; + }(), + defer: function (fn) { + setTimeout(fn, 0); + }, + debounce: function (func, wait, immediate) { + var timeout, result; + return function () { + var context = this, args = arguments, later, callNow; + later = function () { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function (func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function () { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function () { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + tokenizeQuery: function (str) { + return $.trim(str).toLowerCase().split(/[\s]+/); + }, + tokenizeText: function (str) { + return $.trim(str).toLowerCase().split(/[\s\-_]+/); + }, + getProtocol: function () { + return location.protocol; + }, + noop: function () { + } + }; + var EventTarget = function () { + var eventSplitter = /\s+/; + return { + on: function (events, callback) { + var event; + if (!callback) { + return this; + } + this._callbacks = this._callbacks || {}; + events = events.split(eventSplitter); + while (event = events.shift()) { + this._callbacks[event] = this._callbacks[event] || []; + this._callbacks[event].push(callback); + } + return this; + }, + trigger: function (events, data) { + var event, callbacks; + if (!this._callbacks) { + return this; + } + events = events.split(eventSplitter); + while (event = events.shift()) { + if (callbacks = this._callbacks[event]) { + for (var i = 0; i < callbacks.length; i += 1) { + callbacks[i].call(this, { + type: event, + data: data + }); + } + } + } + return this; + } + }; + }(); + var EventBus = function () { + var namespace = "typeahead:"; + + function EventBus(o) { + if (!o || !o.el) { + $.error("EventBus initialized without el"); + } + this.$el = $(o.el); + } + + utils.mixin(EventBus.prototype, { + trigger: function (type) { + var args = [].slice.call(arguments, 1); + this.$el.trigger(namespace + type, args); + } + }); + return EventBus; + }(); + var PersistentStorage = function () { + var ls, methods; + try { + ls = window.localStorage; + ls.setItem("~~~", "!"); + ls.removeItem("~~~"); + } catch (err) { + ls = null; + } + function PersistentStorage(namespace) { + this.prefix = [ "__", namespace, "__" ].join(""); + this.ttlKey = "__ttl__"; + this.keyMatcher = new RegExp("^" + this.prefix); + } + + if (ls && window.JSON) { + methods = { + _prefix: function (key) { + return this.prefix + key; + }, + _ttlKey: function (key) { + return this._prefix(key) + this.ttlKey; + }, + get: function (key) { + if (this.isExpired(key)) { + this.remove(key); + } + return decode(ls.getItem(this._prefix(key))); + }, + set: function (key, val, ttl) { + if (utils.isNumber(ttl)) { + ls.setItem(this._ttlKey(key), encode(now() + ttl)); + } else { + ls.removeItem(this._ttlKey(key)); + } + return ls.setItem(this._prefix(key), encode(val)); + }, + remove: function (key) { + ls.removeItem(this._ttlKey(key)); + ls.removeItem(this._prefix(key)); + return this; + }, + clear: function () { + var i, key, keys = [], len = ls.length; + for (i = 0; i < len; i++) { + if ((key = ls.key(i)).match(this.keyMatcher)) { + keys.push(key.replace(this.keyMatcher, "")); + } + } + for (i = keys.length; i--;) { + this.remove(keys[i]); + } + return this; + }, + isExpired: function (key) { + var ttl = decode(ls.getItem(this._ttlKey(key))); + return utils.isNumber(ttl) && now() > ttl ? true : false; + } + }; + } else { + methods = { + get: utils.noop, + set: utils.noop, + remove: utils.noop, + clear: utils.noop, + isExpired: utils.noop + }; + } + utils.mixin(PersistentStorage.prototype, methods); + return PersistentStorage; + function now() { + return new Date().getTime(); + } + + function encode(val) { + return JSON.stringify(utils.isUndefined(val) ? null : val); + } + + function decode(val) { + return JSON.parse(val); + } + }(); + var RequestCache = function () { + function RequestCache(o) { + utils.bindAll(this); + o = o || {}; + this.sizeLimit = o.sizeLimit || 10; + this.cache = {}; + this.cachedKeysByAge = []; + } + + utils.mixin(RequestCache.prototype, { + get: function (url) { + return this.cache[url]; + }, + set: function (url, resp) { + var requestToEvict; + if (this.cachedKeysByAge.length === this.sizeLimit) { + requestToEvict = this.cachedKeysByAge.shift(); + delete this.cache[requestToEvict]; + } + this.cache[url] = resp; + this.cachedKeysByAge.push(url); + } + }); + return RequestCache; + }(); + var Transport = function () { + var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests, requestCache; + + function Transport(o) { + utils.bindAll(this); + o = utils.isString(o) ? { + url: o + } : o; + requestCache = requestCache || new RequestCache(); + maxPendingRequests = utils.isNumber(o.maxParallelRequests) ? o.maxParallelRequests : maxPendingRequests || 6; + this.url = o.url; + this.wildcard = o.wildcard || "%QUERY"; + this.filter = o.filter; + this.replace = o.replace; + this.ajaxSettings = { + type: "get", + cache: o.cache, + timeout: o.timeout, + dataType: o.dataType || "json", + beforeSend: o.beforeSend + }; + this._get = (/^throttle$/i.test(o.rateLimitFn) ? utils.throttle : utils.debounce)(this._get, o.rateLimitWait || 300); + } + + utils.mixin(Transport.prototype, { + _get: function (url, cb) { + var that = this; + if (belowPendingRequestsThreshold()) { + this._sendRequest(url).done(done); + } else { + this.onDeckRequestArgs = [].slice.call(arguments, 0); + } + function done(resp) { + var data = that.filter ? that.filter(resp) : resp; + cb && cb(data); + requestCache.set(url, resp); + } + }, + _sendRequest: function (url) { + var that = this, jqXhr = pendingRequests[url]; + if (!jqXhr) { + incrementPendingRequests(); + jqXhr = pendingRequests[url] = $.ajax(url, this.ajaxSettings).always(always); + } + return jqXhr; + function always() { + decrementPendingRequests(); + pendingRequests[url] = null; + if (that.onDeckRequestArgs) { + that._get.apply(that, that.onDeckRequestArgs); + that.onDeckRequestArgs = null; + } + } + }, + get: function (query, cb) { + var that = this, encodedQuery = encodeURIComponent(query || ""), url, resp; + cb = cb || utils.noop; + url = this.replace ? this.replace(this.url, encodedQuery) : this.url.replace(this.wildcard, encodedQuery); + if (resp = requestCache.get(url)) { + utils.defer(function () { + cb(that.filter ? that.filter(resp) : resp); + }); + } else { + this._get(url, cb); + } + return !!resp; + } + }); + return Transport; + function incrementPendingRequests() { + pendingRequestsCount++; + } + + function decrementPendingRequests() { + pendingRequestsCount--; + } + + function belowPendingRequestsThreshold() { + return pendingRequestsCount < maxPendingRequests; + } + }(); + var Dataset = function () { + var keys = { + thumbprint: "thumbprint", + protocol: "protocol", + itemHash: "itemHash", + adjacencyList: "adjacencyList" + }; + + function Dataset(o) { + utils.bindAll(this); + if (utils.isString(o.template) && !o.engine) { + $.error("no template engine specified"); + } + if (!o.local && !o.prefetch && !o.remote) { + $.error("one of local, prefetch, or remote is required"); + } + this.name = o.name || utils.getUniqueId(); + this.limit = o.limit || 5; + this.minLength = o.minLength || 1; + this.header = o.header; + this.footer = o.footer; + this.valueKey = o.valueKey || "value"; + this.template = compileTemplate(o.template, o.engine, this.valueKey); + this.local = o.local; + this.prefetch = o.prefetch; + this.remote = o.remote; + this.itemHash = {}; + this.adjacencyList = {}; + this.storage = o.name ? new PersistentStorage(o.name) : null; + } + + utils.mixin(Dataset.prototype, { + _processLocalData: function (data) { + this._mergeProcessedData(this._processData(data)); + }, + _loadPrefetchData: function (o) { + var that = this, thumbprint = VERSION + (o.thumbprint || ""), storedThumbprint, storedProtocol, storedItemHash, storedAdjacencyList, isExpired, deferred; + if (this.storage) { + storedThumbprint = this.storage.get(keys.thumbprint); + storedProtocol = this.storage.get(keys.protocol); + storedItemHash = this.storage.get(keys.itemHash); + storedAdjacencyList = this.storage.get(keys.adjacencyList); + } + isExpired = storedThumbprint !== thumbprint || storedProtocol !== utils.getProtocol(); + o = utils.isString(o) ? { + url: o + } : o; + o.ttl = utils.isNumber(o.ttl) ? o.ttl : 24 * 60 * 60 * 1e3; + if (storedItemHash && storedAdjacencyList && !isExpired) { + this._mergeProcessedData({ + itemHash: storedItemHash, + adjacencyList: storedAdjacencyList + }); + deferred = $.Deferred().resolve(); + } else { + deferred = $.getJSON(o.url).done(processPrefetchData); + } + return deferred; + function processPrefetchData(data) { + var filteredData = o.filter ? o.filter(data) : data, processedData = that._processData(filteredData), itemHash = processedData.itemHash, adjacencyList = processedData.adjacencyList; + if (that.storage) { + that.storage.set(keys.itemHash, itemHash, o.ttl); + that.storage.set(keys.adjacencyList, adjacencyList, o.ttl); + that.storage.set(keys.thumbprint, thumbprint, o.ttl); + that.storage.set(keys.protocol, utils.getProtocol(), o.ttl); + } + that._mergeProcessedData(processedData); + } + }, + _transformDatum: function (datum) { + var value = utils.isString(datum) ? datum : datum[this.valueKey], tokens = datum.tokens || utils.tokenizeText(value), item = { + value: value, + tokens: tokens + }; + if (utils.isString(datum)) { + item.datum = {}; + item.datum[this.valueKey] = datum; + } else { + item.datum = datum; + } + item.tokens = utils.filter(item.tokens, function (token) { + return !utils.isBlankString(token); + }); + item.tokens = utils.map(item.tokens, function (token) { + return token.toLowerCase(); + }); + return item; + }, + _processData: function (data) { + var that = this, itemHash = {}, adjacencyList = {}; + utils.each(data, function (i, datum) { + var item = that._transformDatum(datum), id = utils.getUniqueId(item.value); + itemHash[id] = item; + utils.each(item.tokens, function (i, token) { + var character = token.charAt(0), adjacency = adjacencyList[character] || (adjacencyList[character] = [ id ]); + !~utils.indexOf(adjacency, id) && adjacency.push(id); + }); + }); + return { + itemHash: itemHash, + adjacencyList: adjacencyList + }; + }, + _mergeProcessedData: function (processedData) { + var that = this; + utils.mixin(this.itemHash, processedData.itemHash); + utils.each(processedData.adjacencyList, function (character, adjacency) { + var masterAdjacency = that.adjacencyList[character]; + that.adjacencyList[character] = masterAdjacency ? masterAdjacency.concat(adjacency) : adjacency; + }); + }, + _getLocalSuggestions: function (terms) { + var that = this, firstChars = [], lists = [], shortestList, suggestions = []; + utils.each(terms, function (i, term) { + var firstChar = term.charAt(0); + !~utils.indexOf(firstChars, firstChar) && firstChars.push(firstChar); + }); + utils.each(firstChars, function (i, firstChar) { + var list = that.adjacencyList[firstChar]; + if (!list) { + return false; + } + lists.push(list); + if (!shortestList || list.length < shortestList.length) { + shortestList = list; + } + }); + if (lists.length < firstChars.length) { + return []; + } + utils.each(shortestList, function (i, id) { + var item = that.itemHash[id], isCandidate, isMatch; + isCandidate = utils.every(lists, function (list) { + return ~utils.indexOf(list, id); + }); + isMatch = isCandidate && utils.every(terms, function (term) { + return utils.some(item.tokens, function (token) { + return token.indexOf(term) === 0; + }); + }); + isMatch && suggestions.push(item); + }); + return suggestions; + }, + initialize: function () { + var deferred; + this.local && this._processLocalData(this.local); + this.transport = this.remote ? new Transport(this.remote) : null; + deferred = this.prefetch ? this._loadPrefetchData(this.prefetch) : $.Deferred().resolve(); + this.local = this.prefetch = this.remote = null; + this.initialize = function () { + return deferred; + }; + return deferred; + }, + getSuggestions: function (query, cb) { + var that = this, terms, suggestions, cacheHit = false; + if (query.length < this.minLength) { + return; + } + terms = utils.tokenizeQuery(query); + suggestions = this._getLocalSuggestions(terms).slice(0, this.limit); + if (suggestions.length < this.limit && this.transport) { + cacheHit = this.transport.get(query, processRemoteData); + } + !cacheHit && cb && cb(suggestions); + function processRemoteData(data) { + suggestions = suggestions.slice(0); + utils.each(data, function (i, datum) { + var item = that._transformDatum(datum), isDuplicate; + isDuplicate = utils.some(suggestions, function (suggestion) { + return item.value === suggestion.value; + }); + !isDuplicate && suggestions.push(item); + return suggestions.length < that.limit; + }); + cb && cb(suggestions); + } + } + }); + return Dataset; + function compileTemplate(template, engine, valueKey) { + var renderFn, compiledTemplate; + if (utils.isFunction(template)) { + renderFn = template; + } else if (utils.isString(template)) { + compiledTemplate = engine.compile(template); + renderFn = utils.bind(compiledTemplate.render, compiledTemplate); + } else { + renderFn = function (context) { + return "

" + context[valueKey] + "

"; + }; + } + return renderFn; + } + }(); + var InputView = function () { + function InputView(o) { + var that = this; + utils.bindAll(this); + this.specialKeyCodeMap = { + 9: "tab", + 27: "esc", + 37: "left", + 39: "right", + 13: "enter", + 38: "up", + 40: "down" + }; + this.$hint = $(o.hint); + this.$input = $(o.input).on("blur.tt", this._handleBlur).on("focus.tt", this._handleFocus).on("keydown.tt", this._handleSpecialKeyEvent); + if (!utils.isMsie()) { + this.$input.on("input.tt", this._compareQueryToInputValue); + } else { + this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function ($e) { + if (that.specialKeyCodeMap[$e.which || $e.keyCode]) { + return; + } + utils.defer(that._compareQueryToInputValue); + }); + } + this.query = this.$input.val(); + this.$overflowHelper = buildOverflowHelper(this.$input); + } + + utils.mixin(InputView.prototype, EventTarget, { + _handleFocus: function () { + this.trigger("focused"); + }, + _handleBlur: function () { + this.trigger("blured"); + }, + _handleSpecialKeyEvent: function ($e) { + var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode]; + keyName && this.trigger(keyName + "Keyed", $e); + }, + _compareQueryToInputValue: function () { + var inputValue = this.getInputValue(), isSameQuery = compareQueries(this.query, inputValue), isSameQueryExceptWhitespace = isSameQuery ? this.query.length !== inputValue.length : false; + if (isSameQueryExceptWhitespace) { + this.trigger("whitespaceChanged", { + value: this.query + }); + } else if (!isSameQuery) { + this.trigger("queryChanged", { + value: this.query = inputValue + }); + } + }, + destroy: function () { + this.$hint.off(".tt"); + this.$input.off(".tt"); + this.$hint = this.$input = this.$overflowHelper = null; + }, + focus: function () { + this.$input.focus(); + }, + blur: function () { + this.$input.blur(); + }, + getQuery: function () { + return this.query; + }, + setQuery: function (query) { + this.query = query; + }, + getInputValue: function () { + return this.$input.val(); + }, + setInputValue: function (value, silent) { + this.$input.val(value); + !silent && this._compareQueryToInputValue(); + }, + getHintValue: function () { + return this.$hint.val(); + }, + setHintValue: function (value) { + this.$hint.val(value); + }, + getLanguageDirection: function () { + return (this.$input.css("direction") || "ltr").toLowerCase(); + }, + isOverflow: function () { + this.$overflowHelper.text(this.getInputValue()); + return this.$overflowHelper.width() > this.$input.width(); + }, + isCursorAtEnd: function () { + var valueLength = this.$input.val().length, selectionStart = this.$input[0].selectionStart, range; + if (utils.isNumber(selectionStart)) { + return selectionStart === valueLength; + } else if (document.selection) { + range = document.selection.createRange(); + range.moveStart("character", -valueLength); + return valueLength === range.text.length; + } + return true; + } + }); + return InputView; + function buildOverflowHelper($input) { + return $("").css({ + position: "absolute", + left: "-9999px", + visibility: "hidden", + whiteSpace: "nowrap", + fontFamily: $input.css("font-family"), + fontSize: $input.css("font-size"), + fontStyle: $input.css("font-style"), + fontVariant: $input.css("font-variant"), + fontWeight: $input.css("font-weight"), + wordSpacing: $input.css("word-spacing"), + letterSpacing: $input.css("letter-spacing"), + textIndent: $input.css("text-indent"), + textRendering: $input.css("text-rendering"), + textTransform: $input.css("text-transform") + }).insertAfter($input); + } + + function compareQueries(a, b) { + a = (a || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + b = (b || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + return a === b; + } + }(); + var DropdownView = function () { + var html = { + suggestionsList: '' + }, css = { + suggestionsList: { + display: "block" + }, + suggestion: { + whiteSpace: "nowrap", + cursor: "pointer" + }, + suggestionChild: { + whiteSpace: "normal" + } + }; + + function DropdownView(o) { + utils.bindAll(this); + this.isOpen = false; + this.isEmpty = true; + this.isMouseOverDropdown = false; + this.$menu = $(o.menu).on("mouseenter.tt", this._handleMouseenter).on("mouseleave.tt", this._handleMouseleave).on("click.tt", ".tt-suggestion", this._handleSelection).on("mouseover.tt", ".tt-suggestion", this._handleMouseover); + } + + utils.mixin(DropdownView.prototype, EventTarget, { + _handleMouseenter: function () { + this.isMouseOverDropdown = true; + }, + _handleMouseleave: function () { + this.isMouseOverDropdown = false; + }, + _handleMouseover: function ($e) { + var $suggestion = $($e.currentTarget); + this._getSuggestions().removeClass("tt-is-under-cursor"); + $suggestion.addClass("tt-is-under-cursor"); + }, + _handleSelection: function ($e) { + var $suggestion = $($e.currentTarget); + this.trigger("suggestionSelected", extractSuggestion($suggestion)); + }, + _show: function () { + this.$menu.css("display", "block"); + }, + _hide: function () { + this.$menu.hide(); + }, + _moveCursor: function (increment) { + var $suggestions, $cur, nextIndex, $underCursor; + if (!this.isVisible()) { + return; + } + $suggestions = this._getSuggestions(); + $cur = $suggestions.filter(".tt-is-under-cursor"); + $cur.removeClass("tt-is-under-cursor"); + nextIndex = $suggestions.index($cur) + increment; + nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1; + if (nextIndex === -1) { + this.trigger("cursorRemoved"); + return; + } else if (nextIndex < -1) { + nextIndex = $suggestions.length - 1; + } + $underCursor = $suggestions.eq(nextIndex).addClass("tt-is-under-cursor"); + this._ensureVisibility($underCursor); + this.trigger("cursorMoved", extractSuggestion($underCursor)); + }, + _getSuggestions: function () { + return this.$menu.find(".tt-suggestions > .tt-suggestion"); + }, + _ensureVisibility: function ($el) { + var menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10), menuScrollTop = this.$menu.scrollTop(), elTop = $el.position().top, elBottom = elTop + $el.outerHeight(true); + if (elTop < 0) { + this.$menu.scrollTop(menuScrollTop + elTop); + } else if (menuHeight < elBottom) { + this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); + } + }, + destroy: function () { + this.$menu.off(".tt"); + this.$menu = null; + }, + isVisible: function () { + return this.isOpen && !this.isEmpty; + }, + closeUnlessMouseIsOverDropdown: function () { + if (!this.isMouseOverDropdown) { + this.close(); + } + }, + close: function () { + if (this.isOpen) { + this.isOpen = false; + this.isMouseOverDropdown = false; + this._hide(); + this.$menu.find(".tt-suggestions > .tt-suggestion").removeClass("tt-is-under-cursor"); + this.trigger("closed"); + } + }, + open: function () { + if (!this.isOpen) { + this.isOpen = true; + !this.isEmpty && this._show(); + this.trigger("opened"); + } + }, + setLanguageDirection: function (dir) { + var ltrCss = { + left: "0", + right: "auto" + }, rtlCss = { + left: "auto", + right: " 0" + }; + dir === "ltr" ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss); + }, + moveCursorUp: function () { + this._moveCursor(-1); + }, + moveCursorDown: function () { + this._moveCursor(+1); + }, + getSuggestionUnderCursor: function () { + var $suggestion = this._getSuggestions().filter(".tt-is-under-cursor").first(); + return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; + }, + getFirstSuggestion: function () { + var $suggestion = this._getSuggestions().first(); + return $suggestion.length > 0 ? extractSuggestion($suggestion) : null; + }, + renderSuggestions: function (dataset, suggestions) { + var datasetClassName = "tt-dataset-" + dataset.name, wrapper = '
%body
', compiledHtml, $suggestionsList, $dataset = this.$menu.find("." + datasetClassName), elBuilder, fragment, $el; + if ($dataset.length === 0) { + $suggestionsList = $(html.suggestionsList).css(css.suggestionsList); + $dataset = $("
").addClass(datasetClassName).append(dataset.header).append($suggestionsList).append(dataset.footer).appendTo(this.$menu); + } + if (suggestions.length > 0) { + this.isEmpty = false; + this.isOpen && this._show(); + elBuilder = document.createElement("div"); + fragment = document.createDocumentFragment(); + utils.each(suggestions, function (i, suggestion) { + suggestion.dataset = dataset.name; + compiledHtml = dataset.template(suggestion.datum); + elBuilder.innerHTML = wrapper.replace("%body", compiledHtml); + $el = $(elBuilder.firstChild).css(css.suggestion).data("suggestion", suggestion); + $el.children().each(function () { + $(this).css(css.suggestionChild); + }); + fragment.appendChild($el[0]); + }); + $dataset.show().find(".tt-suggestions").html(fragment); + } else { + this.clearSuggestions(dataset.name); + } + this.trigger("suggestionsRendered"); + }, + clearSuggestions: function (datasetName) { + var $datasets = datasetName ? this.$menu.find(".tt-dataset-" + datasetName) : this.$menu.find('[class^="tt-dataset-"]'), $suggestions = $datasets.find(".tt-suggestions"); + $datasets.hide(); + $suggestions.empty(); + if (this._getSuggestions().length === 0) { + this.isEmpty = true; + this._hide(); + } + } + }); + return DropdownView; + function extractSuggestion($el) { + return $el.data("suggestion"); + } + }(); + var TypeaheadView = function () { + var html = { + wrapper: '', + hint: '', + dropdown: '' + }, css = { + wrapper: { + position: "relative", + display: "inline-block" + }, + hint: { + position: "absolute", + top: "0", + left: "0", + borderColor: "transparent", + boxShadow: "none" + }, + query: { + position: "relative", + verticalAlign: "top", + backgroundColor: "transparent" + }, + dropdown: { + position: "absolute", + top: "100%", + left: "0", + zIndex: "100", + display: "none" + } + }; + if (utils.isMsie()) { + utils.mixin(css.query, { + backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" + }); + } + if (utils.isMsie() && utils.isMsie() <= 7) { + utils.mixin(css.wrapper, { + display: "inline", + zoom: "1" + }); + utils.mixin(css.query, { + marginTop: "-1px" + }); + } + function TypeaheadView(o) { + var $menu, $input, $hint; + utils.bindAll(this); + this.$node = buildDomStructure(o.input); + this.datasets = o.datasets; + this.dir = null; + this.eventBus = o.eventBus; + $menu = this.$node.find(".tt-dropdown-menu"); + $input = this.$node.find(".tt-query"); + $hint = this.$node.find(".tt-hint"); + this.dropdownView = new DropdownView({ + menu: $menu + }).on("suggestionSelected", this._handleSelection).on("cursorMoved", this._clearHint).on("cursorMoved", this._setInputValueToSuggestionUnderCursor).on("cursorRemoved", this._setInputValueToQuery).on("cursorRemoved", this._updateHint).on("suggestionsRendered", this._updateHint).on("opened", this._updateHint).on("closed", this._clearHint).on("opened closed", this._propagateEvent); + this.inputView = new InputView({ + input: $input, + hint: $hint + }).on("focused", this._openDropdown).on("blured", this._closeDropdown).on("blured", this._setInputValueToQuery).on("enterKeyed tabKeyed", this._handleSelection).on("queryChanged", this._clearHint).on("queryChanged", this._clearSuggestions).on("queryChanged", this._getSuggestions).on("whitespaceChanged", this._updateHint).on("queryChanged whitespaceChanged", this._openDropdown).on("queryChanged whitespaceChanged", this._setLanguageDirection).on("escKeyed", this._closeDropdown).on("escKeyed", this._setInputValueToQuery).on("tabKeyed upKeyed downKeyed", this._managePreventDefault).on("upKeyed downKeyed", this._moveDropdownCursor).on("upKeyed downKeyed", this._openDropdown).on("tabKeyed leftKeyed rightKeyed", this._autocomplete); + } + + utils.mixin(TypeaheadView.prototype, EventTarget, { + _managePreventDefault: function (e) { + var $e = e.data, hint, inputValue, preventDefault = false; + switch (e.type) { + case "tabKeyed": + hint = this.inputView.getHintValue(); + inputValue = this.inputView.getInputValue(); + preventDefault = hint && hint !== inputValue; + break; + + case "upKeyed": + case "downKeyed": + preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey; + break; + } + preventDefault && $e.preventDefault(); + }, + _setLanguageDirection: function () { + var dir = this.inputView.getLanguageDirection(); + if (dir !== this.dir) { + this.dir = dir; + this.$node.css("direction", dir); + this.dropdownView.setLanguageDirection(dir); + } + }, + _updateHint: function () { + var suggestion = this.dropdownView.getFirstSuggestion(), hint = suggestion ? suggestion.value : null, dropdownIsVisible = this.dropdownView.isVisible(), inputHasOverflow = this.inputView.isOverflow(), inputValue, query, escapedQuery, beginsWithQuery, match; + if (hint && dropdownIsVisible && !inputHasOverflow) { + inputValue = this.inputView.getInputValue(); + query = inputValue.replace(/\s{2,}/g, " ").replace(/^\s+/g, ""); + escapedQuery = utils.escapeRegExChars(query); + beginsWithQuery = new RegExp("^(?:" + escapedQuery + ")(.*$)", "i"); + match = beginsWithQuery.exec(hint); + this.inputView.setHintValue(inputValue + (match ? match[1] : "")); + } + }, + _clearHint: function () { + this.inputView.setHintValue(""); + }, + _clearSuggestions: function () { + this.dropdownView.clearSuggestions(); + }, + _setInputValueToQuery: function () { + this.inputView.setInputValue(this.inputView.getQuery()); + }, + _setInputValueToSuggestionUnderCursor: function (e) { + var suggestion = e.data; + this.inputView.setInputValue(suggestion.value, true); + }, + _openDropdown: function () { + this.dropdownView.open(); + }, + _closeDropdown: function (e) { + this.dropdownView[e.type === "blured" ? "closeUnlessMouseIsOverDropdown" : "close"](); + }, + _moveDropdownCursor: function (e) { + var $e = e.data; + if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) { + this.dropdownView[e.type === "upKeyed" ? "moveCursorUp" : "moveCursorDown"](); + } + }, + _handleSelection: function (e) { + var byClick = e.type === "suggestionSelected", suggestion = byClick ? e.data : this.dropdownView.getSuggestionUnderCursor(); + if (suggestion) { + this.inputView.setInputValue(suggestion.value); + byClick ? this.inputView.focus() : e.data.preventDefault(); + byClick && utils.isMsie() ? utils.defer(this.dropdownView.close) : this.dropdownView.close(); + this.eventBus.trigger("selected", suggestion.datum, suggestion.dataset); + } + }, + _getSuggestions: function () { + var that = this, query = this.inputView.getQuery(); + if (utils.isBlankString(query)) { + return; + } + utils.each(this.datasets, function (i, dataset) { + dataset.getSuggestions(query, function (suggestions) { + if (query === that.inputView.getQuery()) { + that.dropdownView.renderSuggestions(dataset, suggestions); + } + }); + }); + }, + _autocomplete: function (e) { + var isCursorAtEnd, ignoreEvent, query, hint, suggestion; + if (e.type === "rightKeyed" || e.type === "leftKeyed") { + isCursorAtEnd = this.inputView.isCursorAtEnd(); + ignoreEvent = this.inputView.getLanguageDirection() === "ltr" ? e.type === "leftKeyed" : e.type === "rightKeyed"; + if (!isCursorAtEnd || ignoreEvent) { + return; + } + } + query = this.inputView.getQuery(); + hint = this.inputView.getHintValue(); + if (hint !== "" && query !== hint) { + suggestion = this.dropdownView.getFirstSuggestion(); + this.inputView.setInputValue(suggestion.value); + this.eventBus.trigger("autocompleted", suggestion.datum, suggestion.dataset); + } + }, + _propagateEvent: function (e) { + this.eventBus.trigger(e.type); + }, + destroy: function () { + this.inputView.destroy(); + this.dropdownView.destroy(); + destroyDomStructure(this.$node); + this.$node = null; + }, + setQuery: function (query) { + this.inputView.setQuery(query); + this.inputView.setInputValue(query); + this._clearHint(); + this._clearSuggestions(); + this._getSuggestions(); + } + }); + return TypeaheadView; + function buildDomStructure(input) { + var $wrapper = $(html.wrapper), $dropdown = $(html.dropdown), $input = $(input), $hint = $(html.hint); + $wrapper = $wrapper.css(css.wrapper); + $dropdown = $dropdown.css(css.dropdown); + $hint.css(css.hint).css({ + backgroundAttachment: $input.css("background-attachment"), + backgroundClip: $input.css("background-clip"), + backgroundColor: $input.css("background-color"), + backgroundImage: $input.css("background-image"), + backgroundOrigin: $input.css("background-origin"), + backgroundPosition: $input.css("background-position"), + backgroundRepeat: $input.css("background-repeat"), + backgroundSize: $input.css("background-size") + }); + $input.data("ttAttrs", { + dir: $input.attr("dir"), + autocomplete: $input.attr("autocomplete"), + spellcheck: $input.attr("spellcheck"), + style: $input.attr("style") + }); + $input.addClass("tt-query").attr({ + autocomplete: "off", + spellcheck: false + }).css(css.query); + try { + !$input.attr("dir") && $input.attr("dir", "auto"); + } catch (e) { + } + return $input.wrap($wrapper).parent().prepend($hint).append($dropdown); + } + + function destroyDomStructure($node) { + var $input = $node.find(".tt-query"); + utils.each($input.data("ttAttrs"), function (key, val) { + utils.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); + }); + $input.detach().removeData("ttAttrs").removeClass("tt-query").insertAfter($node); + $node.remove(); + } + }(); + (function () { + var cache = {}, viewKey = "ttView", methods; + methods = { + initialize: function (datasetDefs) { + var datasets; + datasetDefs = utils.isArray(datasetDefs) ? datasetDefs : [ datasetDefs ]; + if (datasetDefs.length === 0) { + $.error("no datasets provided"); + } + datasets = utils.map(datasetDefs, function (o) { + var dataset = cache[o.name] ? cache[o.name] : new Dataset(o); + if (o.name) { + cache[o.name] = dataset; + } + return dataset; + }); + return this.each(initialize); + function initialize() { + var $input = $(this), deferreds, eventBus = new EventBus({ + el: $input + }); + deferreds = utils.map(datasets, function (dataset) { + return dataset.initialize(); + }); + $input.data(viewKey, new TypeaheadView({ + input: $input, + eventBus: eventBus = new EventBus({ + el: $input + }), + datasets: datasets + })); + $.when.apply($, deferreds).always(function () { + utils.defer(function () { + eventBus.trigger("initialized"); + }); + }); + } + }, + destroy: function () { + return this.each(destroy); + function destroy() { + var $this = $(this), view = $this.data(viewKey); + if (view) { + view.destroy(); + $this.removeData(viewKey); + } + } + }, + setQuery: function (query) { + return this.each(setQuery); + function setQuery() { + var view = $(this).data(viewKey); + view && view.setQuery(query); + } + } + }; + jQuery.fn.typeahead = function (method) { + if (methods[method]) { + return methods[method].apply(this, [].slice.call(arguments, 1)); + } else { + return methods.initialize.apply(this, arguments); + } + }; + })(); +})(window.jQuery); \ No newline at end of file diff --git a/templates/views/HeaderView.html b/templates/views/HeaderView.html index 6391b91..2a20496 100755 --- a/templates/views/HeaderView.html +++ b/templates/views/HeaderView.html @@ -41,7 +41,9 @@ Upload {% endif %} + -{% comment %} + {% comment %} -{% endcomment %} + {% endcomment %} diff --git a/templates/views/SearchResultView.html b/templates/views/SearchResultView.html new file mode 100644 index 0000000..df1024f --- /dev/null +++ b/templates/views/SearchResultView.html @@ -0,0 +1,10 @@ + + + + + + + +
+
<%= title %>
+
diff --git a/templates/views/SearchView.html b/templates/views/SearchView.html new file mode 100644 index 0000000..bc7c435 --- /dev/null +++ b/templates/views/SearchView.html @@ -0,0 +1,4 @@ +