From 83c76cdd8713c35148716d7bc7a6ce15f522c35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Fri, 6 Mar 2020 14:01:15 +0100 Subject: [PATCH] use an isolated scope --- .../common/services/blockeditor.service.js | 136 ++++++++++++++---- .../imageblock/imageblock.editor.html | 4 +- .../inlineblock/inlineblock.editor.html | 8 +- .../labelblock/labelblock.editor.html | 6 +- .../textareablock/textareablock.editor.html | 2 +- .../blockeditor.block.component.js | 4 +- .../blocklist/blocklist.component.html | 12 +- .../blocklist/blocklist.component.js | 37 ++--- 8 files changed, 145 insertions(+), 64 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js index 1c67ac1377..c6613e0c60 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditor.service.js @@ -58,7 +58,7 @@ function mapElementTypeValues(fromModel, toModel) { var fromVariant = fromModel.variants[0]; var toVariant = toModel.variants[0]; - + for (var t = 0; t < fromVariant.tabs.length; t++) { var fromTab = fromVariant.tabs[t]; var toTab = toVariant.tabs[t]; @@ -72,12 +72,39 @@ } + function getBlockLabel(blockModel) { + if(blockModel.labelInterpolator) { + // We are just using the contentModel, since its a key/value object that is live synced. (if we need to add additional vars, we could make a shallow copy and apply those.) + return blockModel.labelInterpolator(blockModel.contentModel); + } + return blockModel.contentTypeName; + } + + + /** + * Used to create a scoped watcher for a property on a blockModel. + */ + function createPropWatcher(blockModel, prop) { + + return function() { + + // sync data: + blockModel.contentModel[prop.alias] = prop.value; + + // regenerate label. + // TODO: could use a debounce. + blockModel.label = getBlockLabel(blockModel); + } + + } + + /** * @ngdoc factory * @name umbraco.factory.BlockEditorModelObject * @description A model object used to handle Block Editor data. **/ - function BlockEditorModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations) { + function BlockEditorModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations, propertyScope) { if (!propertyModelValue) { throw new Error("propertyModelValue cannot be undefined, to ensure we keep the binding to the angular model we need minimum an empty object."); @@ -93,6 +120,13 @@ this.scaffolds = []; + this.watchers = []; + + this.isolatedScope = propertyScope.$new(true); + this.isolatedScope.blockModels = {}; + + this.isolatedScope.$on("$destroy", this.onDestroyed.bind(this)); + }; BlockEditorModelObject.prototype = { @@ -164,17 +198,23 @@ var udi = layoutEntry.udi; - var contentModel = this._getContentByUdi(udi); + var contentModel = this._getDataByUdi(udi); + + if (contentModel === null) { + console.error("Couldnt find content model of "+udi) + return null; + } var blockConfiguration = this.getBlockConfiguration(contentModel.contentTypeAlias); if (blockConfiguration === null) { + console.error("The block entry of "+udi+" is not begin initialized cause its contentTypeAlias is not allowed for this PropertyEditor") // This is not an allowed block type, therefor we return null; return null; } var blockModel = {}; - blockModel.key = String.CreateGuid(); + blockModel.key = String.CreateGuid().replace(/-/g, ""); blockModel.config = angular.copy(blockConfiguration); blockModel.labelInterpolator = $interpolate(blockModel.config.label); @@ -191,14 +231,51 @@ blockModel.contentModel = contentModel; blockModel.layoutModel = layoutEntry; + blockModel.watchers = []; // TODO: settings + // Add blockModel to our isolated scope to enable watching its values: + this.isolatedScope.blockModels["_"+blockModel.key] = blockModel; + + // Start watching each property value. + var variant = blockModel.content.variants[0]; + for (var t = 0; t < variant.tabs.length; t++) { + var tab = variant.tabs[t]; + for (var p = 0; p < tab.properties.length; p++) { + var prop = tab.properties[p]; + + // Watch value of property since this is the only value we want to keep synced. + // Do notice that it is not performing a deep watch, meaning that we are only watching primatives and changes directly to the object of property-value. + // But we like to sync non-primative values as well! Yes, and this does happen, just not through this code, but through the nature of JavaScript. + // Non-primative values act as references to the same data and are therefor synced. + blockModel.watchers.push(this.isolatedScope.$watch("blockModels._"+blockModel.key+".content.variants[0].tabs["+t+"].properties["+p+"].value", createPropWatcher(blockModel, prop))); + } + } + return blockModel; }, + removeDataAndDestroyModel: function(blockModel) { + this.destroyBlockModel(blockModel); + this.removeDataByUdi(blockModel.content.udi); + }, + + destroyBlockModel: function(blockModel) { + + // remove property value watchers: + for (const w of blockModel.watchers) { + w(); + } + + // remove model from isolatedScope. + delete this.isolatedScope.blockModels[blockModel.key]; + + }, + + /** * Retrieve block model of a layout entry * @return {Object} Scaffolded Block Content object. @@ -237,7 +314,7 @@ } var entry = { - udi: this._createContent(contentTypeAlias) + udi: this._createDataEntry(contentTypeAlias) } if (blockConfiguration.settingsElementTypeAlias != null) { @@ -262,7 +339,10 @@ return null; } - var contentModel = this._getContentByUdi(layoutEntry.udi); + var contentModel = this._getDataByUdi(layoutEntry.udi); + if(contentModel === null) { + return null; + } mapToPropertyModel(elementTypeContentModel, contentModel); @@ -271,7 +351,7 @@ }, // private - _createContent: function(elementTypeAlias) { + _createDataEntry: function(elementTypeAlias) { var content = { contentTypeAlias: elementTypeAlias, udi: udiService.create("element") @@ -280,40 +360,38 @@ return content.udi; }, // private - _getContentByUdi: function(udi) { - return this.value.data.find(entry => entry.udi === udi); + _getDataByUdi: function(udi) { + return this.value.data.find(entry => entry.udi === udi) || null; }, - - removeContent: function(entry) { - const index = this.value.data.indexOf(entry) + + removeDataByUdi: function(udi) { + const index = this.value.data.findIndex(o => o.udi === udi); if (index !== -1) { - this.value.splice(index, 1); + this.value.data.splice(index, 1); } }, - removeContentByUdi: function(udi) { - const index = this.value.data.findIndex(o => o.udi === udi); - if (index !== -1) { - this.value.splice(index, 1); + onDestroyed: function() { + + for (const key in this.isolatedScope.blockModels) { + this.destroyBlockModel(this.isolatedScope.blockModels[key]); } + + delete this.value; + delete this.propertyEditorAlias; + delete this.blockConfigurations; + delete this.scaffolds; + delete this.watchers; + delete this.isolatedScope; } } return { - createModelObject: function(propertyModelValue, propertyEditorAlias, blockConfigurations) { - return new BlockEditorModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations); + createModelObject: function(propertyModelValue, propertyEditorAlias, blockConfigurations, propertyScope) { + return new BlockEditorModelObject(propertyModelValue, propertyEditorAlias, blockConfigurations, propertyScope); }, mapElementTypeValues: mapElementTypeValues, - getBlockLabel: function(blockModel) { - - - if(blockModel.labelInterpolator) { - // We are just using the contentModel, since its a key/value object that is live synced. (if we need to add additional vars, we could make a shallow copy and apply those.) - return blockModel.labelInterpolator(blockModel.contentModel); - } - - return blockModel.contentTypeName; - } + getBlockLabel: getBlockLabel } } diff --git a/src/Umbraco.Web.UI.Client/src/views/blockelements/imageblock/imageblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/blockelements/imageblock/imageblock.editor.html index 62cd4384b8..a1977cec55 100644 --- a/src/Umbraco.Web.UI.Client/src/views/blockelements/imageblock/imageblock.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/blockelements/imageblock/imageblock.editor.html @@ -1,3 +1,3 @@ - diff --git a/src/Umbraco.Web.UI.Client/src/views/blockelements/inlineblock/inlineblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/blockelements/inlineblock/inlineblock.editor.html index 56fbe13e2b..cd7468af8d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/blockelements/inlineblock/inlineblock.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/blockelements/inlineblock/inlineblock.editor.html @@ -1,10 +1,10 @@
-
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/blockelements/labelblock/labelblock.editor.html b/src/Umbraco.Web.UI.Client/src/views/blockelements/labelblock/labelblock.editor.html index 2036e3ef3a..fecf1ea51e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/blockelements/labelblock/labelblock.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/blockelements/labelblock/labelblock.editor.html @@ -1,4 +1,4 @@ - diff --git a/src/Umbraco.Web.UI.Client/src/views/blockelements/textareablock/textareablock.editor.html b/src/Umbraco.Web.UI.Client/src/views/blockelements/textareablock/textareablock.editor.html index 80aba51a84..bbcef1643b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/blockelements/textareablock/textareablock.editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/blockelements/textareablock/textareablock.editor.html @@ -8,7 +8,7 @@ ng-model="vm.firstProperty.value" ng-keypress="vm.submitOnEnter($event)" ng-blur="vm.onBlur()" - focus-when="{{blockvm.focusThis}}" + focus-when="{{vm.moveFocusToBlock === block}}" > diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockeditor/blockeditor.block.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockeditor/blockeditor.block.component.js index c3397e9433..5945ea9b69 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockeditor/blockeditor.block.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blockeditor/blockeditor.block.component.js @@ -17,7 +17,7 @@ }); function BlockEditorBlockBlockController($scope, blockEditorService) { - + /* var unsubscribe = []; var vm = this; @@ -63,7 +63,7 @@ subscription(); } }); - + */ } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.html index 05f7fff551..07a07ee529 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.html @@ -5,7 +5,7 @@
-
+
- -
+
+
- -
- +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.js index 93773b1598..b5c42f5cf6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/blocklist.component.js @@ -51,15 +51,13 @@ vm.validationLimit = vm.model.config.validationLimit; - console.log("Model TESTS:", vm.model.value === null, typeof vm.model.value !== 'object') - // We need to ensure that the property model value is an object, this is needed for modelObject to recive a reference and keep that updated. - if(vm.model.value === null || typeof vm.model.value !== 'object') {// testing if we have null or undefined value or if the value is set to another type than Object. + if(typeof vm.model.value !== 'object' || vm.model.value === null) {// testing if we have null or undefined value or if the value is set to another type than Object. vm.model.value = {}; } // Create Model Object, to manage our data for this Block Editor. - modelObject = blockEditorService.createModelObject(vm.model.value, vm.model.editor, vm.model.config.blocks); + modelObject = blockEditorService.createModelObject(vm.model.value, vm.model.editor, vm.model.config.blocks, $scope); modelObject.loadScaffolding().then(onLoaded); copyAllBlocksAction = { @@ -161,16 +159,20 @@ function deleteBlock(block) { + var index = vm.blocks.indexOf(block); if(index !== -1) { - vm.blocks.splice(index, 1); - var layoutIndex = layout.findIndex(entry => entry.udi === block.udi); + var layoutIndex = layout.findIndex(entry => entry.udi === block.content.udi); if(layoutIndex !== -1) { - vm.layout.splice(index, 1); + layout.splice(index, 1); + } else { + throw new Error("Could not find layout entry of block with udi: "+block.content.udi) } - modelObject.removeContentByUdi(block.udi); + vm.blocks.splice(index, 1); + + modelObject.removeDataAndDestroyModel(block); } } @@ -189,6 +191,7 @@ view: "views/common/infiniteeditors/elementeditor/elementeditor.html", size: blockModel.config.overlaySize || "medium", submit: function(elementEditorModel) { + // To ensure syncronization gets tricked we transfer blockEditorService.mapElementTypeValues(elementEditorModel.content, blockModel.content) editorService.close(); }, @@ -397,7 +400,7 @@ var moveFromIndex = runtimeSortVars.moveFromIndex; var moveToIndex = ui.item.index(); - if (moveToIndex > -1 && moveFromIndex !== moveToIndex) { + if (moveToIndex !== -1 && moveFromIndex !== moveToIndex) { var movedEntry = layout[moveFromIndex]; layout.splice(moveFromIndex, 1); layout.splice(moveToIndex, 0, movedEntry); @@ -424,19 +427,19 @@ deleteAllBlocksAction.isDisabled = vm.blocks.length === 0; // validate limits: - if (vm.validationLimit.min !== null) { - if (vm.blocks.length < vm.validationLimit.min) { + if (vm.propertyForm) { + if (vm.validationLimit.min !== null && vm.blocks.length < vm.validationLimit.min) { vm.propertyForm.minCount.$setValidity("minCount", false); } else { vm.propertyForm.minCount.$setValidity("minCount", true); } - } - if (vm.validationLimit.max !== null && vm.blocks.length > vm.validationLimit.max) { - vm.propertyForm.maxCount.$setValidity("maxCount", false); - } - else { - vm.propertyForm.maxCount.$setValidity("maxCount", true); + if (vm.validationLimit.max !== null && vm.blocks.length > vm.validationLimit.max) { + vm.propertyForm.maxCount.$setValidity("maxCount", false); + } + else { + vm.propertyForm.maxCount.$setValidity("maxCount", true); + } } }