From 59c0e3f9886fb79d489b4d08b990499b902a9e4b Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 2 Oct 2018 11:45:06 +0100 Subject: [PATCH 1/6] Uses underscorejs omit to remove the 'apps' from the object we copy to avoid a never ending nested tree of apps in the JSON --- .../components/content/umbvariantcontenteditors.directive.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js index 46d0a1c5c1..a3a212a603 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js @@ -186,14 +186,15 @@ } else { vm.openVariants[editorIndex] = variant.language.culture; } - + } //then assign the variant to a view model to the content app var contentApp = _.find(variant.apps, function (a) { return a.alias === "umbContent"; }); - contentApp.viewModel = variant; + + contentApp.viewModel = _.omit(variant, 'apps'); // make sure the same app it set to active in the new variant if(activeAppAlias) { From b2ec2ac592d7050bd25d2c8aae13a35cd600ab84 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 3 Oct 2018 14:51:04 +0100 Subject: [PATCH 2/6] Update bower to angular 1.7.4 --- src/Umbraco.Web.UI.Client/bower.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 9628cddfac..9f9c4fff01 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -16,13 +16,13 @@ "tests" ], "dependencies": { - "angular": "~1.7.2", - "angular-cookies": "~1.7.2", - "angular-sanitize": "~1.7.2", - "angular-touch": "~1.7.2", - "angular-route": "~1.7.2", - "angular-animate": "~1.7.2", - "angular-i18n": "~1.7.2", + "angular": "~1.7.4", + "angular-cookies": "~1.7.4", + "angular-sanitize": "~1.7.4", + "angular-touch": "~1.7.4", + "angular-route": "~1.7.4", + "angular-animate": "~1.7.4", + "angular-i18n": "~1.7.4", "signalr": "^2.2.1", "typeahead.js": "~0.10.5", "underscore": "~1.9.1", From 6d604a37b623ed999126052438debb0d854d1734 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 3 Oct 2018 14:55:15 +0100 Subject: [PATCH 3/6] WIP with Shan - Removes nested content app form validation error messages & then puts them back --- .../components/content/edit.controller.js | 112 +++++++++++++++--- .../validation/valformmanager.directive.js | 18 +-- .../validation/valsubview.directive.js | 25 +++- .../editor/umb-editor-navigation.html | 5 +- .../editor/umb-editor-sub-view.html | 12 +- 5 files changed, 143 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 3eac5439fd..fb86389eda 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -3,7 +3,7 @@ function ContentEditController($rootScope, $scope, $routeParams, $q, $window, appState, contentResource, entityResource, navigationService, notificationsService, - serverValidationManager, contentEditingHelper, treeService, formHelper, umbRequestHelper, + serverValidationManager, contentEditingHelper, treeService, formHelper, umbRequestHelper, editorState, $http, eventsService, relationResource, overlayService) { var evts = []; @@ -56,11 +56,11 @@ } bindEvents(); - - resetVariantFlags(); + + resetVariantFlags(); } - + /** * This will reset isDirty flags if save is true. * When working with multiple variants, this will set the save/publish flags of each one to false. @@ -85,7 +85,7 @@ $scope.content.variants[0].publish = false; } } - + /** Returns true if the save/publish dialog should be shown when pressing the button */ function showSaveOrPublishDialog() { return $scope.content.variants.length > 1; @@ -152,7 +152,7 @@ */ function createButtons(content, app) { - // only create the save/publish/preview buttons if the + // only create the save/publish/preview buttons if the // content app is "Conent" if(app && app.alias !== "umbContent" && app.alias !== "umbInfo") { $scope.defaultButton = null; @@ -172,7 +172,7 @@ unPublish: $scope.unPublish } }); - + $scope.defaultButton = buttons.defaultButton; $scope.subButtons = buttons.subButtons; $scope.page.showPreviewButton = true; @@ -202,7 +202,7 @@ if (infiniteMode || !path) { return; } - + if (!$scope.content.isChildOfListView) { navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) { $scope.page.menu.currentNode = syncArgs.node; @@ -213,7 +213,7 @@ //it's a child item, just sync the ui node to the parent navigationService.syncTree({ tree: $scope.treeAlias, path: path.substring(0, path.lastIndexOf(",")).split(","), forceReload: initialLoad !== true }); - //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node + //if this is a child of a list view and it's the initial load of the editor, we need to get the tree node // from the server so that we can load in the actions menu. umbRequestHelper.resourcePromise( $http.get(content.treeNodeUrl), @@ -223,11 +223,91 @@ } } + function checkValidility(){ + //Get all controls from the 'contentForm' + var allControls = $scope.contentForm.$getControls(); + + //An array to store items in when we find child form fields (no matter how many deep nested forms) + var childFieldsToMarkAsValid = []; + + //Exclude known formControls 'contentHeaderForm' and 'tabbedContentForm' + //Check property - $name === "contentHeaderForm" + allControls = _.filter(allControls, function(obj){ + return obj.$name !== 'contentHeaderForm' && obj.$name !== 'tabbedContentForm' && obj.hasOwnProperty('$submitted'); + }); + + for (var i = 0; i < allControls.length; i++) { + var nestedForm = allControls[i]; + + //Get Nested Controls of this form in the loop + var nestedFormControls = nestedForm.$getControls(); + + //Need to recurse through controls (could be more nested forms) + childFieldsToMarkAsValid = recurseFormControls(nestedFormControls, childFieldsToMarkAsValid); + } + + return childFieldsToMarkAsValid; + } + + //Controls is the + function recurseFormControls(controls, array){ + + //Loop over the controls + for (var i = 0; i < controls.length; i++) { + var controlItem = controls[i]; + + //Check if the controlItem has a property '' + if(controlItem.hasOwnProperty('$submitted')){ + //This item is a form - so lets get the child controls of it & recurse again + var childFormControls = controlItem.$getControls(); + recurseFormControls(childFormControls, array); + } + else { + //We can assume its a field on a form + if(controlItem.hasOwnProperty('$error')){ + //Set the validlity of the error/s to be valid + //String of keys of error invalid messages + var errorKeys = []; + + for(var key in controlItem.$error){ + errorKeys.push(key); + controlItem.$setValidity(key, true); + } + + //Create a basic obj - storing the control item & the error keys + var obj = { 'control': controlItem, 'errorKeys': errorKeys }; + + //Push the updated control into the array - so we can set them back + array.push(obj); + } + } + } + return array; + } + + function resetNestedFieldValiation(array){ + for (var i = 0; i < array.length; i++) { + var item = array[i]; + //Item is an object containing two props + //'control' (obj) & 'errorKeys' (string array) + var fieldControl = item.control; + var fieldErrorKeys = item.errorKeys; + + for(var i = 0; i < fieldErrorKeys.length; i++) { + fieldControl.$setValidity(fieldErrorKeys[i], false); + } + } + } + // This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish function performSave(args) { $scope.page.buttonGroupState = "busy"; + //Used to check validility of nested form - coming from Content Apps mostly + //Set them all to be invalid + var fieldsToRollback = checkValidility(); + eventsService.emit("content.saving", { content: $scope.content, action: args.action }); return contentEditingHelper.contentEditorPerformSave({ @@ -237,19 +317,19 @@ action: args.action, showNotifications: args.showNotifications }).then(function (data) { - //success + //success init($scope.content); - syncTreeNode($scope.content, data.path); $scope.page.buttonGroupState = "success"; eventsService.emit("content.saved", { content: $scope.content, action: args.action }); + resetNestedFieldValiation(fieldsToRollback); + return $q.when(data); }, function (err) { - syncTreeNode($scope.content, $scope.content.path); //error @@ -259,6 +339,8 @@ $scope.page.buttonGroupState = "error"; + resetNestedFieldValiation(fieldsToRollback); + return $q.reject(err); }); } @@ -400,7 +482,7 @@ overlayService.close(); } }; - + overlayService.open(dialog); } } @@ -517,9 +599,9 @@ if (!$scope.busy) { - // Chromes popup blocker will kick in if a window is opened + // Chromes popup blocker will kick in if a window is opened // without the initial scoped request. This trick will fix that. - // + // var previewWindow = $window.open('preview/?init=true', 'umbpreview'); // Build the correct path so both /#/ and #/ work. diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js index 35e9005cc6..b201c877a3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valformmanager.directive.js @@ -3,7 +3,7 @@ * @name umbraco.directives.directive:valFormManager * @restrict A * @require formController -* @description Used to broadcast an event to all elements inside this one to notify that form validation has +* @description Used to broadcast an event to all elements inside this one to notify that form validation has * changed. If we don't use this that means you have to put a watch for each directive on a form's validation * changing which would result in much higher processing. We need to actually watch the whole $error collection of a form * because just watching $valid or $invalid doesn't acurrately trigger form validation changing. @@ -19,7 +19,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location var SAVED_EVENT_NAME = "formSubmitted"; return { - require: ["form", "^^?valFormManager"], + require: ["form", "^^?valFormManager", "^^?valSubView"], restrict: "A", controller: function($scope) { //This exposes an API for direct use with this directive @@ -48,7 +48,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location var formCtrl = ctrls[0]; var parentFormMgr = ctrls.length > 0 ? ctrls[1] : null; - + var subView = ctrls.length > 1 ? ctrls[2] : null; var labels = {}; var labelKeys = [ @@ -83,7 +83,11 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location return sum; }, function (e) { scope.$broadcast("valStatusChanged", { form: formCtrl }); - + + if (subView){ + subView.valStatusChanged({ form: formCtrl }); + } + //find all invalid elements' .control-group's and apply the error class var inError = element.find(".control-group .ng-invalid").closest(".control-group"); inError.addClass("error"); @@ -94,7 +98,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location }); - //This tracks if the user is currently saving a new item, we use this to determine + //This tracks if the user is currently saving a new item, we use this to determine // if we should display the warning dialog that they are leaving the page - if a new item // is being saved we never want to display that dialog, this will also cause problems when there // are server side validation issues. @@ -122,7 +126,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location element.removeClass(SHOW_VALIDATION_CLASS_NAME); scope.showValidation = false; //clear form state as at this point we retrieve new data from the server - //and all validation will have cleared at this point + //and all validation will have cleared at this point formCtrl.$setPristine(); })); @@ -203,7 +207,7 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location $timeout(function(){ formCtrl.$setPristine(); }, 1000); - + } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js index a65a08d17e..8f4d93090b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valsubview.directive.js @@ -10,6 +10,28 @@ function valSubViewDirective() { + function controller($scope, $element) { + //expose api + return { + valStatusChanged: function(args) { + if (!args.form.$valid) { + var subViewContent = $element.find(".ng-invalid"); + + if (subViewContent.length > 0) { + $scope.model.hasError = true; + $element.addClass("show-validation"); + } else { + $scope.model.hasError = false; + $element.removeClass("show-validation"); + } + } + else { + $scope.model.hasError = false; + } + } + } + } + function link(scope, el, attr, ctrl) { //if there are no containing form or valFormManager controllers, then we do nothing @@ -43,7 +65,8 @@ var directive = { require: ['?^^form', '?^^valFormManager'], restrict: "A", - link: link + link: link, + controller: controller }; return directive; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html index 3166992e8a..2bb6c1107b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-navigation.html @@ -1,7 +1,8 @@