Merge pull request #3004 from umbraco/temp8-166-search-ui
fixes: Temp8 166 search ui
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
})();
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}));
|
||||
|
||||
@@ -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);
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user