Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Adding all js/css/png/dependencies to downloads section.

  • Loading branch information...
commit b12dda598f73162cf34c286d77dfe27c25c90fb3 1 parent c39bec2
Samuel Clay samuelclay authored
Showing with 18,532 additions and 10 deletions.
  1. +1 −1  LICENSE
  2. +263 −0 build-min/dependencies.js
  3. BIN  build-min/dependencies.js.gz
  4. +1 −0  build-min/visualsearch-datauri.css
  5. BIN  build-min/visualsearch-datauri.css.gz
  6. +1 −0  build-min/visualsearch.css
  7. BIN  build-min/visualsearch.css.gz
  8. +54 −0 build-min/visualsearch.js
  9. BIN  build-min/visualsearch.js.gz
  10. +2 −0  build-min/visualsearch_templates.js
  11. BIN  build-min/visualsearch_templates.js.gz
  12. +12,247 −0 build/dependencies.js
  13. +284 −0 build/visualsearch-datauri.css
  14. +284 −0 build/visualsearch.css
  15. +1,352 −0 build/visualsearch.js
  16. +7 −0 build/visualsearch_templates.js
  17. +129 −0 demo/github.css
  18. +585 −0 demo/highlight.js
  19. +53 −0 demo/javascript.js
  20. +103 −0 demo/xml.js
  21. +14 −0 docs/backbone_extensions.html
  22. +186 −0 docs/docco.css
  23. +72 −0 docs/hotkeys.html
  24. +53 −0 docs/inflector.html
  25. +114 −0 docs/jquery_extensions.html
  26. +270 −0 docs/search_box.html
  27. +306 −0 docs/search_facet.html
  28. +33 −0 docs/search_facets.html
  29. +219 −0 docs/search_input.html
  30. +85 −0 docs/search_parser.html
  31. +51 −0 docs/search_query.html
  32. +36 −0 docs/visual_search.html
  33. +79 −9 index.html
  34. BIN  public/.DS_Store
  35. +19 −0 public/css/icons.css
  36. +30 −0 public/css/reset.css
  37. +233 −0 public/css/workspace.css
  38. BIN  public/images/embed/icons/cancel_search.png
  39. BIN  public/images/embed/icons/search_glyph.png
  40. +31 −0 public/js/models/search_facets.js
  41. +49 −0 public/js/models/search_query.js
  42. +10 −0 public/js/templates/search_box.jst
  43. +9 −0 public/js/templates/search_facet.jst
  44. +1 −0  public/js/templates/search_input.jst
  45. +15 −0 public/js/utils/backbone_extensions.js
  46. +73 −0 public/js/utils/hotkeys.js
  47. +59 −0 public/js/utils/inflector.js
  48. +122 −0 public/js/utils/jquery_extensions.js
  49. +88 −0 public/js/utils/search_parser.js
  50. +330 −0 public/js/views/search_box.js
  51. +313 −0 public/js/views/search_facet.js
  52. +226 −0 public/js/views/search_input.js
  53. +40 −0 public/js/visual_search.js
