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;
}
}