diff --git a/src/Umbraco.Core/Media/Result.cs b/src/Umbraco.Core/Media/Result.cs index 89e311e2d6..e8e3c6995c 100644 --- a/src/Umbraco.Core/Media/Result.cs +++ b/src/Umbraco.Core/Media/Result.cs @@ -1,7 +1,7 @@ namespace Umbraco.Core.Media { - //NOTE: Could definitely have done with a better name + //TODO: Could definitely have done with a better name public class Result { public Status Status { get; set; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js index 0c0f7feb7c..f8adb14863 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js @@ -188,6 +188,11 @@ angular.module('umbraco.services') */ load: function (pathArray, scope) { var promise; + + if (!angular.isArray(pathArray)) { + throw "pathArray must be an array"; + } + var nonEmpty = _.reject(pathArray, function(item) { return item === undefined || item === ""; }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js index 8b641ebc98..186217e116 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.controller.js @@ -1,20 +1,20 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.TagsController", - function ($rootScope, $scope, $log, assetsService) { - + function ($rootScope, $scope, $log, assetsService, umbRequestHelper) { + //load current value $scope.currentTags = []; if ($scope.model.value) { $scope.currentTags = $scope.model.value.split(","); } - $scope.addTag = function(e) { + $scope.addTag = function (e) { var code = e.keyCode || e.which; if (code == 13) { //Enter keycode //this is required, otherwise the html form will attempt to submit. e.preventDefault(); - + if ($scope.currentTags.indexOf($scope.tagToAdd) < 0) { $scope.currentTags.push($scope.tagToAdd); } @@ -41,5 +41,56 @@ angular.module("umbraco") $scope.currentTags = $scope.model.value.split(","); }; + assetsService.loadJs("lib/typeahead/typeahead.bundle.min.js").then(function () { + + //configure the tags data source + + var tagsHound = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"), + queryTokenizer: Bloodhound.tokenizers.whitespace, + //pre-fetch the tags for this category + prefetch: { + url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", [{ tagGroup: $scope.model.config.group }]), + //TTL = 5 minutes + ttl: 300000, + filter: function (list) { + return _.map(list, function (i) { + return { value: i.text }; + }); + } + }, + //dynamically get the tags for this category + remote: { + url: umbRequestHelper.getApiUrl("tagsDataBaseUrl", "GetTags", [{ tagGroup: $scope.model.config.group }]), + filter: function (list) { + return _.map(list, function (i) { + return { value: i.text }; + }); + } + } + }); + + tagsHound.initialize(); + + //configure the type ahead + + $('#tags-' + $scope.model.alias).typeahead( + //use the default options + null, { + //see: https://github.com/twitter/typeahead.js/blob/master/doc/jquery_typeahead.md#options + // name = the data set name, we'll make this the tag group name + name: $scope.model.config.group, + // apparently thsi should be the same as the value above in the call to Bloodhound.tokenizers.obj.whitespace + // this isn't very clear in the docs but you can see that it's consistent with this statement here: + // http://twitter.github.io/typeahead.js/examples/ + displayKey: "value", + source: tagsHound.ttAdapter() + }); + + }); + + //on destroy: + // $('.typeahead').typeahead('destroy'); + } ); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.html index d0451a41c0..378c48c722 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.html @@ -1,8 +1,10 @@
- {{tag}} - - + + {{tag}} + +
\ No newline at end of file diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 940d7b8251..9778e9daf9 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -209,6 +209,10 @@ namespace Umbraco.Web.Editors { "contentTreeBaseUrl", Url.GetUmbracoApiServiceBaseUrl( controller => controller.GetNodes("-1", null)) + }, + { + "tagsDataBaseUrl", Url.GetUmbracoApiServiceBaseUrl( + controller => controller.GetTags("")) } } }, diff --git a/src/Umbraco.Web/PropertyEditors/RteEmbedController.cs b/src/Umbraco.Web/PropertyEditors/RteEmbedController.cs index 411eeae437..7181780b32 100644 --- a/src/Umbraco.Web/PropertyEditors/RteEmbedController.cs +++ b/src/Umbraco.Web/PropertyEditors/RteEmbedController.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; diff --git a/src/Umbraco.Web/PropertyEditors/TagsDataController.cs b/src/Umbraco.Web/PropertyEditors/TagsDataController.cs new file mode 100644 index 0000000000..5d8e799c6c --- /dev/null +++ b/src/Umbraco.Web/PropertyEditors/TagsDataController.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Umbraco.Web.Editors; +using Umbraco.Web.Models; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; + +namespace Umbraco.Web.PropertyEditors +{ + /// + /// A controller used for type-ahead values for tags + /// + /// + /// DO NOT inherit from UmbracoAuthorizedJsonController since we don't want to use the angularized + /// json formatter as it causes probs. + /// + [PluginController("UmbracoApi")] + public class TagsDataController : UmbracoAuthorizedApiController + { + public IEnumerable GetTags(string tagGroup) + { + return Umbraco.TagQuery.GetAllTags(tagGroup); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0277324e27..ba6de0d3a7 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -356,6 +356,7 @@ + diff --git a/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs b/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs index 81a8f84772..b867ae2f81 100644 --- a/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs +++ b/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs @@ -7,6 +7,7 @@ using System.Net.Http; using System.Net.Http.Formatting; using System.Text; using System.Threading.Tasks; +using Newtonsoft.Json; using Umbraco.Core.Logging; namespace Umbraco.Web.WebApi @@ -19,6 +20,7 @@ namespace Umbraco.Web.WebApi /// public class AngularJsonMediaTypeFormatter : JsonMediaTypeFormatter { + /// /// This will prepend the special chars to the stream output that angular will strip /// @@ -28,43 +30,41 @@ namespace Umbraco.Web.WebApi /// /// /// - public async override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext) + public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext) { + //Before we were calling the base method to do this however it was causing problems: + // http://issues.umbraco.org/issue/U4-4546 + // though I can't seem to figure out why the null ref exception was being thrown, it is very strange. + // This code is basically what the base class does and at least we can track/test exactly what is going on. + + if (type == null) throw new ArgumentNullException("type"); + if (writeStream == null) throw new ArgumentNullException("writeStream"); - using (var memStream = new MemoryStream()) + var task = Task.Factory.StartNew(() => { - try - { - //Let the base class do all the processing using our custom stream - await base.WriteToStreamAsync(type, value, memStream, content, transportContext); - } - catch (Exception ex) - { - LogHelper.Error("An error occurred writing to the output stream", ex); - throw; - } + var effectiveEncoding = SelectCharacterEncoding(content == null ? null : content.Headers); - memStream.Flush(); - memStream.Position = 0; - - //read the result string from the stream - // (see: http://docs.angularjs.org/api/ng.$http) - string output; - using (var reader = new StreamReader(memStream)) + using (var streamWriter = new StreamWriter(writeStream, effectiveEncoding)) + using (var jsonTextWriter = new JsonTextWriter(streamWriter) { - output = reader.ReadToEnd(); - } - - //pre-pend the angular chars to the result - output = ")]}',\n" + output; - - //write out the result to the original stream - using (var writer = new StreamWriter(writeStream)) + CloseOutput = false + }) { - writer.Write(output); + //write the special encoding for angular json to the start + // (see: http://docs.angularjs.org/api/ng.$http) + streamWriter.Write(")]}',\n"); + + if (Indent) + { + jsonTextWriter.Formatting = Formatting.Indented; + } + var jsonSerializer = JsonSerializer.Create(SerializerSettings); + jsonSerializer.Serialize(jsonTextWriter, value); + + jsonTextWriter.Flush(); } - } - + }); + return task; } }