diff --git a/src/Umbraco.Web.UI.Client/lib/throttle/jquery.ba-throttle-debounce.min.js b/src/Umbraco.Web.UI.Client/lib/throttle/jquery.ba-throttle-debounce.min.js new file mode 100644 index 0000000000..c954c1b1ab --- /dev/null +++ b/src/Umbraco.Web.UI.Client/lib/throttle/jquery.ba-throttle-debounce.min.js @@ -0,0 +1,9 @@ +/* + * jQuery throttle / debounce - v1.1 - 3/7/2010 + * http://benalman.com/projects/jquery-throttle-debounce-plugin/ + * + * Copyright (c) 2010 "Cowboy" Ben Alman + * Dual licensed under the MIT and GPL licenses. + * http://benalman.com/about/license/ + */ +(function (b, c) { var $ = b.jQuery || b.Cowboy || (b.Cowboy = {}), a; $.throttle = a = function (e, f, j, i) { var h, d = 0; if (typeof f !== "boolean") { i = j; j = f; f = c } function g() { var o = this, m = +new Date() - d, n = arguments; function l() { d = +new Date(); j.apply(o, n) } function k() { h = c } if (i && !h) { l() } h && clearTimeout(h); if (i === c && m > e) { l() } else { if (f !== true) { h = setTimeout(i ? k : l, i === c ? e - m : e) } } } if ($.guid) { g.guid = j.guid = j.guid || $.guid++ } return g }; $.debounce = function (d, e, f) { return f === c ? a(d, e, false) : a(d, f, e !== false) } })(this); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbsections.directive.js index 13203372b5..d215baadc5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbsections.directive.js @@ -64,7 +64,8 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se navigationService.showHelpDialog(); }; - scope.sectionClick = function(section){ + scope.sectionClick = function (section) { + navigationService.hideSearch(); navigationService.showTree(section.alias); }; diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index e008f3b69f..e1230dfae8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -262,6 +262,17 @@ function entityResource($q, $http, umbRequestHelper) { "Search", [{ query: query }, {type: type}])), 'Failed to retreive entity data for query ' + query); + }, + + searchAll: function (query) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "SearchAll", + [{ query: query }])), + 'Failed to retreive entity data for query ' + query); } }; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js index a5461a20cd..6dd504b119 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/search.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/search.service.js @@ -1,77 +1,158 @@ angular.module('umbraco.services') -.factory('searchService', function ($q, $log, entityResource, contentResource) { - var m = {results: []}; - var service = { - results: m, +.factory('searchService', function ($q, $log, entityResource, contentResource, umbRequestHelper) { - searchMembers: function(args){ - entityResource.search(args.term, "Member").then(function(data){ + function configureMemberResult(el) { + el.menuUrl = umbRequestHelper.getApiUrl("memberTreeBaseUrl", "GetMenu", [{ id: el.Id }, { application: 'member' }]); + el.metaData = { treeAlias: "member" }; + el.title = el.Fields.nodeName; + el.subTitle = el.Fields.email; + el.id = el.Id; + } + + function configureMediaResult(el) + { + el.menuUrl = umbRequestHelper.getApiUrl("mediaTreeBaseUrl", "GetMenu", [{ id: el.Id }, { application: 'media' }]); + el.metaData = { treeAlias: "media" }; + el.title = el.Fields.nodeName; + el.id = el.Id; + } + + function configureContentResult(el) { + el.menuUrl = umbRequestHelper.getApiUrl("contentTreeBaseUrl", "GetMenu", [{ id: el.Id }, { application: 'content' }]); + el.metaData = { treeAlias: "content" }; + el.title = el.Fields.nodeName; + el.id = el.Id; - _.each(data, function(el){ - el.menuUrl = "UmbracoTrees/MemberTree/GetMenu?id=" + el.Id + "&application=member"; - el.metaData = {treeAlias: "member"}; - el.title = el.Fields.nodeName; - el.subTitle = el.Fields.email; - el.id = el.Id; - }); + contentResource.getNiceUrl(el.Id).then(function (url) { + el.subTitle = angular.fromJson(url); + }); + } - args.results.push({ - icon: "icon-user", - editor: "member/member/edit/", - matches: data - }); - }); - }, - searchContent: function(args){ - entityResource.search(args.term, "Document").then(function(data){ + return { + searchMembers: function(args) { - _.each(data, function(el){ - el.menuUrl = "UmbracoTrees/ContentTree/GetMenu?id=" + el.Id + "&application=content"; - el.metaData = {treeAlias: "content"}; - el.title = el.Fields.nodeName; - el.id = el.Id; + if (!args.term) { + throw "args.term is required"; + } - contentResource.getNiceUrl(el.Id).then(function(url){ - el.subTitle = angular.fromJson(url); - }); - }); + return entityResource.search(args.term, "Member").then(function (data) { - args.results.push({ - icon: "icon-document", - editor: "content/content/edit/", - matches: data - }); - }); - }, - searchMedia: function(args){ - entityResource.search(args.term, "Media").then(function(data){ + _.each(data, function(el) { + configureMemberResult(el); + }); - _.each(data, function(el){ - el.menuUrl = "UmbracoTrees/MediaTree/GetMenu?id=" + el.Id + "&application=media"; - el.metaData = {treeAlias: "media"}; - el.title = el.Fields.nodeName; - el.id = el.Id; - }); + var results = (args.results && angular.isArray(args.results)) ? args.results : []; + + results.push({ + icon: "icon-user", + editor: "member/member/edit/", + matches: data + }); - args.results.push({ - icon: "icon-picture", - editor: "media/media/edit/", - matches: data - }); - }); - }, - search: function(term){ - m.results.length = 0; + return results; + }); + }, + searchContent: function(args) { - service.searchMedia({term:term, results:m.results}); - service.searchContent({term:term, results:m.results}); - service.searchMembers({term:term, results:m.results}); - }, - - setCurrent: function(sectionAlias){ - currentSection = sectionAlias; - } - }; + if (!args.term) { + throw "args.term is required"; + } - return service; + return entityResource.search(args.term, "Document").then(function (data) { + + _.each(data, function(el) { + configureContentResult(el); + }); + + var results = (args.results && angular.isArray(args.results)) ? args.results : []; + + args.results.push({ + icon: "icon-document", + editor: "content/content/edit/", + matches: data + }); + + return results; + }); + }, + searchMedia: function(args) { + + if (!args.term) { + throw "args.term is required"; + } + + return entityResource.search(args.term, "Media").then(function (data) { + + _.each(data, function(el) { + configureMediaResult(el); + }); + + var results = (args.results && angular.isArray(args.results)) ? args.results : []; + + args.results.push({ + icon: "icon-picture", + editor: "media/media/edit/", + matches: data + }); + + return results; + }); + }, + searchAll: function (args) { + + if (!args.term) { + throw "args.term is required"; + } + + return entityResource.searchAll(args.term).then(function (data) { + + var results = (args.results && angular.isArray(args.results)) ? args.results : []; + + _.each(data, function(resultByType) { + switch(resultByType.type) { + case "Document": + _.each(resultByType.results, function (el) { + configureContentResult(el); + }); + results.push({ + type: resultByType.type, + icon: "icon-document", + editor: "content/content/edit/", + matches: resultByType.results + }); + break; + case "Media": + _.each(resultByType.results, function (el) { + configureMediaResult(el); + }); + results.push({ + type: resultByType.type, + icon: "icon-picture", + editor: "media/media/edit/", + matches: resultByType.results + }); + break; + case "Member": + _.each(resultByType.results, function (el) { + configureMemberResult(el); + }); + results.push({ + type: resultByType.type, + icon: "icon-user", + editor: "member/member/edit/", + matches: resultByType.results + }); + break; + } + }); + + return results; + }); + + }, + + setCurrent: function(sectionAlias) { + currentSection = sectionAlias; + } + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index cecd1babe9..90b3f4b8ab 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -360,4 +360,23 @@ table thead a { content:"1"; background:#f2f2f2; margin-top: -1px; +} + +/* SEARCH */ + +.umb-search-group li > div { + padding-left: 20px; +} + +.umb-search-group li > div a > i { + height: 100%; +} + +#search-form .form-search div { + margin-top:3px; +} +#search-form .form-search div .btn { + position:relative; + top:0; + left:0; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/loader.js b/src/Umbraco.Web.UI.Client/src/loader.js index f29b2bcea0..43e740cb48 100644 --- a/src/Umbraco.Web.UI.Client/src/loader.js +++ b/src/Umbraco.Web.UI.Client/src/loader.js @@ -40,6 +40,7 @@ yepnope({ 'lib/jquery/jquery.upload/js/jquery.fileupload-process.js', 'lib/jquery/jquery.upload/js/jquery.fileupload-angular.js', + 'lib/throttle/jquery.ba-throttle-debounce.min.js', 'lib/bootstrap/js/bootstrap.js', 'lib/underscore/underscore.js', diff --git a/src/Umbraco.Web.UI.Client/src/views/common/search.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/search.controller.js index 162398e26f..719106387a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/search.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/search.controller.js @@ -8,36 +8,24 @@ * */ function SearchController($scope, searchService, $log, navigationService) { + + $scope.searchTerm = null; + $scope.searchResults = []; + $scope.isSearching = false; - var currentTerm = ""; - navigationService.ui.search = searchService.results; - - $scope.deActivateSearch = function () { - currentTerm = ""; - }; - - $scope.performSearch = function (term) { - if (term != undefined && term != currentTerm) { - navigationService.ui.selectedSearchResult = -1; - navigationService.showSearch(); - currentTerm = term; - searchService.search(term); + //watch the value change but don't do the search on every change - that's far too many queries + // we need to debounce + $scope.$watch("searchTerm", $.debounce(400, function () { + if ($scope.searchTerm) { + $scope.isSearching = true; + navigationService.showSearch(); + searchService.searchAll({ term: $scope.searchTerm }).then(function (result) { + $scope.searchResults = result; + }); } - }; + }), true); - $scope.hideSearch = navigationService.hideSearch; - - $scope.iterateResults = function (direction) { - if (direction == "up" && navigationService.ui.selectedSearchResult < navigationService.ui.searchResults.length) - navigationService.ui.selectedSearchResult++; - else if (navigationService.ui.selectedSearchResult > 0) - navigationService.ui.selectedSearchResult--; - }; - - $scope.selectResult = function () { - navigationService.showMenu(navigationService.ui.searchResults[navigationService.ui.selectedSearchResult], undefined); - }; } //register it angular.module('umbraco').controller("Umbraco.SearchController", SearchController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html index deeb45b3e2..499cc92d5a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/umb-navigation.html @@ -8,56 +8,56 @@