From f759ab4d36f646d2ecdb0f5c1873d4a02a64a9bb Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 14 Jul 2020 09:43:10 +1000 Subject: [PATCH] Fixes block list editor to correctly re-sync it's view model after persisting --- .../blockeditormodelobject.service.js | 45 ++++++++++++++----- .../umbBlockListPropertyEditor.component.js | 30 +++++++------ 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js index ec232b4914..7a80d452be 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js @@ -256,13 +256,34 @@ this.isolatedScope.blockObjects = {}; this.__watchers.push(this.isolatedScope.$on("$destroy", this.destroy.bind(this))); - this.__watchers.push(propertyEditorScope.$on("postFormSubmitting", this.sync.bind(this))); }; BlockEditorModelObject.prototype = { + update: function (propertyModelValue, propertyEditorScope) { + // clear watchers + this.__watchers.forEach(w => { w(); }); + delete this.__watchers; + + // clear block objects + for (const key in this.isolatedScope.blockObjects) { + this.destroyBlockObject(this.isolatedScope.blockObjects[key]); + } + this.isolatedScope.blockObjects = {}; + + // update our values + this.value = propertyModelValue; + this.value.layout = this.value.layout || {}; + this.value.data = this.value.data || []; + + // re-create the watchers + this.__watchers = []; + this.__watchers.push(this.isolatedScope.$on("$destroy", this.destroy.bind(this))); + this.__watchers.push(propertyEditorScope.$on("postFormSubmitting", this.sync.bind(this))); + }, + /** * @ngdoc method * @name getBlockConfiguration @@ -279,8 +300,8 @@ * @ngdoc method * @name load * @methodOf umbraco.services.blockEditorModelObject - * @description Load the scaffolding models for the given configuration, these are needed to provide usefull models for each block. - * @param {Object} blockObject BlockObject to recive data values from. + * @description Load the scaffolding models for the given configuration, these are needed to provide useful models for each block. + * @param {Object} blockObject BlockObject to receive data values from. * @returns {Promise} A Promise object which resolves when all scaffold models are loaded. */ load: function() { @@ -295,7 +316,7 @@ } }); - // removing dublicates. + // removing duplicates. scaffoldKeys = scaffoldKeys.filter((value, index, self) => self.indexOf(value) === index); scaffoldKeys.forEach((contentTypeKey => { @@ -472,7 +493,7 @@ } } - blockObject.retriveValuesFrom = function(content, settings) { + blockObject.retrieveValuesFrom = function(content, settings) { if (this.content !== null) { mapElementValues(content, this.content); } @@ -482,7 +503,7 @@ } - blockObject.sync = function() { + blockObject.sync = function () { if (this.content !== null) { mapToPropertyModel(this.content, this.data); } @@ -499,13 +520,14 @@ addWatchers(blockObject, this.isolatedScope); addWatchers(blockObject, this.isolatedScope, true); - blockObject.destroy = function() { + blockObject.destroy = function () { // remove property value watchers: this.__watchers.forEach(w => { w(); }); delete this.__watchers; // help carbage collector: delete this.config; + delete this.layout; delete this.data; delete this.content; @@ -513,9 +535,12 @@ // remove model from isolatedScope. delete this.__scope.blockObjects["_" + this.key]; + // NOTE: It seems like we should call this.__scope.$destroy(); since that is the only way to remove a scope from it's parent, + // however that is not the case since __scope is actually this.isolatedScope which gets cleaned up when the outer scope is + // destroyed. If we do that here it breaks the scope chain and validation. delete this.__scope; - // removes this method, making it unposible to destroy again. + // removes this method, making it impossible to destroy again. delete this.destroy; // lets remove the key to make things blow up if this is still referenced: @@ -621,8 +646,6 @@ }, - - /** * @ngdoc method * @name sync @@ -636,6 +659,7 @@ }, // private + // TODO: Then this can just be a method in the outer scope _createDataEntry: function(elementTypeKey) { var content = { contentTypeKey: elementTypeKey, @@ -645,6 +669,7 @@ return content.udi; }, // private + // TODO: Then this can just be a method in the outer scope _getDataByUdi: function(udi) { return this.value.data.find(entry => entry.udi === udi) || null; }, diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js index 02e53826b5..919715e074 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/umbBlockListPropertyEditor.component.js @@ -53,8 +53,8 @@ } vm.supportCopy = clipboardService.isSupported(); - vm.layout = [];// The layout object specific to this Block Editor, will be a direct reference from Property Model. - vm.availableBlockTypes = [];// Available block entries of this property editor. + vm.layout = []; // The layout object specific to this Block Editor, will be a direct reference from Property Model. + vm.availableBlockTypes = []; // Available block entries of this property editor. var labels = {}; vm.labels = labels; @@ -63,12 +63,12 @@ labels.content_createEmpty = data[1]; }); - - - - vm.$onInit = function() { + // set the onValueChanged callback, this will tell us if the block list model changed on the server + // once the data is submitted. If so we need to re-initialize + vm.model.onValueChanged = onServerValueChanged; + inlineEditing = vm.model.config.useInlineEditingAsDefault; liveEditing = vm.model.config.useLiveEditing; @@ -121,8 +121,12 @@ } }; - - + // Called when we save the value, the server may return an updated data and our value is re-synced + // we need to deal with that here so that our model values are all in sync so we basically re-initialize. + function onServerValueChanged(newVal, oldVal) { + modelObject.update(newVal, $scope); + onLoaded(); + } function setDirty() { if (vm.propertyForm) { @@ -137,7 +141,7 @@ // Append the blockObjects to our layout. vm.layout.forEach(entry => { - // $block must have the data property to be a valid BlockObject, if not its concidered as a destroyed blockObject. + // $block must have the data property to be a valid BlockObject, if not its considered as a destroyed blockObject. if (entry.$block === undefined || entry.$block === null || entry.$block.data === undefined) { var block = getBlockObject(entry); @@ -176,7 +180,7 @@ block.hideContentInOverlay = block.config.forceHideContentEditorInOverlay === true || inlineEditing === true; block.showSettings = block.config.settingsElementTypeKey != null; - block.showCopy = vm.supportCopy && block.config.contentTypeKey != null;// if we have content, otherwise it dosnt make sense to copy. + block.showCopy = vm.supportCopy && block.config.contentTypeKey != null;// if we have content, otherwise it doesn't make sense to copy. return block; } @@ -211,8 +215,6 @@ } - - function deleteBlock(block) { var layoutIndex = vm.layout.findIndex(entry => entry.udi === block.content.udi); @@ -269,7 +271,7 @@ if (liveEditing === false) { // transfer values when submitting in none-liveediting mode. - blockObject.retriveValuesFrom(blockEditorModel.content, blockEditorModel.settings); + blockObject.retrieveValuesFrom(blockEditorModel.content, blockEditorModel.settings); } blockObject.active = false; @@ -279,7 +281,7 @@ if (liveEditing === true) { // revert values when closing in liveediting mode. - blockObject.retriveValuesFrom(blockContentClone, blockSettingsClone); + blockObject.retrieveValuesFrom(blockContentClone, blockSettingsClone); } if (wasNotActiveBefore === true) {