Merge pull request #3004 from umbraco/temp8-166-search-ui

fixes: Temp8 166 search ui
This commit is contained in:
Robert
2018-09-25 13:06:02 +02:00
committed by GitHub
10 changed files with 323 additions and 161 deletions

View File

@@ -55,6 +55,11 @@
});
}));
scope.searchClick = function() {
var showSearch = appState.getSearchState("show");
appState.setSearchState("show", !showSearch);
};
// toggle the help dialog by raising the global app state to toggle the help drawer
scope.helpClick = function () {
var showDrawer = appState.getDrawerState("showDrawer");

View File

@@ -0,0 +1,118 @@
(function () {
'use strict';
/**
* A component to render the pop up search field
*/
var umbSearch = {
templateUrl: 'views/components/application/umb-search.html',
controllerAs: 'vm',
controller: umbSearchController,
bindings: {
onClose: "&"
}
};
function umbSearchController($timeout, backdropService, searchService) {
var vm = this;
vm.$onInit = onInit;
vm.$onDestroy = onDestroy;
vm.search = search;
vm.clickItem = clickItem;
vm.clearSearch = clearSearch;
vm.handleKeyUp = handleKeyUp;
vm.closeSearch = closeSearch;
vm.focusSearch = focusSearch;
function onInit() {
vm.searchQuery = "";
vm.searchResults = [];
vm.hasResults = false;
focusSearch();
backdropService.open();
}
function onDestroy() {
backdropService.close();
}
/**
* Handles when a search result is clicked
*/
function clickItem() {
closeSearch();
}
/**
* Clears the search query
*/
function clearSearch() {
vm.searchQuery = "";
vm.searchResults = [];
vm.hasResults = false;
focusSearch();
}
/**
* Add focus to the search field
*/
function focusSearch() {
vm.searchHasFocus = false;
$timeout(function(){
vm.searchHasFocus = true;
});
}
/**
* Handles all keyboard events
* @param {object} event
*/
function handleKeyUp(event) {
// esc
if(event.keyCode === 27) {
closeSearch();
}
}
/**
* Used to proxy a callback
*/
function closeSearch() {
if(vm.onClose) {
vm.onClose();
}
}
/**
* Used to search
* @param {string} searchQuery
*/
function search(searchQuery) {
if(searchQuery.length > 0) {
var search = {"term": searchQuery};
searchService.searchAll(search).then(function(result){
//result is a dictionary of group Title and it's results
var filtered = {};
_.each(result, function (value, key) {
if (value.results.length > 0) {
filtered[key] = value;
}
});
// bind to view model
vm.searchResults = filtered;
// check if search has results
vm.hasResults = Object.keys(vm.searchResults).length > 0;
});
} else {
clearSearch();
}
}
}
angular.module('umbraco.directives').component('umbSearch', umbSearch);
})();

View File

@@ -74,6 +74,11 @@ function appState(eventsService) {
showMenu: null
};
var searchState = {
//Whether the search is being shown or not
show: null
};
var drawerState = {
//this view to show
view: null,
@@ -221,6 +226,35 @@ function appState(eventsService) {
setState(menuState, key, value, "menuState");
},
/**
* @ngdoc function
* @name umbraco.services.angularHelper#getSearchState
* @methodOf umbraco.services.appState
* @function
*
* @description
* Returns the current search state value by key - we do not return an object here - we do NOT want this
* to be publicly mutable and allow setting arbitrary values
*
*/
getSearchState: function (key) {
return getState(searchState, key, "searchState");
},
/**
* @ngdoc function
* @name umbraco.services.angularHelper#setSearchState
* @methodOf umbraco.services.appState
* @function
*
* @description
* Sets a section state value by key
*
*/
setSearchState: function (key, value) {
setState(searchState, key, value, "searchState");
},
/**
* @ngdoc function
* @name umbraco.services.angularHelper#getDrawerState

View File

@@ -58,8 +58,11 @@
*
*/
function close() {
args.element = null;
args.show = false;
args.opacity = null,
args.element = null,
args.elementPreventClick = false,
args.disableEventsOnClick = false,
args.show = false
eventsService.emit("appState.backdrop", args);
}

View File

@@ -15,6 +15,8 @@ function MainController($scope, $location, appState, treeService, notificationsS
$scope.touchDevice = appState.getGlobalState("touchDevice");
$scope.editors = [];
$scope.overlay = {};
$scope.drawer = {};
$scope.search = {};
$scope.removeNotification = function (index) {
notificationsService.remove(index);
@@ -41,6 +43,10 @@ function MainController($scope, $location, appState, treeService, notificationsS
eventsService.emit("app.closeDialogs", event);
};
$scope.closeSearch = function() {
appState.setSearchState("show", false);
};
var evts = [];
//when a user logs out or timesout
@@ -114,9 +120,15 @@ function MainController($scope, $location, appState, treeService, notificationsS
};
}));
// events for search
evts.push(eventsService.on("appState.searchState.changed", function (e, args) {
if (args.key === "show") {
$scope.search.show = args.value;
}
}));
// events for drawer
// manage the help dialog by subscribing to the showHelp appState
$scope.drawer = {};
evts.push(eventsService.on("appState.drawerState.changed", function (e, args) {
// set view
if (args.key === "view") {
@@ -156,6 +168,7 @@ function MainController($scope, $location, appState, treeService, notificationsS
$scope.backdrop = args;
}));
// event for infinite editors
evts.push(eventsService.on("appState.editors.add", function (name, args) {
$scope.editors = args.editors;
}));

View File

@@ -1,158 +0,0 @@
/**
* @ngdoc controller
* @name Umbraco.SearchController
* @function
*
* @description
* Controls the search functionality in the site
*
*/
function SearchController($scope, searchService, $log, $location, navigationService, $q) {
$scope.searchTerm = null;
$scope.searchResults = [];
$scope.isSearching = false;
$scope.selectedResult = -1;
$scope.navigateResults = function (ev) {
//38: up 40: down, 13: enter
switch (ev.keyCode) {
case 38:
iterateResults(true);
break;
case 40:
iterateResults(false);
break;
case 13:
if ($scope.selectedItem) {
$location.path($scope.selectedItem.editorPath);
navigationService.hideSearch();
}
break;
}
};
var group = undefined;
var groupNames = [];
var groupIndex = -1;
var itemIndex = -1;
$scope.selectedItem = undefined;
$scope.clearSearch = function () {
$scope.searchTerm = null;
};
function iterateResults(up) {
//default group
if (!group) {
for (var g in $scope.groups) {
if ($scope.groups.hasOwnProperty(g)) {
groupNames.push(g);
}
}
//Sorting to match the groups order
groupNames.sort();
group = $scope.groups[groupNames[0]];
groupIndex = 0;
}
if (up) {
if (itemIndex === 0) {
if (groupIndex === 0) {
gotoGroup(Object.keys($scope.groups).length - 1, true);
} else {
gotoGroup(groupIndex - 1, true);
}
} else {
gotoItem(itemIndex - 1);
}
} else {
if (itemIndex < group.results.length - 1) {
gotoItem(itemIndex + 1);
} else {
if (groupIndex === Object.keys($scope.groups).length - 1) {
gotoGroup(0);
} else {
gotoGroup(groupIndex + 1);
}
}
}
}
function gotoGroup(index, up) {
groupIndex = index;
group = $scope.groups[groupNames[groupIndex]];
if (up) {
gotoItem(group.results.length - 1);
} else {
gotoItem(0);
}
}
function gotoItem(index) {
itemIndex = index;
$scope.selectedItem = group.results[itemIndex];
}
//used to cancel any request in progress if another one needs to take it's place
var canceler = null;
$scope.$watch("searchTerm", _.debounce(function (newVal, oldVal) {
$scope.$apply(function () {
$scope.hasResults = false;
if ($scope.searchTerm) {
if (newVal !== null && newVal !== undefined && newVal !== oldVal) {
//Resetting for brand new search
group = undefined;
groupNames = [];
groupIndex = -1;
itemIndex = -1;
$scope.isSearching = true;
navigationService.showSearch();
$scope.selectedItem = undefined;
//a canceler exists, so perform the cancelation operation and reset
if (canceler) {
canceler.resolve();
canceler = $q.defer();
}
else {
canceler = $q.defer();
}
searchService.searchAll({ term: $scope.searchTerm, canceler: canceler }).then(function (result) {
//result is a dictionary of group Title and it's results
var filtered = {};
_.each(result, function (value, key) {
if (value.results.length > 0) {
filtered[key] = value;
}
});
$scope.groups = filtered;
// check if search has results
$scope.hasResults = Object.keys($scope.groups).length > 0;
//set back to null so it can be re-created
canceler = null;
$scope.isSearching = false;
});
}
}
else {
$scope.isSearching = false;
navigationService.hideSearch();
$scope.selectedItem = undefined;
}
});
}, 200));
}
//register it
angular.module('umbraco').controller("Umbraco.SearchController", SearchController);

View File

@@ -84,6 +84,7 @@
@import "components/application/umb-app-content.less";
@import "components/application/umb-tour.less";
@import "components/application/umb-backdrop.less";
@import "components/application/umb-search.less";
@import "components/application/umb-drawer.less";
@import "components/application/umb-language-picker.less";
@import "components/application/umb-dashboard.less";

View File

@@ -0,0 +1,110 @@
/*
Search wrapper
*/
.umb-search {
position: fixed;
z-index: @zindexUmbOverlay;
width: 660px;
max-width: 90%;
transform: translate(-50%, 0);
left: 50%;
top: 20%;
border-radius: @baseBorderRadius;
background: @white;
position: fixed;
box-shadow: 0 10px 20px rgba(0,0,0,.12),0 6px 6px rgba(0,0,0,.14);
}
/*
Search field
*/
.umb-search-input-icon {
font-size: 22px;
color: @gray-7;
padding-left: 20px;
display: flex;
align-items: center;
height: 70px;
}
.umb-search-input.umb-search-input {
width: 100%;
height: 70px;
border: none;
padding: 20px 20px 20px 15px;
border-radius: @baseBorderRadius;
font-size: 22px;
margin-bottom: 0;
}
.umb-search-input-clear {
background: none;
border: none;
font-size: 12px;
margin-right: 20px;
color: @gray-3;
}
.umb-search-input-clear.ng-enter {
opacity: 0;
transition: opacity 100ms ease-in-out;
}
.umb-search-input-clear.ng-enter.ng-enter-active {
opacity: 1;
}
/*
Search results
*/
.umb-search-results {
max-height: 50vh;
overflow-y: auto;
}
.umb-search-group__title {
background: @gray-10;
padding: 3px 20px;
}
.umb-search-items {
list-style: none;
margin: 0;
padding-top: 4px;
padding-bottom: 4px;
}
.umb-search-item > a {
padding: 6px 20px;
display: flex;
}
.umb-search-item > a:hover,
.umb-search-item > a:focus {
background-color: @gray-10;
text-decoration: none;
outline: none;
}
.umb-search-item > a:focus {
padding-left: 25px;
transition: padding 60ms ease-in-out;
}
.umb-search-result__icon {
font-size: 18px;
margin-right: 8px;
color: @gray-1;
}
.umb-search-result__meta {
display: flex;
flex-direction: column;
}
.umb-search-result__description {
color: @gray-5;
font-size: 13px;
}

View File

@@ -0,0 +1,34 @@
<div class="umb-search" on-outside-click="vm.closeSearch()" ng-keyup="vm.handleKeyUp($event)">
<div class="flex items-center">
<i class="umb-search-input-icon icon-search" ng-click="vm.focusSearch()"></i>
<input
class="umb-search-input"
type="text"
ng-model="vm.searchQuery"
ng-model-options="{ debounce: 200 }"
ng-change="vm.search(vm.searchQuery)"
placeholder="Search..."
focus-when="{{vm.searchHasFocus}}" />
<button ng-if="vm.searchQuery.length > 0" tabindex="-1" class="umb-search-input-clear umb-animated" ng-click="vm.clearSearch()">Clear</button>
</div>
<div class="umb-search-results">
<div class="umb-search-group" ng-repeat="(key, group) in vm.searchResults">
<div class="umb-search-group__title">{{key}}</div>
<ul class="umb-search-items">
<li class="umb-search-item" ng-repeat="result in group.results">
<a class="umb-search-result__link" ng-href="#/{{result.editorPath}}" ng-click="vm.clickItem(result)">
<i class="umb-search-result__icon {{result.icon}}"></i>
<span class="umb-search-result__meta">
<span class="umb-search-result__name">{{result.name}}</span>
<span class="umb-search-result__description" ng-show="result.subTitle">{{result.subTitle}}</span>
</span>
</a>
</li>
</ul>
</div>
</div>
</div>

View File

@@ -88,6 +88,8 @@
<!-- help dialog controller by the help button - this also forces the backoffice UI to shift 400px -->
<umb-drawer data-element="drawer" ng-if="drawer.show" model="drawer.model" view="drawer.view"></umb-drawer>
<umb-search ng-if="search.show" on-close="closeSearch()"></umb-search>
</div>
<umb-backdrop ng-if="backdrop.show"