working on U4-5687 Fix issues with mini content editor that is launched from new Edit button in MNTP

This commit is contained in:
Shannon
2014-10-27 15:24:04 +10:00
parent 145229122c
commit cf444e1702
5 changed files with 202 additions and 146 deletions

View File

@@ -0,0 +1,75 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbLaunchMiniEditor
* @restrict E
* @function
* @description
* Used on a button to launch a mini content editor editor dialog
**/
angular.module("umbraco.directives")
.directive('umbLaunchMiniEditor', function (dialogService, editorState, fileManager, contentEditingHelper) {
return {
restrict: 'A',
replace: false,
scope: {
node: '=umbLaunchMiniEditor',
},
link: function(scope, element, attrs) {
element.click(function() {
//We need to store the current files selected in the file manager locally because the fileManager
// is a singleton and is shared globally. The mini dialog will also be referencing the fileManager
// and we don't want it to be sharing the same files as the main editor. So we'll store the current files locally here,
// clear them out and then launch the dialog. When the dialog closes, we'll reset the fileManager to it's previous state.
var currFiles = _.groupBy(fileManager.getFiles(), "alias");
fileManager.clearFiles();
//We need to store the original editorState entity because it will need to change when the mini editor is loaded so that
// any property editors that are working with editorState get given the correct entity, otherwise strange things will
// start happening.
var currEditorState = editorState.getCurrent();
dialogService.open({
template: "views/common/dialogs/content/edit.html",
id: scope.node.id,
closeOnSave: true,
tabFilter: ["Generic properties"],
callback: function (data) {
//set the node name back
scope.node.name = data.name;
//reset the fileManager to what it was
fileManager.clearFiles();
_.each(currFiles, function (val, key) {
fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; }));
});
//reset the editor state
editorState.set(currEditorState);
//Now we need to check if the content item that was edited was actually the same content item
// as the main content editor and if so, update all property data
if (data.id === scope.node.id) {
var changed = contentEditingHelper.reBindChangedProperties(currEditorState, data);
}
},
closeCallback: function () {
//reset the fileManager to what it was
fileManager.clearFiles();
_.each(currFiles, function (val, key) {
fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; }));
});
//reset the editor state
editorState.set(currEditorState);
}
});
});
}
};
});

View File

