From 5581af2d2589b406aabe2dd54bc2acc0bf73f99c Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 21 Aug 2013 17:54:30 +1000 Subject: [PATCH] Fixed up the file uploading and the readonly property editors and saving/updating their data/control based on the data changed on the server side. U4-2680 Fix file uploading --- src/Umbraco.Core/StringExtensions.cs | 13 +++ .../lib/umbraco/Extensions.js | 11 ++ ...irective.js => umbfileupload.directive.js} | 44 ++++---- .../umbsinglefileupload.directive.js | 33 ++++++ .../services/contenteditinghelper.service.js | 11 +- .../common/services/filemanager.service.js | 64 +++++++++++ .../src/views/common/main.controller.js | 2 +- .../views/content/content.edit.controller.js | 21 ++-- .../src/views/media/media.edit.controller.js | 31 ++---- .../fileupload/fileupload.controller.js | 100 +++++++++++++----- .../fileupload/fileupload.html | 6 +- .../readonlyvalue/readonlyvalue.controller.js | 67 ++++++++---- .../content/edit-content-controller.spec.js | 16 --- .../services/content-editing-helper.spec.js | 4 +- .../unit/common/services/file-manager.spec.js | 33 ++++++ .../FileUploadPropertyEditor.cs | 31 ++++-- .../PropertyEditors/FileUploadValueEditor.cs | 21 ++-- 17 files changed, 362 insertions(+), 146 deletions(-) rename src/Umbraco.Web.UI.Client/src/common/directives/{fileupload.directive.js => umbfileupload.directive.js} (77%) create mode 100644 src/Umbraco.Web.UI.Client/src/common/directives/umbsinglefileupload.directive.js create mode 100644 src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js create mode 100644 src/Umbraco.Web.UI.Client/test/unit/common/services/file-manager.spec.js diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index b2fa0368ab..4feed19a23 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -28,6 +28,19 @@ namespace Umbraco.Core [UmbracoWillObsolete("Do not use this constants. See IShortStringHelper.CleanStringForSafeAliasJavaScriptCode.")] public const string UmbracoInvalidFirstCharacters = "01234567890"; + /// + /// This tries to detect a json string, this is not a fail safe way but it is quicker than doing + /// a try/catch when deserializing when it is not json. + /// + /// + /// + internal static bool DetectIsJson(this string input) + { + input = input.Trim(); + return input.StartsWith("{") && input.EndsWith("}") + || input.StartsWith("[") && input.EndsWith("]"); + } + internal static string ReplaceNonAlphanumericChars(this string input, char replacement) { //any character that is not alphanumeric, convert to a hyphen diff --git a/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js b/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js index 306b9d01c6..a01782de12 100644 --- a/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js +++ b/src/Umbraco.Web.UI.Client/lib/umbraco/Extensions.js @@ -94,6 +94,17 @@ return this; }; } + + if (!String.prototype.trimEnd) { + + /** trims the end of the string*/ + String.prototype.trimEnd = function (str) { + if (this.endsWith(str)) { + return this.substring(0, this.length - str.length); + } + return this; + }; + } if (!String.prototype.utf8Encode) { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/fileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbfileupload.directive.js similarity index 77% rename from src/Umbraco.Web.UI.Client/src/common/directives/fileupload.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/umbfileupload.directive.js index 7beab2ca27..6d53b20427 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/fileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbfileupload.directive.js @@ -1,22 +1,24 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbFileUpload -* @function -* @restrict AE -* @element ANY -* @scope -**/ -function umbFileUpload() { - return { - scope: true, //create a new scope - link: function (scope, el, attrs) { - el.bind('change', function (event) { - var files = event.target.files; - //emit event upward - scope.$emit("filesSelected", { files: files }); - }); - } - }; -} - +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbFileUpload +* @function +* @restrict A +* @scope +* @description +* Listens for file input control changes and emits events when files are selected for use in other controllers. +**/ +function umbFileUpload() { + return { + restrict: "A", + scope: true, //create a new scope + link: function (scope, el, attrs) { + el.bind('change', function (event) { + var files = event.target.files; + //emit event upward + scope.$emit("filesSelected", { files: files }); + }); + } + }; +} + angular.module('umbraco.directives').directive("umbFileUpload", umbFileUpload); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbsinglefileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/umbsinglefileupload.directive.js new file mode 100644 index 0000000000..b8123b9202 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/umbsinglefileupload.directive.js @@ -0,0 +1,33 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbFileUpload +* @function +* @restrict A +* @scope +* @description +* A single file upload field that will reset itself based on the object passed in for the rebuild parameter. This +* is required because the only way to reset an upload control is to replace it's html. +**/ +function umbSingleFileUpload($compile) { + return { + restrict: "E", + scope: { + rebuild: "=" + }, + replace: true, + template: "
", + link: function (scope, el, attrs) { + + scope.$watch("rebuild", function (newVal, oldVal) { + if (newVal && newVal !== oldVal) { + //recompile it! + el.html(""); + $compile(el.contents())(scope); + } + }); + + } + }; +} + +angular.module('umbraco.directives').directive("umbSingleFileUpload", umbSingleFileUpload); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index 10bb81b4d4..38c573bdb3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -38,10 +38,14 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser * @description * re-binds all changed property values to the origContent object from the newContent object and returns an array of changed properties. */ - reBindChangedProperties: function (allOrigProps, allNewProps) { + reBindChangedProperties: function (origContent, newContent) { var changed = []; + //get a list of properties since they are contained in tabs + var allOrigProps = this.getAllProps(origContent); + var allNewProps = this.getAllProps(newContent); + function getNewProp(alias) { if (alias.startsWith("_umb_")) { return null; @@ -54,7 +58,7 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser for (var p in allOrigProps) { var newProp = getNewProp(allOrigProps[p].alias); if (newProp && !_.isEqual(allOrigProps[p].value, newProp.value)) { - //they have changed so set the origContent prop's value to the new value + //they have changed so set the origContent prop to the new one allOrigProps[p].value = newProp.value; changed.push(allOrigProps[p]); } @@ -134,9 +138,6 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser if (!args.allNewProps && !angular.isArray(args.allNewProps)) { throw "args.allNewProps must be a valid array"; } - if (!args.allOrigProps && !angular.isArray(args.allOrigProps)) { - throw "args.allOrigProps must be a valid array"; - } //When the status is a 403 status, we have validation errors. //Otherwise the error is probably due to invalid data (i.e. someone mucking around with the ids or something). diff --git a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js new file mode 100644 index 0000000000..9f0c3fd105 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js @@ -0,0 +1,64 @@ +/** + * @ngdoc service + * @name umbraco.services.fileManager + * @function + * + * @description + * Used by editors to manage any files that require uploading with the posted data, normally called by property editors + * that need to attach files. + */ +function fileManager() { + + var fileCollection = []; + + return { + /** + * @ngdoc function + * @name umbraco.services.fileManager#addFiles + * @methodOf umbraco.services.fileManager + * @function + * + * @description + * Attaches files to the current manager for the current editor for a particular property, if an empty array is set + * for the files collection that effectively clears the files for the specified editor. + */ + setFiles: function(propertyId, files) { + //this will clear the files for the current property and then add the new ones for the current property + fileCollection = _.reject(fileCollection, function (item) { + return item.id === propertyId; + }); + for (var i = 0; i < files.length; i++) { + //save the file object to the files collection + fileCollection.push({ id: propertyId, file: files[i] }); + } + }, + + /** + * @ngdoc function + * @name umbraco.services.fileManager#getFiles + * @methodOf umbraco.services.fileManager + * @function + * + * @description + * Returns all of the files attached to the file manager + */ + getFiles: function() { + return fileCollection; + }, + + /** + * @ngdoc function + * @name umbraco.services.fileManager#clearFiles + * @methodOf umbraco.services.fileManager + * @function + * + * @description + * Removes all files from the manager + */ + clearFiles: function () { + fileCollection = []; + } +}; +} + +angular.module('umbraco.services').factory('fileManager', fileManager); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/main.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/main.controller.js index cd1c6c0162..1812e058ec 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/main.controller.js @@ -63,7 +63,7 @@ function MainController($scope, $routeParams, $rootScope, $timeout, $http, notif if($scope.user.avatar){ $http.get($scope.user.avatar).then(function(){ - alert($scope.user.avatar); + //alert($scope.user.avatar); $scope.avatar = $scope.user.avatar; }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index 8b34ef129b..114a9e9b55 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -6,8 +6,11 @@ * @description * The controller for the content editor */ -function ContentEditController($scope, $routeParams, $location, contentResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper) { +function ContentEditController($scope, $routeParams, $location, contentResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager) { + //initialize the file manager + fileManager.clearFiles(); + if ($routeParams.create) { //we are creating so get an empty content item contentResource.getScaffold($routeParams.id, $routeParams.doctype) @@ -31,18 +34,6 @@ function ContentEditController($scope, $routeParams, $location, contentResource, }); } - $scope.files = []; - $scope.addFiles = function (propertyId, files) { - //this will clear the files for the current property and then add the new ones for the current property - $scope.files = _.reject($scope.files, function (item) { - return item.id == propertyId; - }); - for (var i = 0; i < files.length; i++) { - //save the file object to the scope's files collection - $scope.files.push({ id: propertyId, file: files[i] }); - } - }; - //TODO: Need to figure out a way to share the saving and event broadcasting with all editors! $scope.saveAndPublish = function () { @@ -55,7 +46,7 @@ function ContentEditController($scope, $routeParams, $location, contentResource, serverValidationManager.reset(); - contentResource.publish($scope.content, $routeParams.create, $scope.files) + contentResource.publish($scope.content, $routeParams.create, fileManager.getFiles()) .then(function (data) { contentEditingHelper.handleSuccessfulSave({ @@ -91,7 +82,7 @@ function ContentEditController($scope, $routeParams, $location, contentResource, serverValidationManager.reset(); - contentResource.save($scope.content, $routeParams.create, $scope.files) + contentResource.save($scope.content, $routeParams.create, fileManager.getFiles()) .then(function (data) { contentEditingHelper.handleSuccessfulSave({ diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index 18ccc0aaf9..e5f62595e9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -6,7 +6,10 @@ * @description * The controller for the media editor */ -function mediaEditController($scope, $routeParams, mediaResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper) { +function mediaEditController($scope, $routeParams, mediaResource, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager) { + + //initialize the file manager + fileManager.clearFiles(); if ($routeParams.create) { @@ -30,18 +33,6 @@ function mediaEditController($scope, $routeParams, mediaResource, notificationsS }); } - - $scope.files = []; - $scope.addFiles = function (propertyId, files) { - //this will clear the files for the current property and then add the new ones for the current property - $scope.files = _.reject($scope.files, function (item) { - return item.id == propertyId; - }); - for (var i = 0; i < files.length; i++) { - //save the file object to the scope's files collection - $scope.files.push({ id: propertyId, file: files[i] }); - } - }; $scope.save = function () { @@ -53,28 +44,22 @@ function mediaEditController($scope, $routeParams, mediaResource, notificationsS serverValidationManager.reset(); - mediaResource.save($scope.content, $routeParams.create, $scope.files) + mediaResource.save($scope.content, $routeParams.create, fileManager.getFiles()) .then(function (data) { contentEditingHelper.handleSuccessfulSave({ scope: $scope, newContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties( - contentEditingHelper.getAllProps($scope.content), - contentEditingHelper.getAllProps(data)) + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) }); }, function (err) { - var allNewProps = contentEditingHelper.getAllProps(err.data); - var allOrigProps = contentEditingHelper.getAllProps($scope.content); - contentEditingHelper.handleSaveError({ err: err, redirectOnFailure: true, - allNewProps: allNewProps, - allOrigProps: contentEditingHelper.getAllProps($scope.content), - rebindCallback: contentEditingHelper.reBindChangedProperties(allOrigProps, allNewProps) + allNewProps: contentEditingHelper.getAllProps(err.data), + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data) }); }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index ab17fdd646..e1632c94aa 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -4,41 +4,64 @@ * @function * * @description - * The controller for the file upload property editor + * The controller for the file upload property editor. It is important to note that the $scope.model.value + * doesn't necessarily depict what is saved for this property editor. $scope.model.value can be empty when we + * are submitting files because in that case, we are adding files to the fileManager which is what gets peristed + * on the server. However, when we are clearing files, we are setting $scope.model.value to "{clearFiles: true}" + * to indicate on the server that we are removing files for this property. We will keep the $scope.model.value to + * be the name of the file selected (if it is a newly selected file) or keep it to be it's original value, this allows + * for the editors to check if the value has changed and to re-bind the property if that is true. * */ -function fileUploadController($scope, $element, $compile, umbImageHelper) { +function fileUploadController($scope, $element, $compile, umbImageHelper, fileManager) { /** Clears the file collections when content is saving (if we need to clear) or after saved */ - function clearFiles() { - //TODO: There should be a better way! We don't want to have to know about the parent scope - //clear the parent files collection (we don't want to upload any!) - $scope.$parent.addFiles($scope.id, []); + function clearFiles() { + //clear the files collection (we don't want to upload any!) + fileManager.setFiles($scope.id, []); //clear the current files $scope.files = []; } - //clear the current files - $scope.files = []; - - //create the property to show the list of files currently saved - if ($scope.model.value != "") { - - var images = $scope.model.value.split(","); + /** this method is used to initialize the data and to re-initialize it if the server value is changed */ + function initialize(index) + { + if (!index) { + index = 1; + } - $scope.persistedFiles = _.map(images, function (item) { - return { file: item, isImage: umbImageHelper.detectIfImageByExtension(item) }; + //this is used in order to tell the umb-single-file-upload directive to + //rebuild the html input control (and thus clearing the selected file) since + //that is the only way to manipulate the html for the file input control. + $scope.rebuildInput = { + index: index + }; + //clear the current files + $scope.files = []; + //store the original value so we can restore it if the user clears and then cancels clearing. + $scope.originalValue = $scope.model.value; + + //create the property to show the list of files currently saved + if ($scope.model.value != "") { + + var images = $scope.model.value.split(","); + + $scope.persistedFiles = _.map(images, function (item) { + return { file: item, isImage: umbImageHelper.detectIfImageByExtension(item) }; + }); + } + else { + $scope.persistedFiles = []; + } + + _.each($scope.persistedFiles, function (file) { + file.thumbnail = umbImageHelper.getThumbnailFromPath(file.file); }); + + $scope.clearFiles = false; } - else { - $scope.persistedFiles = []; - } - - _.each($scope.persistedFiles, function (file) { - file.thumbnail = umbImageHelper.getThumbnailFromPath(file.file); - }); - - $scope.clearFiles = false; + + initialize(); //listen for clear files changes to set our model to be sent up to the server $scope.$watch("clearFiles", function (isCleared) { @@ -47,25 +70,48 @@ function fileUploadController($scope, $element, $compile, umbImageHelper) { clearFiles(); } else { - $scope.model.value = ""; + //reset to original value + $scope.model.value = $scope.originalValue; } }); //listen for when a file is selected $scope.$on("filesSelected", function (event, args) { $scope.$apply(function () { - //set the parent files collection - $scope.$parent.addFiles($scope.model.id, args.files); + //set the files collection + fileManager.setFiles($scope.model.id, args.files); //clear the current files $scope.files = []; + var newVal = ""; for (var i = 0; i < args.files.length; i++) { //save the file object to the scope's files collection $scope.files.push({ id: $scope.model.id, file: args.files[i] }); + newVal += args.files[i].name + ","; } //set clear files to false, this will reset the model too $scope.clearFiles = false; + //set the model value to be the concatenation of files selected. Please see the notes + // in the description of this controller, it states that this value isn't actually used for persistence, + // but we need to set it to something so that the editor and the server can detect that it's been changed. + $scope.model.value = "{selectedFiles: '" + newVal.trimEnd(",") + "'}"; }); }); + + //listen for when the model value has changed + $scope.$watch("model.value", function(newVal, oldVal) { + //cannot just check for !newVal because it might be an empty string which we + //want to look for. + if (newVal !== null && newVal !== undefined && newVal !== oldVal) { + //now we need to check if we need to re-initialize our structure which is kind of tricky + // since we only want to do that if the server has changed the value, not if this controller + // has changed the value. There's only 2 scenarios where we change the value internall so + // we know what those values can be, if they are not either of them, then we'll re-initialize. + + if (newVal !== "{clearFiles: true}" && newVal !== $scope.originalValue && !newVal.startsWith("{selectedFiles:")) { + initialize($scope.rebuildInput.index + 1); + } + } + }); }; angular.module("umbraco").controller('Umbraco.Editors.FileUploadController', fileUploadController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html index 852d7c0de1..1910e354ad 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.html @@ -2,7 +2,7 @@
- +
- - + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/readonlyvalue/readonlyvalue.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/readonlyvalue/readonlyvalue.controller.js index 268e53b841..afaac7cb81 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/readonlyvalue/readonlyvalue.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/readonlyvalue/readonlyvalue.controller.js @@ -1,21 +1,48 @@ -angular.module('umbraco').controller("Umbraco.Editors.ReadOnlyValueController", - function($rootScope, $scope, $filter){ +/** + * @ngdoc controller + * @name Umbraco.Editors.ReadOnlyValueController + * @function + * + * @description + * The controller for the readonlyvalue property editor. + * This controller offer more functionality than just a simple label as it will be able to apply formatting to the + * value to be displayed. This means that we also have to apply more complex logic of watching the model value when + * it changes because we are creating a new scope value called displayvalue which will never change based on the server data. + * In some cases after a form submission, the server will modify the data that has been persisted, especially in the cases of + * readonlyvalues so we need to ensure that after the form is submitted that the new data is reflected here. +*/ +function ReadOnlyValueController($rootScope, $scope, $filter) { - if ($scope.model.config && - angular.isArray($scope.model.config) && - $scope.model.config.length > 0 && - $scope.model.config[0] && - $scope.model.config.filter) - { - - if ($scope.model.config.format) { - $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value, $scope.model.config.format); - } - else { - $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value); - } - } - else { - $scope.displayvalue = $scope.model.value; - } -}); + function formatDisplayValue() { + + if ($scope.model.config && + angular.isArray($scope.model.config) && + $scope.model.config.length > 0 && + $scope.model.config[0] && + $scope.model.config.filter) { + + if ($scope.model.config.format) { + $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value, $scope.model.config.format); + } else { + $scope.displayvalue = $filter($scope.model.config.filter)($scope.model.value); + } + } else { + $scope.displayvalue = $scope.model.value; + } + + } + + //format the display value on init: + formatDisplayValue(); + + $scope.$watch("model.value", function (newVal, oldVal) { + //cannot just check for !newVal because it might be an empty string which we + //want to look for. + if (newVal !== null && newVal !== undefined && newVal !== oldVal) { + //update the display val again + formatDisplayValue(); + } + }); +} + +angular.module('umbraco').controller("Umbraco.Editors.ReadOnlyValueController", ReadOnlyValueController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/test/unit/app/content/edit-content-controller.spec.js b/src/Umbraco.Web.UI.Client/test/unit/app/content/edit-content-controller.spec.js index 06f7104c28..34744c6e60 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/app/content/edit-content-controller.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/app/content/edit-content-controller.spec.js @@ -36,23 +36,7 @@ describe('edit content controller tests', function () { })); describe('content edit controller save and publish', function () { - - it('it should define the default properties on construction', function () { - expect(scope.files).toNotBe(undefined); - }); - it('adding a file adds to the collection', function () { - scope.addFiles(123, ["testFile"]); - expect(scope.files.length).toBe(1); - }); - - it('adding a file with the same property id replaces the existing one', function () { - scope.addFiles(123, ["testFile"]); - scope.addFiles(123, ["testFile2"]); - expect(scope.files.length).toBe(1); - expect(scope.files[0].file).toBe("testFile2"); - }); - it('it should have an content object', function() { //controller should have a content object diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js index 3aa47a7eb5..02025f483d 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/content-editing-helper.spec.js @@ -218,9 +218,7 @@ describe('contentEditingHelper tests', function () { newContent.tabs[1].properties[2].value = "origValue4"; //act - var changed = contentEditingHelper.reBindChangedProperties( - contentEditingHelper.getAllProps(origContent), - contentEditingHelper.getAllProps(newContent)); + var changed = contentEditingHelper.reBindChangedProperties(origContent, newContent); //assert expect(changed.length).toBe(2); diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/file-manager.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/file-manager.spec.js new file mode 100644 index 0000000000..f244b61e4b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/file-manager.spec.js @@ -0,0 +1,33 @@ +describe('file manager tests', function () { + var fileManager; + + beforeEach(module('umbraco.services')); + + beforeEach(inject(function ($injector) { + fileManager = $injector.get('fileManager'); + })); + + describe('file management', function () { + + it('adding a file adds to the collection', function () { + fileManager.setFiles(123, ["testFile"]); + expect(fileManager.getFiles().length).toBe(1); + }); + + it('adding a file with the same property id replaces the existing one', function () { + fileManager.setFiles(123, ["testFile"]); + fileManager.setFiles(123, ["testFile2"]); + expect(fileManager.getFiles().length).toBe(1); + expect(fileManager.getFiles()[0].file).toBe("testFile2"); + }); + + it('clears all files', function () { + fileManager.setFiles(123, ["testFile"]); + fileManager.setFiles(234, ["testFile"]); + expect(fileManager.getFiles().length).toBe(2); + fileManager.clearFiles(); + expect(fileManager.getFiles().length).toBe(0); + }); + + }); +}); \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs index 8dc2e3ee41..33a32e922c 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadPropertyEditor.cs @@ -66,20 +66,39 @@ namespace Umbraco.Web.PropertyEditors UmbracoSettings.ImageAutoFillImageProperties.SelectSingleNode( string.Format("uploadField [@alias = \"{0}\"]", p.Alias)); - if (uploadFieldConfigNode != null && p.Value != null && p.Value is string && ((string)p.Value).IsNullOrWhiteSpace() == false) + if (uploadFieldConfigNode != null) { - //there might be multiple, we can only process the first one! - var split = ((string) p.Value).Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); - if (split.Any()) + //now we need to check if there is a value + if (p.Value is string && ((string) p.Value).IsNullOrWhiteSpace() == false) { - var umbracoFile = new UmbracoMediaFile(IOHelper.MapPath(split[0])); - FillProperties(uploadFieldConfigNode, model, umbracoFile); + //there might be multiple, we can only process the first one! + var split = ((string) p.Value).Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); + if (split.Any()) + { + var umbracoFile = new UmbracoMediaFile(IOHelper.MapPath(split[0])); + FillProperties(uploadFieldConfigNode, model, umbracoFile); + } + } + else + { + //there's no value so need to reset to zero + ResetProperties(uploadFieldConfigNode, model); } } } } } + private static void ResetProperties(XmlNode uploadFieldConfigNode, IContentBase content) + { + // only add dimensions to web images + UpdateContentProperty(uploadFieldConfigNode, content, "widthFieldAlias", string.Empty); + UpdateContentProperty(uploadFieldConfigNode, content, "heightFieldAlias", string.Empty); + + UpdateContentProperty(uploadFieldConfigNode, content, "lengthFieldAlias", string.Empty); + UpdateContentProperty(uploadFieldConfigNode, content, "extensionFieldAlias", string.Empty); + } + private static void FillProperties(XmlNode uploadFieldConfigNode, IContentBase content, UmbracoMediaFile um) { // only add dimensions to web images diff --git a/src/Umbraco.Web/PropertyEditors/FileUploadValueEditor.cs b/src/Umbraco.Web/PropertyEditors/FileUploadValueEditor.cs index faec0502ed..078ddeb302 100644 --- a/src/Umbraco.Web/PropertyEditors/FileUploadValueEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/FileUploadValueEditor.cs @@ -28,7 +28,12 @@ namespace Umbraco.Web.PropertyEditors /// /// Overrides the deserialize value so that we can save the file accordingly /// - /// + /// + /// This is value passed in from the editor. We normally don't care what the editorValue.Value is set to because + /// we are more interested in the files collection associated with it, however we do care about the value if we + /// are clearing files. By default the editorValue.Value will just be set to the name of the file (but again, we + /// just ignore this and deal with the file collection in editorValue.AdditionalData.ContainsKey("files") ) + /// /// /// The current value persisted for this property. This will allow us to determine if we want to create a new /// file path or use the existing file path. @@ -36,16 +41,20 @@ namespace Umbraco.Web.PropertyEditors /// public override object DeserializeValue(ContentPropertyData editorValue, object currentValue) { + if (currentValue == null) + { + currentValue = string.Empty; + } - //if the value is the same then just return the current value - if (currentValue != null && editorValue.Value == currentValue.ToString()) + //if the value is the same then just return the current value so we don't re-process everything + if (string.IsNullOrEmpty(currentValue.ToString()) == false && editorValue.Value == currentValue.ToString()) { return currentValue; } - //check the editorValue value to see if we need to clear the files or not + //check the editorValue value to see if we need to clear the files or not. var clear = false; - if (editorValue.Value.IsNullOrWhiteSpace() == false) + if (editorValue.Value.IsNullOrWhiteSpace() == false && editorValue.Value.StartsWith("{clearFiles:")) { try { @@ -59,7 +68,7 @@ namespace Umbraco.Web.PropertyEditors } var currentPersistedValues = new string[] {}; - if (currentValue != null) + if (string.IsNullOrEmpty(currentValue.ToString()) == false) { currentPersistedValues = currentValue.ToString().Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); }