summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRandy Bertram <rbertram@us.ibm.com>2015-02-18 08:29:16 -0500
committerRandy Bertram <rbertram@us.ibm.com>2015-02-20 09:50:47 -0500
commit1e15e5bbb55b838bc4289ebc5c39d9c4dd9cb1e4 (patch)
tree5084c8f6a6fdd76c4ebc8345389b3849972e39e8
parentc5a716cb22ca9b4ef75a91ac6bd43fa582a42f69 (diff)
downloadxstatic-magic-search-1e15e5bbb55b838bc4289ebc5c39d9c4dd9cb1e4.tar.gz
Initializing with MagicSearch 0.1.5.9
First commit of MagicSearch code Change-Id: Idc51127a72a0a80aab706690db26387cc9f72b81
-rw-r--r--.gitignore9
-rwxr-xr-xMANIFEST.in8
-rwxr-xr-xREADME.txt21
-rwxr-xr-xsetup.py27
-rwxr-xr-xxstatic/__init__.py1
-rwxr-xr-xxstatic/pkg/__init__.py1
-rwxr-xr-xxstatic/pkg/magic_search/__init__.py49
-rwxr-xr-xxstatic/pkg/magic_search/data/magic_search.css85
-rwxr-xr-xxstatic/pkg/magic_search/data/magic_search.html49
-rwxr-xr-xxstatic/pkg/magic_search/data/magic_search.js334
-rwxr-xr-xxstatic/pkg/magic_search/data/magic_search.scss87
-rwxr-xr-xxstatic/pkg/magic_search/data/magic_search_bootstrap.html50
-rwxr-xr-xxstatic/pkg/magic_search/data/magic_search_bootstrap.js28
13 files changed, 749 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b3085b8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+*.pyc
+*.sw?
+*.sqlite3
+.DS_STORE
+*.egg-info
+.venv
+.tox
+build
+dist
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100755
index 0000000..1ce94ae
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,8 @@
+include README.txt
+recursive-include xstatic/pkg/magic_search *
+
+global-exclude *.pyc
+global-exclude *.pyo
+global-exclude *.orig
+global-exclude *.rej
+
diff --git a/README.txt b/README.txt
new file mode 100755
index 0000000..24bf752
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,21 @@
+XStatic-MagicSearch
+-------------------
+
+MagicSearch is an AngularJS directive that provides a UI for both faceted
+filtering and as-you-type filtering. It is intended for filtering tables,
+such as an AngularJS smart-table, but it can be used in any situation
+where you can provide it with facets/options and consume its events.
+
+MagicSearch was initially developed by David Kavanagh for Eucalyptus.
+
+
+MagicSearch javascript library packaged for setuptools (easy_install) / pip.
+
+This package is intended to be used by **any** project that needs these files.
+
+It intentionally does **not** provide any extra code except some metadata
+**nor** has any extra requirements. You MAY use some minimal support code from
+the XStatic base package, if you like.
+
+You can find more info about the xstatic packaging way in the package `XStatic`.
+
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..b8765cd
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,27 @@
+from xstatic.pkg import magic_search as xs
+
+# The README.txt file should be written in reST so that PyPI can use
+# it to generate your project's PyPI page.
+long_description = open('README.txt').read()
+
+from setuptools import setup, find_packages
+
+setup(
+ name=xs.PACKAGE_NAME,
+ version=xs.PACKAGE_VERSION,
+ description=xs.DESCRIPTION,
+ long_description=long_description,
+ classifiers=xs.CLASSIFIERS,
+ keywords=xs.KEYWORDS,
+ maintainer=xs.MAINTAINER,
+ maintainer_email=xs.MAINTAINER_EMAIL,
+ license=xs.LICENSE,
+ url=xs.HOMEPAGE,
+ platforms=xs.PLATFORMS,
+ packages=find_packages(),
+ namespace_packages=['xstatic', 'xstatic.pkg', ],
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=[], # nothing! :)
+ # if you like, you MAY use the 'XStatic' package.
+)
diff --git a/xstatic/__init__.py b/xstatic/__init__.py
new file mode 100755
index 0000000..de40ea7
--- /dev/null
+++ b/xstatic/__init__.py
@@ -0,0 +1 @@
+__import__('pkg_resources').declare_namespace(__name__)
diff --git a/xstatic/pkg/__init__.py b/xstatic/pkg/__init__.py
new file mode 100755
index 0000000..de40ea7
--- /dev/null
+++ b/xstatic/pkg/__init__.py
@@ -0,0 +1 @@
+__import__('pkg_resources').declare_namespace(__name__)
diff --git a/xstatic/pkg/magic_search/__init__.py b/xstatic/pkg/magic_search/__init__.py
new file mode 100755
index 0000000..9d03d34
--- /dev/null
+++ b/xstatic/pkg/magic_search/__init__.py
@@ -0,0 +1,49 @@
+"""
+XStatic resource package
+
+See package 'XStatic' for documentation and basic tools.
+"""
+
+DISPLAY_NAME = 'Magic-Search' # official name, upper/lowercase allowed, no spaces
+PACKAGE_NAME = 'XStatic-%s' % DISPLAY_NAME # name used for PyPi
+
+NAME = __name__.split('.')[-1] # package name (e.g. 'foo' or 'foo_bar')
+ # please use a all-lowercase valid python
+ # package name
+
+VERSION = '0.1.5' # version of the packaged files, please use the upstream
+ # version number
+BUILD = '9' # our package build number, so we can release new builds
+ # with fixes for xstatic stuff.
+PACKAGE_VERSION = VERSION + '.' + BUILD # version used for PyPi
+
+DESCRIPTION = "%s %s (XStatic packaging standard)" % (DISPLAY_NAME, VERSION)
+
+PLATFORMS = 'any'
+CLASSIFIERS = []
+KEYWORDS = '%s xstatic' % NAME
+
+# XStatic-* package maintainer:
+MAINTAINER = 'Randy Bertram'
+MAINTAINER_EMAIL = 'rbertram@us.ibm.com'
+
+# this refers to the project homepage of the stuff we packaged:
+HOMEPAGE = 'https://github.com/eucalyptus/magic-search'
+
+# this refers to all files:
+LICENSE = '(same as %s)' % DISPLAY_NAME
+
+from os.path import join, dirname
+BASE_DIR = join(dirname(__file__), 'data')
+# linux package maintainers just can point to their file locations like this:
+#BASE_DIR = '/usr/share/javascript/jquery'
+
+LOCATIONS = {
+ # CDN locations (if no public CDN exists, use an empty dict)
+ # if value is a string, it is a base location, just append relative
+ # path/filename. if value is a dict, do another lookup using the
+ # relative path/filename you want.
+ # your relative path/filenames should usually be without version
+ # information, because either the base dir/url is exactly for this
+ # version or the mapping will care for accessing this version.
+}
diff --git a/xstatic/pkg/magic_search/data/magic_search.css b/xstatic/pkg/magic_search/data/magic_search.css
new file mode 100755
index 0000000..ef63fea
--- /dev/null
+++ b/xstatic/pkg/magic_search/data/magic_search.css
@@ -0,0 +1,85 @@
+/* Copyright 2014-2015 Eucalyptus Systems, Inc. */
+/*-----------------------------------------
+ Colors
+ ----------------------------------------- */
+/*-----------------------------------------
+ Item list
+ ----------------------------------------- */
+/*-----------------------------------------
+ Magic Search bar
+ ----------------------------------------- */
+/* line 30, ../src/magic_search.scss */
+.search-bar {
+ position: relative;
+ border: 1px solid black;
+ background-color: white;
+ margin-bottom: 0.5rem;
+ padding: 4px;
+ height: auto;
+}
+/* line 37, ../src/magic_search.scss */
+.search-bar i.fi-filter {
+ color: #444444;
+ position: absolute;
+ top: 0.5rem;
+ left: 0.65rem;
+}
+/* line 46, ../src/magic_search.scss */
+.search-bar #search-main-area {
+ position: relative;
+ margin-left: 1.65rem;
+ margin-right: 1.65rem;
+ cursor: text;
+}
+/* line 14, ../src/magic_search.scss */
+.search-bar .item-list {
+ margin-bottom: 6px;
+}
+/* line 16, ../src/magic_search.scss */
+.search-bar .item-list .item {
+ color: #333;
+ background-color: #e6e7e8;
+ margin-right: 8px;
+}
+/* line 20, ../src/magic_search.scss */
+.search-bar .item-list .item a {
+ color: white;
+}
+/* line 53, ../src/magic_search.scss */
+.search-bar .item-list {
+ margin-bottom: 2px;
+}
+/* line 56, ../src/magic_search.scss */
+.search-bar #search-selected {
+ background-color: white;
+ color: #444444;
+}
+/* line 60, ../src/magic_search.scss */
+.search-bar #search-entry {
+ display: inline-block;
+ height: 1.5rem;
+}
+/* line 64, ../src/magic_search.scss */
+.search-bar #search-input {
+ width: 220px;
+ border: 0;
+ box-shadow: none;
+ height: 1.5rem;
+ padding: 3px;
+ background-color: white;
+}
+/* line 75, ../src/magic_search.scss */
+.search-bar .match {
+ font-weight: bold;
+}
+/* line 78, ../src/magic_search.scss */
+.search-bar i.cancel {
+ color: #444444;
+ position: absolute;
+ top: 0.5rem;
+ right: 0.65rem;
+}
+/* line 80, ../src/magic_search.scss */
+.search-bar i.cancel:hover {
+ color: darkred;
+}
diff --git a/xstatic/pkg/magic_search/data/magic_search.html b/xstatic/pkg/magic_search/data/magic_search.html
new file mode 100755
index 0000000..e32e795
--- /dev/null
+++ b/xstatic/pkg/magic_search/data/magic_search.html
@@ -0,0 +1,49 @@
+<!--! Magic Searchbar -->
+<div id="magic-search" magic-overrides>
+ <div class="search-bar">
+ <i class="fi-filter fa fa-filter go" ng-class="{'has-items': currentSearch.length &gt; 0}"></i>
+ <div id="search-main-area">
+ <span class="item-list">
+ <span class="label radius secondary item"
+ ng-repeat="facet in currentSearch" ng-cloak="cloak">
+ <span>
+ {{ facet.label[0] }}:<b>{{ facet.label[1] }}</b>
+ </span>
+ <a class="remove" ng-click="removeFacet($index, $event)" title="{{ strings.remove }}">
+ <i class="fi-x fa fa-times"></i>
+ </a>
+ </span>
+ </span>
+ <span id="search-selected" class="label" ng-cloak="" ng-show="facetSelected">
+ {{ facetSelected.label[0] }}:
+ </span>
+ <div id="search-entry" is-open="isMenuOpen">
+ <input id="search-input" type="text" data-dropdown="facet-drop" dropdown-toggle
+ placeholder="{{ strings.prompt }}" autocomplete="off"
+ ng-class="{'has-items': currentSearch.length &gt; 0}" />
+ <ul id="facet-drop" class="f-dropdown dropdown-menu" data-dropdown-content="">
+ <li ng-repeat="facet in filteredObj" ng-show="!facetSelected">
+ <a ng-click="facetClicked($index, $event, facet.name)"
+ ng-show="!isMatchLabel(facet.label)">{{ facet.label }}</a>
+ <a ng-click="facetClicked($index, $event, facet.name)"
+ ng-show="isMatchLabel(facet.label)">
+ {{ facet.label[0] }}<span class="match">{{ facet.label[1] }}</span>{{ facet.label[2] }}
+ </a>
+ </li>
+ <li ng-repeat="option in filteredOptions" ng-show="facetSelected">
+ <a ng-click="optionClicked($index, $event, option.key)"
+ ng-show="!isMatchLabel(option.label)">
+ {{ option.label }}
+ </a>
+ <a ng-click="optionClicked($index, $event, option.key)"
+ ng-show="isMatchLabel(option.label)">
+ {{ option.label[0] }}<span class="match">{{ option.label[1] }}</span>{{ option.label[2] }}
+ </a>
+ </ul>
+ </div>
+ </div>
+ <a ng-click="clearSearch()" ng-show="currentSearch.length &gt; 0" title="{{ strings.cancel }}">
+ <i class="fi-x fa fa-times cancel"></i>
+ </a>
+ </div>
+</div>
diff --git a/xstatic/pkg/magic_search/data/magic_search.js b/xstatic/pkg/magic_search/data/magic_search.js
new file mode 100755
index 0000000..943eda9
--- /dev/null
+++ b/xstatic/pkg/magic_search/data/magic_search.js
@@ -0,0 +1,334 @@
+/**
+ * @fileOverview Magic Search JS
+ * @requires AngularJS
+ *
+ */
+
+// Allow the module to be pre-defined with additional dependencies
+try{
+ angular.module('MagicSearch');
+} catch (exception) {
+ angular.module('MagicSearch', []);
+}
+
+angular.module('MagicSearch')
+ .directive('magicSearch', function($compile) {
+ return {
+ restrict: 'E',
+ scope: {
+ facets_json: '@facets',
+ filter_keys: '=filterKeys',
+ strings: '=strings'
+ },
+ templateUrl: function (scope, elem) {
+ return elem.template;
+ },
+ controller: function ($scope, $timeout) {
+ $scope.currentSearch = [];
+ $scope.initSearch = function() {
+ // Parse facets JSON and convert to a list of facets.
+ $scope.facetsJson = $scope.facets_json.replace(/__apos__/g, "\'").replace(/__dquote__/g, '\\"').replace(/__bslash__/g, "\\");
+ $scope.facetsObj = JSON.parse($scope.facetsJson);
+ // set facets selected and remove them from facetsObj
+ var initialFacets = window.location.search;
+ if (initialFacets.indexOf('?') === 0) {
+ initialFacets = initialFacets.slice(1);
+ }
+ initialFacets = initialFacets.split('&');
+ if (initialFacets.length > 1 || initialFacets[0].length > 0) {
+ $timeout(function() {
+ $scope.strings['prompt'] = '';
+ });
+ }
+ angular.forEach(initialFacets, function(facet, idx) {
+ var facetParts = facet.split('=');
+ angular.forEach($scope.facetsObj, function(value, idx) {
+ if (value.name == facetParts[0]) {
+ if (value.options === undefined) {
+ $scope.currentSearch.push({'name':facet, 'label':[value.label, facetParts[1]]});
+ // allow free-form facets to remain
+ }
+ else {
+ angular.forEach(value.options, function(option, idx) {
+ if (option.key == facetParts[1]) {
+ $scope.currentSearch.push({'name':facet, 'label':[value.label, option.label]});
+ $scope.deleteFacetSelection(facetParts);
+ }
+ });
+ }
+ }
+ });
+ });
+ $scope.filteredObj = $scope.facetsObj;
+ };
+ // removes a facet from the menu
+ $scope.deleteFacetSelection = function(facet_parts) {
+ angular.forEach($scope.facetsObj.slice(), function(facet, idx) {
+ if (facet.name == facet_parts[0]) {
+ if (facet.options === undefined) {
+ return; // allow free-form facets to remain
+ }
+ for (var i=0; i<facet.options.length; i++) {
+ var option = facet.options[i];
+ if (option.key == facet_parts[1]) {
+ $scope.facetsObj[idx].options.splice($scope.facetsObj[idx].options.indexOf(option), 1);
+ }
+ }
+ if (facet.options.length === 0) {
+ $scope.facetsObj.splice($scope.facetsObj.indexOf(facet), 1);
+ }
+ }
+ });
+ };
+ $('#search-input').on('keydown', function($event) { // handle ctrl-char input
+ var key = $event.keyCode || $event.charCode;
+ if (key == 9) { // prevent default when we can.
+ $event.preventDefault();
+ }
+ });
+ $('#search-input').on('keyup', function($event) { // handle ctrl-char input
+ if ($event.metaKey == true) {
+ return;
+ }
+ var search_val = $('#search-input').val();
+ var key = $event.keyCode || $event.charCode;
+ if (key == 9) { // tab, so select facet if narrowed down to 1
+ if ($scope.facetSelected === undefined) {
+ if ($scope.filteredObj.length != 1) return;
+ $scope.facetClicked(0, '', $scope.filteredObj[0].name);
+ }
+ else {
+ if ($scope.filteredOptions.length != 1) return;
+ $scope.optionClicked(0, '', $scope.filteredOptions[0].key);
+ $scope.resetState();
+ }
+ $timeout(function() {
+ $('#search-input').val('');
+ });
+ return;
+ }
+ if (key == 27) { // esc, so cancel and reset everthing
+ $timeout(function() {
+ $scope.hideMenu();
+ $('#search-input').val('');
+ });
+ $scope.resetState();
+ $scope.$emit('textSearch', '', $scope.filter_keys);
+ return;
+ }
+ if (key == 13) { // enter, so accept value
+ // if tag search, treat as regular facet
+ if ($scope.facetSelected && $scope.facetSelected.options === undefined) {
+ var curr = $scope.facetSelected;
+ curr.name = curr.name + '=' + search_val;
+ curr.label[1] = search_val;
+ $scope.currentSearch.push(curr);
+ $scope.resetState();
+ $scope.emitQuery();
+ $scope.showMenu();
+ }
+ // if text search treat as search
+ else {
+ for (i=0; i<$scope.currentSearch.length; i++) {
+ if ($scope.currentSearch[i].name.indexOf('text') === 0) {
+ $scope.currentSearch.splice(i, 1);
+ }
+ }
+ $scope.currentSearch.push({'name':'text='+search_val, 'label':[$scope.strings['text'], search_val]});
+ $scope.$apply();
+ $scope.hideMenu();
+ $('#search-input').val('');
+ $scope.$emit('textSearch', search_val, $scope.filter_keys);
+ }
+ $scope.filteredObj = $scope.facetsObj;
+ }
+ else {
+ if (search_val === '') {
+ $scope.filteredObj = $scope.facetsObj;
+ $scope.$emit('textSearch', '', $scope.filter_keys);
+ }
+ else {
+ $scope.filterFacets(search_val);
+ }
+ }
+ });
+ $('#search-input').on('keypress', function($event) { // handle character input
+ var search_val = $('#search-input').val();
+ var key = $event.which || $event.keyCode || $event.charCode;
+ if (key != 8 && key != 46 && key != 13 && key != 9 && key != 27) {
+ search_val = search_val + String.fromCharCode(key).toLowerCase();
+ }
+ if (search_val == ' ') { // space and field is empty, show menu
+ $scope.showMenu();
+ $timeout(function() {
+ $('#search-input').val('');
+ });
+ return;
+ }
+ if (search_val === '') {
+ $scope.filteredObj = $scope.facetsObj;
+ $scope.$emit('textSearch', '', $scope.filter_keys);
+ return;
+ }
+ if (key != 8 && key != 46) {
+ $scope.filterFacets(search_val);
+ }
+ });
+ $scope.filterFacets = function(search_val) {
+ // try filtering facets/options.. if no facets match, do text search
+ var i, idx, label;
+ var filtered = [];
+ if ($scope.facetSelected === undefined) {
+ $scope.filteredObj = $scope.facetsObj;
+ for (i=0; i<$scope.filteredObj.length; i++) {
+ var facet = $scope.filteredObj[i];
+ idx = facet.label.toLowerCase().indexOf(search_val);
+ if (idx > -1) {
+ label = [facet.label.substring(0, idx), facet.label.substring(idx, idx + search_val.length), facet.label.substring(idx + search_val.length)];
+ filtered.push({'name':facet.name, 'label':label, 'options':facet.options});
+ }
+ }
+ if (filtered.length > 0) {
+ $scope.showMenu();
+ $timeout(function() {
+ $scope.filteredObj = filtered;
+ }, 0.1);
+ }
+ else {
+ $scope.$emit('textSearch', search_val, $scope.filter_keys);
+ $scope.hideMenu();
+ }
+ }
+ else { // assume option search
+ $scope.filteredOptions = $scope.facetOptions;
+ if ($scope.facetOptions === undefined) { // no options, assume free form text facet
+ return;
+ }
+ for (i=0; i<$scope.filteredOptions.length; i++) {
+ var option = $scope.filteredOptions[i];
+ idx = option.label.toLowerCase().indexOf(search_val);
+ if (idx > -1) {
+ label = [option.label.substring(0, idx), option.label.substring(idx, idx + search_val.length), option.label.substring(idx + search_val.length)];
+ filtered.push({'key':option.key, 'label':label});
+ }
+ }
+ if (filtered.length > 0) {
+ $scope.showMenu();
+ $timeout(function() {
+ $scope.filteredOptions = filtered;
+ }, 0.1);
+ }
+ }
+ };
+ // enable text entry when mouse clicked anywhere in search box
+ $('#search-main-area').on("click", function($event) {
+ $('#search-input').trigger("focus");
+ if ($scope.facetSelected === undefined) {
+ $scope.showMenu();
+ }
+ });
+ // when facet clicked, add 1st part of facet and set up options
+ $scope.facetClicked = function($index, $event, name) {
+ $scope.hideMenu();
+ var facet = $scope.filteredObj[$index];
+ var label = facet.label;
+ if (Array.isArray(label)) {
+ label = label.join('');
+ }
+ $scope.facetSelected = {'name':facet.name, 'label':[label, '']};
+ if (facet.options !== undefined) {
+ $scope.filteredOptions = $scope.facetOptions = facet.options;
+ $scope.showMenu();
+ }
+ $timeout(function() {
+ $('#search-input').val('');
+ });
+ $scope.strings['prompt'] = '';
+ $timeout(function() {
+ $('#search-input').focus();
+ });
+ };
+ // when option clicked, complete facet and send event
+ $scope.optionClicked = function($index, $event, name) {
+ $scope.hideMenu();
+ var curr = $scope.facetSelected;
+ curr.name = curr.name + '=' + name;
+ curr.label[1] = $scope.filteredOptions[$index].label;
+ if (Array.isArray(curr.label[1])) {
+ curr.label[1] = curr.label[1].join('');
+ }
+ $scope.currentSearch.push(curr);
+ $scope.resetState();
+ $scope.emitQuery();
+ $scope.showMenu();
+ };
+ // send event with new query string
+ $scope.emitQuery = function(removed) {
+ var query = '';
+ for (var i=0; i<$scope.currentSearch.length; i++) {
+ if ($scope.currentSearch[i].name.indexOf('text') !== 0) {
+ if (query.length > 0) query = query + "&";
+ query = query + $scope.currentSearch[i].name;
+ }
+ }
+ if (removed !== undefined && removed.indexOf('text') === 0) {
+ $scope.$emit('textSearch', '', $scope.filter_keys);
+ }
+ else {
+ $scope.$emit('searchUpdated', query);
+ if ($scope.currentSearch.length > 0) {
+ var newFacet = $scope.currentSearch[$scope.currentSearch.length-1].name;
+ $scope.deleteFacetSelection(newFacet.split('='));
+ }
+ }
+ };
+ // remove facet and either update filter or search
+ $scope.removeFacet = function($index, $event) {
+ var removed = $scope.currentSearch[$index].name;
+ $scope.currentSearch.splice($index, 1);
+ if ($scope.facetSelected === undefined) {
+ $scope.emitQuery(removed);
+ }
+ else {
+ $scope.resetState();
+ $('#search-input').val('');
+ }
+ // facet re-enabled by reload
+ };
+ // clear entire searchbar
+ $scope.clearSearch = function() {
+ if ($scope.currentSearch.length > 0) {
+ $scope.currentSearch = [];
+ $scope.facetsObj = JSON.parse($scope.facetsJson);
+ $scope.resetState();
+ $scope.$emit('searchUpdated', '');
+ $scope.$emit('textSearch', '', $scope.filter_keys);
+ }
+ };
+ $scope.isMatchLabel = function(label) {
+ return Array.isArray(label);
+ };
+ $scope.resetState = function() {
+ $('#search-input').val('');
+ $scope.filteredObj = $scope.facetsObj;
+ $scope.facetSelected = undefined;
+ $scope.facetOptions = undefined;
+ $scope.filteredOptions = undefined
+ };
+ // showMenu and hideMenu depend on foundation's dropdown. They need
+ // to be modified to work with another dropdown implemenation (i.e. bootstrap)
+ $scope.showMenu = function() {
+ $timeout(function() {
+ if ($('#facet-drop').hasClass('open') === false) {
+ $('#search-input').trigger('click');
+ }
+ });
+ };
+ $scope.hideMenu = function() {
+ $(document).foundation('dropdown', 'closeall');
+ };
+ $scope.initSearch();
+ }
+ };
+ })
+;
diff --git a/xstatic/pkg/magic_search/data/magic_search.scss b/xstatic/pkg/magic_search/data/magic_search.scss
new file mode 100755
index 0000000..4993fe3
--- /dev/null
+++ b/xstatic/pkg/magic_search/data/magic_search.scss
@@ -0,0 +1,87 @@
+/* Copyright 2014-2015 Eucalyptus Systems, Inc. */
+
+/*-----------------------------------------
+ Colors
+ ----------------------------------------- */
+$textcolor: #444;
+$background: white;
+$itembackground: #e6e7e8;
+
+/*-----------------------------------------
+ Item list
+ ----------------------------------------- */
+@mixin item-list {
+ .item-list {
+ margin-bottom: 6px;
+ .item {
+ color: #333;
+ background-color: $itembackground;
+ margin-right: 8px;
+ a {
+ color: white;
+ }
+ }
+ }
+}
+
+/*-----------------------------------------
+ Magic Search bar
+ ----------------------------------------- */
+.search-bar {
+ position: relative;
+ border: 1px solid black;
+ background-color: $background;
+ margin-bottom: 0.5rem;
+ padding: 4px;
+ height: auto;
+ i.fi-filter {
+ color: $textcolor;
+ position: absolute;
+ top: 0.5rem;
+ left: 0.65rem;
+ //&.has-items {
+ // margin-top: 6px;
+ //}
+ }
+ #search-main-area {
+ position: relative;
+ margin-left: 1.65rem;
+ margin-right: 1.65rem;
+ cursor: text;
+ }
+ @include item-list;
+ .item-list {
+ margin-bottom: 2px;
+ }
+ #search-selected {
+ background-color: $background;
+ color: $textcolor;
+ }
+ #search-entry {
+ display: inline-block;
+ height: 1.5rem;
+ }
+ #search-input {
+ width: 220px;
+ border: 0;
+ box-shadow: none;
+ height: 1.5rem;
+ padding: 3px;
+ background-color: $background;
+ //&.has-items {
+ // margin-top: 6px;
+ //}
+ }
+ .match {
+ font-weight: bold;
+ }
+ i.cancel {
+ color: $textcolor;
+ &:hover {
+ color: darkred;
+ }
+ position: absolute;
+ top: 0.5rem;
+ right: 0.65rem;
+ }
+}
diff --git a/xstatic/pkg/magic_search/data/magic_search_bootstrap.html b/xstatic/pkg/magic_search/data/magic_search_bootstrap.html
new file mode 100755
index 0000000..b8639b0
--- /dev/null
+++ b/xstatic/pkg/magic_search/data/magic_search_bootstrap.html
@@ -0,0 +1,50 @@
+<!--! Magic Searchbar -->
+<div id="magic-search" magic-overrides>
+ <div class="search-bar">
+ <i class="fi-filter fa fa-filter go" ng-class="{'has-items': currentSearch.length &gt; 0}"></i>
+ <div id="search-main-area">
+ <span class="item-list">
+ <span class="label radius secondary item"
+ ng-repeat="facet in currentSearch" ng-cloak="cloak">
+ <span>
+ {{ facet.label[0] }}:<b>{{ facet.label[1] }}</b>
+ </span>
+ <a class="remove" ng-click="removeFacet($index, $event)" title="{{ strings.remove }}">
+ <i class="fi-x fa fa-times"></i>
+ </a>
+ </span>
+ </span>
+ <span id="search-selected" class="label" ng-cloak="" ng-show="facetSelected">
+ {{ facetSelected.label[0] }}:
+ </span>
+ <!-- For bootstrap, the dropdown attribute is moved from input up to div. -->
+ <div id="search-entry" dropdown is-open="isMenuOpen">
+ <input id="search-input" type="text" dropdown-toggle
+ placeholder="{{ strings.prompt }}" autocomplete="off"
+ ng-class="{'has-items': currentSearch.length &gt; 0}" />
+ <ul id="facet-drop" class="f-dropdown dropdown-menu" data-dropdown-content="">
+ <li ng-repeat="facet in filteredObj" ng-show="!facetSelected">
+ <a ng-click="facetClicked($index, $event, facet.name)"
+ ng-show="!isMatchLabel(facet.label)">{{ facet.label }}</a>
+ <a ng-click="facetClicked($index, $event, facet.name)"
+ ng-show="isMatchLabel(facet.label)">
+ {{ facet.label[0] }}<span class="match">{{ facet.label[1] }}</span>{{ facet.label[2] }}
+ </a>
+ </li>
+ <li ng-repeat="option in filteredOptions" ng-show="facetSelected">
+ <a ng-click="optionClicked($index, $event, option.key)"
+ ng-show="!isMatchLabel(option.label)">
+ {{ option.label }}
+ </a>
+ <a ng-click="optionClicked($index, $event, option.key)"
+ ng-show="isMatchLabel(option.label)">
+ {{ option.label[0] }}<span class="match">{{ option.label[1] }}</span>{{ option.label[2] }}
+ </a>
+ </ul>
+ </div>
+ </div>
+ <a ng-click="clearSearch()" ng-show="currentSearch.length &gt; 0" title="{{ strings.cancel }}">
+ <i class="fi-x fa fa-times cancel"></i>
+ </a>
+ </div>
+</div>
diff --git a/xstatic/pkg/magic_search/data/magic_search_bootstrap.js b/xstatic/pkg/magic_search/data/magic_search_bootstrap.js
new file mode 100755
index 0000000..69810fe
--- /dev/null
+++ b/xstatic/pkg/magic_search/data/magic_search_bootstrap.js
@@ -0,0 +1,28 @@
+angular.module('MagicSearch', ['ui.bootstrap'])
+ .directive('magicOverrides', function() {
+ return {
+ restrict: 'A',
+ controller: function($scope) {
+ // showMenu and hideMenu depend on foundation's dropdown. They need
+ // to be modified to work with another dropdown implemenation.
+ // For bootstrap, they are not needed at all.
+ $scope.showMenu = function() {
+ $scope.isMenuOpen = true;
+ };
+ $scope.hideMenu = function() {
+ $scope.isMenuOpen = false;
+ };
+ $scope.isMenuOpen = false;
+
+ // remove the following when magic_search.js handles changing the facets/options
+ $scope.$watch('facets_json', function(newVal, oldVal) {
+ if (newVal === oldVal) {
+ return;
+ }
+ $scope.currentSearch = [];
+ $scope.initSearch();
+ });
+
+ }
+ };
+ }); \ No newline at end of file