diff --git a/addons/web/__openerp__.py b/addons/web/__openerp__.py
index 26ee5613dc5..417fea91434 100644
--- a/addons/web/__openerp__.py
+++ b/addons/web/__openerp__.py
@@ -36,19 +36,6 @@
"static/lib/underscore/underscore.string.js",
"static/lib/backbone/backbone.js",
- "static/lib/visualsearch/lib/js/visualsearch.js",
- "static/lib/visualsearch/lib/js/utils/backbone_extensions.js",
- "static/lib/visualsearch/lib/js/utils/hotkeys.js",
- "static/lib/visualsearch/lib/js/utils/inflector.js",
- "static/lib/visualsearch/lib/js/utils/jquery_extensions.js",
- "static/lib/visualsearch/lib/js/utils/search_parser.js",
- "static/lib/visualsearch/lib/js/models/search_facets.js",
- "static/lib/visualsearch/lib/js/models/search_query.js",
- "static/lib/visualsearch/lib/js/templates/templates.js",
- "static/lib/visualsearch/lib/js/views/search_facet.js",
- "static/lib/visualsearch/lib/js/views/search_input.js",
- "static/lib/visualsearch/lib/js/views/search_box.js",
-
"static/lib/labjs/LAB.src.js",
"static/lib/py.js/lib/py.js",
"static/src/js/boot.js",
@@ -74,9 +61,6 @@
"static/lib/jquery.ui.timepicker/css/jquery-ui-timepicker-addon.css",
"static/lib/jquery.ui.notify/css/ui.notify.css",
"static/lib/jquery.tipsy/tipsy.css",
- "static/lib/visualsearch/lib/css/reset.css",
- "static/lib/visualsearch/lib/css/workspace.css",
- "static/lib/visualsearch/lib/css/icons.css",
# "static/src/css/base_old.css",
"static/src/css/base.css",
"static/src/css/data_export.css",
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index 12ef0b53524..6eb8ed94909 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -1287,25 +1287,6 @@ class SearchView(View):
del filter['domain']
return filters
- @openerpweb.jsonrequest
- def save_filter(self, req, model, name, context_to_save, domain):
- Model = req.session.model("ir.filters")
- ctx = common.nonliterals.CompoundContext(context_to_save)
- ctx.session = req.session
- ctx = ctx.evaluate()
- domain = common.nonliterals.CompoundDomain(domain)
- domain.session = req.session
- domain = domain.evaluate()
- uid = req.session._uid
- context = req.session.eval_context(req.context)
- to_return = Model.create_or_replace({"context": ctx,
- "domain": domain,
- "model_id": model,
- "name": name,
- "user_id": uid
- }, context)
- return to_return
-
@openerpweb.jsonrequest
def add_to_dashboard(self, req, menu_id, action_id, context_to_save, domain, view_mode, name=''):
ctx = common.nonliterals.CompoundContext(context_to_save)
diff --git a/addons/web/static/lib/visualsearch/LICENSE b/addons/web/static/lib/visualsearch/LICENSE
deleted file mode 100755
index fe0ea1154c4..00000000000
--- a/addons/web/static/lib/visualsearch/LICENSE
+++ /dev/null
@@ -1,22 +0,0 @@
-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
-files (the "Software"), to deal in the Software without
-restriction, including without limitation the rights to use,
-copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following
-conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/addons/web/static/lib/visualsearch/README b/addons/web/static/lib/visualsearch/README
deleted file mode 100755
index c059a9824d6..00000000000
--- a/addons/web/static/lib/visualsearch/README
+++ /dev/null
@@ -1,16 +0,0 @@
- __ ___ _ _____ _ _
- \ \ / (_) | |/ ____| | | (_)
- \ \ / / _ ___ _ _ __ _| | (___ ___ __ _ _ __ ___| |__ _ ___
- \ \/ / | / __| | | |/ _` | |\___ \ / _ \/ _` | '__/ __| '_ \ | / __|
- \ / | \__ \ |_| | (_| | |____) | __/ (_| | | | (__| | | |_| \__ \
- \/ |_|___/\__,_|\__,_|_|_____/ \___|\__,_|_| \___|_| |_(_) |___/
- _/ |
- |__/
-
-VisualSearch.js enhances ordinary search boxes with the ability to autocomplete
-faceted search queries. Specify the facets for completion, along with the
-completable values for any facet. You can retrieve the search query as a
-structured object, so you don't have to parse the query string yourself.
-
-For documentation, pre-packed downloads, demos, and tests, see:
-http://documentcloud.github.com/visualsearch
\ No newline at end of file
diff --git a/addons/web/static/lib/visualsearch/Rakefile b/addons/web/static/lib/visualsearch/Rakefile
deleted file mode 100755
index 192a50b944e..00000000000
--- a/addons/web/static/lib/visualsearch/Rakefile
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'rubygems'
-require 'jammit'
-require 'fileutils'
-
-desc "Use Jammit to compile the multiple versions of Visual Search"
-task :build do
- $VS_MIN = false
- Jammit.package!({
- :config_path => "assets.yml",
- :output_folder => "build"
- })
-
- $VS_MIN = true
- Jammit.package!({
- :config_path => "assets.yml",
- :output_folder => "build-min"
- })
-
- # Move the JSTs back to lib to accomodate the demo page.
- FileUtils.mv("build/visualsearch_templates.js", "lib/js/templates/templates.js")
-
- # Fix image url paths.
- ['build', 'build-min'].each do |build|
- File.open("#{build}/visualsearch.css", 'r+') do |file|
- css = file.read
- css.gsub!(/url\((.*?)images\/embed\/icons/, 'url(../images/embed/icons')
- file.rewind
- file.write(css)
- file.truncate(css.length)
- end
- end
-end
-
-desc "Build the docco documentation"
-task :docs do
- sh "docco lib/js/*.js lib/js/**/*.js"
-end
diff --git a/addons/web/static/lib/visualsearch/assets.yml b/addons/web/static/lib/visualsearch/assets.yml
deleted file mode 100755
index 357c37c74aa..00000000000
--- a/addons/web/static/lib/visualsearch/assets.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-embed_assets: datauri
-javascript_compressor: closure
-template_function: _.template
-gzip_assets: <% if $VS_MIN %>on<% else %>off<% end %>
-compress_assets: <% if $VS_MIN %>on<% else %>off<% end %>
-
-javascripts:
- dependencies:
- - vendor/jquery-*.js
- - vendor/jquery.ui.core.js
- - vendor/jquery.ui.widget.js
- - vendor/jquery.ui.position.js
- - vendor/jquery.ui.*.js
- - vendor/underscore-*.js
- - vendor/backbone-*.js
- visualsearch:
- - lib/js/visualsearch.js
- - lib/js/views/*.js
- - lib/js/utils/*.js
- - lib/js/models/*.js
- - lib/js/templates/*.jst
- <% unless $VS_MIN %>visualsearch_templates:
- - lib/js/templates/*.jst
- <% end %>
-
-stylesheets:
- visualsearch:
- - lib/css/*.css
\ No newline at end of file
diff --git a/addons/web/static/lib/visualsearch/build/visualsearch-datauri.css b/addons/web/static/lib/visualsearch/build/visualsearch-datauri.css
deleted file mode 100755
index 3e3edd0063f..00000000000
--- a/addons/web/static/lib/visualsearch/build/visualsearch-datauri.css
+++ /dev/null
@@ -1,310 +0,0 @@
-.VS-search .VS-icon {
- background-repeat: no-repeat;
- background-position: center center;
- vertical-align: middle;
- width: 16px; height: 16px;
-}
- .VS-search .VS-icon-cancel {
- 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 .VS-icon-cancel:hover {
- background-position: center -11px;
- }
- .VS-search .VS-icon-search {
- 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,
-.VS-interface ul, .VS-interface li, .VS-interface {
- 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;
- -moz-box-shadow: none;
- -webkit-box-shadow: none;
- box-shadow: 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 = */
-/* ========== */
-
-.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: 28px;
- 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 .VS-search-inner {
- position: relative;
- margin: 0 20px 0 22px;
- overflow: hidden;
- }
- .VS-search input {
- width: 100px;
- }
- .VS-search input,
- .VS-search .VS-input-width-tester {
- padding: 6px 0;
- float: left;
- color: #808080;
- font: 13px/17px Helvetica, Arial;
- }
- .VS-search.VS-focus input {
- color: #606060;
- }
- .VS-search .VS-icon-search {
- position: absolute;
- left: 9px; top: 8px;
- }
- .VS-search .VS-icon-cancel {
- position: absolute;
- right: 9px; top: 8px;
- }
-
-/* ================ */
-/* = Search Facet = */
-/* ================ */
-
-.VS-search .search_facet {
- float: left;
- margin: 0;
- padding: 0 0 0 14px;
- position: relative;
- border: 1px solid transparent;
- height: 20px;
- margin: 3px -3px 3px 0;
-}
- .VS-search .search_facet.is_selected {
- margin-left: -3px;
- -webkit-border-radius: 16px;
- -moz-border-radius: 16px;
- border-radius: 16px;
- background-color: #d2e6fd;
- background-image: -moz-linear-gradient(top, #d2e6fd, #b0d1f9); /* FF3.6 */
- background-image: -webkit-gradient(linear, left top, left bottom, from(#d2e6fd), to(#b0d1f9)); /* Saf4+, Chrome */
- background-image: linear-gradient(top, #d2e6fd, #b0d1f9);
- border: 1px solid #6eadf5;
- }
- .VS-search .search_facet .category {
- float: left;
- text-transform: uppercase;
- font-weight: bold;
- font-size: 10px;
- color: #808080;
- padding: 8px 0 5px;
- line-height: 13px;
- cursor: pointer;
- padding: 4px 0 0;
- }
- .VS-search .search_facet.is_selected .category {
- margin-left: 3px;
- }
- .VS-search .search_facet .search_facet_input_container {
- float: left;
- }
- .VS-search .search_facet input {
- margin: 0;
- padding: 0;
- color: #000;
- font-size: 13px;
- line-height: 16px;
- padding: 5px 0 5px 4px;
- height: 16px;
- width: auto;
- z-index: 100;
- position: relative;
- padding-top: 1px;
- padding-bottom: 2px;
- padding-right: 3px;
-
- }
- .VS-search .search_facet.is_editing input,
- .VS-search .search_facet.is_selected input {
- color: #000;
- }
- .VS-search .search_facet .search_facet_remove {
- position: absolute;
- left: 0;
- top: 4px;
- }
- .VS-search .search_facet.is_selected .search_facet_remove {
- opacity: 0.4;
- left: 3px;
- filter: alpha(opacity=40);
- background-position: center -11px;
- }
- .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: #000;
- }
- .VS-search .search_facet.search_facet_maybe_delete .category,
- .VS-search .search_facet.search_facet_maybe_delete input {
- color: darkred;
- }
-
-/* ================ */
-/* = Search Input = */
-/* ================ */
-
-.VS-search .search_input {
- height: 28px;
- float: left;
- margin-left: -1px;
-}
- .VS-search .search_input input {
- padding: 6px 3px 6px 2px;
- line-height: 10px;
- height: 22px;
- margin-top: -4px;
- width: 10px;
- z-index: 100;
- min-width: 4px;
- position: relative;
- }
- .VS-search .search_input.is_editing input {
- color: #202020;
- }
-
-/* ================ */
-/* = Autocomplete = */
-/* ================ */
-
-.VS-interface.ui-autocomplete {
- position: absolute;
- border: 1px solid #C0C0C0;
- border-top: 1px solid #D9D9D9;
- background-color: #F6F6F6;
- cursor: pointer;
- z-index: 10000;
- padding: 0;
- margin: 0;
- width: auto;
- min-width: 80px;
- max-width: 220px;
- max-height: 240px;
- overflow-y: auto;
- overflow-x: hidden;
- font-size: 13px;
- top: 5px;
- opacity: 0.97;
- box-shadow: 3px 4px 5px -2px rgba(0, 0, 0, 0.5); -webkit-box-shadow: 3px 4px 5px -2px rgba(0, 0, 0, 0.5); -moz-box-shadow: 3px 4px 5px -2px rgba(0, 0, 0, 0.5);
-}
- .VS-interface.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;
- }
- .VS-interface.ui-autocomplete .ui-menu-item {
- float: none;
- }
- .VS-interface.ui-autocomplete .ui-menu-item a {
- color: #000;
- outline: none;
- display: block;
- padding: 3px 4px 5px;
- border-radius: none;
- line-height: 1;
- background-color: #F8F8F8;
- background-image: -moz-linear-gradient(top, #F8F8F8, #F3F3F3); /* FF3.6 */
- background-image: -webkit-gradient(linear, left top, left bottom, from(#F8F8F8), to(#F3F3F3)); /* Saf4+, Chrome */
- background-image: linear-gradient(top, #F8F8F8, #F3F3F3);
- border-top: 1px solid #FAFAFA;
- border-bottom: 1px solid #f0f0f0;
- }
- .VS-interface.ui-autocomplete .ui-menu-item a:active {
- outline: none;
- }
- .VS-interface.ui-autocomplete .ui-menu-item .ui-state-hover {
- background-color: #6483F7;
- background-image: -moz-linear-gradient(top, #648bF5, #2465f3); /* FF3.6 */
- background-image: -webkit-gradient(linear, left top, left bottom, from(#648bF5), to(#2465f3)); /* Saf4+, Chrome */
- background-image: linear-gradient(top, #648bF5, #2465f3);
- border-top: 1px solid #5b83ec;
- border-bottom: 1px solid #1459e9;
- border-left: none;
- border-right: none;
- color: white;
- margin: 0;
- }
- .VS-interface.ui-autocomplete .ui-corner-all {
- border-radius: 0;
- }
- .VS-interface.ui-autocomplete li {
- list-style: none;
- width: auto;
- }
diff --git a/addons/web/static/lib/visualsearch/build/visualsearch.css b/addons/web/static/lib/visualsearch/build/visualsearch.css
deleted file mode 100755
index 079ff122300..00000000000
--- a/addons/web/static/lib/visualsearch/build/visualsearch.css
+++ /dev/null
@@ -1,310 +0,0 @@
-.VS-search .VS-icon {
- background-repeat: no-repeat;
- background-position: center center;
- vertical-align: middle;
- width: 16px; height: 16px;
-}
- .VS-search .VS-icon-cancel {
- width: 11px; height: 11px;
- background-position: center 0;
- background-image: url(../images/embed/icons/cancel_search.png?1311104738);
- cursor: pointer;
- }
- .VS-search .VS-icon-cancel:hover {
- background-position: center -11px;
- }
- .VS-search .VS-icon-search {
- width: 12px; height: 12px;
- background-image: url(../images/embed/icons/search_glyph.png?1311104738);
- }
-
-/*------------------------------ 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,
-.VS-interface ul, .VS-interface li, .VS-interface {
- 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;
- -moz-box-shadow: none;
- -webkit-box-shadow: none;
- box-shadow: 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 = */
-/* ========== */
-
-.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: 28px;
- 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 .VS-search-inner {
- position: relative;
- margin: 0 20px 0 22px;
- overflow: hidden;
- }
- .VS-search input {
- width: 100px;
- }
- .VS-search input,
- .VS-search .VS-input-width-tester {
- padding: 6px 0;
- float: left;
- color: #808080;
- font: 13px/17px Helvetica, Arial;
- }
- .VS-search.VS-focus input {
- color: #606060;
- }
- .VS-search .VS-icon-search {
- position: absolute;
- left: 9px; top: 8px;
- }
- .VS-search .VS-icon-cancel {
- position: absolute;
- right: 9px; top: 8px;
- }
-
-/* ================ */
-/* = Search Facet = */
-/* ================ */
-
-.VS-search .search_facet {
- float: left;
- margin: 0;
- padding: 0 0 0 14px;
- position: relative;
- border: 1px solid transparent;
- height: 20px;
- margin: 3px -3px 3px 0;
-}
- .VS-search .search_facet.is_selected {
- margin-left: -3px;
- -webkit-border-radius: 16px;
- -moz-border-radius: 16px;
- border-radius: 16px;
- background-color: #d2e6fd;
- background-image: -moz-linear-gradient(top, #d2e6fd, #b0d1f9); /* FF3.6 */
- background-image: -webkit-gradient(linear, left top, left bottom, from(#d2e6fd), to(#b0d1f9)); /* Saf4+, Chrome */
- background-image: linear-gradient(top, #d2e6fd, #b0d1f9);
- border: 1px solid #6eadf5;
- }
- .VS-search .search_facet .category {
- float: left;
- text-transform: uppercase;
- font-weight: bold;
- font-size: 10px;
- color: #808080;
- padding: 8px 0 5px;
- line-height: 13px;
- cursor: pointer;
- padding: 4px 0 0;
- }
- .VS-search .search_facet.is_selected .category {
- margin-left: 3px;
- }
- .VS-search .search_facet .search_facet_input_container {
- float: left;
- }
- .VS-search .search_facet input {
- margin: 0;
- padding: 0;
- color: #000;
- font-size: 13px;
- line-height: 16px;
- padding: 5px 0 5px 4px;
- height: 16px;
- width: auto;
- z-index: 100;
- position: relative;
- padding-top: 1px;
- padding-bottom: 2px;
- padding-right: 3px;
-
- }
- .VS-search .search_facet.is_editing input,
- .VS-search .search_facet.is_selected input {
- color: #000;
- }
- .VS-search .search_facet .search_facet_remove {
- position: absolute;
- left: 0;
- top: 4px;
- }
- .VS-search .search_facet.is_selected .search_facet_remove {
- opacity: 0.4;
- left: 3px;
- filter: alpha(opacity=40);
- background-position: center -11px;
- }
- .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: #000;
- }
- .VS-search .search_facet.search_facet_maybe_delete .category,
- .VS-search .search_facet.search_facet_maybe_delete input {
- color: darkred;
- }
-
-/* ================ */
-/* = Search Input = */
-/* ================ */
-
-.VS-search .search_input {
- height: 28px;
- float: left;
- margin-left: -1px;
-}
- .VS-search .search_input input {
- padding: 6px 3px 6px 2px;
- line-height: 10px;
- height: 22px;
- margin-top: -4px;
- width: 10px;
- z-index: 100;
- min-width: 4px;
- position: relative;
- }
- .VS-search .search_input.is_editing input {
- color: #202020;
- }
-
-/* ================ */
-/* = Autocomplete = */
-/* ================ */
-
-.VS-interface.ui-autocomplete {
- position: absolute;
- border: 1px solid #C0C0C0;
- border-top: 1px solid #D9D9D9;
- background-color: #F6F6F6;
- cursor: pointer;
- z-index: 10000;
- padding: 0;
- margin: 0;
- width: auto;
- min-width: 80px;
- max-width: 220px;
- max-height: 240px;
- overflow-y: auto;
- overflow-x: hidden;
- font-size: 13px;
- top: 5px;
- opacity: 0.97;
- box-shadow: 3px 4px 5px -2px rgba(0, 0, 0, 0.5); -webkit-box-shadow: 3px 4px 5px -2px rgba(0, 0, 0, 0.5); -moz-box-shadow: 3px 4px 5px -2px rgba(0, 0, 0, 0.5);
-}
- .VS-interface.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;
- }
- .VS-interface.ui-autocomplete .ui-menu-item {
- float: none;
- }
- .VS-interface.ui-autocomplete .ui-menu-item a {
- color: #000;
- outline: none;
- display: block;
- padding: 3px 4px 5px;
- border-radius: none;
- line-height: 1;
- background-color: #F8F8F8;
- background-image: -moz-linear-gradient(top, #F8F8F8, #F3F3F3); /* FF3.6 */
- background-image: -webkit-gradient(linear, left top, left bottom, from(#F8F8F8), to(#F3F3F3)); /* Saf4+, Chrome */
- background-image: linear-gradient(top, #F8F8F8, #F3F3F3);
- border-top: 1px solid #FAFAFA;
- border-bottom: 1px solid #f0f0f0;
- }
- .VS-interface.ui-autocomplete .ui-menu-item a:active {
- outline: none;
- }
- .VS-interface.ui-autocomplete .ui-menu-item .ui-state-hover {
- background-color: #6483F7;
- background-image: -moz-linear-gradient(top, #648bF5, #2465f3); /* FF3.6 */
- background-image: -webkit-gradient(linear, left top, left bottom, from(#648bF5), to(#2465f3)); /* Saf4+, Chrome */
- background-image: linear-gradient(top, #648bF5, #2465f3);
- border-top: 1px solid #5b83ec;
- border-bottom: 1px solid #1459e9;
- border-left: none;
- border-right: none;
- color: white;
- margin: 0;
- }
- .VS-interface.ui-autocomplete .ui-corner-all {
- border-radius: 0;
- }
- .VS-interface.ui-autocomplete li {
- list-style: none;
- width: auto;
- }
diff --git a/addons/web/static/lib/visualsearch/build/visualsearch.js b/addons/web/static/lib/visualsearch/build/visualsearch.js
deleted file mode 100755
index 319d978db3c..00000000000
--- a/addons/web/static/lib/visualsearch/build/visualsearch.js
+++ /dev/null
@@ -1,1845 +0,0 @@
-// This is the annotated source code for
-// [VisualSearch.js](http://documentcloud.github.com/visualsearch/),
-// a rich search box for real data.
-//
-// The annotated source HTML is generated by
-// [Docco](http://jashkenas.github.com/docco/).
-
-/** @license VisualSearch.js 0.2.2
- * (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() {
-
- var $ = jQuery; // Handle namespaced jQuery
-
- // Setting up VisualSearch globals. These will eventually be made instance-based.
- 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 = {};
-
- // Sets the version for VisualSearch to be used programatically elsewhere.
- VS.VERSION = '0.2.2';
-
- VS.VisualSearch = function(options) {
- var defaults = {
- container : '',
- query : '',
- unquotable : [],
- callbacks : {
- search : $.noop,
- focus : $.noop,
- blur : $.noop,
- facetMatches : $.noop,
- valueMatches : $.noop
- }
- };
- this.options = _.extend({}, defaults, options);
- this.options.callbacks = _.extend({}, defaults.callbacks, options.callbacks);
-
- VS.app.hotkeys.initialize();
- this.searchQuery = new VS.model.SearchQuery();
- this.searchBox = new VS.ui.SearchBox({app: this});
-
- if (options.container) {
- var searchBox = this.searchBox.render().el;
- $(this.options.container).html(searchBox);
- }
- this.searchBox.value(this.options.query || '');
-
- // Disable page caching for browsers that incorrectly cache the visual search inputs.
- // This is forced the browser to re-render the page when it is retrieved in its history.
- $(window).bind('unload', function(e) {});
-
- // Gives the user back a reference to the `searchBox` so they
- // can use public methods.
- return this;
- };
-
- // Entry-point used to tie all parts of VisualSearch together. It will either attach
- // itself to `options.container`, or pass back the `searchBox` so it can be rendered
- // at will.
- VS.init = function(options) {
- return new VS.VisualSearch(options);
- };
-
-})();
-(function() {
-
-var $ = jQuery; // Handle namespaced jQuery
-
-// The search box is responsible for managing the many facet views and input views.
-VS.ui.SearchBox = Backbone.View.extend({
-
- id : 'search',
-
- events : {
- 'click .VS-cancel-search-box' : 'clearSearch',
- 'mousedown .VS-search-box' : 'maybeFocusSearch',
- 'dblclick .VS-search-box' : 'highlightSearch',
- 'click .VS-search-box' : 'maybeTripleClick'
- },
-
- // Creating a new SearchBox registers handlers for re-rendering facets when necessary,
- // as well as handling typing when a facet is selected.
- initialize : function() {
- this.app = this.options.app;
- this.flags = {
- allSelected : false
- };
- this.facetViews = [];
- this.inputViews = [];
- _.bindAll(this, 'renderFacets', '_maybeDisableFacets', 'disableFacets',
- 'deselectAllFacets', 'addedFacet', 'removedFacet', 'changedFacet');
- this.app.searchQuery
- .bind('reset', this.renderFacets)
- .bind('add', this.addedFacet)
- .bind('remove', this.removedFacet)
- .bind('change', this.changedFacet);
- $(document).bind('keydown', this._maybeDisableFacets);
- },
-
- // Renders the search box, but requires placement on the page through `this.el`.
- render : function() {
- $(this.el).append(JST['search_box']({}));
- $(document.body).setMode('no', 'search');
-
- return this;
- },
-
- // # Querying Facets #
-
- // Either gets a serialized query string or sets the faceted query from a query string.
- value : function(query) {
- if (query == null) return this.serialize();
- 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.
- serialize : function() {
- var query = [];
- var inputViewsCount = this.inputViews.length;
-
- this.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());
- }
-
- return _.compact(query).join(' ');
- },
-
- // Takes a query string and uses the SearchParser to parse and render it. Note that
- // `VS.app.SearchParser` refreshes the `VS.app.searchQuery` collection, which is bound
- // here to call `this.renderFacets`.
- setQuery : function(query) {
- this.currentQuery = query;
- VS.app.SearchParser.parse(this.app, query);
- },
-
- // 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;
- },
-
- // Used to launch a search. Hitting enter or clicking the search button.
- searchEvent : function(e) {
- var query = this.value();
- this.focusSearch(e);
- this.value(query);
- this.app.options.callbacks.search(query, this.app.searchQuery);
- },
-
- // # 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;
-
- var model = new VS.model.SearchFacet({
- category : category,
- value : initialQuery || '',
- app : this.app
- });
- this.app.searchQuery.add(model, {at: position});
- },
-
- // Renders a newly added facet, and selects it.
- addedFacet : function (model) {
- this.renderFacets();
- var facetView = _.detect(this.facetViews, function(view) {
- if (view.model == model) return true;
- });
-
- _.defer(function() {
- facetView.enableEdit();
- });
- },
-
- // Changing a facet programmatically re-renders it.
- changedFacet: function () {
- this.renderFacets();
- },
-
- // When removing a facet, potentially do something. For now, the adjacent
- // remaining facet is selected, but this is handled by the facet's view,
- // since its position is unknown by the time the collection triggers this
- // remove callback.
- removedFacet : function (facet, query, options) {},
-
- // Renders each facet as a searchFacet view.
- renderFacets : function() {
- this.facetViews = [];
- this.inputViews = [];
-
- this.$('.VS-search-inner').empty();
-
- this.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({
- app : this.app,
- model : facet,
- order : position
- });
-
- // Input first, facet second.
- this.renderSearchInput();
- this.facetViews.push(view);
- this.$('.VS-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, app: this.app});
- this.$('.VS-search-inner').append(input.render().el);
- this.inputViews.push(input);
- },
-
- // # Modifying Facets #
-
- // Clears out the search box. Command+A + delete can trigger this, as can a cancel button.
- //
- // If a `clearSearch` callback was provided, the callback is invoked and
- // provided with a function performs the actual removal of the data. This
- // allows third-party developers to either clear data asynchronously, or
- // prior to performing their custom "clear" logic.
- clearSearch : function(e) {
- var actualClearSearch = _.bind(function() {
- this.disableFacets();
- this.value('');
- this.flags.allSelected = false;
- this.searchEvent(e);
- this.focusSearch(e);
- }, this);
-
- if (this.app.options.callbacks.clearSearch) {
- this.app.options.callbacks.clearSearch(actualClearSearch);
- } else {
- actualClearSearch();
- }
- },
-
- // Command+A selects all facets.
- selectAllFacets : function() {
- this.flags.allSelected = true;
-
- $(document).one('click.selectAllFacets', this.deselectAllFacets);
-
- _.each(this.facetViews, function(facetView, i) {
- facetView.selectFacet();
- });
- _.each(this.inputViews, function(inputView, i) {
- inputView.selectText();
- });
- },
-
- // Used by facets and input to see if all facets are currently selected.
- allSelected : function(deselect) {
- if (deselect) this.flags.allSelected = false;
- return this.flags.allSelected;
- },
-
- // After `selectAllFacets` is engaged, this method is bound to the entire document.
- // This immediate disables and deselects all facets, but it also checks if the user
- // has clicked on either a facet or an input, and properly selects the view.
- deselectAllFacets : function(e) {
- this.disableFacets();
-
- if (this.$(e.target).is('.category,input')) {
- var el = $(e.target).closest('.search_facet,.search_input');
- var view = _.detect(this.facetViews.concat(this.inputViews), function(v) {
- return v.el == el[0];
- });
- if (view.type == 'facet') {
- view.selectFacet();
- } else if (view.type == 'input') {
- _.defer(function() {
- view.enableEdit(true);
- });
- }
- }
- },
-
- // 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.inputViews, function(view) {
- if (view && view != keepView &&
- (view.modes.editing == 'is' || view.modes.selected == 'is')) {
- view.disableEdit();
- }
- });
- _.each(this.facetViews, function(view) {
- if (view && view != keepView &&
- (view.modes.editing == 'is' || view.modes.selected == 'is')) {
- view.disableEdit();
- view.deselectFacet();
- }
- });
-
- this.flags.allSelected = false;
- this.removeFocus();
- $(document).unbind('click.selectAllFacets');
- },
-
- // 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();
- }
- });
- },
-
- // 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') {
- e.preventDefault();
- this.clearSearch(e);
- return false;
- } else if (this.flags.allSelected && VS.app.hotkeys.printable(e)) {
- this.clearSearch(e);
- }
- },
-
- // # Focusing Facets #
-
- // 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);
-
- if (!options.skipToFacet) {
- // Correct for bouncing between matching text and facet arrays.
- 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) {
- // Special case of looping around to a facet from the last search input box.
- viewPosition = 0;
- direction = 0;
- }
- var view, next = Math.min(viewCount, viewPosition + direction);
-
- if (currentView.type == 'text') {
- if (next >= 0 && next < viewCount) {
- view = this.facetViews[next];
- } else if (next == viewCount) {
- view = this.inputViews[this.inputViews.length-1];
- }
- if (view && options.selectFacet && view.type == 'facet') {
- view.selectFacet();
- } else if (view) {
- view.enableEdit();
- view.setCursorAtEnd(direction || options.startAtEnd);
- }
- } else if (currentView.type == 'facet') {
- if (options.skipToFacet) {
- if (next >= viewCount || next < 0) {
- view = _.last(this.inputViews);
- view.enableEdit();
- } else {
- view = this.facetViews[next];
- view.enableEdit();
- view.setCursorAtEnd(direction || options.startAtEnd);
- }
- } else {
- view = this.inputViews[next];
- view.enableEdit();
- }
- }
- if (options.selectText) view.selectText();
- this.resizeFacets();
- },
-
- maybeFocusSearch : function(e) {
- if ($(e.target).is('.VS-search-box') ||
- $(e.target).is('.VS-search-inner') ||
- e.type == 'keydown') {
- this.focusSearch(e);
- }
- },
-
- // Bring focus to last input field.
- focusSearch : function(e, selectText) {
- var view = this.inputViews[this.inputViews.length-1];
- view.enableEdit(selectText);
- if (!selectText) view.setCursorAtEnd(-1);
- if (e.type == 'keydown') {
- view.keydown(e);
- view.box.trigger('keydown');
- }
- _.defer(_.bind(function() {
- if (!this.$('input:focus').length) {
- view.enableEdit(selectText);
- }
- }, this));
- },
-
- // Double-clicking on the search wrapper should select the existing text in
- // the last search input. Also start the triple-click timer.
- highlightSearch : function(e) {
- if ($(e.target).is('.VS-search-box') ||
- $(e.target).is('.VS-search-inner') ||
- e.type == 'keydown') {
- var lastinput = this.inputViews[this.inputViews.length-1];
- lastinput.startTripleClickTimer();
- this.focusSearch(e, true);
- }
- },
-
- maybeTripleClick : function(e) {
- var lastinput = this.inputViews[this.inputViews.length-1];
- return lastinput.maybeTripleClick(e);
- },
-
- // Used to show the user is focused on some input inside the search box.
- addFocus : function() {
- this.app.options.callbacks.focus();
- this.$('.VS-search-box').addClass('VS-focus');
- },
-
- // User is no longer focused on anything in the search box.
- removeFocus : function() {
- this.app.options.callbacks.blur();
- 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. This is unused for now.
- showFacetCategoryMenu : function(e) {
- e.preventDefault();
- e.stopPropagation();
- 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.$('.VS-icon-search').after(menu.render().open().content);
- return false;
- }
-
-});
-
-})();
-
-(function() {
-
-var $ = jQuery; // Handle namespaced jQuery
-
-// 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',
- 'mousedown input' : 'enableEdit',
- 'mouseover .VS-icon-cancel' : 'showDelete',
- 'mouseout .VS-icon-cancel' : 'hideDelete',
- 'click .VS-icon-cancel' : 'remove'
- },
-
- initialize : function(options) {
- this.flags = {
- canClose : false
- };
- _.bindAll(this, 'set', 'keydown', 'deselectFacet', 'deferDisableEdit');
- },
-
- // Rendering the facet sets up autocompletion, events on blur, and populates
- // the facet's input with its starting value.
- render : function() {
- $(this.el).html(JST['search_facet']({
- model : this.model
- }));
-
- this.setMode('not', 'editing');
- this.setMode('not', 'selected');
- this.box = this.$('input');
- this.box.val(this.model.get('value'));
- this.box.bind('blur', this.deferDisableEdit);
- // Handle paste events with `propertychange`
- this.box.bind('input propertychange', this.keydown);
- this.setupAutocomplete();
-
- return this;
- },
-
- // This method is used to setup the facet's input to auto-grow.
- // This is defered in 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');
- this.box.bind('updated.autogrow', _.bind(this.moveAutocomplete, this));
- },
-
- // Forces a recalculation of this facet's input field's value. Called when
- // the facet is focused, removed, or otherwise modified.
- resize : function(e) {
- this.box.trigger('resize.autogrow', e);
- },
-
- // Watches the facet's input field to see if it matches the beginnings of
- // words in `autocompleteValues`, which is different for every category.
- // If the value, when selected from the autocompletion menu, is different
- // than what it was, commit the facet and search for it.
- setupAutocomplete : function() {
- this.box.autocomplete({
- source : _.bind(this.autocompleteValues, this),
- minLength : 0,
- delay : 0,
- autoFocus : true,
- position : {offset : "0 5"},
- create : _.bind(function(e, ui) {
- $(this.el).find('.ui-autocomplete-input').css('z-index','auto');
- }, this),
- select : _.bind(function(e, ui) {
- e.preventDefault();
- var originalValue = this.model.get('value');
- this.set(ui.item.value);
- if (originalValue != ui.item.value || this.box.val() != ui.item.value) {
- this.search(e);
- }
- return false;
- }, this),
- open : _.bind(function(e, ui) {
- var box = this.box;
- this.box.autocomplete('widget').find('.ui-menu-item').each(function() {
- var $value = $(this);
- if ($value.data('item.autocomplete')['value'] == box.val()) {
- box.data('autocomplete').menu.activate(new $.Event("mouseover"), $value);
- }
- });
- }, this)
- });
-
- this.box.autocomplete('widget').addClass('VS-interface');
- },
-
- // As the facet's input field grows, it may move to the next line in the
- // search box. `autoGrowInput` triggers an `updated` event on the input
- // field, which is bound to this method to move the autocomplete menu.
- 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 : "flip",
- offset : "0 5"
- });
- }
- },
-
- // When a user enters a facet and it is being edited, immediately show
- // the autocomplete menu and size it to match the contents.
- searchAutocomplete : function(e) {
- var autocomplete = this.box.data('autocomplete');
- if (autocomplete) {
- var menu = autocomplete.menu.element;
- autocomplete.search();
-
- // Resize the menu based on the correctly measured width of what's bigger:
- // the menu's original size or the menu items' new size.
- menu.outerWidth(Math.max(
- menu.width('').outerWidth(),
- autocomplete.element.outerWidth()
- ));
- }
- },
-
- // Closes the autocomplete menu. Called on disabling, selecting, deselecting,
- // and anything else that takes focus out of the facet's input field.
- closeAutocomplete : function() {
- var autocomplete = this.box.data('autocomplete');
- if (autocomplete) autocomplete.close();
- },
-
- // Search terms used in the autocomplete menu. These are specific to the facet,
- // and only match for the facet's category. The values are then matched on the
- // first letter of any word in matches, and finally sorted according to the
- // value's own category. You can pass `preserveOrder` as an option in the
- // `facetMatches` callback to skip any further ordering done client-side.
- autocompleteValues : function(req, resp) {
- var category = this.model.get('category');
- var value = this.model.get('value');
- var searchTerm = req.term;
-
- this.options.app.options.callbacks.valueMatches(category, searchTerm, function(matches, options) {
- options = options || {};
- matches = matches || [];
-
- if (searchTerm && value != searchTerm) {
- if (options.preserveMatches) {
- return matches;
- } else {
- 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);
- });
- }
- }
-
- if (options.preserveOrder) {
- resp(matches);
- } else {
- resp(_.sortBy(matches, function(match) {
- if (match == value || match.value == value) return '';
- else return match;
- }));
- }
- });
-
- },
-
- // Sets the facet's model's value.
- set : function(value) {
- if (!value) return;
- this.model.set({'value': value});
- },
-
- // Before the searchBox performs a search, we need to close the
- // autocomplete menu.
- search : function(e, direction) {
- if (!direction) direction = 1;
- this.closeAutocomplete();
- this.options.app.searchBox.searchEvent(e);
- _.defer(_.bind(function() {
- this.options.app.searchBox.focusNextFacet(this, direction, {viewPosition: this.options.order});
- }, this));
- },
-
- // Begin editing the facet's input. This is called when the user enters
- // the input either from another facet or directly clicking on it.
- //
- // This method tells all other facets and inputs to disable so it can have
- // the sole focus. It also prepares the autocompletion menu.
- enableEdit : function() {
- if (this.modes.editing != 'is') {
- this.setMode('is', 'editing');
- this.deselectFacet();
- if (this.box.val() == '') {
- this.box.val(this.model.get('value'));
- }
- }
-
- this.flags.canClose = false;
- this.options.app.searchBox.disableFacets(this);
- this.options.app.searchBox.addFocus();
- _.defer(_.bind(function() {
- this.options.app.searchBox.addFocus();
- }, this));
- this.resize();
- this.searchAutocomplete();
- this.box.focus();
- },
-
- // When the user blurs the input, they may either be going to another input
- // or off the search box entirely. If they go to another input, this facet
- // will be instantly disabled, and the canClose flag will be turned back off.
- //
- // However, if the user clicks elsewhere on the page, this method starts a timer
- // that checks if any of the other inputs are selected or are being edited. If
- // not, then it can finally close itself and its autocomplete menu.
- deferDisableEdit : function() {
- this.flags.canClose = true;
- _.delay(_.bind(function() {
- if (this.flags.canClose && !this.box.is(':focus') &&
- this.modes.editing == 'is' && this.modes.selected != 'is') {
- this.disableEdit();
- }
- }, this), 250);
- },
-
- // Called either by other facets receiving focus or by the timer in `deferDisableEdit`,
- // this method will turn off the facet, remove any text selection, and close
- // the autocomplete menu.
- disableEdit : function() {
- var newFacetQuery = VS.utils.inflector.trim(this.box.val());
- if (newFacetQuery != this.model.get('value')) {
- this.set(newFacetQuery);
- }
- this.flags.canClose = false;
- this.box.selectRange(0, 0);
- this.box.blur();
- this.setMode('not', 'editing');
- this.closeAutocomplete();
- this.options.app.searchBox.removeFocus();
- },
-
- // Selects the facet, which blurs the facet's input and highlights the facet.
- // If this is the only facet being selected (and not part of a select all event),
- // we attach a mouse/keyboard watcher to check if the next action by the user
- // should delete this facet or just deselect it.
- selectFacet : function(e) {
- if (e) e.preventDefault();
- var allSelected = this.options.app.searchBox.allSelected();
- if (this.modes.selected == 'is') return;
-
- if (this.box.is(':focus')) {
- this.box.setCursorPosition(0);
- this.box.blur();
- }
-
- this.flags.canClose = false;
- this.closeAutocomplete();
- this.setMode('is', 'selected');
- this.setMode('not', 'editing');
- if (!allSelected || e) {
- $(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));
- this.options.app.searchBox.disableFacets(this);
- this.options.app.searchBox.addFocus();
- }
- return false;
- },
-
- // Turns off highlighting on the facet. Called in a variety of ways, this
- // only deselects the facet if it is selected, and then cleans up the
- // keyboard/mouse watchers that were created when the facet was first
- // selected.
- deselectFacet : function(e) {
- if (e) e.preventDefault();
- if (this.modes.selected == 'is') {
- this.setMode('not', 'selected');
- this.closeAutocomplete();
- this.options.app.searchBox.removeFocus();
- }
- $(document).unbind('keydown.facet', this.keydown);
- $(document).unbind('click.facet', this.deselectFacet);
- return false;
- },
-
- // Is the user currently focused in this facet's input field?
- isFocused : function() {
- return this.box.is(':focus');
- },
-
- // Hovering over the delete button styles the facet so the user knows that
- // the delete button will kill the entire facet.
- showDelete : function() {
- $(this.el).addClass('search_facet_maybe_delete');
- },
-
- // On `mouseout`, the user is no longer hovering on the delete button.
- hideDelete : function() {
- $(this.el).removeClass('search_facet_maybe_delete');
- },
-
- // When switching between facets, depending on the direction the cursor is
- // coming from, the cursor in this facet's input field should match the original
- // direction.
- setCursorAtEnd : function(direction) {
- if (direction == -1) {
- this.box.setCursorPosition(this.box.val().length);
- } else {
- this.box.setCursorPosition(0);
- }
- },
-
- // Deletes the facet and sends the cursor over to the nearest input field.
- remove : function(e) {
- var committed = this.model.get('value');
- this.deselectFacet();
- this.disableEdit();
- this.options.app.searchQuery.remove(this.model);
- if (committed) {
- this.search(e, -1);
- } else {
- this.options.app.searchBox.renderFacets();
- this.options.app.searchBox.focusNextFacet(this, -1, {viewPosition: this.options.order});
- }
- },
-
- // Selects the text in the facet's input field. When the user tabs between
- // facets, convention is to highlight the entire field.
- selectText: function() {
- this.box.selectRange(0, this.box.val().length);
- },
-
- // Handles all keyboard inputs when in the facet's input field. This checks
- // for movement between facets and inputs, entering a new value that needs
- // to be autocompleted, as well as the removal of this facet.
- keydown : function(e) {
- var key = VS.app.hotkeys.key(e);
-
- if (key == 'enter' && this.box.val()) {
- this.disableEdit();
- this.search(e);
- } else if (key == 'left') {
- if (this.modes.selected == 'is') {
- this.deselectFacet();
- this.options.app.searchBox.focusNextFacet(this, -1, {startAtEnd: -1});
- } else if (this.box.getCursorPosition() == 0 && !this.box.getSelection().length) {
- 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();
- this.options.app.searchBox.focusNextFacet(this, 1);
- }
- } else if (VS.app.hotkeys.shift && key == 'tab') {
- e.preventDefault();
- this.options.app.searchBox.focusNextFacet(this, -1, {
- startAtEnd : -1,
- skipToFacet : true,
- selectText : true
- });
- } else if (key == 'tab') {
- e.preventDefault();
- this.options.app.searchBox.focusNextFacet(this, 1, {
- skipToFacet : true,
- selectText : true
- });
- } else if (VS.app.hotkeys.command && (e.which == 97 || e.which == 65)) {
- e.preventDefault();
- this.options.app.searchBox.selectAllFacets();
- return false;
- } else if (VS.app.hotkeys.printable(e) && this.modes.selected == 'is') {
- this.options.app.searchBox.focusNextFacet(this, -1, {startAtEnd: -1});
- this.remove(e);
- } 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);
-
- // Handle paste events
- if (e.which == null) {
- this.searchAutocomplete(e);
- _.defer(_.bind(this.resize, this, e));
- }
- }
-
-});
-
-})();
-
-(function() {
-
-var $ = jQuery; // Handle namespaced jQuery
-
-// 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',
- 'click input' : 'maybeTripleClick',
- 'dblclick input' : 'startTripleClickTimer'
- },
-
- initialize : function() {
- this.app = this.options.app;
- this.flags = {
- canClose : false
- };
- _.bindAll(this, 'removeFocus', 'addFocus', 'moveAutocomplete', 'deferDisableEdit');
- },
-
- // Rendering the input sets up autocomplete, events on focusing and blurring
- // the input, and the auto-grow of the input.
- render : function() {
- $(this.el).html(JST['search_input']({}));
-
- this.setMode('not', 'editing');
- this.setMode('not', 'selected');
- this.box = this.$('input');
- this.box.autoGrowInput();
- this.box.bind('updated.autogrow', this.moveAutocomplete);
- this.box.bind('blur', this.deferDisableEdit);
- this.box.bind('focus', this.addFocus);
- this.setupAutocomplete();
-
- return this;
- },
-
- // Watches the input and presents an autocompleted menu, taking the
- // remainder of the input field and adding a separate facet for it.
- //
- // See `addTextFacetRemainder` for explanation on how the remainder works.
- setupAutocomplete : function() {
- this.box.autocomplete({
- minLength : 1,
- delay : 50,
- autoFocus : true,
- position : {offset : "0 -1"},
- source : _.bind(this.autocompleteValues, this),
- create : _.bind(function(e, ui) {
- $(this.el).find('.ui-autocomplete-input').css('z-index','auto');
- }, this),
- select : _.bind(function(e, ui) {
- e.preventDefault();
- e.stopPropagation();
- var remainder = this.addTextFacetRemainder(ui.item.value);
- var position = this.options.position + (remainder ? 1 : 0);
- this.app.searchBox.addFacet(ui.item.value, '', position);
- return false;
- }, this)
- });
-
- // Renders the results grouped by the categories they belong to.
- this.box.data('autocomplete')._renderMenu = function(ul, items) {
- var category = '';
- _.each(items, _.bind(function(item, i) {
- if (item.category && item.category != category) {
- ul.append('
'+item.category+'
');
- category = item.category;
- }
- this._renderItem(ul, item);
- }, this));
- };
-
- this.box.autocomplete('widget').addClass('VS-interface');
- },
-
- // Search terms used in the autocomplete menu. The values are matched on the
- // first letter of any word in matches, and finally sorted according to the
- // value's own category. You can pass `preserveOrder` as an option in the
- // `facetMatches` callback to skip any further ordering done client-side.
- 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] || ' ');
- this.app.options.callbacks.facetMatches(function(prefixes, options) {
- options = options || {};
- prefixes = prefixes || [];
-
- // Only match from the beginning of the word.
- var matcher = new RegExp('^' + re, 'i');
- var matches = $.grep(prefixes, function(item) {
- return item && matcher.test(item.label || item);
- });
-
- if (options.preserveOrder) {
- resp(matches);
- } else {
- resp(_.sortBy(matches, function(match) {
- if (match.label) return match.category + '-' + match.label;
- else return match;
- }));
- }
- });
-
- },
-
- // Closes the autocomplete menu. Called on disabling, selecting, deselecting,
- // and anything else that takes focus out of the facet's input field.
- closeAutocomplete : function() {
- var autocomplete = this.box.data('autocomplete');
- if (autocomplete) autocomplete.close();
- },
-
- // As the input field grows, it may move to the next line in the
- // search box. `autoGrowInput` triggers an `updated` event on the input
- // field, which is bound to this method to move the autocomplete menu.
- 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",
- offset : '0 -1'
- });
- }
- },
-
- // When a user enters a facet and it is being edited, immediately show
- // the autocomplete menu and size it to match the contents.
- searchAutocomplete : function(e) {
- var autocomplete = this.box.data('autocomplete');
- if (autocomplete) {
- var menu = autocomplete.menu.element;
- autocomplete.search();
-
- // Resize the menu based on the correctly measured width of what's bigger:
- // the menu's original size or the menu items' new size.
- menu.outerWidth(Math.max(
- menu.width('').outerWidth(),
- autocomplete.element.outerWidth()
- ));
- }
- },
-
- // If a user searches for "word word category", the category would be
- // matched and autocompleted, and when selected, the "word word" would
- // also be caught as the remainder and then added in its own facet.
- addTextFacetRemainder : function(facetValue) {
- var boxValue = this.box.val();
- var lastWord = boxValue.match(/\b(\w+)$/);
- var matcher = new RegExp(lastWord[0], "i");
- if (lastWord && facetValue.search(matcher) == 0) {
- boxValue = boxValue.replace(/\b(\w+)$/, '');
- }
- boxValue = boxValue.replace('^\s+|\s+$', '');
- if (boxValue) {
- this.app.searchBox.addFacet('text', boxValue, this.options.position);
- }
- return boxValue;
- },
-
- // Directly called to focus the input. This is different from `addFocus`
- // because this is not called by a focus event. This instead calls a
- // focus event causing the input to become focused.
- enableEdit : function(selectText) {
- this.addFocus();
- if (selectText) {
- this.selectText();
- }
- this.box.focus();
- },
-
- // Event called on user focus on the input. Tells all other input and facets
- // to give up focus, and starts revving the autocomplete.
- addFocus : function() {
- this.flags.canClose = false;
- if (!this.app.searchBox.allSelected()) {
- this.app.searchBox.disableFacets(this);
- }
- this.app.searchBox.addFocus();
- this.setMode('is', 'editing');
- this.setMode('not', 'selected');
- this.searchAutocomplete();
- },
-
- // Directly called to blur the input. This is different from `removeFocus`
- // because this is not called by a blur event.
- disableEdit : function() {
- this.box.blur();
- this.removeFocus();
- },
-
- // Event called when user blur's the input, either through the keyboard tabbing
- // away or the mouse clicking off. Cleans up
- removeFocus : function() {
- this.flags.canClose = false;
- this.app.searchBox.removeFocus();
- this.setMode('not', 'editing');
- this.setMode('not', 'selected');
- this.closeAutocomplete();
- },
-
- // When the user blurs the input, they may either be going to another input
- // or off the search box entirely. If they go to another input, this facet
- // will be instantly disabled, and the canClose flag will be turned back off.
- //
- // However, if the user clicks elsewhere on the page, this method starts a timer
- // that checks if any of the other inputs are selected or are being edited. If
- // not, then it can finally close itself and its autocomplete menu.
- deferDisableEdit : function() {
- this.flags.canClose = true;
- _.delay(_.bind(function() {
- if (this.flags.canClose &&
- !this.box.is(':focus') &&
- this.modes.editing == 'is') {
- this.disableEdit();
- }
- }, this), 250);
- },
-
- // Starts a timer that will cause a triple-click, which highlights all facets.
- startTripleClickTimer : function() {
- this.tripleClickTimer = setTimeout(_.bind(function() {
- this.tripleClickTimer = null;
- }, this), 500);
- },
-
- // Event on click that checks if a triple click is in play. The
- // `tripleClickTimer` is counting down, ready to be engaged and intercept
- // the click event to force a select all instead.
- maybeTripleClick : function(e) {
- if (!!this.tripleClickTimer) {
- e.preventDefault();
- this.app.searchBox.selectAllFacets();
- return false;
- }
- },
-
- // Is the user currently focused in the input field?
- isFocused : function() {
- return this.box.is(':focus');
- },
-
- // When serializing the facets, the inputs need to also have their values represented,
- // in case they contain text that is not yet faceted (but will be once the search is
- // completed).
- value : function() {
- return this.box.val();
- },
-
- // When switching between facets and inputs, depending on the direction the cursor
- // is coming from, the cursor in this facet's input field should match the original
- // direction.
- setCursorAtEnd : function(direction) {
- if (direction == -1) {
- this.box.setCursorPosition(this.box.val().length);
- } else {
- this.box.setCursorPosition(0);
- }
- },
-
- // Selects the entire range of text in the input. Useful when tabbing between inputs
- // and facets.
- selectText : function() {
- this.box.selectRange(0, this.box.val().length);
- if (!this.app.searchBox.allSelected()) {
- this.box.focus();
- } else {
- this.setMode('is', 'selected');
- }
- },
-
- // Before the searchBox performs a search, we need to close the
- // autocomplete menu.
- search : function(e, direction) {
- if (!direction) direction = 0;
- this.closeAutocomplete();
- this.app.searchBox.searchEvent(e);
- _.defer(_.bind(function() {
- this.app.searchBox.focusNextFacet(this, direction);
- }, this));
- },
-
- // 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);
-
- if (key == 'enter') {
- return this.search(e, 100);
- } else if (VS.app.hotkeys.colon(e)) {
- this.box.trigger('resize.autogrow', e);
- var query = this.box.val();
- var prefixes = [];
- if (this.app.options.callbacks.facetMatches) {
- this.app.options.callbacks.facetMatches(function(p) {
- prefixes = p;
- });
- }
- var labels = _.map(prefixes, function(prefix) {
- if (prefix.label) return prefix.label;
- else return prefix;
- });
- if (_.contains(labels, query)) {
- e.preventDefault();
- var remainder = this.addTextFacetRemainder(query);
- var position = this.options.position + (remainder?1:0);
- this.app.searchBox.addFacet(query, '', position);
- return false;
- }
- } else if (key == 'backspace') {
- if (this.box.getCursorPosition() == 0 && !this.box.getSelection().length) {
- e.preventDefault();
- e.stopPropagation();
- e.stopImmediatePropagation();
- this.app.searchBox.resizeFacets();
- return false;
- }
- }
- },
-
- // Handles all keyboard inputs when in the input field. This checks
- // for movement between facets and inputs, entering a new value that needs
- // to be autocompleted, as well as stepping between facets with backspace.
- keydown : function(e) {
- var key = VS.app.hotkeys.key(e);
-
- if (key == 'left') {
- if (this.box.getCursorPosition() == 0) {
- e.preventDefault();
- this.app.searchBox.focusNextFacet(this, -1, {startAtEnd: -1});
- }
- } else if (key == 'right') {
- if (this.box.getCursorPosition() == this.box.val().length) {
- e.preventDefault();
- this.app.searchBox.focusNextFacet(this, 1, {selectFacet: true});
- }
- } else if (VS.app.hotkeys.shift && key == 'tab') {
- e.preventDefault();
- this.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);
- var position = this.options.position + (remainder?1:0);
- this.app.searchBox.addFacet(value, '', position);
- } else {
- this.app.searchBox.focusNextFacet(this, 0, {
- skipToFacet: true,
- selectText: true
- });
- }
- } else if (VS.app.hotkeys.command &&
- String.fromCharCode(e.which).toLowerCase() == 'a') {
- e.preventDefault();
- this.app.searchBox.selectAllFacets();
- return false;
- } else if (key == 'backspace' && !this.app.searchBox.allSelected()) {
- if (this.box.getCursorPosition() == 0 && !this.box.getSelection().length) {
- e.preventDefault();
- this.app.searchBox.focusNextFacet(this, -1, {backspace: true});
- return false;
- }
- }
-
- this.box.trigger('resize.autogrow', e);
- }
-
-});
-
-})();
-
-(function(){
-
- var $ = jQuery; // Handle namespaced jQuery
-
- // 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;
- };
-
-})();
-(function() {
-
-var $ = jQuery; // Handle namespaced jQuery
-
-// DocumentCloud workspace hotkeys. To tell if a key is currently being pressed,
-// just ask `VS.app.hotkeys.[key]` on `keypress`, or ask `VS.app.hotkeys.key(e)`
-// on `keydown`.
-//
-// For the most headache-free way to use this utility, check modifier keys,
-// like shift and command, with `VS.app.hotkeys.shift`, and check every other
-// key with `VS.app.hotkeys.key(e) == 'key_name'`.
-VS.app.hotkeys = {
-
- // Keys that will be mapped to the `hotkeys` namespace.
- KEYS: {
- '16': 'shift',
- '17': 'command',
- '91': 'command',
- '93': 'command',
- '224': 'command',
- '13': 'enter',
- '37': 'left',
- '38': 'upArrow',
- '39': 'right',
- '40': 'downArrow',
- '46': 'delete',
- '8': 'backspace',
- '9': 'tab',
- '188': 'comma'
- },
-
- // Binds global keydown and keyup events to listen for keys that match `this.KEYS`.
- initialize : function() {
- _.bindAll(this, 'down', 'up', 'blur');
- $(document).bind('keydown', this.down);
- $(document).bind('keyup', this.up);
- $(window).bind('blur', this.blur);
- },
-
- // On `keydown`, turn on all keys that match.
- down : function(e) {
- var key = this.KEYS[e.which];
- if (key) this[key] = true;
- },
-
- // On `keyup`, turn off all keys that match.
- up : function(e) {
- var key = this.KEYS[e.which];
- if (key) this[key] = false;
- },
-
- // If an input is blurred, all keys need to be turned off, since they are no longer
- // able to modify the document.
- blur : function(e) {
- for (var key in this.KEYS) this[this.KEYS[key]] = false;
- },
-
- // Check a key from an event and return the common english name.
- key : function(e) {
- return this.KEYS[e.which];
- },
-
- // Colon is special, since the value is different between browsers.
- colon : function(e) {
- var charCode = e.which;
- return charCode && String.fromCharCode(charCode) == ":";
- },
-
- // Check a key from an event and match it against any known characters.
- // The `keyCode` is different depending on the event type: `keydown` vs. `keypress`.
- //
- // These were determined by looping through every `keyCode` and `charCode` that
- // resulted from `keydown` and `keypress` events and counting what was printable.
- 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 {
- // [space]!"#$%&'()*+,-.0-9:;<=>?@A-Z[\]^_`a-z{|} and unicode characters
- if ((code >= 32 && code <= 126) ||
- (code >= 160 && code <= 500) ||
- (String.fromCharCode(code) == ":")) {
- return true;
- }
- }
- return false;
- }
-
-};
-
-})();
-(function() {
-
-var $ = jQuery; // Handle namespaced jQuery
-
-// Naive English transformations on words. Only used for a few transformations
-// in VisualSearch.js.
-VS.utils.inflector = {
-
- // Delegate to the ECMA5 String.prototype.trim function, if available.
- trim : function(s) {
- return s.trim ? s.trim() : s.replace(/^\s+|\s+$/g, '');
- },
-
- // Escape strings that are going to be used in a regex. Escapes punctuation
- // that would be incorrect in a regex.
- escapeRegExp : function(s) {
- return s.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
- }
-};
-
-})();
-(function() {
-
-var $ = jQuery; // Handle namespaced jQuery
-
-$.fn.extend({
-
- // Makes the selector 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.
- 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;
- },
-
- // When attached to an input element, this will cause the width of the input
- // to match its contents. This calculates the width of the contents of the input
- // by measuring a hidden shadow div that should match the styling of the input.
- autoGrowInput: function() {
- return this.each(function() {
- var $input = $(this);
- var $tester = $('').css({
- opacity : 0,
- top : -9999,
- left : -9999,
- position : 'absolute',
- whiteSpace : 'nowrap'
- }).addClass('VS-input-width-tester').addClass('VS-interface');
-
- // Watch for input value changes on all of these events. `resize`
- // event is called explicitly when the input has been changed without
- // a single keypress.
- var events = 'keydown.autogrow keypress.autogrow ' +
- 'resize.autogrow change.autogrow';
- $input.next('.VS-input-width-tester').remove();
- $input.after($tester);
- $input.unbind(events).bind(events, function(e, realEvent) {
- if (realEvent) e = realEvent;
- var value = $input.val();
-
- // Watching for the backspace key is tricky because it may not
- // actually be deleting the character, but instead the key gets
- // redirected to move the cursor from facet to facet.
- 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 += String.fromCharCode(e.which);
- }
- value = value.replace(/&/g, '&')
- .replace(/\s/g,' ')
- .replace(//g, '>');
-
- $tester.html(value);
- $input.width($tester.width() + 3);
- $input.trigger('updated.autogrow');
- });
-
- // Sets the width of the input on initialization.
- $input.trigger('resize.autogrow');
- });
- },
-
-
- // Cross-browser method used for calculating where the cursor is in an
- // input field.
- 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;
- }
-
- return position;
- },
-
- // A simple proxy for `selectRange` that sets the cursor position in an
- // input field.
- setCursorPosition: function(position) {
- return this.each(function() {
- return $(this).selectRange(position, position);
- });
- },
-
- // Cross-browser way to select text in an input field.
- 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);
- if (end - start >= 0) range.select();
- }
- });
- },
-
- // Returns an object that contains the text selection range values for
- // an input field.
- 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};
- }
-
-});
-
-// Debugging in Internet Explorer. This allows you to use
-// `console.log(['message', var1, var2, ...])`. Just remove the `false` and
-// add your console.logs. This will automatically stringify objects using
-// `JSON.stringify', so you can read what's going out. Think of this as a
-// *Diet Firebug Lite Zero with Lemon*.
-if ($.browser.msie && false) {
- window.console = {};
- var _$ied;
- window.console.log = function(msg) {
- if (_.isArray(msg)) {
- var message = msg[0];
- var vars = _.map(msg.slice(1), function(arg) {
- return JSON.stringify(arg);
- }).join(' - ');
- }
- if(!_$ied){
- _$ied = $('
').css({
- 'borderBottom': '1px solid #999999'
- });
- _$ied.find('ol').append($message);
- _.delay(function() {
- $message.fadeOut(500);
- }, 5000);
- };
-
-}
-
-})();
-
-(function() {
-
-var $ = jQuery; // Handle namespaced jQuery
-
-// Used to extract keywords and facets from the free text search.
-var FREETEXT_RE = '(\'[^\']+\'|"[^"]+"|[^\'"\\s]\\S*)';
-var CATEGORY_RE = FREETEXT_RE + ':\\s*';
-VS.app.SearchParser = {
-
- // Matches `category: "free text"`, with and without quotes.
- ALL_FIELDS : new RegExp(CATEGORY_RE + FREETEXT_RE, 'g'),
-
- // Matches a single category without the text. Used to correctly extract facets.
- CATEGORY : new RegExp(CATEGORY_RE),
-
- // Called to parse a query into a collection of `SearchFacet` models.
- parse : function(instance, query) {
- var searchFacets = this._extractAllFacets(instance, query);
- instance.searchQuery.reset(searchFacets);
- return searchFacets;
- },
-
- // Walks the query and extracts facets, categories, and free text.
- _extractAllFacets : function(instance, 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.CATEGORY)[1].replace(/(^['"]|['"]$)/g, '');
- value = field.replace(this.CATEGORY, '').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, ''));
- }
-
- if (category && value) {
- var searchFacet = new VS.model.SearchFacet({
- category : category,
- value : VS.utils.inflector.trim(value),
- app : instance
- });
- facets.push(searchFacet);
- }
- if (originalQuery == query) break;
- }
-
- return facets;
- },
-
- // Extracts the first field found, capturing any free text that comes
- // before the category.
- _extractNextField : function(query) {
- var textRe = new RegExp('^\\s*(\\S+)\\s+(?=' + CATEGORY_RE + FREETEXT_RE + ')');
- var textMatch = query.match(textRe);
- if (textMatch && textMatch.length >= 1) {
- return textMatch[1];
- } else {
- return this._extractFirstField(query);
- }
- },
-
- // If there is no free text before the facet, extract the category and value.
- _extractFirstField : function(query) {
- var fields = query.match(this.ALL_FIELDS);
- return fields && fields.length && fields[0];
- },
-
- // If the found match is not a category and facet, extract the trimmed free text.
- _extractSearchText : function(query) {
- query = query || '';
- var text = VS.utils.inflector.trim(query.replace(this.ALL_FIELDS, ''));
- return text;
- }
-
-};
-
-})();
-
-(function() {
-
-var $ = jQuery; // Handle namespaced jQuery
-
-// The model that holds individual search facets and their categories.
-// Held in a collection by `VS.app.searchQuery`.
-VS.model.SearchFacet = Backbone.Model.extend({
-
- // Extract the category and value and serialize it in preparation for
- // turning the entire searchBox into a search query that can be sent
- // to the server for parsing and searching.
- serialize : function() {
- var category = this.quoteCategory(this.get('category'));
- var value = VS.utils.inflector.trim(this.get('value'));
-
- if (!value) return '';
-
- if (!_.contains(this.get("app").options.unquotable || [], category) && category != 'text') {
- value = this.quoteValue(value);
- }
-
- if (category != 'text') {
- category = category + ': ';
- } else {
- category = "";
- }
- return category + value;
- },
-
- // Wrap categories that have spaces or any kind of quote with opposite matching
- // quotes to preserve the complex category during serialization.
- quoteCategory : function(category) {
- var hasDoubleQuote = (/"/).test(category);
- var hasSingleQuote = (/'/).test(category);
- var hasSpace = (/\s/).test(category);
-
- if (hasDoubleQuote && !hasSingleQuote) {
- return "'" + category + "'";
- } else if (hasSpace || (hasSingleQuote && !hasDoubleQuote)) {
- return '"' + category + '"';
- } else {
- return category;
- }
- },
-
- // Wrap values that have quotes in opposite matching quotes. If a value has
- // both single and double quotes, just use the double quotes.
- quoteValue : function(value) {
- var hasDoubleQuote = (/"/).test(value);
- var hasSingleQuote = (/'/).test(value);
-
- if (hasDoubleQuote && !hasSingleQuote) {
- return "'" + value + "'";
- } else {
- return '"' + value + '"';
- }
- }
-
-});
-
-})();
-(function() {
-
-var $ = jQuery; // Handle namespaced jQuery
-
-// Collection which holds all of the individual facets (category: value).
-// Used for finding and removing specific facets.
-VS.model.SearchQuery = Backbone.Collection.extend({
-
- // Model holds the category and value of the facet.
- model : VS.model.SearchFacet,
-
- // Turns all of the facets into a single serialized string.
- serialize : function() {
- return this.map(function(facet){ return facet.serialize(); }).join(' ');
- },
-
- facets : function() {
- return this.map(function(facet) {
- var value = {};
- value[facet.get('category')] = facet.get('value');
- return value;
- });
- },
-
- // Find a facet by its category. Multiple facets with the same category
- // is fine, but only the first is returned.
- find : function(category) {
- var facet = this.detect(function(facet) {
- return facet.get('category') == category;
- });
- return facet && facet.get('value');
- },
-
- // Counts the number of times a specific category is in the search query.
- count : function(category) {
- return this.select(function(facet) {
- return facet.get('category') == category;
- }).length;
- },
-
- // Returns an array of extracted values from each facet in a category.
- values : function(category) {
- var facets = this.select(function(facet) {
- return facet.get('category') == category;
- });
- return _.map(facets, function(facet) { return facet.get('value'); });
- },
-
- // Checks all facets for matches of either a category or both category and value.
- has : function(category, value) {
- return this.any(function(facet) {
- var categoryMatched = facet.get('category') == category;
- if (!value) return categoryMatched;
- return categoryMatched && facet.get('value') == value;
- });
- },
-
- // Used to temporarily hide a specific category and serialize the search query.
- withoutCategory : function(category) {
- return this.map(function(facet) {
- if (facet.get('category') != category) return facet.serialize();
- }).join(' ');
- }
-
-});
-
-})();(function(){
-window.JST = window.JST || {};
-
-window.JST['search_box'] = _.template('
\n
\n \n \n \n
\n
');
-window.JST['search_facet'] = _.template('<% if (model.has(\'category\')) { %>\n
- VisualSearch.js
- enhances ordinary search boxes with the ability to autocomplete
- faceted search queries. Specify the facets for completion, along with the
- completable values for any facet. You can retrieve the search query as a
- structured object, so you don't have to parse the query string yourself.
-
- The project is
- hosted on GitHub.
- You can report bugs and discuss features on the
- issues page,
- on Freenode in the #documentcloud channel,
- or send tweets to @documentcloud.
-
-
-
- VisualSearch.js is an open-source component of DocumentCloud.
-
-
Demo Try searching for: account, filter, access, title, city, state, or country.
- VisualSearch.js
- enhances ordinary search boxes with the ability to autocomplete
- faceted search queries. Specify the facets for completion, along with the
- completable values for any facet. You can retrieve the search query as a
- structured object, so you don't have to parse the query string yourself.
-
- The project is
- hosted on GitHub.
- You can report bugs and discuss features on the
- issues page,
- on Freenode in the #documentcloud channel,
- or send tweets to @documentcloud.
-
callbacks : {
- ...
- // These are the facets that will be autocompleted in an empty input.
- facetMatches : function(callback) {
- callback([
- 'account', 'filter', 'access', 'title',
- { label: 'city', category: 'location' },
- { label: 'address', category: 'location' },
- { label: 'country', category: 'location' },
- { label: 'state', category: 'location' },
- ]);
- }
- ...
- // These are the values that match specific categories, autocompleted
- // in a category's input field. searchTerm can be used to filter the
- // list on the server-side, prior to providing a list to the widget.
- valueMatches : function(facet, searchTerm, callback) {
- switch (facet) {
- case 'account':
- callback([
- { value: '1-amanda', label: 'Amanda' },
- { value: '2-aron', label: 'Aron' },
- { value: '3-eric', label: 'Eric' },
- { value: '4-jeremy', label: 'Jeremy' },
- { value: '5-samuel', label: 'Samuel' },
- { value: '6-scott', label: 'Scott' }
- ]);
- break;
- case 'filter':
- callback(['published', 'unpublished', 'draft']);
- break;
- case 'access':
- callback(['public', 'private', 'protected']);
- break;
- case 'title':
- callback([
- 'Pentagon Papers',
- 'CoffeeScript Manual',
- 'Laboratory for Object Oriented Thinking',
- 'A Repository Grows in Brooklyn'
- ]);
- break;
- }
- }
-...
-}
-
-
Inspect the Visual Search box
-
// Returns the unstructured search query
-visualSearch.searchBox.value()
-// "country: "South Africa" account: 5-samuel title: "Pentagon Papers""
-
-// Returns an array of Facet model instances
-visualSearch.searchQuery.facets()
-// [FacetModel<country:"South Africa">,
-// FacetModel<account:5-samuel>,
-// FacetModel<title:"Pentagon Papers">]
-
-// Set the search query with raw text
-visualSearch.searchBox.value("Country: US State: \"New York\" Key: Value")
-
-
-
-
-
-
-
Change Log
-
-
- 0.2.2March 10th, 2012
- If you do not want to automatically filter the value matches, you can pass an
- options hash with preserveMatches: true as the second argument to the callback.
- See pull request #44 for details.
-
-
- 0.2.1November 14th, 2011
- The autocompleted facets and values that are provided by your callbacks facetMatches
- and valueMatches can now preserve the order of items you give them. Simply pass an
- options hash with preserveOrder: true as the second argument to the callback. See
- the demo page for an example.
-
-
- 0.2.0August 10th, 2011
- Multiple instances of VisualSearch on a single page. VS.init now returns
- a reference to the instance. The search callback now contains both the
- serialized search query and a reference to the search query collection (as a
- Backbone.Collection),
- which can be used to manipulate each facet directly. See
- the source code for search_query.js for available
- methods on the collection.
-
-
- 0.1.0June 23rd, 2011
- Initial release of VisualSearch.js.
-