2  LICENSE
View
@@ -1,4 +1,4 @@
-Copyright (c) 2011 Samuel Clay, DocumentCloud
+Copyright (c) 2011 Samuel Clay, @samuelclay, DocumentCloud
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
263 build-min/dependencies.js
View
263 additions, 0 deletions not shown
BIN  build-min/dependencies.js.gz
View
Binary file not shown
1  build-min/visualsearch-datauri.css
View
@@ -0,0 +1 @@
+.VS-search .icon{background-repeat:no-repeat;background-position:center center;vertical-align:middle;width:16px;height:16px}.VS-search .icon.cancel_search{width:11px;height:11px;background-position:center 0;background-image:url("data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAWCAYAAAAW5GZjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAb9JREFUeNqUUr1qAkEQ3j0khQp6kihaeGgEEa18gTQR0iRY+BaBSMDGwidIEUKqFL6BopgqBAJ5AMFGjUU0d4WHEvwJarvZ77gRIzGYgb1hZr+Z75vZ40IIzqTNZrPj8Xicn0wmmcViEXS73aaqqq+BQODG6/W+A8MBNk3zfDAY3C6Xy0O2ZS6X6zMSiVwHg8FHLjtq7Xb7RQKj7BeTzVCgJ5PJU2U0GhUk7REuMpkMi8fjFggeMeecrVYrFRId0CgTAgDDMFg4HLbA8IjJgHNgGEr0er0fQIphUmZAwdSUADUB4RFDsz3oSMF6CLzZkQqgGebz+Z75dDqNdTqdp13bgDmdTj2VSp0oWHg0Gr2UNH2Z/9o+yMv7K4/HY/C/XhDUfr//jl7QQVT9fp/V63VWqVRYt9tliUSCZbPZg1wux9Lp9PqFeK1Wu9A0DdXz7YM87i0FrVZLs4Fi1wmFQh/NZjOmVKvVgq7rR/QflMtlixGedjwcDlUpMQ9tbzalkAAB2/R297mNW+sT2wUbUnA//V/nYrH4QOBNABUQuFQq3TNMuc82sDVrz41G42yvPeODAwZQ0QzwiJEnzLcAAwBJ6WXlwoBgZAAAAABJRU5ErkJggg==");cursor:pointer}.VS-search .icon.cancel_search:hover{background-position:center -11px}.VS-search .icon.search_glyph{width:12px;height:12px;background-image:url("data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAUZJREFUeNpUUM2qgmAQzS8NiUgLzTIXLZQW1QuI9AY9QPSW9gQ9QiriwpJQEBVrVWT2d7p2L9xZzDdzZs7M+YYqy/J8Ptu2vd/v4zgeDAaqqk4mE47jar9GnU6nzWbjOA5FUa/Xq0Jns9l8Pud5vkpp58cwAOzhcBhFkeu6GNztdg3D+Db5vo9nOp2iiWGYTqdDCMFe4LquI0aVpGmKR9M0lmUbjQY8YiBJklTb4YkoilBzOBzq9TogeMQIJEmqmlAlo9EIyXa7tSyrKAp4xEBkWUb5q2k8Hh+PR8/zwjCEgufz+aESstvtoKnVan2GgY31kBkEAfT1ej1FUZDiNIIgrFYr9H1ug3teLpfH43G/3/FBUJGu1+s8z8FZLpc0mmiabrfbf5fEumazuVgsTNO8Xq+3242qRNT+G0CMz7IMzH6//xZgAA60tj6rqzxpAAAAAElFTkSuQmCC")}.VS-search div,.VS-search span,.VS-search a,.VS-search img,.VS-search ul,.VS-search li,.VS-search form,.VS-search label,.ui-autocomplete ul,.ui-autocomplete li,.ui-autocomplete{margin:0;padding:0;border:0;outline:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline}.VS-search :focus{outline:0}.VS-search{line-height:1;color:black}.VS-search ol,.VS-search ul{list-style:none}.VS-search{font-family:Arial,sans-serif;color:#373737;font-size:12px}.VS-search input{display:block;border:none;outline:none;margin:0;padding:4px;background:transparent;font-size:16px;line-height:20px;width:100%}.VS-interface,.VS-search .dialog,.VS-search input{font-family:"Lucida Grande","Lucida Sans Unicode",Helvetica,Arial,sans-serif!important;line-height:1.1em}#search_button{float:right;margin:3px 29px 0 6px}input.search_box{width:10px;z-index:100;position:relative}#search_box_wrapper .menu_content{z-index:100}.VS-search .VS-search-box{cursor:text;position:relative;background:transparent;border:2px solid #ccc;border-radius:16px;-webkit-border-radius:16px;-moz-border-radius:16px;background-color:#fafafa;-webkit-box-shadow:inset 0 0 3px #ccc;-moz-box-shadow:inset 0 0 3px #ccc;box-shadow:inset 0 0 3px #ccc;min-height:29px;height:auto}.VS-search .VS-search-box.VS-focus{border-color:#acf;-webkit-box-shadow:inset 0 0 3px #acf;-moz-box-shadow:inset 0 0 3px #acf;box-shadow:inset 0 0 3px #acf}.VS-search .search_inner{position:relative;margin:0 25px 0 18px;padding:0 3px;overflow:hidden}.VS-search .search_facet{float:left;margin:0;padding:0 0 0 13px;position:relative}.VS-search .search_facet.is_selected{margin:5px -3px 5px -3px;-webkit-border-radius:16px;-moz-border-radius:16px;border-radius:16px}.VS-search .search_facet.is_selected{background-color:#cbd1f1;background-image:-moz-linear-gradient(top,#cbd1f1,#afb5ec);background-image:-ms-linear-gradient(top,#cbd1f1,#afb5ec);background-image:-o-linear-gradient(top,#cbd1f1,#afb5ec);background-image:-webkit-gradient(linear,left top,left bottom,from(#cbd1f1),to(#afb5ec));background-image:-webkit-linear-gradient(top,#cbd1f1,#afb5ec);background-image:linear-gradient(top,#cbd1f1,#afb5ec);filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#CBD1F1',EndColorStr='#AFB5EC')}.VS-search .search_facet .category{float:left;text-transform:uppercase;font-weight:bold;font-size:10px;color:#b0b0b0;text-shadow:1px 1px 0 #FFF;padding:9px 0 6px;line-height:13px;cursor:pointer}.VS-search .search_facet.is_selected .category{padding:4px 0 0;margin-left:3px;text-shadow:1px 1px 0 #d0d0d0}.VS-search .search_facet .search_facet_input_container{float:left}.VS-search .search_facet input{margin:0;padding:0;color:#999;font-size:13px;line-height:16px;padding:6px 0 7px 4px;height:16px;width:auto;z-index:100;position:relative}.VS-search .search_facet.is_selected input{padding-top:1px;padding-bottom:2px;padding-right:3px}.VS-search .search_facet.is_editing input,.VS-search .search_facet.is_selected input{color:#202020}.VS-search .search_facet .search_facet_remove{position:absolute;left:0;top:9px;opacity:.5}.VS-search .search_facet.is_selected .search_facet_remove{top:4px;left:3px}.VS-search .search_facet .search_facet_remove:hover{opacity:1}.VS-search .search_facet.is_editing .category,.VS-search .search_facet.is_selected .category{color:darkblue}.VS-search .search_facet.search_facet_maybe_delete .category,.VS-search .search_facet.search_facet_maybe_delete input{color:darkred}.VS-search .search_input{height:28px;float:left}.VS-search .search_input input{padding:6px 2px 7px 2px}.VS-search input{width:100px}.VS-search input,.VS-search .input_width_tester{padding:6px 0;float:left}.VS-search input,.VS-search .placeholder,.VS-search .input_width_tester{color:#808080;font:13px/17px Helvetica,Arial}.VS-search.VS-focus .search_facet.is_editing input{color:#000}.VS-search.VS-focus input{color:#606060}.VS-search .search_glyph{position:absolute;left:9px;top:8px}.VS-search .cancel_search{position:absolute;right:9px;top:8px}.ui-autocomplete{position:absolute;border:1px solid #c0c0c0;background-color:#f6f6f6;cursor:pointer;z-index:10000;width:auto;min-width:80px;max-width:220px;max-height:240px;overflow-y:auto;overflow-x:hidden;font-size:13px}.ui-autocomplete .ui-autocomplete-category{text-transform:capitalize;font-size:11px;padding:4px 4px 4px;border-top:1px solid #a2a2a2;border-bottom:1px solid #a2a2a2;background-color:#b7b7b7;text-shadow:0 -1px 0 #999;font-weight:bold;color:white;cursor:default}.ui-autocomplete .ui-menu-item a{display:block;padding:3px 3px 5px}.ui-autocomplete .ui-menu-item .ui-state-hover{background-color:#2a61cd;color:white}.ui-autocomplete li{list-style:none;width:auto}
BIN  build-min/visualsearch-datauri.css.gz
View
Binary file not shown
1  build-min/visualsearch.css
View
@@ -0,0 +1 @@
+.VS-search .icon{background-repeat:no-repeat;background-position:center center;vertical-align:middle;width:16px;height:16px}.VS-search .icon.cancel_search{width:11px;height:11px;background-position:center 0;background-image:url(../images/embed/icons/cancel_search.png?1281992275);cursor:pointer}.VS-search .icon.cancel_search:hover{background-position:center -11px}.VS-search .icon.search_glyph{width:12px;height:12px;background-image:url(../images/embed/icons/search_glyph.png?1278683131)}.VS-search div,.VS-search span,.VS-search a,.VS-search img,.VS-search ul,.VS-search li,.VS-search form,.VS-search label,.ui-autocomplete ul,.ui-autocomplete li,.ui-autocomplete{margin:0;padding:0;border:0;outline:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline}.VS-search :focus{outline:0}.VS-search{line-height:1;color:black}.VS-search ol,.VS-search ul{list-style:none}.VS-search{font-family:Arial,sans-serif;color:#373737;font-size:12px}.VS-search input{display:block;border:none;outline:none;margin:0;padding:4px;background:transparent;font-size:16px;line-height:20px;width:100%}.VS-interface,.VS-search .dialog,.VS-search input{font-family:"Lucida Grande","Lucida Sans Unicode",Helvetica,Arial,sans-serif!important;line-height:1.1em}#search_button{float:right;margin:3px 29px 0 6px}input.search_box{width:10px;z-index:100;position:relative}#search_box_wrapper .menu_content{z-index:100}.VS-search .VS-search-box{cursor:text;position:relative;background:transparent;border:2px solid #ccc;border-radius:16px;-webkit-border-radius:16px;-moz-border-radius:16px;background-color:#fafafa;-webkit-box-shadow:inset 0 0 3px #ccc;-moz-box-shadow:inset 0 0 3px #ccc;box-shadow:inset 0 0 3px #ccc;min-height:29px;height:auto}.VS-search .VS-search-box.VS-focus{border-color:#acf;-webkit-box-shadow:inset 0 0 3px #acf;-moz-box-shadow:inset 0 0 3px #acf;box-shadow:inset 0 0 3px #acf}.VS-search .search_inner{position:relative;margin:0 25px 0 18px;padding:0 3px;overflow:hidden}.VS-search .search_facet{float:left;margin:0;padding:0 0 0 13px;position:relative}.VS-search .search_facet.is_selected{margin:5px -3px 5px -3px;-webkit-border-radius:16px;-moz-border-radius:16px;border-radius:16px}.VS-search .search_facet.is_selected{background-color:#cbd1f1;background-image:-moz-linear-gradient(top,#cbd1f1,#afb5ec);background-image:-ms-linear-gradient(top,#cbd1f1,#afb5ec);background-image:-o-linear-gradient(top,#cbd1f1,#afb5ec);background-image:-webkit-gradient(linear,left top,left bottom,from(#cbd1f1),to(#afb5ec));background-image:-webkit-linear-gradient(top,#cbd1f1,#afb5ec);background-image:linear-gradient(top,#cbd1f1,#afb5ec);filter:progid:DXImageTransform.Microsoft.gradient(startColorStr='#CBD1F1',EndColorStr='#AFB5EC')}.VS-search .search_facet .category{float:left;text-transform:uppercase;font-weight:bold;font-size:10px;color:#b0b0b0;text-shadow:1px 1px 0 #FFF;padding:9px 0 6px;line-height:13px;cursor:pointer}.VS-search .search_facet.is_selected .category{padding:4px 0 0;margin-left:3px;text-shadow:1px 1px 0 #d0d0d0}.VS-search .search_facet .search_facet_input_container{float:left}.VS-search .search_facet input{margin:0;padding:0;color:#999;font-size:13px;line-height:16px;padding:6px 0 7px 4px;height:16px;width:auto;z-index:100;position:relative}.VS-search .search_facet.is_selected input{padding-top:1px;padding-bottom:2px;padding-right:3px}.VS-search .search_facet.is_editing input,.VS-search .search_facet.is_selected input{color:#202020}.VS-search .search_facet .search_facet_remove{position:absolute;left:0;top:9px;opacity:.5}.VS-search .search_facet.is_selected .search_facet_remove{top:4px;left:3px}.VS-search .search_facet .search_facet_remove:hover{opacity:1}.VS-search .search_facet.is_editing .category,.VS-search .search_facet.is_selected .category{color:darkblue}.VS-search .search_facet.search_facet_maybe_delete .category,.VS-search .search_facet.search_facet_maybe_delete input{color:darkred}.VS-search .search_input{height:28px;float:left}.VS-search .search_input input{padding:6px 2px 7px 2px}.VS-search input{width:100px}.VS-search input,.VS-search .input_width_tester{padding:6px 0;float:left}.VS-search input,.VS-search .placeholder,.VS-search .input_width_tester{color:#808080;font:13px/17px Helvetica,Arial}.VS-search.VS-focus .search_facet.is_editing input{color:#000}.VS-search.VS-focus input{color:#606060}.VS-search .search_glyph{position:absolute;left:9px;top:8px}.VS-search .cancel_search{position:absolute;right:9px;top:8px}.ui-autocomplete{position:absolute;border:1px solid #c0c0c0;background-color:#f6f6f6;cursor:pointer;z-index:10000;width:auto;min-width:80px;max-width:220px;max-height:240px;overflow-y:auto;overflow-x:hidden;font-size:13px}.ui-autocomplete .ui-autocomplete-category{text-transform:capitalize;font-size:11px;padding:4px 4px 4px;border-top:1px solid #a2a2a2;border-bottom:1px solid #a2a2a2;background-color:#b7b7b7;text-shadow:0 -1px 0 #999;font-weight:bold;color:white;cursor:default}.ui-autocomplete .ui-menu-item a{display:block;padding:3px 3px 5px}.ui-autocomplete .ui-menu-item .ui-state-hover{background-color:#2a61cd;color:white}.ui-autocomplete li{list-style:none;width:auto}
BIN  build-min/visualsearch.css.gz
View
Binary file not shown
54 build-min/visualsearch.js
View
@@ -0,0 +1,54 @@
+(function(){if(!window.VS)window.VS={};if(!VS.app)VS.app={};if(!VS.ui)VS.ui={};if(!VS.model)VS.model={};if(!VS.utils)VS.utils={};VS.init=function(a){var b={callbacks:{search:$.noop,focus:$.noop,categoryMatches:$.noop,facetMatches:$.noop}};VS.options=_.extend({},b,a);VS.options.callbacks=_.extend({},b.callbacks,a.callbacks);VS.app.hotkeys.initialize();VS.app.searchQuery=new VS.model.SearchQuery;VS.app.searchBox=new VS.ui.SearchBox(a);if(a.container){b=VS.app.searchBox.render().el;$(a.container).html(b)}VS.app.searchBox.value(a.query||
+"");return VS.app.searchBox}})();
+VS.ui.SearchBox=Backbone.View.extend({id:"search",events:{"click .search_glyph":"showFacetCategoryMenu","click .cancel_search_box":"clearSearch","mousedown .VS-search-box":"focusSearch","dblclick .VS-search-box":"highlightSearch","click #search_button":"searchEvent"},initialize:function(){this.flags={allSelected:false};this.facetViews=[];this.inputViews=[];_.bindAll(this,"hideSearch","renderFacets","_maybeDisableFacets","disableFacets");VS.app.searchQuery.bind("refresh",this.renderFacets);$(document).bind("keydown",
+this._maybeDisableFacets)},render:function(){$(this.el).append(JST.search_box({}));$(document.body).setMode("no","search");return this},hideSearch:function(){$(document.body).setMode(null,"search")},_maybeDisableFacets:function(a){if(this.flags.allSelected&&(VS.app.hotkeys.key(a)=="backspace"||VS.app.hotkeys.printable(a)))this.clearSearch();else if(this.flags.allSelected){console.log(["_maybeDisableFacets",this.flags.allSelected]);this.flags.allSelected=false;this.disableFacets()}},clearSearch:function(){console.log(["clearSearch"]);
+this.value("");this.flags.allSelected=false;this.focusSearch()},searchEvent:function(a){var b=this.value();console.log(["real searchEvent",a,b,VS.options.callbacks]);this.focusSearch();VS.options.callbacks.search(b)!==false&&this.value(b)},value:function(a){if(a==null)return this.getQuery();return this.setQuery(a)},getQuery:function(){var a=[],b=this.inputViews.length;VS.app.searchQuery.each(_.bind(function(c,d){a.push(this.inputViews[d].value());a.push(c.serialize())},this));b&&a.push(this.inputViews[b-
+1].value());console.log(["getQuery",a,_.compact(a)]);return _.compact(a).join(" ")},setQuery:function(a){this.currentQuery=a;VS.app.SearchParser.parse(a);this.clearInputs()},viewPosition:function(a){a=_.indexOf(a.type=="facet"?this.facetViews:this.inputViews,a);if(a==-1)a=0;return a},addFacet:function(a,b,c){a=VS.utils.inflector.trim(a);b=VS.utils.inflector.trim(b||"");if(a){console.log(["addFacet",a,b,c]);var d=new VS.model.SearchFacet({category:a,value:b||""});VS.app.searchQuery.add(d,{at:c});this.renderFacets();
+_.detect(this.facetViews,function(f){if(f.model==d)return true}).enableEdit()}},renderFacets:function(){this.facetViews=[];this.inputViews=[];this.$(".search_inner").empty();VS.app.searchQuery.each(_.bind(function(a,b){this.renderFacet(a,b)},this));this.renderSearchInput()},renderFacet:function(a,b){var c=new VS.ui.SearchFacet({model:a,order:b});this.renderSearchInput();this.facetViews.push(c);this.$(".search_inner").children().eq(b*2).after(c.render().el);c.calculateSize();_.defer(_.bind(c.calculateSize,
+c));return c},renderSearchInput:function(){var a=new VS.ui.SearchInput({position:this.inputViews.length});this.$(".search_inner").append(a.render().el);this.inputViews.push(a)},clearInputs:function(){_.each(this.inputViews,function(a){a.clear()})},focusNextFacet:function(a,b,c){c=c||{};var d=this.facetViews.length,f=c.viewPosition||this.viewPosition(a);console.log(["focusNextFacet",d,f,a.type,b,c]);if(c.skipToFacet){if(c.skipToFacet&&a.type=="text"&&d==f&&b>=0)b=f=0}else{if(a.type=="text"&&b>0)b-=
+1;if(a.type=="facet"&&b<0)b+=1}var e;f=Math.min(d,f+b);if(a.type=="text"&&f>=0&&f<d){e=this.facetViews[f];if(c.selectFacet)e.selectFacet();else{e.enableEdit();e.setCursorAtEnd(b||c.startAtEnd)}}else if(a.type=="facet")if(c.skipToFacet)if(f>=d||f<0){e=_.last(this.inputViews);e.focus()}else{e=this.facetViews[f];e.enableEdit();e.setCursorAtEnd(b||c.startAtEnd)}else{e=this.inputViews[f];console.log(["next view",f,e]);e.focus();c.selectText&&e.selectText()}c.selectText&&e.selectText();this.resizeFacets()},
+selectAllFacets:function(){_.each(this.facetViews,function(a){a.selectFacet(null,true)});_.each(this.inputViews,function(a){a.selectText()});this.flags.allSelected=true;$(document).one("click",this.disableFacets)},allSelected:function(){return this.flags.allSelected},disableFacets:function(a){_.each(this.facetViews,function(b){if(b!=a&&(b.modes.editing=="is"||b.modes.selected=="is")){console.log(["disabling view",b.model.get("category")]);b.disableEdit();b.deselectFacet()}});this.removeFocus()},resizeFacets:function(a){_.each(this.facetViews,
+function(b){if(!a||b==a)b.resize()})},focusSearch:function(a,b){console.log(["focusSearch",a,!a||$(a.target).is(".VS-search-box")||$(a.target).is(".search_inner")]);if(!a||$(a.target).is(".VS-search-box")||$(a.target).is(".search_inner")){this.disableFacets();_.defer(_.bind(function(){this.$("input:focus").length||this.inputViews[this.inputViews.length-1].focus(b)},this))}},highlightSearch:function(a){this.focusSearch(a,true)},addFocus:function(){VS.options.callbacks.focus();this.$(".VS-search-box").addClass("VS-focus")},
+removeFocus:function(){_.any(this.facetViews.concat(this.inputViews),function(a){return a.isFocused()})||this.$(".VS-search-box").removeClass("VS-focus")},showFacetCategoryMenu:function(a){a.preventDefault();a.stopPropagation();console.log(["showFacetCategoryMenu",a]);if(this.facetCategoryMenu&&this.facetCategoryMenu.modes.open=="is")return this.facetCategoryMenu.close();a=[{title:"Account",onClick:_.bind(this.addFacet,this,"account","")},{title:"Project",onClick:_.bind(this.addFacet,this,"project",
+"")},{title:"Filter",onClick:_.bind(this.addFacet,this,"filter","")},{title:"Access",onClick:_.bind(this.addFacet,this,"access","")}];a=this.facetCategoryMenu||(this.facetCategoryMenu=new dc.ui.Menu({items:a,standalone:true}));this.$(".search_glyph").after(a.render().open().content);return false}});
+VS.ui.SearchFacet=Backbone.View.extend({type:"facet",className:"search_facet",events:{"click .category":"selectFacet","keydown input":"keydown","keypress input":"keypress","mousedown input":"enableEdit","mouseover .cancel_search":"showDelete","mouseout .cancel_search":"hideDelete","click .cancel_search":"remove"},initialize:function(){this.setMode("not","editing");_.bindAll(this,"set","keydown","deselectFacet","deferDisableEdit")},render:function(){console.log(["search facet",this.model.get("category"),
+this.model.get("value")]);(this.$el=$(this.el)).html(JST.search_facet({model:this.model}));this.setMode("not","editing");this.box=this.$("input");this.box.val(this.model.get("value"));this.box.bind("blur",this.deferDisableEdit);this.setupAutocomplete();return this},calculateSize:function(){this.box.autoGrowInput();this.box.unbind("updated.autogrow").bind("updated.autogrow",_.bind(this.moveAutocomplete,this))},setupAutocomplete:function(){this.box.autocomplete({source:_.bind(this.autocompleteValues,
+this),minLength:0,delay:0,autoFocus:true,select:_.bind(function(a,b){a.preventDefault();var c=this.model.get("value");console.log(["autocomplete facet",b.item.value,c]);this.set(b.item.value);if(c!=b.item.value||this.box.val()!=b.item.value)this.search(a);return false},this)});this.box.autocomplete("widget").addClass("VS-interface")},moveAutocomplete:function(){var a=this.box.data("autocomplete");a&&a.menu.element.position({my:"left top",at:"left bottom",of:this.box.data("autocomplete").element,collision:"none"})},
+set:function(a){if(a){console.log(["set facet",a,this.model.get("value"),this.model]);this.model.set({value:a})}},search:function(a){console.log(["facet search",a]);this.closeAutocomplete();VS.app.searchBox.searchEvent(a)},enableEdit:function(){this.canClose=false;console.log(["enableEdit",this.model.get("category"),this.modes.editing]);if(this.modes.editing!="is"){this.setMode("is","editing");this.deselectFacet();this.box.val()==""&&this.box.val(this.model.get("value"))}this.searchAutocomplete();
+VS.app.searchBox.disableFacets(this);VS.app.searchBox.addFocus();_.defer(function(){VS.app.searchBox.addFocus()});this.box.is(":focus")||this.box.focus();this.resize()},deferDisableEdit:function(){console.log(["deferDisableEdit",this.model.get("category"),this.box.is(":focus")]);this.canClose=true;_.delay(_.bind(function(){console.log(["deferDisableEdit post",this.canClose,this.box.is(":focus"),this.modes.editing]);this.canClose&&!this.box.is(":focus")&&this.modes.editing=="is"&&this.modes.selected!=
+"is"&&this.disableEdit()},this),250)},disableEdit:function(){console.log(["disableEdit",this.model.get("category"),this.box.is(":focus")]);var a=VS.utils.inflector.trim(this.box.val());a!=this.model.get("value")&&this.set(a);this.canClose=false;this.box.selectRange(0,0);this.box.blur();this.setMode("not","editing");this.closeAutocomplete();VS.app.searchBox.removeFocus()},selectFacet:function(a,b){console.log(["selectFacet",this.model.get("category"),b]);this.canClose=false;this.box.setCursorPosition(0);
+this.box.is(":focus")&&this.box.blur();this.setMode("is","selected");this.setMode("not","editing");this.closeAutocomplete();if(!b){$(document).unbind("keydown.facet",this.keydown);$(document).unbind("click.facet",this.deselectFacet);_.defer(_.bind(function(){$(document).unbind("keydown.facet").bind("keydown.facet",this.keydown);$(document).unbind("click.facet").one("click.facet",this.deselectFacet)},this));VS.app.searchBox.disableFacets(this);VS.app.searchBox.addFocus()}},deselectFacet:function(){console.log(["deselectFacet",
+this.model.get("category")]);if(this.modes.selected=="is"){this.setMode("not","selected");this.closeAutocomplete();VS.app.searchBox.removeFocus()}$(document).unbind("keydown.facet",this.keydown);$(document).unbind("click.facet",this.deselectFacet)},isFocused:function(){return this.box.is(":focus")},searchAutocomplete:function(){var a=this.box.data("autocomplete");a&&a.search()},closeAutocomplete:function(){console.log(["closeAutocomplete",this.model.get("category")]);var a=this.box.data("autocomplete");
+a&&a.close()},autocompleteValues:function(a,b){var c=this.model.get("category"),d=this.model.get("value"),f=a.term;c=VS.options.callbacks.facetMatches(c)||[];if(f&&d!=f){f=VS.utils.inflector.escapeRegExp(f||"");var e=RegExp("\\b"+f,"i");c=$.grep(c,function(g){return e.test(g)||e.test(g.value)||e.test(g.label)})}b(_.sortBy(c,function(g){return g==d||g.value==d?"":g}))},showDelete:function(){this.$el.addClass("search_facet_maybe_delete")},hideDelete:function(){this.$el.removeClass("search_facet_maybe_delete")},
+setCursorAtEnd:function(a){a==-1?this.box.setCursorPosition(this.box.val().length):this.box.setCursorPosition(0)},remove:function(a){console.log(["remove facet",a,this.model]);a=this.model.has("value");this.deselectFacet();this.disableEdit();VS.app.searchQuery.remove(this.model);a?this.search():VS.app.searchBox.renderFacets();VS.app.searchBox.focusNextFacet(this,0,{viewPosition:this.options.order})},removeLastCharacter:function(){console.log(["removeLastCharacter",this.box.val()]);var a=this.box.val();
+this.box.val(a.substr(0,a.length-1));this.resize()},selectText:function(){this.box.selectRange(0,this.box.val().length)},keypress:function(a){VS.app.hotkeys.key(a)},keydown:function(a){var b=VS.app.hotkeys.key(a);console.log(["facet keydown",b,this.box.val(),this.box.getCursorPosition(),this.box.getSelection().length,VS.app.hotkeys.left,VS.app.hotkeys.right]);if(b=="enter"&&this.box.val()){this.disableEdit();this.search(a)}else if(b=="left"){if(this.box.getCursorPosition()==0&&!this.box.getSelection().length)if(this.modes.selected==
+"is"){this.deselectFacet();VS.app.searchBox.focusNextFacet(this,-1,{startAtEnd:true})}else this.selectFacet()}else if(b=="right")if(this.modes.selected=="is"){a.preventDefault();this.deselectFacet();this.setCursorAtEnd(0);this.enableEdit()}else{if(this.box.getCursorPosition()==this.box.val().length){a.preventDefault();this.disableEdit();VS.app.searchBox.focusNextFacet(this,1)}}else if(VS.app.hotkeys.shift&&b=="tab"){a.preventDefault();this.deselectFacet();this.disableEdit();VS.app.searchBox.focusNextFacet(this,
+-1,{startAtEnd:true,skipToFacet:true,selectText:true})}else if(b=="tab"){a.preventDefault();this.deselectFacet();this.disableEdit();VS.app.searchBox.focusNextFacet(this,1,{skipToFacet:true,selectText:true})}else if(VS.app.hotkeys.command&&(a.which==97||a.which==65)){a.preventDefault();VS.app.searchBox.selectAllFacets();return false}else if(VS.app.hotkeys.printable(a)&&this.modes.selected=="is"){VS.app.searchBox.focusNextFacet(this,-1,{startAtEnd:true});this.remove()}else if(b=="backspace")if(this.modes.selected==
+"is"){a.preventDefault();this.remove(a)}else if(this.box.getCursorPosition()==0&&!this.box.getSelection().length){a.preventDefault();this.selectFacet()}this.resize(a)},resize:function(a){this.box.trigger("resize.autogrow",a)}});
+VS.ui.SearchInput=Backbone.View.extend({type:"text",className:"search_input",events:{"keypress input":"keypress","keydown input":"keydown"},initialize:function(){_.bindAll(this,"removeFocus","addFocus","moveAutocomplete")},render:function(){$(this.el).html(JST.search_input({}));this.box=this.$("input");this.box.autoGrowInput();this.box.bind("updated.autogrow",this.moveAutocomplete);this.box.bind("blur",this.removeFocus);this.box.bind("focus",this.addFocus);this.setupAutocomplete();return this},setupAutocomplete:function(){this.box.autocomplete({minLength:1,
+delay:50,autoFocus:true,source:_.bind(this.autocompleteValues,this),select:_.bind(function(a,b){console.log(["select autocomplete",a,b]);a.preventDefault();a.stopPropagation();var c=this.addTextFacetRemainder(b.item.value);VS.app.searchBox.addFacet(b.item.value,"",this.options.position+(c?1:0));this.clear();return false},this)});this.box.data("autocomplete")._renderMenu=function(a,b){var c="";_.each(b,_.bind(function(d){if(d.category&&d.category!=c){a.append('<li class="ui-autocomplete-category">'+
+d.category+"</li>");c=d.category}this._renderItem(a,d)},this))};this.box.autocomplete("widget").addClass("VS-interface")},autocompleteValues:function(a,b){var c=a.term.match(/\w+$/);c=VS.utils.inflector.escapeRegExp(c&&c[0]||" ");var d=VS.options.callbacks.categoryMatches()||[],f=RegExp("^"+c,"i");c=$.grep(d,function(e){return f.test(e.label||e)});b(_.sortBy(c,function(e){return e.label?e.category+"-"+e.label:e}))},moveAutocomplete:function(){var a=this.box.data("autocomplete");a&&a.menu.element.position({my:"left top",
+at:"left bottom",of:this.box.data("autocomplete").element,collision:"none"})},closeAutocomplete:function(){var a=this.box.data("autocomplete");a&&a.close()},addTextFacetRemainder:function(a){var b=this.box.val(),c=b.match(/\b(\w+)$/);if(c&&a.indexOf(c[0])==0)b=b.replace(/\b(\w+)$/,"");(b=b.replace("^s+|s+$",""))&&VS.app.searchBox.addFacet("text",b,this.options.position);return b},focus:function(a){console.log(["input focus",a]);this.addFocus();this.box.focus();a&&this.selectText()},blur:function(){console.log(["input blur"]);
+this.box.blur();this.removeFocus()},removeFocus:function(a){console.log(["removeFocus",a]);VS.app.searchBox.removeFocus()},addFocus:function(a){console.log(["addFocus",a]);VS.app.searchBox.disableFacets(this);VS.app.searchBox.addFocus()},isFocused:function(){return this.box.is(":focus")},clear:function(){this.box.val("")},value:function(){return this.box.val()},selectText:function(){this.box.selectRange(0,this.box.val().length);this.box.focus()},keypress:function(a){var b=VS.app.hotkeys.key(a);console.log(["input keypress",
+a.keyCode,b,this.box.getCursorPosition()]);if(b=="enter")return VS.app.searchBox.searchEvent(a);else if(VS.app.hotkeys.colon(a)){this.box.trigger("resize.autogrow",a);b=this.box.val();var c=VS.options.callbacks.categoryMatches()||[];_.map(c,function(d){return d.label?d.label:d});if(_.contains(_.pluck(c,"label"),b)){a.preventDefault();a=this.addTextFacetRemainder(b);VS.app.searchBox.addFacet(b,"",this.options.position+(a?1:0));this.clear();return false}}else if(b=="backspace")if(this.box.getCursorPosition()==
+0&&!this.box.getSelection().length){a.preventDefault();a.stopPropagation();a.stopImmediatePropagation();VS.app.searchBox.resizeFacets();return false}},keydown:function(a){var b=VS.app.hotkeys.key(a);console.log(["input keydown",b,a.which,this.box.getCursorPosition()]);this.box.trigger("resize.autogrow",a);if(b=="left"){if(this.box.getCursorPosition()==0){a.preventDefault();VS.app.searchBox.focusNextFacet(this,-1,{startAtEnd:true})}}else if(b=="right"){if(this.box.getCursorPosition()==this.box.val().length){a.preventDefault();
+VS.app.searchBox.focusNextFacet(this,1,{selectFacet:true})}}else if(VS.app.hotkeys.shift&&b=="tab"){a.preventDefault();VS.app.searchBox.focusNextFacet(this,-1,{selectText:true})}else if(b=="tab"){a.preventDefault();a=this.box.val();if(a.length){b=this.addTextFacetRemainder(a);VS.app.searchBox.addFacet(a,"",this.options.position+(b?1:0))}else VS.app.searchBox.focusNextFacet(this,0,{skipToFacet:true,selectText:true})}else if(VS.app.hotkeys.command&&(a.which==97||a.which==65)){a.preventDefault();VS.app.searchBox.selectAllFacets();
+return false}else if(b=="backspace"&&!VS.app.searchBox.allSelected())if(this.box.getCursorPosition()==0&&!this.box.getSelection().length){a.preventDefault();VS.app.searchBox.focusNextFacet(this,-1,{backspace:true});return false}}});(function(){Backbone.View.prototype.setMode=function(a,b){this.modes||(this.modes={});if(this.modes[b]!==a){$(this.el).setMode(a,b);this.modes[b]=a}}})();
+VS.app.hotkeys={KEYS:{"16":"shift","17":"control","91":"command","93":"command","224":"command","13":"enter","37":"left","38":"upArrow","39":"right","40":"downArrow","46":"delete","8":"backspace","9":"tab","188":"comma"},initialize:function(){_.bindAll(this,"down","up","blur");$(document).bind("keydown",this.down);$(document).bind("keyup",this.up);$(window).bind("blur",this.blur)},down:function(a){if(a=this.KEYS[a.which])this[a]=true},up:function(a){if(a=this.KEYS[a.which])this[a]=false},blur:function(){for(var a in this.KEYS)this[this.KEYS[a]]=
+false},key:function(a){return this.KEYS[a.which]},colon:function(a){return(a=a.which)&&String.fromCharCode(a)==":"},printable:function(a){var b=a.which;if(a.type=="keydown"){if(b==32||b>=48&&b<=90||b>=96&&b<=111||b>=186&&b<=192||b>=219&&b<=222)return true}else if(b>=32&&b<=126||b>=160&&b<=500||String.fromCharCode(b)==":")return true;return false}};
+VS.utils.inflector={small:"(a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|v[.]?|via|vs[.]?)",punct:"([!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]*)",pluralize:function(a,b){if(b==1)return a;if(a=="this")return"these";if(a=="person")return"people";if(a=="this")return"these";if(a.match(/y$/i))return a.replace(/y$/i,"ies");return a+"s"},titleize:function(a){a=a.replace(/[-.\/_]/g," ").replace(/\s+/gm," ");for(var b=this.capitalize,c=[],d=/[:.;?!] |(?: |^)["\u00d2]/g,f=0;;){var e=d.exec(a);c.push(a.substring(f,
+e?e.index:a.length).replace(/\b([A-Za-z][a-z.'\u00d5]*)\b/g,function(g){return/[A-Za-z]\.[A-Za-z]/.test(g)?g:b(g)}).replace(RegExp("\\b"+this.small+"\\b","ig"),this.lowercase).replace(RegExp("^"+this.punct+this.small+"\\b","ig"),function(g,h,i){return h+b(i)}).replace(RegExp("\\b"+this.small+this.punct+"$","ig"),b));f=d.lastIndex;if(e)c.push(e[0]);else break}return c.join("").replace(/ V(s?)\. /ig," v$1. ").replace(/(['\u00d5])S\b/ig,"$1s").replace(/\b(AT&T|Q&A)\b/ig,function(g){return g.toUpperCase()})},
+trim:function(a){return a.trim?a.trim():a.replace(/^\s+|\s+$/g,"")},truncate:function(a,b,c){b=b||30;c=c==null?"...":c;return a.length>b?a.slice(0,b-c.length)+c:a},escapeRegExp:function(a){return a.replace(/([.*+?^${}()|[\]\/\\])/g,"\\$1")}};
+(function(a){a.fn.extend({setMode:function(b,c){c=c||"mode";var d=RegExp("\\w+_"+c+"(\\s|$)","g"),f=b===null?"":b+"_"+c;this.each(function(){this.className=(this.className.replace(d,"")+" "+f).replace(/\s\s/g," ")});return f},autoGrowInput:function(){return this.each(function(){var b=a(this),c=a("<div />").css({opacity:0,top:-9999,left:-9999,position:"absolute",whiteSpace:"nowrap"}).addClass("input_width_tester").addClass("VS-interface");b.after(c);b.unbind("keydown.autogrow keypress.autogrow resize.autogrow change.autogrow").bind("keydown.autogrow keypress.autogrow resize.autogrow change.autogrow",
+function(d,f){if(f)d=f;var e=b.val();VS.app.hotkeys.down(d);if(VS.app.hotkeys.key(d)=="backspace"){var g=b.getCursorPosition();if(g>0)e=e.slice(0,g-1)+e.slice(g,e.length)}else if(VS.app.hotkeys.printable(d)&&!VS.app.hotkeys.command)e+=VS.app.hotkeys.shift?String.fromCharCode(d.which):String.fromCharCode(d.which).toLowerCase();e=e.replace(/&/g,"&amp;").replace(/\s/g,"&nbsp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");c.html(e);console.log(["autoGrow",d.type,d.which,VS.app.hotkeys.printable(d),e]);
+b.width(c.width()+3);b.trigger("updated.autogrow")});b.trigger("resize.autogrow")})},getCursorPosition:function(){var b=0,c=this.get(0);if(document.selection){c.focus();b=document.selection.createRange();var d=document.selection.createRange().text.length;b.moveStart("character",-c.value.length);b=b.text.length-d}else if(c&&a(c).is(":visible")&&c.selectionStart!=null)b=c.selectionStart;return b},setCursorPosition:function(b){return this.each(function(){return a(this).selectRange(b,b)})},selectRange:function(b,
+c){return this.each(function(){if(this.setSelectionRange){this.focus();this.setSelectionRange(b,c)}else if(this.createTextRange){var d=this.createTextRange();d.collapse(true);d.moveEnd("character",c);d.moveStart("character",b);d.select()}})},getSelection:function(){var b=this[0];if(b.selectionStart!=null){var c=b.selectionStart,d=b.selectionEnd;return{start:c,end:d,length:d-c,text:b.value.substr(c,d-c)}}else if(document.selection){var f=document.selection.createRange();if(f){b=b.createTextRange();
+c=b.duplicate();b.moveToBookmark(f.getBookmark());c.setEndPoint("EndToStart",b);c=c.text.length;d=c+f.text.length;return{start:c,end:d,length:d-c,text:f.text}}}return{start:0,end:0,length:0}}})})(jQuery);
+VS.app.SearchParser={ALL_FIELDS:/('.+?'|".+?"|[^'"\s]{2}\S*):\s*('.+?'|".+?"|[^'"\s]{2}\S*)/g,FIELD:/(.+?):\s*/,ONE_ENTITY:/(city|country|term|state|person|place|organization|email|phone):\s*(([^'"][^'"]\S*)|'(.+?)'|"(.+?)")/i,ALL_ENTITIES:/(city|country|term|state|person|place|organization|email|phone):\s*(([^'"][^'"]\S*)|'(.+?)'|"(.+?)")/ig,parse:function(a){a=this.extractAllFacets(a);VS.app.searchQuery.refresh(a)},extractAllFacets:function(a){for(var b=[],c=a;a;){var d,f;c=a;var e=this.extractNextField(a);
+if(e)if(e.indexOf(":")!=-1){d=e.match(this.FIELD)[1];f=e.replace(this.FIELD,"").replace(/(^['"]|['"]$)/g,"");a=VS.utils.inflector.trim(a.replace(e,""))}else{if(e.indexOf(":")==-1){d="text";f=e;a=VS.utils.inflector.trim(a.replace(f,""))}}else{d="text";f=this.extractSearchText(a);a=VS.utils.inflector.trim(a.replace(f,""))}if(d&&f){e=new VS.model.SearchFacet({category:d,value:VS.utils.inflector.trim(f)});b.push(e)}if(c==a)break}return b},extractNextField:function(a){var b=a.match(/^\s*(\S+)\s+(?=\w+:\s?(('.+?'|".+?")|([^'"]{2}\S*)))/);
+console.log(["extractNextField",a,b]);return b&&b.length>=1?b[1]:this.extractFirstField(a)},extractFirstField:function(a){return(a=a.match(this.ALL_FIELDS))&&a.length&&a[0]},extractSearchText:function(a){a=a||"";var b=VS.utils.inflector.trim(a.replace(this.ALL_FIELDS,""));console.log(["extractSearchText",a,b]);return b},extractEntities:function(a){var b=this.ONE_ENTITY;a=a.match(this.ALL_ENTITIES)||[];return _.sortBy(_.map(a,function(c){c=c.match(b);return{type:c[1],value:c[3]||c[4]||c[5]}}),function(c){return c.value.toLowerCase()}).reverse()}};
+VS.model.SearchFacet=Backbone.Model.extend({UNQUOTABLE_CATEGORIES:["text","account","document","filter","group","access","related","projectid"],serialize:function(){var a=this.get("category"),b=VS.utils.inflector.trim(this.get("value"));if(!b)return"";_.contains(this.UNQUOTABLE_CATEGORIES,a)||(b='"'+b+'"');if(a!="text")a+=": ";else a="";return a+b}});
+VS.model.SearchQuery=Backbone.Collection.extend({model:VS.model.SearchFacet,value:function(){return this.map(function(a){return a.serialize()}).join(" ")},find:function(a){var b=this.detect(function(c){return c.get("category")==a});return b&&b.get("value")},count:function(a){return this.select(function(b){return b.get("category")==a}).length},values:function(a){var b=this.select(function(c){return c.get("category")==a});return _.map(b,function(c){return c.get("value")})},has:function(a,b){return this.any(function(c){return b?
+c.get("category")==a&&c.get("value")==b:c.get("category")==a})},withoutCategory:function(a){return this.map(function(b){if(b.get("category")!=a)return b.serialize()}).join(" ")}});
+(function(){window.JST=window.JST||{};window.JST.search_box=_.template('<div class="VS-search"> <div id="search_container"> <div id="search_button" class="minibutton">Search</div> <div id="search_box_wrapper" class="VS-search-box"> <div class="icon search_glyph"></div> <div class="search_inner"></div> <div class="icon cancel_search cancel_search_box" title="clear search"></div> </div> </div></div>');window.JST.search_facet=_.template('<% if (model.has(\'category\')) { %> <div class="category"><%= model.get(\'category\') %>:</div><% } %><div class="search_facet_input_container"> <input type="text" class="search_facet_input VS-interface" value="" /></div><div class="search_facet_remove icon cancel_search"></div>');
+window.JST.search_input=_.template('<input class="search_box" type="text" />')})();
BIN  build-min/visualsearch.js.gz
View
Binary file not shown
2  build-min/visualsearch_templates.js
View
@@ -0,0 +1,2 @@
+(function(){window.JST=window.JST||{};window.JST.search_box=_.template('<div class="VS-search"> <div id="search_container"> <div id="search_button" class="minibutton">Search</div> <div id="search_box_wrapper" class="VS-search-box"> <div class="icon search_glyph"></div> <div class="search_inner"></div> <div class="icon cancel_search cancel_search_box" title="clear search"></div> </div> </div></div>');window.JST.search_facet=_.template('<% if (model.has(\'category\')) { %> <div class="category"><%= model.get(\'category\') %>:</div><% } %><div class="search_facet_input_container"> <input type="text" class="search_facet_input VS-interface" value="" /></div><div class="search_facet_remove icon cancel_search"></div>');
+window.JST.search_input=_.template('<input class="search_box" type="text" />')})();
BIN  build-min/visualsearch_templates.js.gz
View
Binary file not shown
12,247 build/dependencies.js
View
12,247 additions, 0 deletions not shown
284 build/visualsearch-datauri.css
View
@@ -0,0 +1,284 @@
+.VS-search .icon {
+ background-repeat: no-repeat;
+ background-position: center center;
+ vertical-align: middle;
+ width: 16px; height: 16px;
+}
+.VS-search .icon.cancel_search {
+ width: 11px; height: 11px;
+ background-position: center 0;
+ background-image: url("data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAWCAYAAAAW5GZjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAb9JREFUeNqUUr1qAkEQ3j0khQp6kihaeGgEEa18gTQR0iRY+BaBSMDGwidIEUKqFL6BopgqBAJ5AMFGjUU0d4WHEvwJarvZ77gRIzGYgb1hZr+Z75vZ40IIzqTNZrPj8Xicn0wmmcViEXS73aaqqq+BQODG6/W+A8MBNk3zfDAY3C6Xy0O2ZS6X6zMSiVwHg8FHLjtq7Xb7RQKj7BeTzVCgJ5PJU2U0GhUk7REuMpkMi8fjFggeMeecrVYrFRId0CgTAgDDMFg4HLbA8IjJgHNgGEr0er0fQIphUmZAwdSUADUB4RFDsz3oSMF6CLzZkQqgGebz+Z75dDqNdTqdp13bgDmdTj2VSp0oWHg0Gr2UNH2Z/9o+yMv7K4/HY/C/XhDUfr//jl7QQVT9fp/V63VWqVRYt9tliUSCZbPZg1wux9Lp9PqFeK1Wu9A0DdXz7YM87i0FrVZLs4Fi1wmFQh/NZjOmVKvVgq7rR/QflMtlixGedjwcDlUpMQ9tbzalkAAB2/R297mNW+sT2wUbUnA//V/nYrH4QOBNABUQuFQq3TNMuc82sDVrz41G42yvPeODAwZQ0QzwiJEnzLcAAwBJ6WXlwoBgZAAAAABJRU5ErkJggg==");
+ cursor: pointer;
+}
+ .VS-search .icon.cancel_search:hover {
+ background-position: center -11px;
+ }
+.VS-search .icon.search_glyph {
+ width: 12px; height: 12px;
+ background-image: url("data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAUZJREFUeNpUUM2qgmAQzS8NiUgLzTIXLZQW1QuI9AY9QPSW9gQ9QiriwpJQEBVrVWT2d7p2L9xZzDdzZs7M+YYqy/J8Ptu2vd/v4zgeDAaqqk4mE47jar9GnU6nzWbjOA5FUa/Xq0Jns9l8Pud5vkpp58cwAOzhcBhFkeu6GNztdg3D+Db5vo9nOp2iiWGYTqdDCMFe4LquI0aVpGmKR9M0lmUbjQY8YiBJklTb4YkoilBzOBzq9TogeMQIJEmqmlAlo9EIyXa7tSyrKAp4xEBkWUb5q2k8Hh+PR8/zwjCEgufz+aESstvtoKnVan2GgY31kBkEAfT1ej1FUZDiNIIgrFYr9H1ug3teLpfH43G/3/FBUJGu1+s8z8FZLpc0mmiabrfbf5fEumazuVgsTNO8Xq+3242qRNT+G0CMz7IMzH6//xZgAA60tj6rqzxpAAAAAElFTkSuQmCC");
+}
+
+/*------------------------------ RESET + DEFAULT STYLES ---------------------------------*/
+
+/*
+Eric Meyer's final reset.css
+Source: http://meyerweb.com/eric/thoughts/2007/05/01/reset-reloaded/
+*/
+.VS-search div, .VS-search span, .VS-search a, .VS-search img,
+.VS-search ul, .VS-search li, .VS-search form, .VS-search label,
+.ui-autocomplete ul, .ui-autocomplete li, .ui-autocomplete {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-weight: inherit;
+ font-style: inherit;
+ font-size: 100%;
+ font-family: inherit;
+ vertical-align: baseline;
+}
+
+.VS-search :focus {
+ outline: 0;
+}
+.VS-search {
+ line-height: 1;
+ color: black;
+}
+.VS-search ol, .VS-search ul {
+ list-style: none;
+}
+
+/* ===================== */
+/* = General and Reset = */
+/* ===================== */
+
+.VS-search {
+ font-family: Arial, sans-serif;
+ color: #373737;
+ font-size: 12px;
+}
+.VS-search input {
+ display: block;
+ border: none;
+ outline: none;
+ margin: 0; padding: 4px;
+ background: transparent;
+ font-size: 16px;
+ line-height: 20px;
+ width: 100%;
+}
+.VS-interface, .VS-search .dialog, .VS-search input {
+ font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, sans-serif !important;
+ line-height: 1.1em;
+}
+
+/* ========== */
+/* = Layout = */
+/* ========== */
+
+#search_button {
+ float: right;
+ margin: 3px 29px 0 6px;
+}
+#search_box_wrapper {
+}
+ input.search_box {
+ width: 10px;
+ z-index: 100;
+ position: relative;
+ }
+ #search_box_wrapper .menu_content {
+ z-index: 100;
+ }
+
+.VS-search .VS-search-box {
+ cursor: text;
+ position: relative;
+ background: transparent;
+ border: 2px solid #ccc;
+ border-radius: 16px; -webkit-border-radius: 16px; -moz-border-radius: 16px;
+ background-color: #fafafa;
+ -webkit-box-shadow: inset 0px 0px 3px #ccc;
+ -moz-box-shadow: inset 0px 0px 3px #ccc;
+ box-shadow: inset 0px 0px 3px #ccc;
+ min-height: 29px;
+ height: auto;
+}
+ .VS-search .VS-search-box.VS-focus {
+ border-color: #acf;
+ -webkit-box-shadow: inset 0px 0px 3px #acf;
+ -moz-box-shadow: inset 0px 0px 3px #acf;
+ box-shadow: inset 0px 0px 3px #acf;
+ }
+ .VS-search .search_inner {
+ position: relative;
+ margin: 0 25px 0 18px;
+ padding: 0 3px;
+ overflow: hidden;
+ }
+ .VS-search .search_facets {
+ }
+ .VS-search .search_facet {
+ float: left;
+ margin: 0;
+ padding: 0 0 0 13px;
+ position: relative;
+ }
+ .VS-search .search_facet.is_selected {
+ margin: 5px -3px 5px -3px;
+ -webkit-border-radius: 16px;
+ -moz-border-radius: 16px;
+ border-radius: 16px;
+ }
+ .VS-search .search_facet.is_selected {
+ background-color: #CBD1F1;
+ background-image: -moz-linear-gradient(top, #CBD1F1, #AFB5EC); /* FF3.6 */
+ background-image: -ms-linear-gradient(top, #CBD1F1, #AFB5EC); /* IE10 */
+ background-image: -o-linear-gradient(top, #CBD1F1, #AFB5EC); /* Opera 11.10+ */
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#CBD1F1), to(#AFB5EC)); /* Saf4+, Chrome */
+ background-image: -webkit-linear-gradient(top, #CBD1F1, #AFB5EC); /* Chrome 10+, Saf5.1+ */
+ background-image: linear-gradient(top, #CBD1F1, #AFB5EC);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#CBD1F1', EndColorStr='#AFB5EC'); /* IE6–IE9 */
+ }
+ .VS-search .search_facet .category {
+ float: left;
+ text-transform: uppercase;
+ font-weight: bold;
+ font-size: 10px;
+ color: #B0B0B0;
+ text-shadow: 1px 1px 0 #FFF;
+ padding: 9px 0 6px;
+ line-height: 13px;
+ cursor: pointer;
+ }
+ .VS-search .search_facet.is_selected .category {
+ padding: 4px 0 0;
+ margin-left: 3px;
+ text-shadow: 1px 1px 0 #D0D0D0;
+ }
+ .VS-search .search_facet .search_facet_input_container {
+ float: left;
+ }
+ .VS-search .search_facet input {
+ margin: 0;
+ padding: 0;
+ color: #999;
+ font-size: 13px;
+ line-height: 16px;
+ padding: 6px 0 7px 4px;
+ height: 16px;
+ width: auto;
+ z-index: 100;
+ position: relative;
+
+ }
+ .VS-search .search_facet.is_selected input {
+ padding-top: 1px;
+ padding-bottom: 2px;
+ padding-right: 3px;
+ }
+ .VS-search .search_facet.is_editing input,
+ .VS-search .search_facet.is_selected input {
+ color: #202020;
+ }
+ .VS-search .search_facet .search_facet_remove {
+ position: absolute;
+ left: 0;
+ top: 9px;
+ opacity: .5;
+ }
+ .VS-search .search_facet.is_selected .search_facet_remove {
+ top: 4px;
+ left: 3px;
+ }
+ .VS-search .search_facet .search_facet_remove:hover {
+ opacity: 1;
+ }
+ .VS-search .search_facet.is_editing .category,
+ .VS-search .search_facet.is_selected .category {
+ color: darkblue;
+ }
+ .VS-search .search_facet.search_facet_maybe_delete .category,
+ .VS-search .search_facet.search_facet_maybe_delete input {
+ color: darkred;
+ }
+ .VS-search .search_input {
+ height: 28px;
+ float: left;
+ }
+ .VS-search .search_input input {
+ padding: 6px 2px 7px 2px;
+ }
+ .VS-search input {
+ width: 100px;
+ }
+ .VS-search input,
+ .VS-search .input_width_tester {
+ padding: 6px 0;
+ float: left;
+ }
+ .VS-search input,
+ .VS-search .placeholder,
+ .VS-search .input_width_tester {
+ color: #808080;
+ font: 13px/17px Helvetica, Arial;
+ }
+ .VS-search.VS-focus .search_facet.is_editing input {
+ color: #000;
+ }
+ .VS-search.VS-focus input {
+ color: #606060;
+ }
+ .VS-search .search_glyph {
+ position: absolute;
+ left: 9px; top: 8px;
+ }
+ .VS-search .cancel_search {
+ position: absolute;
+ right: 9px; top: 8px;
+ }
+
+/* ================ */
+/* = Autocomplete = */
+/* ================ */
+
+.ui-autocomplete {
+ position: absolute;
+ border: 1px solid #C0C0C0;
+ background-color: #F6F6F6;
+ cursor: pointer;
+ z-index: 10000;
+ width: auto;
+ min-width: 80px;
+ max-width: 220px;
+ max-height: 240px;
+ overflow-y: auto;
+ overflow-x: hidden;
+ font-size: 13px;
+}
+ .ui-autocomplete .ui-autocomplete-category {
+ text-transform: capitalize;
+ font-size: 11px;
+ padding: 4px 4px 4px;
+ border-top: 1px solid #A2A2A2;
+ border-bottom: 1px solid #A2A2A2;
+ background-color: #B7B7B7;
+ text-shadow: 0 -1px 0 #999;
+ font-weight: bold;
+ color: white;
+ cursor: default;
+ }
+ .ui-autocomplete .ui-menu-item a {
+ display: block;
+ padding: 3px 3px 5px;
+ }
+ .ui-autocomplete .ui-menu-item .ui-state-hover {
+ background-color: #2A61CD;
+ color: white;
+ }
+ .ui-autocomplete li {
+ list-style: none;
+ width: auto;
+ }
+
284 build/visualsearch.css
View
@@ -0,0 +1,284 @@
+.VS-search .icon {
+ background-repeat: no-repeat;
+ background-position: center center;
+ vertical-align: middle;
+ width: 16px; height: 16px;
+}
+.VS-search .icon.cancel_search {
+ width: 11px; height: 11px;
+ background-position: center 0;
+ background-image: url(../images/embed/icons/cancel_search.png?1281992275);
+ cursor: pointer;
+}
+ .VS-search .icon.cancel_search:hover {
+ background-position: center -11px;
+ }
+.VS-search .icon.search_glyph {
+ width: 12px; height: 12px;
+ background-image: url(../images/embed/icons/search_glyph.png?1278683131);
+}
+
+/*------------------------------ RESET + DEFAULT STYLES ---------------------------------*/
+
+/*
+Eric Meyer's final reset.css
+Source: http://meyerweb.com/eric/thoughts/2007/05/01/reset-reloaded/
+*/
+.VS-search div, .VS-search span, .VS-search a, .VS-search img,
+.VS-search ul, .VS-search li, .VS-search form, .VS-search label,
+.ui-autocomplete ul, .ui-autocomplete li, .ui-autocomplete {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-weight: inherit;
+ font-style: inherit;
+ font-size: 100%;
+ font-family: inherit;
+ vertical-align: baseline;
+}
+
+.VS-search :focus {
+ outline: 0;
+}
+.VS-search {
+ line-height: 1;
+ color: black;
+}
+.VS-search ol, .VS-search ul {
+ list-style: none;
+}
+
+/* ===================== */
+/* = General and Reset = */
+/* ===================== */
+
+.VS-search {
+ font-family: Arial, sans-serif;
+ color: #373737;
+ font-size: 12px;
+}
+.VS-search input {
+ display: block;
+ border: none;
+ outline: none;
+ margin: 0; padding: 4px;
+ background: transparent;
+ font-size: 16px;
+ line-height: 20px;
+ width: 100%;
+}
+.VS-interface, .VS-search .dialog, .VS-search input {
+ font-family: "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, sans-serif !important;
+ line-height: 1.1em;
+}
+
+/* ========== */
+/* = Layout = */
+/* ========== */
+
+#search_button {
+ float: right;
+ margin: 3px 29px 0 6px;
+}
+#search_box_wrapper {
+}
+ input.search_box {
+ width: 10px;
+ z-index: 100;
+ position: relative;
+ }
+ #search_box_wrapper .menu_content {
+ z-index: 100;
+ }
+
+.VS-search .VS-search-box {
+ cursor: text;
+ position: relative;
+ background: transparent;
+ border: 2px solid #ccc;
+ border-radius: 16px; -webkit-border-radius: 16px; -moz-border-radius: 16px;
+ background-color: #fafafa;
+ -webkit-box-shadow: inset 0px 0px 3px #ccc;
+ -moz-box-shadow: inset 0px 0px 3px #ccc;
+ box-shadow: inset 0px 0px 3px #ccc;
+ min-height: 29px;
+ height: auto;
+}
+ .VS-search .VS-search-box.VS-focus {
+ border-color: #acf;
+ -webkit-box-shadow: inset 0px 0px 3px #acf;
+ -moz-box-shadow: inset 0px 0px 3px #acf;
+ box-shadow: inset 0px 0px 3px #acf;
+ }
+ .VS-search .search_inner {
+ position: relative;
+ margin: 0 25px 0 18px;
+ padding: 0 3px;
+ overflow: hidden;
+ }
+ .VS-search .search_facets {
+ }
+ .VS-search .search_facet {
+ float: left;
+ margin: 0;
+ padding: 0 0 0 13px;
+ position: relative;
+ }
+ .VS-search .search_facet.is_selected {
+ margin: 5px -3px 5px -3px;
+ -webkit-border-radius: 16px;
+ -moz-border-radius: 16px;
+ border-radius: 16px;
+ }
+ .VS-search .search_facet.is_selected {
+ background-color: #CBD1F1;
+ background-image: -moz-linear-gradient(top, #CBD1F1, #AFB5EC); /* FF3.6 */
+ background-image: -ms-linear-gradient(top, #CBD1F1, #AFB5EC); /* IE10 */
+ background-image: -o-linear-gradient(top, #CBD1F1, #AFB5EC); /* Opera 11.10+ */
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#CBD1F1), to(#AFB5EC)); /* Saf4+, Chrome */
+ background-image: -webkit-linear-gradient(top, #CBD1F1, #AFB5EC); /* Chrome 10+, Saf5.1+ */
+ background-image: linear-gradient(top, #CBD1F1, #AFB5EC);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#CBD1F1', EndColorStr='#AFB5EC'); /* IE6–IE9 */
+ }
+ .VS-search .search_facet .category {
+ float: left;
+ text-transform: uppercase;
+ font-weight: bold;
+ font-size: 10px;
+ color: #B0B0B0;
+ text-shadow: 1px 1px 0 #FFF;
+ padding: 9px 0 6px;
+ line-height: 13px;
+ cursor: pointer;
+ }
+ .VS-search .search_facet.is_selected .category {
+ padding: 4px 0 0;
+ margin-left: 3px;
+ text-shadow: 1px 1px 0 #D0D0D0;
+ }
+ .VS-search .search_facet .search_facet_input_container {
+ float: left;
+ }
+ .VS-search .search_facet input {
+ margin: 0;
+ padding: 0;
+ color: #999;
+ font-size: 13px;
+ line-height: 16px;
+ padding: 6px 0 7px 4px;
+ height: 16px;
+ width: auto;
+ z-index: 100;
+ position: relative;
+
+ }
+ .VS-search .search_facet.is_selected input {
+ padding-top: 1px;
+ padding-bottom: 2px;
+ padding-right: 3px;
+ }
+ .VS-search .search_facet.is_editing input,
+ .VS-search .search_facet.is_selected input {
+ color: #202020;
+ }
+ .VS-search .search_facet .search_facet_remove {
+ position: absolute;
+ left: 0;
+ top: 9px;
+ opacity: .5;
+ }
+ .VS-search .search_facet.is_selected .search_facet_remove {
+ top: 4px;
+ left: 3px;
+ }
+ .VS-search .search_facet .search_facet_remove:hover {
+ opacity: 1;
+ }
+ .VS-search .search_facet.is_editing .category,
+ .VS-search .search_facet.is_selected .category {
+ color: darkblue;
+ }
+ .VS-search .search_facet.search_facet_maybe_delete .category,
+ .VS-search .search_facet.search_facet_maybe_delete input {
+ color: darkred;
+ }
+ .VS-search .search_input {
+ height: 28px;
+ float: left;
+ }
+ .VS-search .search_input input {
+ padding: 6px 2px 7px 2px;
+ }
+ .VS-search input {
+ width: 100px;
+ }
+ .VS-search input,
+ .VS-search .input_width_tester {
+ padding: 6px 0;
+ float: left;
+ }
+ .VS-search input,
+ .VS-search .placeholder,
+ .VS-search .input_width_tester {
+ color: #808080;
+ font: 13px/17px Helvetica, Arial;
+ }
+ .VS-search.VS-focus .search_facet.is_editing input {
+ color: #000;
+ }
+ .VS-search.VS-focus input {
+ color: #606060;
+ }
+ .VS-search .search_glyph {
+ position: absolute;
+ left: 9px; top: 8px;
+ }
+ .VS-search .cancel_search {
+ position: absolute;
+ right: 9px; top: 8px;
+ }
+
+/* ================ */
+/* = Autocomplete = */
+/* ================ */
+
+.ui-autocomplete {
+ position: absolute;
+ border: 1px solid #C0C0C0;
+ background-color: #F6F6F6;
+ cursor: pointer;
+ z-index: 10000;
+ width: auto;
+ min-width: 80px;
+ max-width: 220px;
+ max-height: 240px;
+ overflow-y: auto;
+ overflow-x: hidden;
+ font-size: 13px;
+}
+ .ui-autocomplete .ui-autocomplete-category {
+ text-transform: capitalize;
+ font-size: 11px;
+ padding: 4px 4px 4px;
+ border-top: 1px solid #A2A2A2;
+ border-bottom: 1px solid #A2A2A2;
+ background-color: #B7B7B7;
+ text-shadow: 0 -1px 0 #999;
+ font-weight: bold;
+ color: white;
+ cursor: default;
+ }
+ .ui-autocomplete .ui-menu-item a {
+ display: block;
+ padding: 3px 3px 5px;
+ }
+ .ui-autocomplete .ui-menu-item .ui-state-hover {
+ background-color: #2A61CD;
+ color: white;
+ }
+ .ui-autocomplete li {
+ list-style: none;
+ width: auto;
+ }
+
1,352 build/visualsearch.js
View
@@ -0,0 +1,1352 @@
+// VisualSearch.js 0.1.0
+// (c) 2011 Samuel Clay, @samuelclay, DocumentCloud Inc.
+// VisualSearch.js may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://documentcloud.github.com/visualsearch
+
+(function() {
+
+ if (!window.VS) window.VS = {};
+ if (!VS.app) VS.app = {};
+ if (!VS.ui) VS.ui = {};
+ if (!VS.model) VS.model = {};
+ if (!VS.utils) VS.utils = {};
+
+ VS.init = function(options) {
+ var defaults = {
+ callbacks : {
+ search : $.noop,
+ focus : $.noop,
+ categoryMatches : $.noop,
+ facetMatches : $.noop
+ }
+ };
+ VS.options = _.extend({}, defaults, options);
+ VS.options.callbacks = _.extend({}, defaults.callbacks, options.callbacks);
+
+ VS.app.hotkeys.initialize();
+ VS.app.searchQuery = new VS.model.SearchQuery();
+ VS.app.searchBox = new VS.ui.SearchBox(options);
+
+ if (options.container) {
+ var searchBox = VS.app.searchBox.render().el;
+ $(options.container).html(searchBox);
+ }
+ VS.app.searchBox.value(options.query || '');
+
+ return VS.app.searchBox;
+ };
+
+})();
+// The search box is responsible for managing the many facet views and input views.
+VS.ui.SearchBox = Backbone.View.extend({
+
+ id : 'search',
+
+ events : {
+ 'click .search_glyph' : 'showFacetCategoryMenu',
+ 'click .cancel_search_box' : 'clearSearch',
+ 'mousedown .VS-search-box' : 'focusSearch',
+ 'dblclick .VS-search-box' : 'highlightSearch',
+ 'click #search_button' : 'searchEvent'
+ },
+
+ // Creating a new SearchBox registers handlers for
+ initialize : function() {
+ this.flags = {
+ allSelected : false
+ };
+ this.facetViews = [];
+ this.inputViews = [];
+ _.bindAll(this, 'hideSearch', 'renderFacets', '_maybeDisableFacets', 'disableFacets');
+ VS.app.searchQuery.bind('refresh', this.renderFacets);
+ $(document).bind('keydown', this._maybeDisableFacets);
+ },
+
+ render : function() {
+ $(this.el).append(JST['search_box']({}));
+ $(document.body).setMode('no', 'search');
+
+ return this;
+ },
+
+ hideSearch : function() {
+ $(document.body).setMode(null, 'search');
+ },
+
+ // Handles keydown events on the document. Used to complete the Cmd+A deletion, and
+ // blurring focus.
+ _maybeDisableFacets : function(e) {
+ if (this.flags.allSelected &&
+ (VS.app.hotkeys.key(e) == 'backspace' || VS.app.hotkeys.printable(e))) {
+ this.clearSearch();
+ } else if (this.flags.allSelected) {
+ console.log(['_maybeDisableFacets', this.flags.allSelected]);
+ this.flags.allSelected = false;
+ this.disableFacets();
+ }
+ },
+
+ // Clears out the search box. Command+A + delete can trigger this, as can a cancel button.
+ clearSearch : function() {
+ console.log(['clearSearch']);
+ this.value('');
+ this.flags.allSelected = false;
+ this.focusSearch();
+ },
+
+ // Used to launch a search. Hitting enter or clicking the search button.
+ searchEvent : function(e) {
+ var query = this.value();
+ console.log(['real searchEvent', e, query, VS.options.callbacks]);
+ this.focusSearch();
+ if (VS.options.callbacks.search(query) !== false) {
+ this.value(query);
+ }
+ },
+
+ // Either gets a serialized query string or sets the faceted query from a query string.
+ value : function(query) {
+ if (query == null) return this.getQuery();
+ return this.setQuery(query);
+ },
+
+ // Uses the VS.app.searchQuery collection to serialize the current query from the various
+ // facets that are in the search box.
+ getQuery : function() {
+ var query = [];
+ var inputViewsCount = this.inputViews.length;
+
+ VS.app.searchQuery.each(_.bind(function(facet, i) {
+ query.push(this.inputViews[i].value());
+ query.push(facet.serialize());
+ }, this));
+
+ if (inputViewsCount) {
+ query.push(this.inputViews[inputViewsCount-1].value());
+ }
+ console.log(['getQuery', query, _.compact(query)]);
+
+ return _.compact(query).join(' ');
+ },
+
+ // Takes a query string and uses the SearchParser to parse and render it.
+ setQuery : function(query) {
+ this.currentQuery = query;
+ VS.app.SearchParser.parse(query);
+ this.clearInputs();
+ },
+
+ // Returns the position of a facet/input view. Useful when moving between facets.
+ viewPosition : function(view) {
+ var views = view.type == 'facet' ? this.facetViews : this.inputViews;
+ var position = _.indexOf(views, view);
+ if (position == -1) position = 0;
+ return position;
+ },
+
+ // ====================
+ // = Rendering Facets =
+ // ====================
+
+ // Add a new facet. Facet will be focused and ready to accept a value. Can also
+ // specify position, in the case of adding facets from an inbetween input.
+ addFacet : function(category, initialQuery, position) {
+ category = VS.utils.inflector.trim(category);
+ initialQuery = VS.utils.inflector.trim(initialQuery || '');
+ if (!category) return;
+
+ console.log(['addFacet', category, initialQuery, position]);
+ var model = new VS.model.SearchFacet({
+ category : category,
+ value : initialQuery || ''
+ });
+ VS.app.searchQuery.add(model, {at: position});
+ this.renderFacets();
+ var facetView = _.detect(this.facetViews, function(view) {
+ if (view.model == model) return true;
+ });
+ facetView.enableEdit();
+ },
+
+ // Renders each facet as a searchFacet view.
+ renderFacets : function() {
+ this.facetViews = [];
+ this.inputViews = [];
+
+ this.$('.search_inner').empty();
+
+ VS.app.searchQuery.each(_.bind(function(facet, i) {
+ this.renderFacet(facet, i);
+ }, this));
+
+ // Add on an n+1 empty search input on the very end.
+ this.renderSearchInput();
+ },
+
+ // Render a single facet, using its category and query value.
+ renderFacet : function(facet, position) {
+ var view = new VS.ui.SearchFacet({
+ model : facet,
+ order : position
+ });
+
+ // Input first, facet second.
+ this.renderSearchInput();
+ this.facetViews.push(view);
+ this.$('.search_inner').children().eq(position*2).after(view.render().el);
+
+ view.calculateSize();
+ _.defer(_.bind(view.calculateSize, view));
+
+ return view;
+ },
+
+ // Render a single input, used to create and autocomplete facets
+ renderSearchInput : function() {
+ var input = new VS.ui.SearchInput({position: this.inputViews.length});
+ this.$('.search_inner').append(input.render().el);
+ this.inputViews.push(input);
+ },
+
+ // When setting the entire query, clear out old inputs in between facets.
+ clearInputs : function() {
+ _.each(this.inputViews, function(input) {
+ input.clear();
+ });
+ },
+
+ // Move focus between facets and inputs. Takes a direction as well as many options
+ // for skipping over inputs and only to facets, placement of cursor position in facet
+ // (i.e. at the end), and selecting the text in the input/facet.
+ focusNextFacet : function(currentView, direction, options) {
+ options = options || {};
+ var viewCount = this.facetViews.length;
+ var viewPosition = options.viewPosition || this.viewPosition(currentView);
+
+ console.log(['focusNextFacet', viewCount, viewPosition, currentView.type, direction, options]);
+ // Correct for bouncing between matching text and facet arrays.
+ if (!options.skipToFacet) {
+ if (currentView.type == 'text' && direction > 0) direction -= 1;
+ if (currentView.type == 'facet' && direction < 0) direction += 1;
+ } else if (options.skipToFacet && currentView.type == 'text' &&
+ viewCount == viewPosition && direction >= 0) {
+ viewPosition = 0;
+ direction = 0;
+ }
+ var view, next = Math.min(viewCount, viewPosition + direction);
+
+ if (currentView.type == 'text' && next >= 0 && next < viewCount) {
+ view = this.facetViews[next];
+ if (options.selectFacet) {
+ view.selectFacet();
+ } else {
+ view.enableEdit();
+ view.setCursorAtEnd(direction || options.startAtEnd);
+ }
+ } else if (currentView.type == 'facet') {
+ if (options.skipToFacet) {
+ var view;
+ if (next >= viewCount || next < 0) {
+ view = _.last(this.inputViews);
+ view.focus();
+ } else {
+ view = this.facetViews[next];
+ view.enableEdit();
+ view.setCursorAtEnd(direction || options.startAtEnd);
+ }
+ } else {
+ view = this.inputViews[next];
+ console.log(['next view', next, view]);
+ view.focus();
+ if (options.selectText) view.selectText();
+ }
+ }
+ if (options.selectText) view.selectText();
+ this.resizeFacets();
+ },
+
+ // Command+A selects all facets.
+ selectAllFacets : function() {
+ _.each(this.facetViews, function(facetView, i) {
+ facetView.selectFacet(null, true);
+ });
+ _.each(this.inputViews, function(inputView, i) {
+ inputView.selectText();
+ });
+ this.flags.allSelected = true;
+
+ $(document).one('click', this.disableFacets);
+ },
+
+ // Used by facets and input to see if all facets are currently selected.
+ allSelected : function() {
+ return this.flags.allSelected;
+ },
+
+ // Disables all facets except for the passed in view. Used when switching between
+ // facets, so as not to have to keep state of active facets.
+ disableFacets : function(keepView) {
+ _.each(this.facetViews, function(view) {
+ if (view != keepView &&
+ (view.modes.editing == 'is' ||
+ view.modes.selected == 'is')) {
+ console.log(['disabling view', view.model.get('category')]);
+ view.disableEdit();
+ view.deselectFacet();
+ }
+ });
+ this.removeFocus();
+ },
+
+ // Resize all inputs to account for extra keystrokes which may be changing the facet
+ // width incorrectly. This is a safety check to ensure inputs are correctly sized.
+ resizeFacets : function(view) {
+ _.each(this.facetViews, function(facetView, i) {
+ if (!view || facetView == view) {
+ facetView.resize();
+ }
+ });
+ },
+
+ // Bring focus to last input field.
+ focusSearch : function(e, selectText) {
+ console.log(['focusSearch', e, !e || $(e.target).is('.VS-search-box') || $(e.target).is('.search_inner')]);
+ if (!e || $(e.target).is('.VS-search-box') || $(e.target).is('.search_inner')) {
+ this.disableFacets();
+ _.defer(_.bind(function() {
+ if (!this.$('input:focus').length) {
+ this.inputViews[this.inputViews.length-1].focus(selectText);
+ }
+ }, this));
+ }
+ },
+
+ // Double-clicking on the search wrapper should select the existing text in
+ // the last search input.
+ highlightSearch : function(e) {
+ this.focusSearch(e, true);
+ },
+
+ // Used to show the user is focused on some input inside the search box.
+ addFocus : function() {
+ VS.options.callbacks.focus();
+ this.$('.VS-search-box').addClass('VS-focus');
+ },
+
+ // User is no longer focused on anything in the search box.
+ removeFocus : function() {
+ var focus = _.any(this.facetViews.concat(this.inputViews), function(view) {
+ return view.isFocused();
+ });
+ if (!focus) this.$('.VS-search-box').removeClass('VS-focus');
+ },
+
+ // Show a menu which adds pre-defined facets to the search box.
+ showFacetCategoryMenu : function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ console.log(['showFacetCategoryMenu', e]);
+ if (this.facetCategoryMenu && this.facetCategoryMenu.modes.open == 'is') {
+ return this.facetCategoryMenu.close();
+ }
+
+ var items = [
+ {title: 'Account', onClick: _.bind(this.addFacet, this, 'account', '')},
+ {title: 'Project', onClick: _.bind(this.addFacet, this, 'project', '')},
+ {title: 'Filter', onClick: _.bind(this.addFacet, this, 'filter', '')},
+ {title: 'Access', onClick: _.bind(this.addFacet, this, 'access', '')}
+ ];
+
+ var menu = this.facetCategoryMenu || (this.facetCategoryMenu = new dc.ui.Menu({
+ items : items,
+ standalone : true
+ }));
+
+ this.$('.search_glyph').after(menu.render().open().content);
+ return false;
+ }
+
+});
+// This is the visual search facet that holds the category and its autocompleted
+// input field.
+VS.ui.SearchFacet = Backbone.View.extend({
+
+ type : 'facet',
+
+ className : 'search_facet',
+
+ events : {
+ 'click .category' : 'selectFacet',
+ 'keydown input' : 'keydown',
+ 'keypress input' : 'keypress',
+ 'mousedown input' : 'enableEdit',
+ 'mouseover .cancel_search' : 'showDelete',
+ 'mouseout .cancel_search' : 'hideDelete',
+ 'click .cancel_search' : 'remove'
+ },
+
+ initialize : function(options) {
+ this.setMode('not', 'editing');
+ _.bindAll(this, 'set', 'keydown', 'deselectFacet', 'deferDisableEdit');
+ },
+
+ render : function() {
+ console.log(['search facet', this.model.get('category'), this.model.get('value')]);
+ var $el = this.$el = $(this.el);
+ $el.html(JST['search_facet']({
+ model : this.model
+ }));
+ this.setMode('not', 'editing');
+ this.box = this.$('input');
+ this.box.val(this.model.get('value'));
+ this.box.bind('blur', this.deferDisableEdit);
+ this.setupAutocomplete();
+
+ return this;
+ },
+
+ // This is defered from the searchBox so it can be attached to the
+ // DOM to get the correct font-size.
+ calculateSize : function() {
+ this.box.autoGrowInput();
+ this.box.unbind('updated.autogrow').bind('updated.autogrow', _.bind(this.moveAutocomplete, this));
+ },
+
+ setupAutocomplete : function() {
+ this.box.autocomplete({
+ source : _.bind(this.autocompleteValues, this),
+ minLength : 0,
+ delay : 0,
+ autoFocus : true,
+ select : _.bind(function(e, ui) {
+ e.preventDefault();
+ var originalValue = this.model.get('value');
+ console.log(['autocomplete facet', ui.item.value, originalValue]);
+ this.set(ui.item.value);
+ if (originalValue != ui.item.value || this.box.val() != ui.item.value) this.search(e);
+ return false;
+ }, this)
+ });
+
+ this.box.autocomplete('widget').addClass('VS-interface');
+ },
+
+ moveAutocomplete : function() {
+ var autocomplete = this.box.data('autocomplete');
+ if (autocomplete) {
+ autocomplete.menu.element.position({
+ my: "left top",
+ at: "left bottom",
+ of: this.box.data('autocomplete').element,
+ collision: "none"
+ });
+ }
+ },
+
+ set : function(value) {
+ if (!value) return;
+ // this.box.data('autocomplete').close();
+ console.log(['set facet', value, this.model.get('value'), this.model]);
+ this.model.set({'value': value});
+ },
+
+ search : function(e) {
+ console.log(['facet search', e]);
+ this.closeAutocomplete();
+ VS.app.searchBox.searchEvent(e);
+ },
+
+ enableEdit : function() {
+ this.canClose = false;
+ console.log(['enableEdit', this.model.get('category'), this.modes.editing]);
+ if (this.modes.editing != 'is') {
+ this.setMode('is', 'editing');
+ this.deselectFacet();
+ if (this.box.val() == '') {
+ this.box.val(this.model.get('value'));
+ }
+ }
+ this.searchAutocomplete();
+ VS.app.searchBox.disableFacets(this);
+ VS.app.searchBox.addFocus();
+ _.defer(function() {
+ VS.app.searchBox.addFocus();
+ });
+ if (!this.box.is(':focus')) this.box.focus();
+ this.resize();
+ },
+
+ deferDisableEdit : function() {
+ console.log(['deferDisableEdit', this.model.get('category'), this.box.is(':focus')]);
+ this.canClose = true;
+ _.delay(_.bind(function() {
+ console.log(['deferDisableEdit post', this.canClose, this.box.is(':focus'), this.modes.editing]);
+ if (this.canClose && !this.box.is(':focus') && this.modes.editing == 'is' && this.modes.selected != 'is') {
+ this.disableEdit();
+ }
+ }, this), 250);
+ },
+
+ disableEdit : function() {
+ console.log(['disableEdit', this.model.get('category'), this.box.is(':focus')]);
+ var newFacetQuery = VS.utils.inflector.trim(this.box.val());
+ if (newFacetQuery != this.model.get('value')) {
+ this.set(newFacetQuery);
+ }
+ this.canClose = false;
+ this.box.selectRange(0, 0);
+ this.box.blur();
+ this.setMode('not', 'editing');
+ this.closeAutocomplete();
+ VS.app.searchBox.removeFocus();
+ },
+
+ selectFacet : function(e, selectAll) {
+ console.log(['selectFacet', this.model.get('category'), selectAll]);
+ this.canClose = false;
+ this.box.setCursorPosition(0);
+ if (this.box.is(':focus')) this.box.blur();
+ this.setMode('is', 'selected');
+ this.setMode('not', 'editing');
+ this.closeAutocomplete();
+ if (!selectAll) {
+ $(document).unbind('keydown.facet', this.keydown);
+ $(document).unbind('click.facet', this.deselectFacet);
+ _.defer(_.bind(function() {
+ $(document).unbind('keydown.facet').bind('keydown.facet', this.keydown);
+ $(document).unbind('click.facet').one('click.facet', this.deselectFacet);
+ }, this));
+ VS.app.searchBox.disableFacets(this);
+ VS.app.searchBox.addFocus();
+ }
+ },
+
+ deselectFacet : function() {
+ console.log(['deselectFacet', this.model.get('category')]);
+ if (this.modes.selected == 'is') {
+ this.setMode('not', 'selected');
+ this.closeAutocomplete();
+ VS.app.searchBox.removeFocus();
+ }
+ $(document).unbind('keydown.facet', this.keydown);
+ $(document).unbind('click.facet', this.deselectFacet);
+ },
+
+ isFocused : function() {
+ return this.box.is(':focus');
+ },
+
+ searchAutocomplete : function(e) {
+ // console.log(['searchAutocomplete', e]);
+ var autocomplete = this.box.data('autocomplete');
+ if (autocomplete) autocomplete.search();
+ },
+
+ closeAutocomplete : function() {
+ console.log(['closeAutocomplete', this.model.get('category')]);
+ var autocomplete = this.box.data('autocomplete');
+ if (autocomplete) autocomplete.close();
+ },
+
+ autocompleteValues : function(req, resp) {
+ var category = this.model.get('category');
+ var value = this.model.get('value');
+ var searchTerm = req.term;
+
+ var matches = VS.options.callbacks.facetMatches(category) || [];
+
+ if (searchTerm && value != searchTerm) {
+ var re = VS.utils.inflector.escapeRegExp(searchTerm || '');
+ var matcher = new RegExp('\\b' + re, 'i');
+ matches = $.grep(matches, function(item) {
+ return matcher.test(item) || matcher.test(item.value) || matcher.test(item.label);
+ });
+ }
+
+ resp(_.sortBy(matches, function(match) {
+ if (match == value || match.value == value) return '';
+ else return match;
+ }));
+ },
+
+ showDelete : function() {
+ this.$el.addClass('search_facet_maybe_delete');
+ },
+
+ hideDelete : function() {
+ this.$el.removeClass('search_facet_maybe_delete');
+ },
+
+ setCursorAtEnd : function(direction) {
+ if (direction == -1) {
+ this.box.setCursorPosition(this.box.val().length);
+ } else {
+ this.box.setCursorPosition(0);
+ }
+ },
+
+ remove : function(e) {
+ console.log(['remove facet', e, this.model]);
+ var committed = this.model.has('value');
+ this.deselectFacet();
+ this.disableEdit();
+ VS.app.searchQuery.remove(this.model);
+ if (committed) {
+ this.search();
+ } else {
+ VS.app.searchBox.renderFacets();
+ }
+ VS.app.searchBox.focusNextFacet(this, 0, {viewPosition: this.options.order});
+ },
+
+ removeLastCharacter : function() {
+ console.log(['removeLastCharacter', this.box.val()]);
+ var value = this.box.val();
+ this.box.val(value.substr(0, value.length-1));
+ this.resize();
+ },
+
+ selectText: function() {
+ this.box.selectRange(0, this.box.val().length);
+ },
+
+ keypress : function(e) {
+ var key = VS.app.hotkeys.key(e);
+
+ if (key == 'backspace') {
+ // VS.app.searchBox.resizeFacets(this);
+ }
+ },
+
+ keydown : function(e) {
+ var key = VS.app.hotkeys.key(e);
+ console.log(['facet keydown', key, this.box.val(), this.box.getCursorPosition(), this.box.getSelection().length, VS.app.hotkeys.left, VS.app.hotkeys.right]);
+
+ if (key == 'enter' && this.box.val()) {
+ this.disableEdit();
+ this.search(e);
+ } else if (key == 'left') {
+ if (this.box.getCursorPosition() == 0 && !this.box.getSelection().length) {
+ if (this.modes.selected == 'is') {
+ this.deselectFacet();
+ VS.app.searchBox.focusNextFacet(this, -1, {startAtEnd: true});
+ } else {
+ this.selectFacet();
+ }
+ }
+ } else if (key == 'right') {
+ if (this.modes.selected == 'is') {
+ e.preventDefault();
+ this.deselectFacet();
+ this.setCursorAtEnd(0);
+ this.enableEdit();
+ } else if (this.box.getCursorPosition() == this.box.val().length) {
+ e.preventDefault();
+ this.disableEdit();
+ VS.app.searchBox.focusNextFacet(this, 1);
+ }
+ } else if (VS.app.hotkeys.shift && key == 'tab') {
+ e.preventDefault();
+ this.deselectFacet();
+ this.disableEdit();
+ VS.app.searchBox.focusNextFacet(this, -1, {startAtEnd: true, skipToFacet: true, selectText: true});
+ } else if (key == 'tab') {
+ e.preventDefault();
+ this.deselectFacet();
+ this.disableEdit();
+ VS.app.searchBox.focusNextFacet(this, 1, {skipToFacet: true, selectText: true});
+ } else if (VS.app.hotkeys.command && (e.which == 97 || e.which == 65)) {
+ e.preventDefault();
+ VS.app.searchBox.selectAllFacets();
+ return false;
+ } else if (VS.app.hotkeys.printable(e) && this.modes.selected == 'is') {
+ VS.app.searchBox.focusNextFacet(this, -1, {startAtEnd: true});
+ this.remove();
+ } else if (key == 'backspace') {
+ if (this.modes.selected == 'is') {
+ e.preventDefault();
+ this.remove(e);
+ } else if (this.box.getCursorPosition() == 0 && !this.box.getSelection().length) {
+ e.preventDefault();
+ this.selectFacet();
+ }
+ }
+
+ this.resize(e);
+ },
+
+ resize : function(e) {
+ this.box.trigger('resize.autogrow', e);
+ }
+
+});
+// This is the visual search input that is responsible for creating new facets.
+// There is one input placed in between all facets.
+VS.ui.SearchInput = Backbone.View.extend({
+
+ type : 'text',
+
+ className : 'search_input',
+
+ events : {
+ 'keypress input' : 'keypress',
+ 'keydown input' : 'keydown'
+ },
+
+ initialize : function() {
+ _.bindAll(this, 'removeFocus', 'addFocus', 'moveAutocomplete');
+ },
+
+ render : function() {
+ $(this.el).html(JST['search_input']({}));
+
+ this.box = this.$('input');
+ this.box.autoGrowInput();
+ this.box.bind('updated.autogrow', this.moveAutocomplete);
+ this.box.bind('blur', this.removeFocus);
+ this.box.bind('focus', this.addFocus);
+ this.setupAutocomplete();
+
+ return this;
+ },
+
+ setupAutocomplete : function() {
+ this.box.autocomplete({
+ minLength : 1,
+ delay : 50,
+ autoFocus : true,
+ source : _.bind(this.autocompleteValues, this),
+ select : _.bind(function(e, ui) {
+ console.log(['select autocomplete', e, ui]);
+ e.preventDefault();
+ e.stopPropagation();
+ var remainder = this.addTextFacetRemainder(ui.item.value);
+ VS.app.searchBox.addFacet(ui.item.value, '', this.options.position + (remainder?1:0));
+ this.clear();
+ return false;
+ }, this)
+ });
+
+ this.box.data('autocomplete')._renderMenu = function(ul, items) {
+ // Renders the results under the categories they belong to.
+ var category = '';
+ _.each(items, _.bind(function(item, i) {
+ if (item.category && item.category != category) {
+ ul.append('<li class="ui-autocomplete-category">' + item.category + '</li>');
+ category = item.category;
+ }
+ this._renderItem(ul, item);
+ }, this));
+ };
+
+ this.box.autocomplete('widget').addClass('VS-interface');
+ },
+
+ autocompleteValues : function(req, resp) {
+ var searchTerm = req.term;
+ var lastWord = searchTerm.match(/\w+$/); // Autocomplete only last word.
+ var re = VS.utils.inflector.escapeRegExp(lastWord && lastWord[0] || ' ');
+ var prefixes = VS.options.callbacks.categoryMatches() || [];
+
+ // Only match from the beginning of the word.
+ var matcher = new RegExp('^' + re, 'i');
+ var matches = $.grep(prefixes, function(item) {
+ return matcher.test(item.label || item);
+ });
+
+ resp(_.sortBy(matches, function(match) {
+ if (match.label) return match.category + '-' + match.label;
+ else return match;
+ }));
+ },
+
+ moveAutocomplete : function() {
+ var autocomplete = this.box.data('autocomplete');
+ if (autocomplete) {
+ autocomplete.menu.element.position({
+ my: "left top",
+ at: "left bottom",
+ of: this.box.data('autocomplete').element,
+ collision: "none"
+ });
+ }
+ },
+
+ closeAutocomplete : function() {
+ var autocomplete = this.box.data('autocomplete');
+ if (autocomplete) autocomplete.close();
+ },
+
+ addTextFacetRemainder : function(facetValue) {
+ var boxValue = this.box.val();
+ var lastWord = boxValue.match(/\b(\w+)$/);
+ if (lastWord && facetValue.indexOf(lastWord[0]) == 0) boxValue = boxValue.replace(/\b(\w+)$/, '');
+ boxValue = boxValue.replace('^\s+|\s+$', '');
+ // console.log(['addTextFacetRemainder', facetValue, lastWord, boxValue]);
+ if (boxValue) {
+ VS.app.searchBox.addFacet('text', boxValue, this.options.position);
+ }
+ return boxValue;
+ },
+
+ focus : function(selectText) {
+ console.log(['input focus', selectText]);
+ this.addFocus();
+ this.box.focus();
+ if (selectText) {
+ this.selectText();
+ }
+ },
+
+ blur : function() {
+ console.log(['input blur']);
+ this.box.blur();
+ this.removeFocus();
+ },
+
+ removeFocus : function(e) {
+ console.log(['removeFocus', e]);
+ VS.app.searchBox.removeFocus();
+ },
+
+ addFocus : function(e) {
+ console.log(['addFocus', e]);
+ VS.app.searchBox.disableFacets(this);
+ VS.app.searchBox.addFocus();
+ },
+
+ isFocused : function() {
+ return this.box.is(':focus');
+ },
+
+ clear : function() {
+ this.box.val('');
+ },
+
+ value : function() {
+ return this.box.val();
+ },
+
+ selectText : function() {
+ this.box.selectRange(0, this.box.val().length);
+ this.box.focus();
+ },
+
+ // Callback fired on key press in the search box. We search when they hit return.
+ keypress : function(e) {
+ var key = VS.app.hotkeys.key(e);
+ console.log(['input keypress', e.keyCode, key, this.box.getCursorPosition()]);
+
+ if (key == 'enter') {
+ return VS.app.searchBox.searchEvent(e);
+ } else if (VS.app.hotkeys.colon(e)) {
+ this.box.trigger('resize.autogrow', e);
+ var query = this.box.val();
+ var prefixes = VS.options.callbacks.categoryMatches() || [];
+ var labels = _.map(prefixes, function(prefix) {
+ if (prefix.label) return prefix.label;
+ else return prefix;
+ });
+ if (_.contains(_.pluck(prefixes, 'label'), query)) {
+ e.preventDefault();
+ var remainder = this.addTextFacetRemainder(query);
+ VS.app.searchBox.addFacet(query, '', this.options.position + (remainder?1:0));
+ this.clear();
+ return false;
+ }
+ } else if (key == 'backspace') {
+ if (this.box.getCursorPosition() == 0 && !this.box.getSelection().length) {
+ e.preventDefault();
+ e.stopPropagation();
+ e.stopImmediatePropagation();
+ VS.app.searchBox.resizeFacets();
+ return false;
+ }
+ }
+ },
+
+ keydown : function(e) {
+ var key = VS.app.hotkeys.key(e);
+ console.log(['input keydown', key, e.which, this.box.getCursorPosition()]);
+ this.box.trigger('resize.autogrow', e);
+
+ if (key == 'left') {
+ if (this.box.getCursorPosition() == 0) {
+ e.preventDefault();
+ VS.app.searchBox.focusNextFacet(this, -1, {startAtEnd: true});
+ }
+ } else if (key == 'right') {
+ if (this.box.getCursorPosition() == this.box.val().length) {
+ e.preventDefault();
+ VS.app.searchBox.focusNextFacet(this, 1, {selectFacet: true});
+ }
+ } else if (VS.app.hotkeys.shift && key == 'tab') {
+ e.preventDefault();
+ VS.app.searchBox.focusNextFacet(this, -1, {selectText: true});
+ } else if (key == 'tab') {
+ e.preventDefault();
+ var value = this.box.val();
+ if (value.length) {
+ var remainder = this.addTextFacetRemainder(value);
+ VS.app.searchBox.addFacet(value, '', this.options.position + (remainder?1:0));
+ } else {
+ VS.app.searchBox.focusNextFacet(this, 0, {skipToFacet: true, selectText: true});
+ }
+ } else if (VS.app.hotkeys.command && (e.which == 97 || e.which == 65)) {
+ e.preventDefault();
+ VS.app.searchBox.selectAllFacets();
+ return false;
+ } else if (key == 'backspace' && !VS.app.searchBox.allSelected()) {
+ if (this.box.getCursorPosition() == 0 && !this.box.getSelection().length) {
+ e.preventDefault();
+ VS.app.searchBox.focusNextFacet(this, -1, {backspace: true});
+ return false;
+ }
+ }
+ }
+
+});
+(function(){
+
+ // Makes the view enter a mode. Modes have both a 'mode' and a 'group',
+ // and are mutually exclusive with any other modes in the same group.
+ // Setting will update the view's modes hash, as well as set an HTML class
+ // of *[mode]_[group]* on the view's element. Convenient way to swap styles
+ // and behavior.
+ Backbone.View.prototype.setMode = function(mode, group) {
+ this.modes || (this.modes = {});
+ if (this.modes[group] === mode) return;
+ $(this.el).setMode(mode, group);
+ this.modes[group] = mode;
+ };
+
+})();
+// DocumentCloud workspace hotkeys. To tell if a key is currently being pressed,
+// just ask: `VS.app.hotkeys.control`
+VS.app.hotkeys = {
+
+ KEYS: {
+ '16': 'shift',
+ '17': 'control',
+ '91': 'command',
+ '93': 'command',
+ '224': 'command',
+ '13': 'enter',
+ '37': 'left',
+ '38': 'upArrow',
+ '39': 'right',
+ '40': 'downArrow',
+ '46': 'delete',
+ '8': 'backspace',
+ '9': 'tab',
+ '188': 'comma'
+ },
+
+ initialize : function() {
+ _.bindAll(this, 'down', 'up', 'blur');
+ $(document).bind('keydown', this.down);
+ $(document).bind('keyup', this.up);
+ $(window).bind('blur', this.blur);
+ },
+
+ down : function(e) {
+ var key = this.KEYS[e.which];
+ if (key) this[key] = true;
+ },
+
+ up : function(e) {
+ var key = this.KEYS[e.which];
+ if (key) this[key] = false;
+ },
+
+ blur : function(e) {
+ for (var key in this.KEYS) this[this.KEYS[key]] = false;
+ },
+
+ key : function(e) {
+ return this.KEYS[e.which];
+ },
+
+ colon : function(e) {
+ // Colon is special, since the value is different between browsers.
+ var charCode = e.which;
+ return charCode && String.fromCharCode(charCode) == ":";
+ },
+
+ printable : function(e) {
+ var code = e.which;
+ if (e.type == 'keydown') {
+ if (code == 32 || // space
+ (code >= 48 && code <= 90) || // 0-1a-z
+ (code >= 96 && code <= 111) || // 0-9+-/*.
+ (code >= 186 && code <= 192) || // ;=,-./^
+ (code >= 219 && code <= 222)) { // (\)'
+ return true;
+ }
+ } else {
+ if ((code >= 32 && code <= 126) || // [space]!"#$%&'()*+,-.0-9:;<=>?@A-Z[\]^_`a-z{|}
+ (code >= 160 && code <= 500) || // unicode
+ (String.fromCharCode(code) == ":")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+};
+// Naive English transformations on words.
+VS.utils.inflector = {
+
+ small : "(a|an|and|as|at|but|by|en|for|if|in|of|on|or|the|to|v[.]?|via|vs[.]?)",
+ punct : "([!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]*)",
+
+ // Only works for words that pluralize by adding an 's', end in a 'y', or
+ // that we've special-cased. Not comprehensive.
+ pluralize : function(s, count) {
+ if (count == 1) return s;
+ if (s == 'this') return 'these';
+ if (s == 'person') return 'people';
+ if (s == 'this') return 'these';
+ if (s.match(/y$/i)) return s.replace(/y$/i, 'ies');
+ return s + 's';
+ },
+
+ // Titleize function by John Resig after John Gruber. MIT Licensed.
+ titleize : function(s) {
+ s = s.replace(/[-.\/_]/g, ' ').replace(/\s+/gm, ' ');
+ var cap = this.capitalize;
+ var parts = [], split = /[:.;?!] |(?: |^)["Ò]/g, index = 0;
+ while (true) {
+ var m = split.exec(s);
+ parts.push( s.substring(index, m ? m.index : s.length)
+ .replace(/\b([A-Za-z][a-z.'Õ]*)\b/g, function(all){
+ return (/[A-Za-z]\.[A-Za-z]/).test(all) ? all : cap(all);
+ })
+ .replace(RegExp("\\b" + this.small + "\\b", "ig"), this.lowercase)
+ .replace(RegExp("^" + this.punct + this.small + "\\b", "ig"), function(all, punct, word) {
+ return punct + cap(word);
+ })
+ .replace(RegExp("\\b" + this.small + this.punct + "$", "ig"), cap));
+ index = split.lastIndex;
+ if ( m ) parts.push( m[0] );
+ else break;
+ }
+ return parts.join("").replace(/ V(s?)\. /ig, " v$1. ")
+ .replace(/(['Õ])S\b/ig, "$1s")
+ .replace(/\b(AT&T|Q&A)\b/ig, function(all){
+ return all.toUpperCase();
+ });
+ },
+
+ // Delegate to the ECMA5 String.prototype.trim function, if available.
+ trim : function(s) {
+ return s.trim ? s.trim() : s.replace(/^\s+|\s+$/g, '');
+ },
+
+ truncate : function(s, length, truncation) {
+ length = length || 30;
+ truncation = truncation == null ? '...' : truncation;
+ return s.length > length ? s.slice(0, length - truncation.length) + truncation : s;
+ },
+
+ escapeRegExp : function(s) {
+ return s.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
+ }
+};
+(function($) {
+
+ $.fn.extend({
+
+ // See Backbone.View#setMode...
+ setMode : function(state, group) {
+ group = group || 'mode';
+ var re = new RegExp("\\w+_" + group + "(\\s|$)", 'g');
+ var mode = (state === null) ? "" : state + "_" + group;
+ this.each(function(){
+ this.className = (this.className.replace(re, '') + ' ' + mode).replace(/\s\s/g, ' ');
+ });
+ return mode;
+ },
+
+ autoGrowInput: function() {
+ return this.each(function() {
+ var $input = $(this);
+ var $tester = $('<div />').css({
+ opacity : 0,
+ top : -9999,
+ left : -9999,
+ position : 'absolute',
+ whiteSpace : 'nowrap'
+ }).addClass('input_width_tester').addClass('VS-interface');
+
+ $input.after($tester);
+ $input.unbind('keydown.autogrow keypress.autogrow resize.autogrow change.autogrow')
+ .bind('keydown.autogrow keypress.autogrow resize.autogrow change.autogrow', function(e, realEvent) {
+ if (realEvent) e = realEvent;
+ var value = $input.val();
+ VS.app.hotkeys.down(e);
+
+ if (VS.app.hotkeys.key(e) == 'backspace') {
+ var position = $input.getCursorPosition();
+ if (position > 0) value = value.slice(0, position-1) + value.slice(position, value.length);
+ } else if (VS.app.hotkeys.printable(e) && !VS.app.hotkeys.command) {
+ value += VS.app.hotkeys.shift ?
+ String.fromCharCode(e.which) :
+ String.fromCharCode(e.which).toLowerCase();
+ }
+ value = value.replace(/&/g, '&amp;')
+ .replace(/\s/g,'&nbsp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+
+ $tester.html(value);
+ console.log(['autoGrow', e.type, e.which, VS.app.hotkeys.printable(e), value]);
+ $input.width($tester.width() + 3);
+ $input.trigger('updated.autogrow');
+ });
+ $input.trigger('resize.autogrow');
+ });
+ },
+
+ getCursorPosition: function() {
+ var position = 0;
+ var input = this.get(0);
+
+ if (document.selection) {
+ // IE
+ input.focus();
+ var sel = document.selection.createRange();
+ var selLen = document.selection.createRange().text.length;
+ sel.moveStart('character', -input.value.length);
+ position = sel.text.length - selLen;
+ } else if (input && $(input).is(':visible') && input.selectionStart != null) {
+ // Firefox/Safari
+ position = input.selectionStart;
+ }
+
+ // console.log(['getCursorPosition', position]);
+ return position;
+ },
+
+ setCursorPosition: function(position) {
+ return this.each(function() {
+ return $(this).selectRange(position, position);
+ });
+ },
+
+ selectRange: function(start, end) {
+ return this.each(function() {
+ if (this.setSelectionRange) { // FF/Webkit
+ this.focus();
+ this.setSelectionRange(start, end);
+ } else if (this.createTextRange) { // IE
+ var range = this.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', end);
+ range.moveStart('character', start);
+ range.select();
+ }
+ });
+ },
+
+ getSelection: function() {
+ var input = this[0];
+
+ if (input.selectionStart != null) { // FF/Webkit
+ var start = input.selectionStart;
+ var end = input.selectionEnd;
+ return {start: start, end: end, length: end-start, text: input.value.substr(start, end-start)};
+ } else if (document.selection) { // IE
+ var range = document.selection.createRange();
+ if (range) {
+ var textRange = input.createTextRange();
+ var copyRange = textRange.duplicate();
+ textRange.moveToBookmark(range.getBookmark());
+ copyRange.setEndPoint('EndToStart', textRange);
+ var start = copyRange.text.length;
+ var end = start + range.text.length;
+ return {start: start, end: end, length: end-start, text: range.text};
+ }
+ }
+ return {start: 0, end: 0, length: 0};
+ }
+
+ });
+
+})(jQuery);
+
+// A parallel partial JS implementation of lib/dc/search/parser.rb
+// Used to extract keywords from the free text search.
+VS.app.SearchParser = {
+
+ ALL_FIELDS : /('.+?'|".+?"|[^'"\s]{2}\S*):\s*('.+?'|".+?"|[^'"\s]{2}\S*)/g,
+ // ALL_FIELDS : /\w+:\s?(('.+?'|".+?")|([^'"]{2}\S*))/g,
+
+ FIELD : /(.+?):\s*/,
+
+ ONE_ENTITY : /(city|country|term|state|person|place|organization|email|phone):\s*(([^'"][^'"]\S*)|'(.+?)'|"(.+?)")/i,
+
+ ALL_ENTITIES : /(city|country|term|state|person|place|organization|email|phone):\s*(([^'"][^'"]\S*)|'(.+?)'|"(.+?)")/ig,
+
+ parse : function(query) {
+ var searchFacets = this.extractAllFacets(query);
+ VS.app.searchQuery.refresh(searchFacets);
+ },
+
+ extractAllFacets : function(query) {
+ var facets = [];
+ var originalQuery = query;
+
+ while (query) {
+ var category, value;
+ originalQuery = query;
+ var field = this.extractNextField(query);
+ if (!field) {
+ category = 'text';
+ value = this.extractSearchText(query);
+ query = VS.utils.inflector.trim(query.replace(value, ''));
+ } else if (field.indexOf(':') != -1) {
+ category = field.match(this.FIELD)[1];
+ value = field.replace(this.FIELD, '').replace(/(^['"]|['"]$)/g, '');
+ query = VS.utils.inflector.trim(query.replace(field, ''));
+ } else if (field.indexOf(':') == -1) {
+ category = 'text';
+ value = field;
+ query = VS.utils.inflector.trim(query.replace(value, ''));
+ }
+ // console.log(['extractAllFacets', query, category, value, field]);
+ if (category && value) {
+ var searchFacet = new VS.model.SearchFacet({
+ category : category,
+ value : VS.utils.inflector.trim(value)
+ });
+ facets.push(searchFacet);
+ }
+ if (originalQuery == query) break;
+ }
+
+ return facets;
+ },
+
+ extractNextField : function(query) {
+ var textRe = /^\s*(\S+)\s+(?=\w+:\s?(('.+?'|".+?")|([^'"]{2}\S*)))/;
+ var textMatch = query.match(textRe);
+ console.log(['extractNextField', query, textMatch]);
+ if (textMatch && textMatch.length >= 1) {
+ return textMatch[1];
+ } else {
+ return this.extractFirstField(query);
+ }
+ },
+
+ extractFirstField : function(query) {
+ var fields = query.match(this.ALL_FIELDS);
+ return fields && fields.length && fields[0];
+ },
+
+ extractSearchText : function(query) {
+ query = query || '';
+ var text = VS.utils.inflector.trim(query.replace(this.ALL_FIELDS, ''));
+ console.log(['extractSearchText', query, text]);
+ return text;
+ },
+
+ extractEntities : function(query) {
+ var all = this.ALL_ENTITIES, one = this.ONE_ENTITY;
+ var entities = query.match(all) || [];
+ return _.sortBy(_.map(entities, function(ent){
+ var match = ent.match(one);
+ return {type : match[1], value : match[3] || match[4] || match[5]};
+ }), function(ent) {
+ return ent.value.toLowerCase();
+ }).reverse();
+ }
+
+};
+VS.model.SearchFacet = Backbone.Model.extend({
+
+ UNQUOTABLE_CATEGORIES : [
+ 'text',
+ 'account',
+ 'document',
+ 'filter',
+ 'group',
+ 'access',
+ 'related',
+ 'projectid'
+ ],
+
+ serialize : function() {
+ var category = this.get('category');
+ var value = VS.utils.inflector.trim(this.get('value'));
+
+ if (!value) return '';
+
+ if (!_.contains(this.UNQUOTABLE_CATEGORIES, category)) value = '"' + value + '"';
+
+ if (category != 'text') {
+ category = category + ': ';
+ } else {
+ category = "";
+ }
+
+ return category + value;
+ }
+
+});
+VS.model.SearchQuery = Backbone.Collection.extend({
+
+ model : VS.model.SearchFacet,
+
+ value : function() {
+ return this.map(function(facet) {
+ return facet.serialize();
+ }).join(' ');
+ },
+
+ find : function(category) {
+ var facet = this.detect(function(facet) {
+ return facet.get('category') == category;
+ });
+ return facet && facet.get('value');
+ },
+
+ count : function(category) {
+ return this.select(function(facet) {
+ return facet.get('category') == category;
+ }).length;
+ },
+
+ values : function(category) {
+ var facets = this.select(function(facet) {
+ return facet.get('category') == category;
+ });
+ return _.map(facets, function(facet) { return facet.get('value'); });
+ },
+
+ has : function(category, value) {
+ return this.any(function(facet) {
+ if (value) {
+ return facet.get('category') == category && facet.get('value') == value;
+ } else {
+ return facet.get('category') == category;
+ }
+ });
+ },
+
+ withoutCategory : function(category) {
+ var query = this.map(function(facet) {
+ if (facet.get('category') != category) return facet.serialize();
+ }).join(' ');
+
+ return query;
+ }
+
+});(function(){
+window.JST = window.JST || {};
+
+window.JST['search_box'] = _.template('<div class="VS-search"> <div id="search_container"> <div id="search_button" class="minibutton">Search</div> <div id="search_box_wrapper" class="VS-search-box"> <div class="icon search_glyph"></div> <div class="search_inner"></div> <div class="icon cancel_search cancel_search_box" title="clear search"></div> </div> </div></div>');
+window.JST['search_facet'] = _.template('<% if (model.has(\'category\')) { %> <div class="category"><%= model.get(\'category\') %>:</div><% } %><div class="search_facet_input_container"> <input type="text" class="search_facet_input VS-interface" value="" /></div><div class="search_facet_remove icon cancel_search"></div>');
+window.JST['search_input'] = _.template('<input class="search_box" type="text" />');
+})();
7 build/visualsearch_templates.js
View
@@ -0,0 +1,7 @@
+(function(){
+window.JST = window.JST || {};
+
+window.JST['search_box'] = _.template('<div class="VS-search"> <div id="search_container"> <div id="search_button" class="minibutton">Search</div> <div id="search_box_wrapper" class="VS-search-box"> <div class="icon search_glyph"></div> <div class="search_inner"></div> <div class="icon cancel_search cancel_search_box" title="clear search"></div> </div> </div></div>');
+window.JST['search_facet'] = _.template('<% if (model.has(\'category\')) { %> <div class="category"><%= model.get(\'category\') %>:</div><% } %><div class="search_facet_input_container"> <input type="text" class="search_facet_input VS-interface" value="" /></div><div class="search_facet_remove icon cancel_search"></div>');
+window.JST['search_input'] = _.template('<input class="search_box" type="text" />');
+})();
129 demo/github.css
View
@@ -0,0 +1,129 @@
+/*
+
+github.com style (c) Vasily Polovnyov <vast@whiteants.net>
+
+*/
+
+pre code {
+ display: block; padding: 0.5em;
+ color: #000;
+ background: #f8f8ff
+}
+
+pre .comment,
+pre .template_comment,
+pre .diff .header,
+pre .javadoc {
+ color: #998;
+ font-style: italic
+}
+
+pre .keyword,
+pre .css .rule .keyword,
+pre .winutils,
+pre .javascript .title,
+pre .lisp .title,
+pre .subst {
+ color: #000;
+ font-weight: bold
+}
+
+pre .number,
+pre .hexcolor {
+ color: #40a070
+}
+
+pre .string,
+pre .tag .value,
+pre .phpdoc,
+pre .tex .formula {
+ color: #d14
+}