@@ -259,11 +259,11 @@ angular.module('umbraco.services').factory("editorState", function() {
*/
reset: function() {
current = null;
}
},
/**
* @ngdoc function
* @name umbraco.services.angularHelper#current
* @name umbraco.services.angularHelper#getCurrent
* @methodOf umbraco.services.editorState
* @function
*
@@ -276,8 +276,13 @@ angular.module('umbraco.services').factory("editorState", function() {
* editorState.current can not be overwritten, you should only read values from it
* since modifying individual properties should be handled by the property editors
*/
getCurrent: function() {
return current;
}
};
//TODO: This shouldn't be removed! use getCurrent() method instead of a hacked readonly property which is confusing.
//create a get/set property but don't allow setting
Object.defineProperty(state, "current", {
get: function () {

View File

@@ -1,4 +1,4 @@
function ContentEditDialogController($scope, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, umbModelMapper, $http) {
function ContentEditDialogController($scope, editorState, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, umbModelMapper, $http) {
$scope.defaultButton = null;
$scope.subButtons = [];
@@ -48,6 +48,12 @@ function ContentEditDialogController($scope, $routeParams, $q, $timeout, $window
});
$scope.defaultButton = buttons.defaultButton;
$scope.subButtons = buttons.subButtons;
//This is a total hack but we have really no other way of sharing data to the property editors of this
// content item, so we'll just set the property on the content item directly
$scope.content.isDialogEditor = true;
editorState.set($scope.content);
}
//check if the entity is being passed in, otherwise load it from the server

View File

@@ -3,163 +3,133 @@
angular.module('umbraco')
.controller("Umbraco.PropertyEditors.ContentPickerController",
function ($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager) {
$scope.renderModel = [];
$scope.ids = $scope.model.value ? $scope.model.value.split(',') : [];
function ($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper) {
//configuration
$scope.cfg = {
multiPicker: "0",
entityType: "Document",
filterCssClass: "not-allowed not-published",
$scope.renderModel = [];
$scope.ids = $scope.model.value ? $scope.model.value.split(',') : [];
startNode: {
query: "",
type: "content",
id: -1
}
};
$scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true;
if ($scope.model.config) {
$scope.cfg = angular.extend($scope.cfg, $scope.model.config);
}
//configuration
$scope.cfg = {
multiPicker: "0",
entityType: "Document",
filterCssClass: "not-allowed not-published",
//Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that!
$scope.cfg.multiPicker = ($scope.cfg.multiPicker === "0" ? false : true);
startNode: {
query: "",
type: "content",
id: -1
}
};
if ($scope.cfg.startNode.type === "member") {
$scope.cfg.entityType = "Member";
}
else if ($scope.cfg.startNode.type === "media") {
$scope.cfg.entityType = "Media";
}
if ($scope.model.config) {
$scope.cfg = angular.extend($scope.cfg, $scope.model.config);
}
//if we have a query for the startnode, we will use that.
if($scope.cfg.startNode.query){
var rootId = $routeParams.id;
entityResource.getByQuery($scope.cfg.startNode.query, rootId, "Document").then(function(ent){
$scope.cfg.startNodeId = ent.id;
});
}else{
$scope.cfg.startNodeId = $scope.cfg.startNode.id;
}
//Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that!
$scope.cfg.multiPicker = ($scope.cfg.multiPicker === "0" ? false : true);
$scope.cfg.callback = populate;
$scope.cfg.treeAlias = $scope.cfg.startNode.type;
$scope.cfg.section = $scope.cfg.startNode.type;
if ($scope.cfg.startNode.type === "member") {
$scope.cfg.entityType = "Member";
}
else if ($scope.cfg.startNode.type === "media") {
$scope.cfg.entityType = "Media";
}
//load current data
entityResource.getByIds($scope.ids, $scope.cfg.entityType).then(function (data) {
_.each(data, function (item, i) {
item.icon = iconHelper.convertFromLegacyIcon(item.icon);
$scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon });
});
});
//if we have a query for the startnode, we will use that.
if ($scope.cfg.startNode.query) {
var rootId = $routeParams.id;
entityResource.getByQuery($scope.cfg.startNode.query, rootId, "Document").then(function (ent) {
$scope.cfg.startNodeId = ent.id;
});
} else {
$scope.cfg.startNodeId = $scope.cfg.startNode.id;
}
$scope.cfg.callback = populate;
$scope.cfg.treeAlias = $scope.cfg.startNode.type;
$scope.cfg.section = $scope.cfg.startNode.type;
//load current data
entityResource.getByIds($scope.ids, $scope.cfg.entityType).then(function (data) {
_.each(data, function (item, i) {
item.icon = iconHelper.convertFromLegacyIcon(item.icon);
$scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon });
});
});
//dialog
$scope.openContentPicker = function () {
var d = dialogService.treePicker($scope.cfg);
};
//dialog
$scope.openContentPicker = function () {
var d = dialogService.treePicker($scope.cfg);
};
$scope.remove = function (index) {
$scope.renderModel.splice(index, 1);
};
$scope.remove = function (index) {
$scope.renderModel.splice(index, 1);
};
$scope.add = function (item) {
if ($scope.ids.indexOf(item.id) < 0) {
item.icon = iconHelper.convertFromLegacyIcon(item.icon);
$scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon });
}
};
$scope.edit = function (node) {
$scope.clear = function () {
$scope.renderModel = [];
};
//We need to store the current files selected in the file manager locally because the fileManager
//is a singleton and is shared globally. The mini dialog will also be referencing the fileManager
//and we don't want it to be sharing the same files as the main editor. So we'll store the current files locally here,
//clear them out and then launch the dialog. When the dialog closes, we'll reset the fileManager to it's previous state.
//We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required
// because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable
// occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs.
// In their source code there is no event so we need to just subscribe to our model changes here.
//This also makes it easier to manage models, we update one and the rest will just work.
$scope.$watch(function () {
//return the joined Ids as a string to watch
return _.map($scope.renderModel, function (i) {
return i.id;
}).join();
}, function (newVal) {
$scope.ids = _.map($scope.renderModel, function (i) {
return i.id;
});
$scope.model.value = trim($scope.ids.join(), ",");
var currFiles = _.groupBy(fileManager.getFiles(), "alias");
fileManager.clearFiles();
//Validate!
if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) {
$scope.contentPickerForm.minCount.$setValidity("minCount", false);
}
else {
$scope.contentPickerForm.minCount.$setValidity("minCount", true);
}
dialogService.open({
template: "views/common/dialogs/content/edit.html",
id: node.id,
closeOnSave:true,
tabFilter: ["Generic properties"],
callback: function(data) {
node.name = data.name;
//reset the fileManager to what it was
fileManager.clearFiles();
_.each(currFiles, function (val, key) {
fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; }));
});
},
closeCallback: function() {
//reset the fileManager to what it was
fileManager.clearFiles();
_.each(currFiles, function(val, key) {
fileManager.setFiles(key, _.map(currFiles['upload'], function (i) { return i.file; }));
});
}
});
};
if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) {
$scope.contentPickerForm.maxCount.$setValidity("maxCount", false);
}
else {
$scope.contentPickerForm.maxCount.$setValidity("maxCount", true);
}
});
$scope.add = function (item) {
if ($scope.ids.indexOf(item.id) < 0) {
item.icon = iconHelper.convertFromLegacyIcon(item.icon);
$scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon });
}
};
$scope.$on("formSubmitting", function (ev, args) {
$scope.model.value = trim($scope.ids.join(), ",");
});
$scope.clear = function () {
$scope.renderModel = [];
};
function trim(str, chr) {
var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
return str.replace(rgxtrim, '');
}
//We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required
// because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable
// occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs.
// In their source code there is no event so we need to just subscribe to our model changes here.
//This also makes it easier to manage models, we update one and the rest will just work.
$scope.$watch(function () {
//return the joined Ids as a string to watch
return _.map($scope.renderModel, function (i) {
return i.id;
}).join();
}, function (newVal) {
$scope.ids = _.map($scope.renderModel, function (i) {
return i.id;
});
$scope.model.value = trim($scope.ids.join(), ",");
//Validate!
if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) {
$scope.contentPickerForm.minCount.$setValidity("minCount", false);
}
else {
$scope.contentPickerForm.minCount.$setValidity("minCount", true);
}
if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) {
$scope.contentPickerForm.maxCount.$setValidity("maxCount", false);
}
else {
$scope.contentPickerForm.maxCount.$setValidity("maxCount", true);
}
});
$scope.$on("formSubmitting", function (ev, args) {
$scope.model.value = trim($scope.ids.join(), ",");
});
function trim(str, chr) {
var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
return str.replace(rgxtrim, '');
}
function populate(data) {
if (angular.isArray(data)) {
_.each(data, function (item, i) {
$scope.add(item);
});
} else {
$scope.clear();
$scope.add(data);
}
}
});
function populate(data) {
if (angular.isArray(data)) {
_.each(data, function (item, i) {
$scope.add(item);
});
} else {
$scope.clear();
$scope.add(data);
}
}
});

View File

@@ -15,8 +15,8 @@
<i class="{{node.icon}} hover-hide"></i>
{{node.name}}
</a>
<div>
<small><a href ng-click="edit(node)">Edit</a></small>
<div ng-if="!dialogEditor">
<small><a href umb-launch-mini-editor="node">Edit</a></small>
</div>
</li>
</ul>