Use <umb-node-preview> component in dynamic root nodes (#15510)

This commit is contained in:
Bjarne Fyrstenborg
2024-02-13 13:41:25 +01:00
committed by GitHub
parent 3553e7546a
commit f78c5675ea
5 changed files with 282 additions and 136 deletions

View File

@@ -85,9 +85,11 @@
@param {boolean} allowRemove (<code>binding</code>): Show/Hide the remove button.
@param {boolean} allowOpen (<code>binding</code>): Show/Hide the open button.
@param {boolean} allowEdit (<code>binding</code>): Show/Hide the edit button (Added in version 7.7.0).
@param {boolean} allowChange (<code>binding</code>): Show/Hide the change button (Added in version 10.1.0).
@param {function} onRemove (<code>expression</code>): Callback function when the remove button is clicked.
@param {function} onOpen (<code>expression</code>): Callback function when the open button is clicked.
@param {function} onEdit (<code>expression</code>): Callback function when the edit button is clicked (Added in version 7.7.0).
@param {function} onChange (<code>expression</code>): Callback function when the change button is clicked (Added in version 10.1.0).
@param {string} openUrl (<code>binding</code>): Fallback URL for <code>onOpen</code> (Added in version 7.12.0).
@param {string} editUrl (<code>binding</code>): Fallback URL for <code>onEdit</code> (Added in version 7.12.0).
@param {string} removeUrl (<code>binding</code>): Fallback URL for <code>onRemove</code> (Added in version 7.12.0).

View File

@@ -432,22 +432,21 @@ table thead button:focus{
/* UI interactions */
.ui-sortable-handle {
cursor: move;
.ui-sortable:not(.ui-sortable-disabled) .ui-sortable-handle {
cursor: move;
}
.umb-table tbody.ui-sortable tr
{
cursor:pointer;
.umb-table tbody.ui-sortable tr {
cursor: pointer;
}
.umb-table tbody.ui-sortable tr.ui-sortable-helper {
background-color: @sortableHelperBg;
border: none;
}
.umb-table tbody.ui-sortable tr.ui-sortable-helper td
{
border:none;
.umb-table tbody.ui-sortable tr.ui-sortable-helper td {
border: none;
}
.umb-table tbody.ui-sortable tr.ui-sortable-placeholder {

View File

@@ -48,7 +48,7 @@
</button>
<!-- If removeUrl has a value we render a link otherwise a button -->
<a class="umb-node-preview__action umb-node-preview__action--red"
<a class="umb-node-preview__action"
ng-href="{{changeUrl}}"
ng-if="allowChange && changeUrl"
ng-click="onChange()">
@@ -56,7 +56,7 @@
</a>
<button type="button"
class="umb-node-preview__action umb-node-preview__action--red"
class="umb-node-preview__action"
ng-if="allowChange && !changeUrl"
ng-click="onChange()">
<localize key="general_change">Change</localize><span class="sr-only">&nbsp;{{name}}</span>

View File

@@ -3,9 +3,39 @@
angular.module('umbraco')
.controller("Umbraco.PrevalueEditors.TreeSourceController",
function($scope, $timeout, entityResource, iconHelper, editorService, eventsService){
function ($scope, $filter, $timeout, $q, entityResource, angularHelper, iconHelper, editorService, eventsService, localizationService, udiService, udiParser) {
$scope.showXPath = false;
const vm = this;
vm.clear = clear;
vm.clearXPath = clearXPath;
vm.clearDynamicStartNode = clearDynamicStartNode;
vm.chooseDynamicStartNode = chooseDynamicStartNode;
vm.chooseXPath = chooseXPath;
vm.openContentPicker = openContentPicker;
vm.openDynamicRootOriginPicker = openDynamicRootOriginPicker;
vm.appendDynamicQueryStep = appendDynamicQueryStep;
vm.removeQueryStep = removeQueryStep;
vm.dynamicRootOrigin = null;
vm.querySteps = [];
vm.sortableModel = [];
vm.sortableOptionsForQuerySteps = {
axis: "y",
containment: "parent",
distance: 10,
opacity: 0.7,
tolerance: "pointer",
scroll: true,
zIndex: 6000,
update: function (e, ui) {
setDirty();
}
};
vm.showDynamicStartNode = false;
vm.showXPath = false;
if (!$scope.model) {
$scope.model = {};
@@ -21,8 +51,15 @@ angular.module('umbraco')
};
}
if($scope.model.value.id && $scope.model.value.type !== "member"){
entityResource.getById($scope.model.value.id, entityType()).then(function(item){
if ($scope.model.value.dynamicRoot && $scope.model.value.dynamicRoot.querySteps) {
vm.sortableModel = $scope.model.value.dynamicRoot.querySteps;
syncRenderModel();
}
if ($scope.model.value.id && $scope.model.value.type !== "member") {
entityResource.getById($scope.model.value.id, entityType()).then(item => {
populate(item);
});
} else {
@@ -33,7 +70,7 @@ angular.module('umbraco')
function entityType() {
var ent = "Document";
if($scope.model.value.type === "media"){
if ($scope.model.value.type === "media"){
ent = "Media";
}
else if ($scope.model.value.type === "member") {
@@ -42,8 +79,15 @@ angular.module('umbraco')
return ent;
}
$scope.openContentPicker = function() {
var treePicker = {
function setDirty() {
const currentForm = angularHelper.getCurrentForm($scope);
if (currentForm) {
currentForm.$setDirty();
}
}
function openContentPicker() {
const treePicker = {
idType: $scope.model.config.idType,
section: $scope.model.value.type,
treeAlias: $scope.model.value.type,
@@ -56,38 +100,44 @@ angular.module('umbraco')
close: function() {
editorService.close();
}
};
editorService.treePicker(treePicker);
};
};
$scope.chooseXPath = function() {
$scope.showXPath = true;
editorService.treePicker(treePicker);
}
function chooseXPath() {
vm.showXPath = true;
$scope.model.value.dynamicRoot = null;
};
$scope.chooseDynamicStartNode = function() {
$scope.showXPath = false;
}
function chooseDynamicStartNode() {
vm.showXPath = false;
$scope.model.value.dynamicRoot = {
originAlias: "Parent",
querySteps: []
};
};
}
$scope.clearXPath = function() {
function clearXPath() {
vm.showXPath = false;
$scope.model.value.query = null;
$scope.showXPath = false;
};
$scope.clearDynamicStartNode = function() {
$scope.model.value.dynamicRoot = null;
$scope.showDynamicStartNode = false;
};
}
$scope.clear = function() {
function clearDynamicStartNode() {
vm.showDynamicStartNode = false;
vm.querySteps = [];
vm.sortableModel = [];
$scope.model.value.dynamicRoot = null;
setSortingState(vm.querySteps);
}
function clear() {
$scope.model.value.id = null;
$scope.node = null;
$scope.model.value.query = null;
$scope.model.value.dynamicRoot = null;
treeSourceChanged();
};
}
function treeSourceChanged() {
eventsService.emit("treeSourceChanged", { value: $scope.model.value.type });
@@ -95,7 +145,7 @@ angular.module('umbraco')
//we always need to ensure we dont submit anything broken
var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
if($scope.model.value.type === "member") {
if ($scope.model.value.type === "member") {
$scope.model.value.id = null;
$scope.model.value.query = "";
$scope.model.value.dynamicRoot = null;
@@ -108,7 +158,7 @@ angular.module('umbraco')
});
function populate(item) {
$scope.clear();
clear();
item.icon = iconHelper.convertFromLegacyIcon(item.icon);
$scope.node = item;
$scope.node.path = "";
@@ -118,13 +168,69 @@ angular.module('umbraco')
});
}
// Dynamic Root specific
$scope.$watch("model.value.dynamicRoot", function (newVal, oldVal) {
// Dynamic Root specific:
const alias = newVal ? newVal.originAlias : null;
const originKey = newVal ? newVal.originKey : null;
if (!alias) {
vm.dynamicRootOrigin = null;
return;
}
const icon = getIconForOriginAlias(alias);
const key = `dynamicRoot_origin${alias}Title`;
vm.dynamicRootOrigin = {
alias: alias,
icon: icon
};
setSortingState(newVal.querySteps);
if (originKey) {
const lookupId = udiService.build($scope.model.value.type === 'content' ? 'document' : $scope.model.value.type, originKey);
vm.dynamicRootOrigin.description = "Loading...";
//vm.dynamicRootOrigin.description = $filter('ncNodeName')(lookupId);
const udi = udiParser.parse(lookupId);
if (udi) {
entityResource.getById(udi.value, udi.entityType).then(ent => {
vm.dynamicRootOrigin.description = ent.name;
})
}
}
localizationService.localize(key).then(data => {
vm.dynamicRootOrigin.name = data;
});
});
$scope.$watchCollection("vm.sortableModel", function (newVal, oldVal) {
if (newVal !== oldVal) {
$scope.model.value.dynamicRoot.querySteps = newVal;
syncRenderModel();
}
});
function syncRenderModel() {
const promises = [];
$scope.model.value.dynamicRoot.querySteps.forEach(x => {
promises.push(getDataForQueryStep(x));
});
$q.all(promises).then(data => {
vm.querySteps = data;
setSortingState(vm.querySteps);
});
}
$scope.dynamicRootOriginIcon = null;
$scope.$watch("model.value.dynamicRoot.originAlias", function (newVal, oldVal) {
$scope.dynamicRootOriginIcon = getIconForOriginAlias(newVal);
})
function getIconForOriginAlias(originAlias) {
switch (originAlias) {
case "Root":
@@ -139,9 +245,9 @@ angular.module('umbraco')
return "icon-wand";
}
}
$scope.getIconForQueryStepAlias = getIconForQueryStepAlias;
function getIconForQueryStepAlias(originAlias) {
switch (originAlias) {
function getIconForQueryStep(queryStep) {
switch (queryStep.alias) {
case "NearestAncestorOrSelf":
return "icon-chevron-up";
case "FurthestAncestorOrSelf":
@@ -154,32 +260,82 @@ angular.module('umbraco')
return "icon-lab";
}
$scope.sortableOptionsForQuerySteps = {
axis: "y",
containment: "parent",
distance: 10,
opacity: 0.7,
tolerance: "pointer",
scroll: true,
zIndex: 6000
};
function getNameKeyForQueryStep(queryStep) {
$scope.removeQueryStep = function (queryStep) {
var index = $scope.model.value.dynamicRoot.querySteps.indexOf(queryStep);
if(index !== -1) {
let key = "";
switch (queryStep.alias) {
case "NearestAncestorOrSelf":
case "FurthestAncestorOrSelf":
case "NearestDescendantOrSelf":
case "FurthestDescendantOrSelf":
key = `dynamicRoot_queryStep${queryStep.alias}Title`;
}
return key;
}
function getDataForQueryStep(queryStep) {
const deferred = $q.defer();
const icon = getIconForQueryStep(queryStep);
let nameKey = getNameKeyForQueryStep(queryStep);
const keys = [
nameKey,
"dynamicRoot_queryStepTypes"
];
localizationService.localizeMany(keys).then(values => {
const name = values[0] === "[]" ? queryStep.alias : values[0];
let description = null;
if (queryStep.anyOfDocTypeKeys && queryStep.anyOfDocTypeKeys.length > 0) {
description = (values[1] || "That matches types: ") + queryStep.anyOfDocTypeKeys.join(", ")
}
const obj = {
alias: queryStep.alias,
name: name,
description: description,
icon: icon
};
deferred.resolve(obj);
});
return deferred.promise;
}
function removeQueryStep(queryStep, index) {
if (index !== -1) {
$scope.model.value.dynamicRoot.querySteps.splice(index, 1);
vm.sortableModel = $scope.model.value.dynamicRoot.querySteps;
vm.querySteps.splice(index, 1);
setSortingState(vm.querySteps);
}
}
$scope.openDynamicRootOriginPicker = function() {
var originPicker = {
function setSortingState(items) {
// disable sorting if the list only consist of one item
if (items.length <= 1 || $scope.readonly) {
vm.sortableOptionsForQuerySteps.disabled = true;
} else {
vm.sortableOptionsForQuerySteps.disabled = false;
}
}
function openDynamicRootOriginPicker() {
const originPicker = {
view: "views/common/infiniteeditors/pickdynamicrootorigin/pickdynamicrootorigin.html",
contentType: $scope.model.value.type,
size: "small",
value: {...$scope.model.value.dynamicRoot},
multiPicker: false,
submit: function(model) {
$scope.model.value.dynamicRoot = model.value;
$scope.model.value.dynamicRoot = model.value;
editorService.close();
},
close: function() {
@@ -187,19 +343,32 @@ angular.module('umbraco')
}
};
editorService.open(originPicker);
};
}
$scope.appendDynamicQueryStep = function() {
var queryStepPicker = {
function appendDynamicQueryStep() {
const queryStepPicker = {
view: "views/common/infiniteeditors/pickdynamicrootquerystep/pickdynamicrootquerystep.html",
contentType: $scope.model.value.type,
size: "small",
multiPicker: false,
submit: function(model) {
if(!$scope.model.value.dynamicRoot.querySteps) {
if (!$scope.model.value.dynamicRoot.querySteps) {
$scope.model.value.dynamicRoot.querySteps = [];
}
$scope.model.value.dynamicRoot.querySteps.push(model.value);
vm.sortableModel = $scope.model.value.dynamicRoot.querySteps;
const promises = [
getDataForQueryStep(model.value)
];
$q.all(promises).then(data => {
vm.querySteps.push(data[0]);
setSortingState(vm.querySteps);
});
editorService.close();
},
close: function() {
@@ -207,5 +376,6 @@ angular.module('umbraco')
}
};
editorService.open(queryStepPicker);
};
}
});

View File

@@ -1,48 +1,49 @@
<div ng-controller="Umbraco.PrevalueEditors.TreeSourceController" class="umb-property-editor umb-contentpicker">
<div ng-controller="Umbraco.PrevalueEditors.TreeSourceController as vm" class="umb-property-editor umb-contentpicker">
<select ng-model="model.value.type" class="umb-property-editor" ng-change="clear()">
<select ng-model="model.value.type" class="umb-property-editor" ng-change="vm.clear()">
<option value="content">Content</option>
<option value="media">Media</option>
<option value="member">Members</option>
</select>
<h5 ng-if="node"><localize key="contentPicker_configurationStartNodeTitle">Root node</localize></h5>
<umb-node-preview
ng-if="node"
class="mt1"
icon="node.icon"
name="node.name"
published="node.published"
description="node.path"
allow-remove="true"
allow-edit="true"
on-remove="clear()"
on-edit="openContentPicker()">
ng-if="node"
class="mt1"
icon="node.icon"
name="node.name"
published="node.published"
description="node.path"
allow-remove="true"
allow-edit="true"
on-remove="vm.clear()"
on-edit="vm.openContentPicker()">
</umb-node-preview>
<div ng-if="!node && model.value.type === 'content'" class="mt2">
<div ng-hide="(showXPath || model.value.query) || (showDynamicStartNode || model.value.dynamicRoot)" class="flex">
<div ng-hide="(vm.showXPath || model.value.query) || (vm.showDynamicStartNode || model.value.dynamicRoot)" class="flex">
<button
type="button"
class="umb-node-preview-add"
ng-click="openContentPicker()">
ng-click="vm.openContentPicker()">
<localize key="contentPicker_defineRootNode">Pick root node</localize>
</button>
<button
type="button"
class="umb-node-preview-add"
ng-click="chooseXPath()">
ng-click="vm.chooseXPath()">
<localize key="contentPicker_defineXPathOrigin">Specify root via XPath</localize>
</button>
<button
type="button"
class="umb-node-preview-add"
ng-click="chooseDynamicStartNode()">
ng-click="vm.chooseDynamicStartNode()">
<localize key="contentPicker_defineDynamicRoot">Specify a Dynamic Root</localize>
</button>
</div>
<div ng-if="showXPath || model.value.query">
<div ng-if="vm.showXPath || model.value.query">
<h5><localize key="contentPicker_configurationXPathTitle">XPath Query</localize></h5>
@@ -85,77 +86,51 @@
</li>
<li>
<umb-icon icon="icon-delete" class="icon red"></umb-icon>
<button type="button" class="btn-link" ng-click="clearXPath()">Cancel and clear query</button>
<button type="button" class="btn-link" ng-click="vm.clearXPath()">Cancel and clear query</button>
</li>
</ul>
</div>
<div ng-if="showDynamicStartNode || model.value.dynamicRoot">
<div ng-if="vm.showDynamicStartNode || model.value.dynamicRoot">
<h5><localize key="dynamicRoot_configurationTitle">Dynamic Root Query</localize></h5>
<!-- origin -->
<div class="umb-node-preview" single>
<div class="flex">
<umb-icon class="umb-node-preview__icon" icon="{{dynamicRootOriginIcon}}"></umb-icon>
<div class="umb-node-preview__content">
<div class="umb-node-preview__name">
<!-- ng-repeat is used here to enforce re-rendering when originAlias is changed: -->
<localize ng-repeat="originAlias in [model.value.dynamicRoot.originAlias]" key="dynamicRoot_origin{{model.value.dynamicRoot.originAlias}}Title"></localize>
</div>
<div class="umb-node-preview__description" ng-if="model.value.dynamicRoot.originKey">
{{ ("umb://" + (model.value.type === 'content' ? 'document' : model.value.type) + "/" + model.value.dynamicRoot.originKey | ncNodeName)}}
</div>
</div>
</div>
<div class="umb-node-preview__actions">
<button type="button" class="umb-node-preview__action" ng-click="openDynamicRootOriginPicker()"><localize key="general_change">Change</localize></button>
</div>
</div>
<!-- Origin -->
<umb-node-preview
single
icon="vm.dynamicRootOrigin.icon"
name="vm.dynamicRootOrigin.name"
description="vm.dynamicRootOrigin.description"
allow-change="true"
allow-remove="false"
on-change="vm.openDynamicRootOriginPicker()">
</umb-node-preview>
<!-- list of query steps -->
<div ui-sortable="sortableOptionsForQuerySteps" ng-model="model.value.dynamicRoot.querySteps">
<div class="umb-node-preview" single ng-repeat="queryStep in model.value.dynamicRoot.querySteps track by $index">
<div class="flex">
<umb-icon class="umb-node-preview__icon" icon="{{getIconForQueryStepAlias(queryStep.alias)}}"></umb-icon>
<div class="umb-node-preview__content">
<div class="umb-node-preview__name">
<localize ng-if="queryStep.alias === 'NearestAncestorOrSelf' ||
queryStep.alias === 'FurthestAncestorOrSelf' ||
queryStep.alias === 'NearestDescendantOrSelf' ||
queryStep.alias === 'FurthestDescendantOrSelf'" key="dynamicRoot_queryStep{{queryStep.alias}}Title"></localize>
<span ng-if="queryStep.alias !== 'NearestAncestorOrSelf' &&
queryStep.alias !== 'FurthestAncestorOrSelf' &&
queryStep.alias !== 'NearestDescendantOrSelf' &&
queryStep.alias !== 'FurthestDescendantOrSelf'">{{queryStep.alias}}</span>
</div>
<div class="umb-node-preview__description" ng-if="queryStep.anyOfDocTypeKeys && queryStep.anyOfDocTypeKeys.length > 0">
<!-- todo: Maybe get the name to display here. -->
<localize key="dynamicRoot_queryStepTypes">of type: </localize>
<span ng-repeat="key in queryStep.anyOfDocTypeKeys track by $index">
{{ key | umbCmsJoinArray:', '}}
</span>
</div>
</div>
</div>
<div class="umb-node-preview__actions">
<button type="button" class="umb-node-preview__action" ng-click="removeQueryStep(queryStep)"><localize key="general_remove">Remove</localize></button>
</div>
</div>
<!-- List of query steps -->
<div ui-sortable="vm.sortableOptionsForQuerySteps" ng-model="vm.sortableModel" ng-if="vm.querySteps">
<umb-node-preview
ng-repeat="queryStep in vm.querySteps track by $id(queryStep)"
single
class="mt1"
icon="queryStep.icon"
name="queryStep.name"
description="queryStep.description"
allow-edit="false"
allow-remove="true"
on-remove="vm.removeQueryStep(queryStep, $index)">
</umb-node-preview>
</div>
<button
type="button"
class="umb-node-preview-add"
ng-click="appendDynamicQueryStep()">
class="umb-node-preview-add mt1"
ng-click="vm.appendDynamicQueryStep()">
<localize key="dynamicRoot_addQueryStep">Add query step</localize>
</button>
<ul class="unstyled list-icons mt3">
<li>
<umb-icon icon="icon-delete" class="icon red"></umb-icon>
<button type="button" class="btn-link" ng-click="clearDynamicStartNode()"><localize key="dynamicRoot_cancelAndClearQuery">Cancel and clear query</localize></button>
<button type="button" class="btn-link" ng-click="vm.clearDynamicStartNode()"><localize key="dynamicRoot_cancelAndClearQuery">Cancel and clear query</localize></button>
</li>
</ul>
</div>