You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by rl...@apache.org on 2018/01/05 23:04:45 UTC
[49/50] [abbrv] ambari git commit: AMBARI-22670 Ambari 3.0: Implement
new design for Admin View: Integrate visual-search box. (atkach)
AMBARI-22670 Ambari 3.0: Implement new design for Admin View: Integrate visual-search box. (atkach)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/72657b6f
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/72657b6f
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/72657b6f
Branch: refs/heads/branch-feature-AMBARI-20859
Commit: 72657b6f3888654266a4359089bf9ea8b6c97b6b
Parents: b171ae3
Author: Andrii Tkach <at...@apache.org>
Authored: Tue Dec 19 14:53:08 2017 +0200
Committer: Robert Levas <rl...@hortonworks.com>
Committed: Fri Jan 5 17:54:17 2018 -0500
----------------------------------------------------------------------
.../main/resources/ui/admin-web/app/index.html | 1 +
.../controllers/ambariViews/ViewsListCtrl.js | 104 +++--
.../remoteClusters/RemoteClustersListCtrl.js | 3 +-
.../stackVersions/StackVersionsListCtrl.js | 1 +
.../app/scripts/directives/comboSearch.js | 455 +++++++++++++++++++
.../ui/admin-web/app/styles/combo-search.css | 164 +++++++
.../resources/ui/admin-web/app/styles/main.css | 36 ++
.../app/views/ambariViews/viewsList.html | 45 +-
.../app/views/directives/comboSearch.html | 63 +++
.../app/views/remoteClusters/list.html | 2 +-
.../admin-web/app/views/stackVersions/list.html | 2 +-
.../ambariViews/ViewsListCtrl_test.js | 167 +++++++
.../test/unit/directives/comboSearch_test.js | 242 ++++++++++
13 files changed, 1201 insertions(+), 84 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/72657b6f/ambari-admin/src/main/resources/ui/admin-web/app/index.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/index.html b/ambari-admin/src/main/resources/ui/admin-web/app/index.html
index bf033e6..a1346ed 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/index.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/index.html
@@ -150,6 +150,7 @@
<script src="scripts/directives/PasswordVerify.js"></script>
<script src="scripts/directives/disabledTooltip.js"></script>
<script src="scripts/directives/editableList.js"></script>
+<script src="scripts/directives/comboSearch.js"></script>
<script src="scripts/services/Utility.js"></script>
<script src="scripts/services/UserConstants.js"></script>
<script src="scripts/services/User.js"></script>
http://git-wip-us.apache.org/repos/asf/ambari/blob/72657b6f/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js
index 8b37dca..8c61a25 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/ambariViews/ViewsListCtrl.js
@@ -24,6 +24,29 @@ angular.module('ambariAdminConsole')
$scope.isLoading = false;
$scope.minInstanceForPagination = Settings.minRowsToShowPagination;
+ $scope.filters = [
+ {
+ key: 'short_url_name',
+ label: $t('common.name'),
+ options: []
+ },
+ {
+ key: 'url',
+ label: $t('urls.url'),
+ options: []
+ },
+ {
+ key: 'view_name',
+ label: $t('views.table.viewType'),
+ options: []
+ },
+ {
+ key: 'instance_name',
+ label: $t('urls.viewInstance'),
+ options: []
+ }
+ ];
+
function checkViewVersionStatus(view, versionObj, versionNumber) {
var deferred = View.checkViewVersionStatus(view.view_name, versionNumber);
@@ -66,27 +89,13 @@ angular.module('ambariAdminConsole')
$scope.instances.push(instance.ViewInstanceInfo);
});
});
- initTypeFilter();
+ $scope.initFilterOptions();
$scope.filterInstances();
}).catch(function (data) {
Alert.error($t('views.alerts.cannotLoadViews'), data.data.message);
});
}
- function initTypeFilter() {
- var uniqTypes = $.unique($scope.instances.map(function(instance) {
- return instance.view_name;
- }));
- $scope.typeFilterOptions = [ { label: $t('common.all'), value: '*'} ]
- .concat(uniqTypes.map(function(type) {
- return {
- label: type,
- value: type
- };
- }));
- $scope.instanceTypeFilter = $scope.typeFilterOptions[0];
- }
-
function showInstancesOnPage() {
var startIndex = ($scope.currentPage - 1) * $scope.instancesPerPage + 1;
var endIndex = $scope.currentPage * $scope.instancesPerPage;
@@ -110,11 +119,7 @@ angular.module('ambariAdminConsole')
$scope.instances = [];
$scope.instancesPerPage = 10;
$scope.currentPage = 1;
- $scope.instanceNameFilter = '';
- $scope.instanceUrlFilter = '';
$scope.maxVisiblePages = 10;
- $scope.isNotEmptyFilter = true;
- $scope.instanceTypeFilter = '';
$scope.tableInfo = {
filtered: 0,
showed: 0
@@ -122,25 +127,46 @@ angular.module('ambariAdminConsole')
loadViews();
- $scope.filterInstances = function() {
+ $scope.initFilterOptions = function() {
+ $scope.filters.forEach(function(filter) {
+ filter.options = $.unique($scope.instances.map(function(instance) {
+ if (filter.key === 'url') {
+ return '/main/view/' + instance.view_name + '/' + instance.short_url;
+ }
+ return instance[filter.key];
+ })).map(function(item) {
+ return {
+ key: item,
+ label: item
+ }
+ });
+ });
+ };
+
+ $scope.filterInstances = function(appliedFilters) {
var filteredCount = 0;
angular.forEach($scope.instances, function(instance) {
- if ($scope.instanceNameFilter && instance.short_url_name.indexOf($scope.instanceNameFilter) === -1) {
- return instance.isFiltered = false;
- }
- if ($scope.instanceUrlFilter && ('/main/view/'+ instance.view_name + '/' + instance.short_url).indexOf($scope.instanceUrlFilter) === -1) {
- return instance.isFiltered = false;
- }
- if ($scope.instanceTypeFilter.value !== '*' && instance.view_name.indexOf($scope.instanceTypeFilter.value) === -1) {
- return instance.isFiltered = false;
- }
- filteredCount++;
- instance.isFiltered = true;
+ instance.isFiltered = !(appliedFilters && appliedFilters.length > 0 && appliedFilters.some(function(filter) {
+ if (filter.key === 'url') {
+ return filter.values.every(function(value) {
+ return ('/main/view/' + instance.view_name + '/' + instance.short_url).indexOf(value) === -1;
+ });
+ }
+ return filter.values.every(function(value) {
+ return instance[filter.key].indexOf(value) === -1;
+ });
+ }));
+
+ filteredCount += ~~instance.isFiltered;
});
$scope.tableInfo.filtered = filteredCount;
$scope.resetPagination();
};
+ $scope.toggleSearchBox = function() {
+ $('.search-box-button .popup-arrow-up, .search-box-row').toggleClass('hide');
+ };
+
$scope.pageChanged = function() {
showInstancesOnPage();
};
@@ -150,22 +176,6 @@ angular.module('ambariAdminConsole')
showInstancesOnPage();
};
- $scope.clearFilters = function () {
- $scope.instanceNameFilter = '';
- $scope.instanceUrlFilter = '';
- $scope.instanceTypeFilter = $scope.typeFilterOptions[0];
- $scope.resetPagination();
- };
-
- $scope.$watch(
- function (scope) {
- return Boolean(scope.instanceNameFilter || scope.instanceUrlFilter || (scope.instanceTypeFilter && scope.instanceTypeFilter.value !== '*'));
- },
- function (newValue, oldValue, scope) {
- scope.isNotEmptyFilter = newValue;
- }
- );
-
$scope.cloneInstance = function(instanceClone) {
$scope.createInstance(instanceClone);
};
http://git-wip-us.apache.org/repos/asf/ambari/blob/72657b6f/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/remoteClusters/RemoteClustersListCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/remoteClusters/RemoteClustersListCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/remoteClusters/RemoteClustersListCtrl.js
index 9d47307..4726357 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/remoteClusters/RemoteClustersListCtrl.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/remoteClusters/RemoteClustersListCtrl.js
@@ -18,9 +18,10 @@
'use strict';
angular.module('ambariAdminConsole')
-.controller('RemoteClustersListCtrl', ['$scope', '$routeParams', '$translate', 'RemoteCluster', function ($scope, $routeParams, $translate, RemoteCluster) {
+.controller('RemoteClustersListCtrl', ['$scope', '$routeParams', '$translate', 'RemoteCluster', 'Settings', function ($scope, $routeParams, $translate, RemoteCluster, Settings) {
var $t = $translate.instant;
+ $scope.minInstanceForPagination = Settings.minRowsToShowPagination;
$scope.clusterName = $routeParams.clusterName;
$scope.isLoading = false;
http://git-wip-us.apache.org/repos/asf/ambari/blob/72657b6f/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/stackVersions/StackVersionsListCtrl.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/stackVersions/StackVersionsListCtrl.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/stackVersions/StackVersionsListCtrl.js
index 003d472..ae00978 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/stackVersions/StackVersionsListCtrl.js
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/controllers/stackVersions/StackVersionsListCtrl.js
@@ -23,6 +23,7 @@ angular.module('ambariAdminConsole')
$scope.getConstant = function (key) {
return $t(key).toLowerCase();
};
+ $scope.minInstanceForPagination = Settings.minRowsToShowPagination;
$scope.isLoading = false;
$scope.clusterName = $routeParams.clusterName;
$scope.filter = {
http://git-wip-us.apache.org/repos/asf/ambari/blob/72657b6f/ambari-admin/src/main/resources/ui/admin-web/app/scripts/directives/comboSearch.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/scripts/directives/comboSearch.js b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/directives/comboSearch.js
new file mode 100644
index 0000000..af25167
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/scripts/directives/comboSearch.js
@@ -0,0 +1,455 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+'use strict';
+
+angular.module('ambariAdminConsole')
+.directive('comboSearch', function() {
+ return {
+ restrict: 'E',
+ templateUrl: 'views/directives/comboSearch.html',
+ scope: {
+ suggestions: '=',
+ filterChange: '=',
+ placeholder: '@',
+ supportCategories: '@'
+ },
+ controller: ['$scope', function($scope) {
+ return {
+ suggestions: $scope.suggestions,
+ placeholder: $scope.placeholder,
+ filterChange: $scope.filterChange,
+ supportCategories: $scope.supportCategories === "true"
+ }
+ }],
+ link: function($scope, $elem, $attr, $ctrl) {
+ var idCounter = 1;
+ var suggestions = $ctrl.suggestions;
+ var supportCategories = $ctrl.supportCategories;
+ var mainInputElement = $elem.find('.main-input.combo-search-input');
+ $scope.paceholder = $ctrl.placeholder;
+ $scope.searchFilterInput = '';
+ $scope.filterSuggestions = [];
+ $scope.showAutoComplete = false;
+ $scope.appliedFilters = [];
+
+ attachInputWidthSetter(mainInputElement);
+ initKeyHandlers();
+ initBlurHandler();
+
+ $scope.$watch(function () {
+ return $scope.appliedFilters.length;
+ }, function () {
+ attachInputWidthSetter($elem.find('.combo-search-input'));
+ });
+
+ $scope.removeFilter = function(filter) {
+ $scope.appliedFilters = $scope.appliedFilters.filter(function(item) {
+ return filter.id !== item.id;
+ });
+ $scope.observeSearchFilterInput(event);
+ mainInputElement.focus();
+ $scope.updateFilters($scope.appliedFilters);
+ };
+
+ $scope.clearFilters = function() {
+ $scope.appliedFilters = [];
+ $scope.updateFilters($scope.appliedFilters);
+ };
+
+ $scope.selectFilter = function(filter, event) {
+ var newAppliedFilter = {
+ id: 'filter_' + idCounter++,
+ currentOption: null,
+ filteredOptions: [],
+ searchOptionInput: '',
+ key: filter.key,
+ label: filter.label,
+ options: filter.options || [],
+ showAutoComplete: false
+ };
+ $scope.appliedFilters.push(newAppliedFilter);
+ if (event) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ $scope.isEditing = false;
+ $scope.showAutoComplete = false;
+ $scope.searchFilterInput = '';
+ _.debounce(function() {
+ $('input[name=' + newAppliedFilter.id + ']').focus().width(4);
+ }, 100)();
+ };
+
+ $scope.selectOption = function(event, option, filter) {
+ $('input[name=' + filter.id + ']').val(option.label).trigger('input');
+ filter.showAutoComplete = false;
+ mainInputElement.focus();
+ $scope.observeSearchFilterInput(event);
+ filter.currentOption = option;
+ $scope.updateFilters($scope.appliedFilters);
+ };
+
+ $scope.hideAutocomplete = function(filter) {
+ _.debounce(function() {
+ if (filter) {
+ filter.showAutoComplete = false;
+ } else {
+ if (!$scope.isEditing) {
+ $scope.showAutoComplete = false;
+ }
+ }
+ $scope.$apply();
+ }, 100)();
+ };
+
+ $scope.forceFocus = function(event, filter) {
+ $(event.currentTarget).find('.combo-search-input').focus();
+ $scope.showAutoComplete = false;
+ $scope.observeSearchOptionInput(filter);
+ event.stopPropagation();
+ event.preventDefault();
+ };
+
+ $scope.makeActive = function(active, all) {
+ if (active.isCategory) {
+ return false;
+ }
+ all.forEach(function(item) {
+ item.active = active.key === item.key;
+ });
+ };
+
+ $scope.observeSearchFilterInput = function(event) {
+ if (event) {
+ mainInputElement.focus();
+ $scope.isEditing = true;
+ event.stopPropagation();
+ event.preventDefault();
+ }
+
+ var filteredSuggestions = suggestions.filter(function(item) {
+ return (!$scope.searchFilterInput || item.label.toLowerCase().indexOf($scope.searchFilterInput.toLowerCase()) !== -1);
+ });
+ if (filteredSuggestions.length > 0) {
+ $scope.makeActive(filteredSuggestions[0], filteredSuggestions);
+ $scope.showAutoComplete = true;
+ } else {
+ $scope.showAutoComplete = false;
+ }
+ $scope.filterSuggestions = supportCategories ? formatCategorySuggestions(filteredSuggestions) : filteredSuggestions;
+ };
+
+ $scope.observeSearchOptionInput = function(filter) {
+ var appliedOptions = {};
+ $scope.appliedFilters.forEach(function(item) {
+ if (item.key === filter.key && item.currentOption) {
+ appliedOptions[item.currentOption.key] = true;
+ }
+ });
+
+ if (filter.currentOption && filter.currentOption.key !== filter.searchOptionInput) {
+ filter.currentOption = null;
+ }
+ filter.filteredOptions = filter.options.filter(function(option) {
+ return !(option.key === '' || option.key === undefined || appliedOptions[option.key])
+ && (!filter.searchOptionInput || option.label.toLowerCase().indexOf(filter.searchOptionInput.toLowerCase()) !== -1);
+ });
+ filter.showAutoComplete = filter.filteredOptions.length > 0;
+ if (filter.filteredOptions.length > 0) {
+ $scope.makeActive(filter.filteredOptions[0], filter.filteredOptions);
+ }
+ };
+
+ $scope.extractFilters = function(filters) {
+ var map = {};
+ var result = [];
+
+ filters.forEach(function(filter) {
+ if (filter.currentOption) {
+ if (!map[filter.key]) {
+ map[filter.key] = [];
+ }
+ map[filter.key].push(filter.currentOption.key);
+ }
+ });
+ for(var key in map) {
+ result.push({
+ key: key,
+ values: map[key]
+ });
+ }
+ return result;
+ };
+
+ $scope.updateFilters = function(appliedFilters) {
+ $ctrl.filterChange($scope.extractFilters(appliedFilters));
+ };
+
+ function formatCategorySuggestions(suggestions) {
+ var categories = {};
+ var result = [];
+ suggestions.forEach(function(item) {
+ if (!item.category) {
+ item.category = 'default';
+ }
+ if (!categories[item.category]) {
+ categories[item.category] = [];
+ }
+ categories[item.category].push(item);
+ });
+
+ for(var cat in categories) {
+ result.push({
+ key: cat,
+ label: cat,
+ isCategory: true,
+ isDefault: cat === 'default'
+ });
+ result = result.concat(categories[cat]);
+ }
+ return result;
+ }
+
+ function initBlurHandler() {
+ $(document).click(function() {
+ $scope.isEditing = false;
+ $scope.hideAutocomplete();
+ });
+ }
+
+ function findActiveByName(array, name) {
+ for (var i = 0; i < array.length; i++) {
+ if (array[i].id === name) {
+ return i;
+ }
+ }
+ return null;
+ }
+
+ function findActiveByProperty(array) {
+ for (var i = 0; i < array.length; i++) {
+ if (array[i].active) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ function focusInput(filter) {
+ $('input[name=' + filter.id + ']').focus();
+ $scope.showAutoComplete = false;
+ $scope.observeSearchOptionInput(filter);
+ }
+
+ function initKeyHandlers() {
+ $(document).keydown(function(event) {
+ if (event.which === 13) { // "Enter" key
+ enterKeyHandler();
+ $scope.$apply();
+ }
+ if (event.which === 8) { // "Backspace" key
+ backspaceKeyHandler(event);
+ $scope.$apply();
+ }
+ if (event.which === 38) { // "Up" key
+ upKeyHandler();
+ $scope.$apply();
+ }
+ if (event.which === 40) { // "Down" key
+ downKeyHandler();
+ $scope.$apply();
+ }
+ if (event.which === 39) { // "Right Arrow" key
+ rightArrowKeyHandler();
+ $scope.$apply();
+ }
+ if (event.which === 37) { // "Left Arrow" key
+ leftArrowKeyHandler();
+ $scope.$apply();
+ }
+ });
+ }
+
+ function leftArrowKeyHandler() {
+ var activeElement = $(document.activeElement);
+ if (activeElement.is('input') && activeElement[0].selectionStart === 0) {
+ if (activeElement.hasClass('main-input')) {
+ focusInput($scope.appliedFilters[$scope.appliedFilters.length - 1]);
+ } else {
+ var activeIndex = findActiveByName($scope.appliedFilters, activeElement.attr('name'));
+ if (activeIndex !== null && activeIndex > 0) {
+ focusInput($scope.appliedFilters[activeIndex - 1]);
+ }
+ }
+ }
+ }
+
+ function rightArrowKeyHandler() {
+ var activeElement = $(document.activeElement);
+ if (activeElement.is('input') && activeElement[0].selectionStart === activeElement.val().length) {
+ if (!activeElement.hasClass('main-input')) {
+ var activeIndex = findActiveByName($scope.appliedFilters, activeElement.attr('name'));
+ if (activeIndex !== null) {
+ if (activeIndex === $scope.appliedFilters.length - 1) {
+ mainInputElement.focus();
+ $scope.observeSearchFilterInput();
+ } else {
+ focusInput($scope.appliedFilters[activeIndex + 1]);
+ }
+ }
+ }
+ }
+ }
+
+ function downKeyHandler() {
+ var activeIndex = 0;
+ var nextIndex = null;
+
+ if ($scope.showAutoComplete) {
+ activeIndex = findActiveByProperty($scope.filterSuggestions);
+ if (activeIndex < $scope.filterSuggestions.length - 1) {
+ if ($scope.filterSuggestions[activeIndex + 1].isCategory && activeIndex + 2 < $scope.filterSuggestions.length) {
+ nextIndex = activeIndex + 2;
+ } else {
+ nextIndex = activeIndex + 1;
+ }
+ } else {
+ nextIndex = ($scope.filterSuggestions[0].isCategory) ? 1 : 0;
+ }
+ if (nextIndex !== null) {
+ $scope.makeActive($scope.filterSuggestions[nextIndex], $scope.filterSuggestions);
+ }
+ } else {
+ var activeAppliedFilters = $scope.appliedFilters.filter(function(item) {
+ return item.showAutoComplete;
+ });
+ if (activeAppliedFilters.length > 0) {
+ var filteredOptions = activeAppliedFilters[0].filteredOptions;
+ activeIndex = findActiveByProperty(filteredOptions);
+ nextIndex = (activeIndex < filteredOptions.length - 1) ? activeIndex + 1 : 0;
+ }
+ if (nextIndex !== null) {
+ $scope.makeActive(filteredOptions[nextIndex], filteredOptions);
+ }
+ }
+ }
+
+ function upKeyHandler() {
+ var activeIndex = 0;
+ var nextIndex = null;
+
+ if ($scope.showAutoComplete) {
+ activeIndex = findActiveByProperty($scope.filterSuggestions);
+ if (activeIndex > 0) {
+ if ($scope.filterSuggestions[activeIndex - 1].isCategory) {
+ nextIndex = (activeIndex - 2 > 0) ? activeIndex - 2 : $scope.filterSuggestions.length - 1;
+ } else {
+ nextIndex = activeIndex - 1;
+ }
+ } else {
+ nextIndex = $scope.filterSuggestions.length - 1;
+ }
+ if (nextIndex !== null) {
+ $scope.makeActive($scope.filterSuggestions[nextIndex], $scope.filterSuggestions);
+ }
+ } else {
+ var activeAppliedFilters = $scope.appliedFilters.filter(function(item) {
+ return item.showAutoComplete;
+ });
+ if (activeAppliedFilters.length > 0) {
+ var filteredOptions = activeAppliedFilters[0].filteredOptions;
+ activeIndex = findActiveByProperty(filteredOptions);
+ nextIndex = (activeIndex > 0) ? activeIndex - 1 : filteredOptions.length - 1;
+ }
+ if (nextIndex !== null) {
+ $scope.makeActive(filteredOptions[nextIndex], filteredOptions);
+ }
+ }
+ }
+
+ function enterKeyHandler() {
+ if ($scope.showAutoComplete) {
+ var activeFilters = $scope.filterSuggestions.filter(function(item) {
+ return item.active;
+ });
+ if (activeFilters.length > 0) {
+ $scope.selectFilter(activeFilters[0]);
+ }
+ } else {
+ var activeAppliedFilters = $scope.appliedFilters.filter(function(item) {
+ return item.showAutoComplete;
+ });
+ if (activeAppliedFilters.length > 0) {
+ var activeOptions = activeAppliedFilters[0].filteredOptions.filter(function(item) {
+ return item.active;
+ });
+ if (activeOptions.length > 0) {
+ $scope.selectOption(null, activeOptions[0], activeAppliedFilters[0]);
+ }
+ } else {
+ $scope.appliedFilters.filter(function(item) {
+ return !item.currentOption;
+ }).forEach(function(item) {
+ if (item.searchOptionInput !== '') {
+ $scope.selectOption(null, {
+ key: item.searchOptionInput,
+ label: item.searchOptionInput
+ }, item);
+ }
+ });
+ }
+ }
+ }
+
+ function backspaceKeyHandler (event) {
+ if ($(document.activeElement).is('input') && $(document.activeElement)[0].selectionStart === 0) {
+ if ($(document.activeElement).hasClass('main-input') && $scope.appliedFilters.length > 0) {
+ var lastFilter = $scope.appliedFilters[$scope.appliedFilters.length - 1];
+ focusInput(lastFilter);
+ event.stopPropagation();
+ event.preventDefault();
+ } else {
+ var name = $(document.activeElement).attr('name');
+ var activeFilter = $scope.appliedFilters.filter(function(item) {
+ return name === item.id;
+ })[0];
+ if (activeFilter) {
+ $scope.removeFilter(activeFilter);
+ }
+ }
+ }
+ }
+
+ function attachInputWidthSetter(element) {
+ var textPadding = 4;
+ element.on('input', function() {
+ var inputWidth = $(this).textWidth();
+ $(this).css({
+ width: inputWidth + textPadding
+ })
+ }).trigger('input');
+ }
+ }
+ };
+});
+
+$.fn.textWidth = function(text, font) {
+ if (!$.fn.textWidth.fakeEl) $.fn.textWidth.fakeEl = $('<span>').hide().appendTo(document.body);
+ $.fn.textWidth.fakeEl.text(text || this.val() || this.text() || this.attr('placeholder')).css('font', font || this.css('font'));
+ return $.fn.textWidth.fakeEl.width();
+};
http://git-wip-us.apache.org/repos/asf/ambari/blob/72657b6f/ambari-admin/src/main/resources/ui/admin-web/app/styles/combo-search.css
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/styles/combo-search.css b/ambari-admin/src/main/resources/ui/admin-web/app/styles/combo-search.css
new file mode 100644
index 0000000..ee9909c
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/styles/combo-search.css
@@ -0,0 +1,164 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.combo-search .combo-search-inner {
+ position: relative;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ box-shadow: 0 0 1px #fff inset;
+ min-height: 34px;
+ line-height: 1em;
+ cursor: text;
+ padding-right: 30px;
+}
+
+.combo-search .combo-search-close {
+ font-size: 13px;
+ color: #999;
+ position: absolute;
+ right: 10px;
+ top: 30%;
+ cursor: pointer;
+}
+.combo-search .combo-search-close:hover {
+ color: #333;
+}
+.combo-search .combo-search-input {
+ background: transparent;
+ display: inline-block;
+ min-width: 4px;
+ width: 4px;
+ line-height: 10px;
+ height: 100%;
+ border: none;
+ outline: none;
+ margin-left: 1px;
+}
+
+.combo-search .combo-search-input-wrapper {
+ display: inline-block;
+ position: relative;
+ height: 32px;
+ margin-left: 5px;
+}
+
+.combo-search .combo-search-content {
+ display: inline-block;
+}
+
+.combo-search .combo-search-dropdown {
+ position: absolute;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ background-color: #fff;
+ cursor: pointer;
+ z-index: 10;
+ 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: 30px;
+ left: 5px;
+ box-shadow: 3px 4px 5px -2px rgba(0, 0, 0, 0.5);
+}
+
+.combo-search .combo-search-dropdown ul {
+ max-height: 250px;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.combo-search .filter a {
+ display: block;
+ width: auto;
+ text-decoration: none;
+ color: initial;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ background: none;
+ border: none;
+ padding: 3px 10px 5px 5px;
+}
+
+.combo-search .category a {
+ display: block;
+ width: auto;
+ text-decoration: none;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding: 3px 10px 5px 5px;
+ text-transform: capitalize;
+ font-size: 11px;
+ font-weight: bold;
+ color: white;
+ cursor: default;
+ border-bottom: 1px solid #A2A2A2;
+ background-color: #B7B7B7;
+ text-shadow: 0 -1px 0 #999;
+}
+
+.combo-search .filter a.active {
+ background-color: #ddd;
+}
+
+.combo-search .combo-search-applied-filter {
+ position: relative;
+ display: inline-block;
+ padding: 0 3px 0 18px;
+ background-color: #dddddd;
+ border-radius: 4px;
+ margin: 4px 0 0 4px;
+ vertical-align: top;
+ border: 1px solid #d2d2d2;
+}
+
+.combo-search .combo-search-applied-filter i {
+ position: absolute;
+ left: 5px;
+ font-size: 12px;
+ top: 5px;
+ color: #999;
+ cursor: pointer;
+}
+
+.combo-search .combo-search-applied-filter i:hover {
+ color: #333;
+}
+
+.combo-search .combo-search-applied-filter span,
+.combo-search .combo-search-applied-filter input {
+ color: #666;
+ font-size: 11px;
+}
+
+.combo-search .combo-search-applied-filter span {
+ font-weight: bold;
+}
+
+.combo-search .combo-search-applied-filter .combo-search-input-wrapper {
+ height: 22px;
+ margin-left: 0;
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/72657b6f/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css b/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css
index b4aa558..8f693a8 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/styles/main.css
@@ -1331,3 +1331,39 @@ th.entity-actions {
.entity-actions a:focus:hover {
text-decoration: none;
}
+
+.search-box-button {
+ position: relative;
+ margin-right: 5px;
+}
+
+.search-box-button .btn {
+ padding: 10px;
+}
+
+.search-box-row {
+ padding-top: 15px;
+ padding-bottom: 5px;
+}
+
+.popup-arrow-up {
+ background: inherit;
+ z-index: 1;
+ left: 6px;
+ position: absolute;
+ width: 24px;
+ height: 16px;
+ overflow: hidden;
+}
+
+.popup-arrow-up:after {
+ content: "";
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ background: #fff;
+ transform: rotate(45deg);
+ top: 10px;
+ left: 2px;
+ box-shadow: -2px -2px 10px -3px rgba(0, 0, 0, 0.5);
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/72657b6f/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/viewsList.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/viewsList.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/viewsList.html
index ae57b86..9e9cb55 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/viewsList.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/ambariViews/viewsList.html
@@ -24,11 +24,21 @@
{{'views.create' | translate}}
</button>
</div>
+ <div class="search-box-button pull-right">
+ <button class="btn btn-default" ng-click="toggleSearchBox()">
+ <i class="fa fa-filter" aria-hidden="true"></i>
+ </button>
+ <div class="popup-arrow-up hide"></div>
+ </div>
+ </div>
+
+ <div class="search-box-row hide">
+ <combo-search suggestions="filters" filter-change="filterInstances" placeholder="Search"></combo-search>
</div>
<table class="table table-striped table-hover">
<thead>
- <tr class="fix-bottom">
+ <tr>
<th class="col-md-2">
<span>{{'common.name' | translate}}</span>
</th>
@@ -45,39 +55,6 @@
<span>{{'common.actions' | translate}}</span>
</th>
</tr>
- <tr class="fix-top">
- <th>
- <div class="search-container">
- <input type="text" class="form-control" placeholder="{{'common.any' | translate}}"
- ng-model="instanceNameFilter" ng-change="filterInstances()">
- <button type="button" class="close clearfilter" ng-show="instanceNameFilter"
- ng-click="instanceNameFilter=''; filterInstances()">
- <span aria-hidden="true">×</span>
- <span class="sr-only">{{'common.controls.close' | translate}}</span>
- </button>
- </div>
- </th>
- <th>
- <div class="search-container">
- <input type="text" class="form-control" placeholder="{{'common.any' | translate}}"
- ng-model="instanceUrlFilter" ng-change="filterInstances()">
- <button type="button" class="close clearfilter" ng-show="instanceUrlFilter"
- ng-click="instanceUrlFilter=''; filterInstances()">
- <span aria-hidden="true">×</span>
- <span class="sr-only">{{'common.controls.close' | translate}}</span>
- </button>
- </div>
- </th>
- <th>
- <select class="form-control typefilter v-small-input"
- ng-model="instanceTypeFilter"
- ng-options="item.label for item in typeFilterOptions"
- ng-change="filterInstances()">
- </select>
- </th>
- <th></th>
- <th></th>
- </tr>
</thead>
<tbody>
http://git-wip-us.apache.org/repos/asf/ambari/blob/72657b6f/ambari-admin/src/main/resources/ui/admin-web/app/views/directives/comboSearch.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/directives/comboSearch.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/directives/comboSearch.html
new file mode 100644
index 0000000..a4fdfc2
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/directives/comboSearch.html
@@ -0,0 +1,63 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+-->
+
+<div class="combo-search">
+ <div class="combo-search-inner" ng-click="observeSearchFilterInput($event)">
+ <div class="combo-search-applied-filter" ng-repeat="filter in appliedFilters" ng-click="forceFocus($event, filter)">
+ <i class="fa fa-times-circle" ng-click="removeFilter(filter)"></i>
+ <span>{{filter.label}}:</span>
+ <div class="combo-search-input-wrapper">
+ <input type="text"
+ autocomplete="off"
+ ng-attr-name="{{filter.id}}"
+ class="combo-search-input"
+ ng-model="filter.searchOptionInput"
+ ng-change="observeSearchOptionInput(filter)"
+ ng-blur="hideAutocomplete(filter)"/>
+ <div class="combo-search-dropdown" ng-show="filter.showAutoComplete">
+ <ul>
+ <li ng-repeat="item in filter.filteredOptions" class="filter">
+ <a ng-click="selectOption($event, item, filter)"
+ ng-class="{active: item.active}"
+ ng-mouseover="makeActive(item, filter.filteredOptions)">{{item.label}}</a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div class="combo-search-input-wrapper">
+ <input type="text"
+ autocomplete="off"
+ placeholder="{{appliedFilters.length === 0 ? placeholder : ''}}"
+ class="combo-search-input main-input"
+ ng-model="searchFilterInput"
+ ng-change="observeSearchFilterInput()"/>
+ <div class="combo-search-dropdown" ng-show="showAutoComplete">
+ <ul>
+ <li ng-repeat="item in filterSuggestions"
+ ng-class="{category: item.isCategory, filter: !item.isCategory, hide: (item.isCategory && item.isDefault)}">
+ <a ng-click="selectFilter(item, $event)"
+ ng-class="{active: item.active}"
+ ng-mouseover="makeActive(item, filterSuggestions)">{{item.label}}</a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <i class="combo-search-close fa fa-times-circle" ng-show="appliedFilters.length" ng-click="clearFilters()"></i>
+ </div>
+</div>
http://git-wip-us.apache.org/repos/asf/ambari/blob/72657b6f/ambari-admin/src/main/resources/ui/admin-web/app/views/remoteClusters/list.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/remoteClusters/list.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/remoteClusters/list.html
index 67d650e..7a8e6f4 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/views/remoteClusters/list.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/remoteClusters/list.html
@@ -61,7 +61,7 @@
<div class="alert empty-table-alert col-sm-12" ng-show="!remoteClusters.length && !isLoading">
{{'common.alerts.noRemoteClusterDisplay' | translate}}
</div>
- <div class="col-sm-12 table-bar">
+ <div class="col-sm-12 table-bar" ng-show="tableInfo.total >= minInstanceForPagination">
<div class="pull-left filtered-info">
<span>{{'common.filterInfo' | translate:{showed: tableInfo.showed, total: tableInfo.total, term: constants.groups} }}</span>
<span ng-show="isNotEmptyFilter">- <a href ng-click="clearFilters()">{{'common.controls.clearFilters' | translate}}</a></span>
http://git-wip-us.apache.org/repos/asf/ambari/blob/72657b6f/ambari-admin/src/main/resources/ui/admin-web/app/views/stackVersions/list.html
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/app/views/stackVersions/list.html b/ambari-admin/src/main/resources/ui/admin-web/app/views/stackVersions/list.html
index 279343b..9d81543 100644
--- a/ambari-admin/src/main/resources/ui/admin-web/app/views/stackVersions/list.html
+++ b/ambari-admin/src/main/resources/ui/admin-web/app/views/stackVersions/list.html
@@ -123,7 +123,7 @@
<div class="alert empty-table-alert col-sm-12" ng-show="!repos.length && !isLoading">
{{'common.alerts.nothingToDisplay' | translate:{term: getConstant("common.version")} }}
</div>
- <div class="col-sm-12 table-bar">
+ <div class="col-sm-12 table-bar" ng-show="tableInfo.total >= minInstanceForPagination">
<div class="pull-left filtered-info">
<span>{{'common.filterInfo' | translate:{showed: tableInfo.showed, total: tableInfo.total, term: getConstant("common.versions")} }}</span>
<span ng-show="isNotEmptyFilter">- <a href ng-click="clearFilters()">{{'common.controls.clearFilters' | translate}}</a></span>
http://git-wip-us.apache.org/repos/asf/ambari/blob/72657b6f/ambari-admin/src/main/resources/ui/admin-web/test/unit/controllers/ambariViews/ViewsListCtrl_test.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/test/unit/controllers/ambariViews/ViewsListCtrl_test.js b/ambari-admin/src/main/resources/ui/admin-web/test/unit/controllers/ambariViews/ViewsListCtrl_test.js
new file mode 100644
index 0000000..362b94a
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/test/unit/controllers/ambariViews/ViewsListCtrl_test.js
@@ -0,0 +1,167 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+describe('#Cluster', function () {
+ describe('ViewsListCtrl', function() {
+ var scope, ctrl;
+
+ beforeEach(function () {
+ module('ambariAdminConsole');
+ inject(function($rootScope, $controller) {
+ scope = $rootScope.$new();
+ ctrl = $controller('ViewsListCtrl', {$scope: scope});
+ });
+ scope.instances = [
+ {
+ short_url_name: 'sun1',
+ url: 'url1',
+ view_name: 'vn1',
+ instance_name: 'in1',
+ short_url: 'su1'
+ },
+ {
+ short_url_name: 'sun2',
+ url: 'url2',
+ view_name: 'vn2',
+ instance_name: 'in2',
+ short_url: 'su2'
+ }
+ ];
+ });
+
+ describe('#initFilterOptions()', function () {
+ beforeEach(function() {
+ scope.initFilterOptions();
+ });
+
+ it('should fill short_url_name options', function() {
+ expect(scope.filters[0].options).toEqual([
+ {
+ key: 'sun1',
+ label: 'sun1'
+ },
+ {
+ key: 'sun2',
+ label: 'sun2'
+ }
+ ]);
+ });
+
+ it('should fill url options', function() {
+ expect(scope.filters[1].options).toEqual([
+ {
+ key: '/main/view/vn1/su1',
+ label: '/main/view/vn1/su1'
+ },
+ {
+ key: '/main/view/vn2/su2',
+ label: '/main/view/vn2/su2'
+ }
+ ]);
+ });
+
+ it('should fill view_name options', function() {
+ expect(scope.filters[2].options).toEqual([
+ {
+ key: 'vn1',
+ label: 'vn1'
+ },
+ {
+ key: 'vn2',
+ label: 'vn2'
+ }
+ ]);
+ });
+
+ it('should fill instance_name options', function() {
+ expect(scope.filters[3].options).toEqual([
+ {
+ key: 'in1',
+ label: 'in1'
+ },
+ {
+ key: 'in2',
+ label: 'in2'
+ }
+ ]);
+ });
+ });
+
+
+ describe('#filterInstances', function() {
+ beforeEach(function() {
+ spyOn(scope, 'resetPagination');
+ });
+
+ it('all should be filtered when filters not applied', function() {
+ scope.filterInstances();
+ expect(scope.tableInfo.filtered).toEqual(2);
+ scope.filterInstances([]);
+ expect(scope.tableInfo.filtered).toEqual(2);
+ });
+
+ it('resetPagination should be called', function() {
+ scope.filterInstances();
+ expect(scope.resetPagination).toHaveBeenCalled();
+ });
+
+ it('one view should be filtered', function() {
+ var appliedFilters = [
+ {
+ key: 'view_name',
+ values: ['vn1']
+ }
+ ];
+ scope.filterInstances(appliedFilters);
+ expect(scope.tableInfo.filtered).toEqual(1);
+ expect(scope.instances[0].isFiltered).toBeTruthy();
+ expect(scope.instances[1].isFiltered).toBeFalsy();
+ });
+
+ it('two views should be filtered', function() {
+ var appliedFilters = [
+ {
+ key: 'view_name',
+ values: ['vn1', 'vn2']
+ }
+ ];
+ scope.filterInstances(appliedFilters);
+ expect(scope.tableInfo.filtered).toEqual(2);
+ expect(scope.instances[0].isFiltered).toBeTruthy();
+ expect(scope.instances[1].isFiltered).toBeTruthy();
+ });
+
+ it('one views should be filtered with combo filter', function() {
+ var appliedFilters = [
+ {
+ key: 'view_name',
+ values: ['vn1', 'vn2']
+ },
+ {
+ key: 'instance_name',
+ values: ['in2']
+ }
+ ];
+ scope.filterInstances(appliedFilters);
+ expect(scope.tableInfo.filtered).toEqual(1);
+ expect(scope.instances[0].isFiltered).toBeFalsy();
+ expect(scope.instances[1].isFiltered).toBeTruthy();
+ });
+ });
+ });
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/72657b6f/ambari-admin/src/main/resources/ui/admin-web/test/unit/directives/comboSearch_test.js
----------------------------------------------------------------------
diff --git a/ambari-admin/src/main/resources/ui/admin-web/test/unit/directives/comboSearch_test.js b/ambari-admin/src/main/resources/ui/admin-web/test/unit/directives/comboSearch_test.js
new file mode 100644
index 0000000..9bc7083
--- /dev/null
+++ b/ambari-admin/src/main/resources/ui/admin-web/test/unit/directives/comboSearch_test.js
@@ -0,0 +1,242 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+describe('#comboSearch', function () {
+ var scope, element;
+
+ beforeEach(module('ambariAdminConsole'));
+ beforeEach(module('views/directives/comboSearch.html'));
+
+ beforeEach(inject(function($rootScope, $compile) {
+ scope = $rootScope.$new();
+
+ var preCompiledElement = '<combo-search suggestions="filters" filter-change="filterItems" placeholder="Search"></combo-search>';
+
+ scope.filters = [
+ {
+ key: 'f1',
+ label: 'filter1',
+ options: []
+ },
+ {
+ key: 'f2',
+ label: 'filter2',
+ options: []
+ }
+ ];
+ scope.filterItems = angular.noop;
+ spyOn(scope, 'filterItems');
+
+
+ element = $compile(preCompiledElement)(scope);
+ scope.$digest();
+ }));
+
+ afterEach(function() {
+ element.remove();
+ });
+
+
+ describe('#removeFilter', function() {
+ it('should remove filter by id', function () {
+ var isoScope = element.isolateScope();
+ isoScope.appliedFilters.push({
+ id: 1
+ });
+ spyOn(isoScope, 'observeSearchFilterInput');
+ spyOn(isoScope, 'updateFilters');
+
+ isoScope.removeFilter({id: 1});
+
+ expect(isoScope.appliedFilters).toEqual([]);
+ expect(isoScope.observeSearchFilterInput).toHaveBeenCalled();
+ expect(isoScope.updateFilters).toHaveBeenCalledWith([]);
+ });
+ });
+
+ describe('#clearFilters', function() {
+ it('should empty appliedFilters', function () {
+ var isoScope = element.isolateScope();
+ isoScope.appliedFilters.push({
+ id: 1
+ });
+ spyOn(isoScope, 'updateFilters');
+
+ isoScope.clearFilters();
+
+ expect(isoScope.appliedFilters).toEqual([]);
+ expect(isoScope.updateFilters).toHaveBeenCalledWith([]);
+ });
+ });
+
+ describe('#selectFilter', function() {
+ it('should add new filter to appliedFilters', function () {
+ var isoScope = element.isolateScope();
+
+ isoScope.selectFilter({
+ key: 'f1',
+ label: 'filter1',
+ options: []
+ });
+
+ expect(isoScope.appliedFilters[0]).toEqual({
+ id: 'filter_1',
+ currentOption: null,
+ filteredOptions: [],
+ searchOptionInput: '',
+ key: 'f1',
+ label: 'filter1',
+ options: [],
+ showAutoComplete: false
+ });
+ expect(isoScope.isEditing).toBeFalsy();
+ expect(isoScope.showAutoComplete).toBeFalsy();
+ expect(isoScope.searchFilterInput).toEqual('');
+ });
+ });
+
+ describe('#selectOption', function() {
+ it('should set value to appliedFilter', function () {
+ var isoScope = element.isolateScope();
+ var filter = {};
+
+ spyOn(isoScope, 'observeSearchFilterInput');
+ spyOn(isoScope, 'updateFilters');
+
+ isoScope.selectOption(null, {
+ key: 'o1',
+ label: 'option1'
+ }, filter);
+
+ expect(filter.currentOption).toEqual({
+ key: 'o1',
+ label: 'option1'
+ });
+ expect(filter.showAutoComplete).toBeFalsy();
+ expect(isoScope.observeSearchFilterInput).toHaveBeenCalled();
+ expect(isoScope.updateFilters).toHaveBeenCalled();
+ });
+ });
+
+ describe('#hideAutocomplete', function() {
+
+ it('showAutoComplete should be false when filter passed', function () {
+ var isoScope = element.isolateScope();
+ var filter = {
+ showAutoComplete: true
+ };
+ jasmine.Clock.useMock();
+
+ isoScope.hideAutocomplete(filter);
+
+ jasmine.Clock.tick(101);
+ expect(filter.showAutoComplete).toBeFalsy();
+ });
+
+ it('showAutoComplete should be false when isEditing = false', function () {
+ var isoScope = element.isolateScope();
+ jasmine.Clock.useMock();
+
+ isoScope.isEditing = false;
+ isoScope.showAutoComplete = true;
+ isoScope.hideAutocomplete();
+
+ jasmine.Clock.tick(101);
+ expect(isoScope.showAutoComplete).toBeFalsy();
+ });
+
+ it('showAutoComplete should be false when isEditing = true', function () {
+ var isoScope = element.isolateScope();
+ jasmine.Clock.useMock();
+
+ isoScope.isEditing = true;
+ isoScope.showAutoComplete = true;
+ isoScope.hideAutocomplete();
+
+ jasmine.Clock.tick(101);
+ expect(isoScope.showAutoComplete).toBeTruthy();
+ });
+ });
+
+ describe('#makeActive', function() {
+ it('category option can not be active', function () {
+ var isoScope = element.isolateScope();
+ var active = {
+ key: 'o1',
+ isCategory: true,
+ active: false
+ };
+
+ isoScope.makeActive(active, [active]);
+
+ expect(active.active).toBeFalsy();
+ });
+
+ it('value option can be active', function () {
+ var isoScope = element.isolateScope();
+ var active = {
+ key: 'o1',
+ isCategory: false,
+ active: false
+ };
+
+ isoScope.makeActive(active, [active]);
+
+ expect(active.active).toBeTruthy();
+ });
+ });
+
+ describe('#updateFilters', function() {
+ it('filter function from parent scope should be called', function () {
+ var isoScope = element.isolateScope();
+ spyOn(isoScope, 'extractFilters').andReturn([{}]);
+
+ isoScope.updateFilters([{}]);
+
+ expect(scope.filterItems).toHaveBeenCalledWith([{}]);
+ });
+ });
+
+ describe('#extractFilters', function() {
+ it('should extract filters', function () {
+ var isoScope = element.isolateScope();
+ var filters = [
+ {
+ currentOption: { key: 'o1'},
+ key: 'f1'
+ },
+ {
+ currentOption: { key: 'o2'},
+ key: 'f1'
+ },
+ {
+ currentOption: null,
+ key: 'f2'
+ }
+ ];
+
+ expect(isoScope.extractFilters(filters)).toEqual([
+ {
+ key: 'f1',
+ values: ['o1', 'o2']
+ }
+ ]);
+ });
+ });
+
+});