use an isolated scope

This commit is contained in:
Niels Lyngsø
2020-03-06 14:01:15 +01:00
parent 790430f2bb
commit 83c76cdd87
8 changed files with 145 additions and 64 deletions
@@ -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
}
}
@@ -1,3 +1,3 @@
<button type="button" class="btn-reset umb-outline blockelement-imageblock-editor blockelement__draggable-element" focus-when="{{blockvm.focusThis}}" ng-click="vm.blockEditorApi.editBlock(blockItem)">
<img src="{{blockItem.content.temp_image}}">
<button type="button" class="btn-reset umb-outline blockelement-imageblock-editor blockelement__draggable-element" focus-when="{{vm.moveFocusToBlock === block}}" ng-click="vm.blockEditorApi.editBlock(block)">
<img src="{{block.content.temp_image}}">
</button>
@@ -1,10 +1,10 @@
<div class="blockelement-inlineblock-editor" ng-class="{'--open': bc.isOpen}" ng-controller="Umbraco.PropertyEditors.BlockEditor.InlineBlockEditor as bc">
<button type="button" class="btn-reset umb-outline blockelement__draggable-element" ng-click="bc.openBlock(blockItem)" focus-when="{{blockvm.focusThis}}">
<button type="button" class="btn-reset umb-outline blockelement__draggable-element" ng-click="bc.openBlock(block)" focus-when="{{vm.moveFocusToBlock === block}}">
<span class="caret"></span>
<i class="icon {{blockItem.content.icon}}"></i>
<span>{{blockItem.label}}</span>
<i class="icon {{block.content.icon}}"></i>
<span>{{block.label}}</span>
</button>
<div class="blockelement-inlineblock-editor__inner" ng-if="bc.isOpen === true">
<umb-element-content-editor content="blockItem.content"></umb-element-content-editor>
<umb-element-content-editor content="block.content"></umb-element-content-editor>
</div>
</div>
@@ -1,4 +1,4 @@
<button type="button" class="btn-reset umb-outline blockelement-labelblock-editor blockelement__draggable-element" ng-click="vm.blockEditorApi.editBlock(blockItem)" focus-when="{{vm.focusThis}}">
<i class="icon {{blockItem.content.icon}}"></i>
<span>{{blockItem.label}}</span>
<button type="button" class="btn-reset umb-outline blockelement-labelblock-editor blockelement__draggable-element" ng-click="vm.blockEditorApi.editBlock(block)" focus-when="{{vm.moveFocusToBlock === block}}">
<i class="icon {{block.content.icon}}"></i>
<span>{{block.label}}</span>
</button>
@@ -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}}"
>
</textarea>
@@ -17,7 +17,7 @@
});
function BlockEditorBlockBlockController($scope, blockEditorService) {
/*
var unsubscribe = [];
var vm = this;
@@ -63,7 +63,7 @@
subscription();
}
});
*/
}
@@ -5,7 +5,7 @@
<div ui-sortable="vm.sortableOptions" ng-model="vm.blocks">
<div ng-repeat="blockItem in vm.blocks track by blockItem.key">
<div ng-repeat="block in vm.blocks track by block.key">
<button
type="button"
@@ -14,25 +14,25 @@
>
</button>
<block-editor-block block="blockItem" focus-this-block="vm.moveFocusToBlock === blockItem" block-editor-api="vm.blockEditorApi" show-copy="vm.showCopy" class="umb-block-list__block">
<div class="umb-block-list__block--content" ng-if="blockItem.view" ng-include="blockItem.view">
<div class="umb-block-list__block">
<div class="umb-block-list__block--content" ng-if="block.view" ng-include="block.view">
</div>
<div class="umb-block-list__block--actions">
<button type="button" class="btn-reset action --copy" localize="title" title="actions_copy" ng-click="vm.blockEditorApi.requestCopyBlock(blockItem);" ng-if="vm.showCopy">
<button type="button" class="btn-reset action --copy" localize="title" title="actions_copy" ng-click="vm.blockEditorApi.requestCopyBlock(block);" ng-if="vm.showCopy">
<i class="icon icon-documents" aria-hidden="true"></i>
<span class="sr-only">
<localize key="general_copy">Copy</localize>
</span>
</button>
<button type="button" class="btn-reset action --delete" localize="title" title="actions_delete" ng-click="vm.blockEditorApi.requestDeleteBlock(blockItem);">
<button type="button" class="btn-reset action --delete" localize="title" title="actions_delete" ng-click="vm.blockEditorApi.requestDeleteBlock(block);">
<i class="icon icon-trash" aria-hidden="true"></i>
<span class="sr-only">
<localize key="general_delete">Delete</localize>
</span>
</button>
</div>
</block-editor-block>
</div>
</div>
</div>
@@ -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);
}
}
}