Merge branch '7.0.0' of https://github.com/umbraco/Umbraco-CMS into 7.0.0

This commit is contained in:
perploug
2013-11-15 08:35:53 +01:00
51 changed files with 968 additions and 593 deletions

View File

@@ -0,0 +1,14 @@
/**
* Brazilian translation for bootstrap-datetimepicker
* Cauan Cabral <cauan@radig.com.br>
*/
; (function ($) {
$.fn.datetimepicker.dates['pt-BR'] = {
days: ["Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado", "Domingo"],
daysShort: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"],
daysMin: ["Do", "Se", "Te", "Qu", "Qu", "Se", "Sa", "Do"],
months: ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"],
monthsShort: ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"],
today: "Hoje"
};
}(jQuery));

View File

@@ -16,7 +16,8 @@ function appState($rootScope) {
touchDevice: null,
showTray: null,
stickyNavigation: null,
navMode: null
navMode: null,
editingEntity: null
};
var sectionState = {

View File

@@ -2,11 +2,13 @@
/**
* @ngdoc service
* @name umbraco.services.contentEditingHelper
* @description A helper service for content/media/member controllers when editing/creating/saving content.
* @description A helper service for most editors, some methods are specific to content/media/member model types but most are used by
* all editors to share logic and reduce the amount of replicated code among editors.
**/
function contentEditingHelper($location, $routeParams, notificationsService, serverValidationManager, dialogService, formHelper) {
function contentEditingHelper($location, $routeParams, notificationsService, serverValidationManager, dialogService, formHelper, appState) {
return {
/**
* @ngdoc method
@@ -36,15 +38,15 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser
* @function
*
* @description
* re-binds all changed property values to the origContent object from the newContent object and returns an array of changed properties.
* re-binds all changed property values to the origContent object from the savedContent object and returns an array of changed properties.
*/
reBindChangedProperties: function (origContent, newContent) {
reBindChangedProperties: function (origContent, savedContent) {
var changed = [];
//get a list of properties since they are contained in tabs
var allOrigProps = this.getAllProps(origContent);
var allNewProps = this.getAllProps(newContent);
var allNewProps = this.getAllProps(savedContent);
function getNewProp(alias) {
return _.find(allNewProps, function (item) {
@@ -66,8 +68,8 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser
continue;
}
if (!_.isEqual(origContent[o], newContent[o])) {
origContent[o] = newContent[o];
if (!_.isEqual(origContent[o], savedContent[o])) {
origContent[o] = savedContent[o];
}
}
@@ -109,9 +111,6 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser
if (!args.err) {
throw "args.err cannot be null";
}
if (!args.allNewProps && !angular.isArray(args.allNewProps)) {
throw "args.allNewProps must be a valid array";
}
if (args.redirectOnFailure === undefined || args.redirectOnFailure === null) {
throw "args.redirectOnFailure must be set to true or false";
}
@@ -168,11 +167,11 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser
if (!args) {
throw "args cannot be null";
}
if (!args.newContent) {
throw "args.newContent cannot be null";
if (!args.savedContent) {
throw "args.savedContent cannot be null";
}
if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.newContent.id)) {
if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.savedContent.id)) {
//we are not redirecting because this is not new content, it is existing content. In this case
// we need to detect what properties have changed and re-bind them with the server data.

View File

@@ -1,2 +0,0 @@
angular.module("umbraco.services")
.value('editorContext', undefined);

View File

@@ -176,15 +176,11 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
appState.setGlobalState("showTray", false);
},
//adding this to get clean global access to the main tree directive
//there will only ever be one main tree event handler
//we need to pass in the current scope for binding these actions
//TODO: How many places are we assigning a currentNode?? Now we're assigning a currentNode arbitrarily to this
// scope - which looks to be the scope of the navigation controller - but then we are assigning a global current
// node on the ui object?? This is a mess.
setupTreeEvents: function(treeEventHandler, scope) {
/**
Called to assign the main tree event handler - this is called by the navigation controller.
TODO: Potentially another dev could call this which would kind of mung the whole app so potentially there's a better way.
*/
setupTreeEvents: function(treeEventHandler) {
mainTreeEventHandler = treeEventHandler;
//when a tree is loaded into a section, we need to put it into appState
@@ -199,12 +195,6 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
//set the current selected node
appState.setTreeState("selectedNode", args.node);
}
//TODO: what the heck is going on here? - this seems really zany, allowing us to modify the
// navigationController.scope from within the navigationService to assign back to the args
// so that we can change the navigationController.scope from within the umbTree directive. Hrm.
args.scope = scope;
});
//this reacts to the options item in the tree
@@ -215,11 +205,6 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
//Set the current action node (this is not the same as the current selected node!)
appState.setMenuState("currentNode", args.node);
//TODO: what the heck is going on here? - this seems really zany, allowing us to modify the
// navigationController.scope from within the navigationService to assign back to the args
// so that we can change the navigationController.scope from within the umbTree directive. Hrm.
args.scope = scope;
if (args.event && args.event.altKey) {
args.skipDefault = true;
}
@@ -231,11 +216,6 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
ev.stopPropagation();
ev.preventDefault();
//TODO: what the heck is going on here? - this seems really zany, allowing us to modify the
// navigationController.scope from within the navigationService to assign back to the args
// so that we can change the navigationController.scope from within the umbTree directive. Hrm.
args.scope = scope;
args.skipDefault = true;
service.showMenu(ev, args);
});
@@ -457,8 +437,19 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
setMode("tree");
},
/** Executes a given menu action */
executeMenuAction: function (action, node, section) {
if (!action) {
throw "action cannot be null";
}
if (!node) {
throw "node cannot be null";
}
if (!section) {
throw "section cannot be null";
}
if (action.metaData && action.metaData["jsAction"] && angular.isString(action.metaData["jsAction"])) {
//we'll try to get the jsAction from the injector
@@ -471,12 +462,12 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
eval(js);
}
else {
var _service = $injector.get(menuAction[0]);
if (!_service) {
var menuActionService = $injector.get(menuAction[0]);
if (!menuActionService) {
throw "The angular service " + menuAction[0] + " could not be found";
}
var method = _service[menuAction[1]];
var method = menuActionService[menuAction[1]];
if (!method) {
throw "The method " + menuAction[1] + " on the angular service " + menuAction[0] + " could not be found";

View File

@@ -227,12 +227,13 @@ function treeService($q, treeResource, iconHelper, notificationsService, $rootSc
return this.getChildren(args)
.then(function(data) {
//set state to done and expand
//set state to done and expand (only if there actually are children!)
args.node.loading = false;
args.node.children = data;
args.node.expanded = true;
args.node.hasChildren = true;
if (args.node.children && args.node.children.length > 0) {
args.node.expanded = true;
args.node.hasChildren = true;
}
return data;
}, function(reason) {

View File

@@ -70,4 +70,15 @@ iframe, .content-column-body {
.legacy-custom-file{
width: 16px; height: 16px; margin-right: 11px; display: inline-block;
background-position: center center;
}
/*
missing icon names in helveticons that are in font-awesome - used by the datepicker,
basically making them equivalent to their helviton icon
*/
.icon-chevron-up:before {
content: "\e128";
}
.icon-chevron-down:before {
content: "\e0c9";
}

View File

@@ -14,7 +14,7 @@ function NavigationController($scope, $rootScope, $location, $log, $routeParams,
//TODO: Need to think about this and an nicer way to acheive what this is doing.
//the tree event handler i used to subscribe to the main tree click events
$scope.treeEventHandler = $({});
navigationService.setupTreeEvents($scope.treeEventHandler, $scope);
navigationService.setupTreeEvents($scope.treeEventHandler);
//Put the navigation service on this scope so we can use it's methods/properties in the view.
// IMPORTANT: all properties assigned to this scope are generally available on the scope object on dialogs since

View File

@@ -6,7 +6,7 @@
* @description
* The controller for the content editor
*/
function ContentEditController($scope, $routeParams, $q, $timeout, $window, appState, contentResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, editorContext, treeService, fileManager, formHelper, umbRequestHelper, keyboardService) {
function ContentEditController($scope, $routeParams, $q, $timeout, $window, appState, contentResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper) {
//setup scope vars
$scope.defaultButton = null;
@@ -15,9 +15,6 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS
$scope.currentSection = appState.getSectionState("currentSection");
$scope.currentNode = null; //the editors affiliated node
//we need this to share our content object with property editors
editorContext = $scope.content;
//This sets up the action buttons based on what permissions the user has.
//The allowedActions parameter contains a list of chars, each represents a button by permission so
//here we'll build the buttons according to the chars of the user.
@@ -123,11 +120,15 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS
contentEditingHelper.handleSuccessfulSave({
scope: $scope,
newContent: data,
savedContent: data,
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
});
//update appState
appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content));
configureButtons(data);
navigationService.syncTree({ tree: "content", path: data.path.split(","), forceReload: true }).then(function (syncArgs) {
$scope.currentNode = syncArgs.node;
});
@@ -139,10 +140,12 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS
contentEditingHelper.handleSaveError({
redirectOnFailure: true,
err: err,
allNewProps: contentEditingHelper.getAllProps(err.data),
allOrigProps: contentEditingHelper.getAllProps($scope.content)
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
});
//update appState
appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content));
deferred.reject(err);
});
}
@@ -159,6 +162,8 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS
.then(function(data) {
$scope.loaded = true;
$scope.content = data;
//put this into appState
appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content));
configureButtons($scope.content);
});
}
@@ -168,6 +173,8 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS
.then(function(data) {
$scope.loaded = true;
$scope.content = data;
//put this into appState
appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content));
configureButtons($scope.content);
//in one particular special case, after we've created a new item we redirect back to the edit
@@ -194,7 +201,7 @@ function ContentEditController($scope, $routeParams, $q, $timeout, $window, appS
contentEditingHelper.handleSuccessfulSave({
scope: $scope,
newContent: data,
savedContent: data,
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
});

View File

@@ -45,8 +45,8 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig
$scope.preValues = [];
if ($routeParams.create) {
//we are creating so get an empty content item
dataTypeResource.getScaffold($routeParams.id, $routeParams.doctype)
//we are creating so get an empty data type item
dataTypeResource.getScaffold($routeParams.id)
.then(function(data) {
$scope.loaded = true;
$scope.preValuesLoaded = true;
@@ -101,7 +101,7 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig
contentEditingHelper.handleSuccessfulSave({
scope: $scope,
newContent: data,
savedContent: data,
rebindCallback: function() {
createPreValueProps(data.preValues);
}
@@ -117,9 +117,7 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig
// to be the same thing since that only really matters for content/media.
contentEditingHelper.handleSaveError({
redirectOnFailure: false,
err: err,
allNewProps: $scope.preValues,
allOrigProps: $scope.preValues
err: err
});
});
}

View File

@@ -6,7 +6,7 @@
* @description
* The controller for the media editor
*/
function mediaEditController($scope, $routeParams, appState, mediaResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, treeService, formHelper) {
function mediaEditController($scope, $routeParams, appState, mediaResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, treeService, formHelper, umbModelMapper) {
//setup scope vars
$scope.nav = navigationService;
@@ -19,7 +19,8 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, navi
.then(function (data) {
$scope.loaded = true;
$scope.content = data;
//put this into appState
appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content));
});
}
else {
@@ -27,7 +28,9 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, navi
.then(function (data) {
$scope.loaded = true;
$scope.content = data;
//put this into appState
appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content));
//in one particular special case, after we've created a new item we redirect back to the edit
// route but there might be server validation errors in the collection which we need to display
// after the redirect, so we will bind all subscriptions which will show the server validation errors
@@ -52,10 +55,13 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, navi
contentEditingHelper.handleSuccessfulSave({
scope: $scope,
newContent: data,
savedContent: data,
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
});
//update appState
appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content));
navigationService.syncTree({ tree: "media", path: data.path, forceReload: true }).then(function (syncArgs) {
$scope.currentNode = syncArgs.node;
});
@@ -65,9 +71,11 @@ function mediaEditController($scope, $routeParams, appState, mediaResource, navi
contentEditingHelper.handleSaveError({
err: err,
redirectOnFailure: true,
allNewProps: contentEditingHelper.getAllProps(err.data),
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
});
//update appState
appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content));
});
}

View File

@@ -6,19 +6,29 @@
* @description
* The controller for the member editor
*/
function MemberEditController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, treeService) {
function MemberEditController($scope, $routeParams, $location, $q, $window, appState, memberResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, fileManager, formHelper, umbModelMapper) {
//setup scope vars
$scope.nav = navigationService;
$scope.currentSection = appState.getSectionState("currentSection");
$scope.currentNode = null; //the editors affiliated node
//build a path to sync the tree with
function buildTreePath(data) {
//TODO: Will this work for the 'other' list ?
var path = data.name[0] + "," + data.key;
path = path.replace(/-/g, '');
return path;
}
if ($routeParams.create) {
//we are creating so get an empty member item
memberResource.getScaffold($routeParams.doctype)
.then(function(data) {
$scope.loaded = true;
$scope.content = data;
//put this into appState
appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content));
});
}
else {
@@ -40,9 +50,10 @@ function MemberEditController($scope, $routeParams, $location, $q, $window, appS
$scope.loaded = true;
$scope.content = data;
//build a path to sync the tree with
var path = data.name[0]+"," + data.key;
path = path.replace(/-/g,'');
//put this into appState
appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content));
var path = buildTreePath(data);
navigationService.syncTree({ tree: "member", path: path.split(",") }).then(function (syncArgs) {
$scope.currentNode = syncArgs.node;
@@ -69,13 +80,18 @@ function MemberEditController($scope, $routeParams, $location, $q, $window, appS
contentEditingHelper.handleSuccessfulSave({
scope: $scope,
newContent: data,
savedContent: data,
//specify a custom id to redirect to since we want to use the GUID
redirectId: data.key,
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
});
navigationService.syncTree({ tree: "member", path: path.split(",") }).then(function (syncArgs) {
//update appState
appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content));
var path = buildTreePath(data);
navigationService.syncTree({ tree: "member", path: path.split(","), forceReload: true }).then(function (syncArgs) {
$scope.currentNode = syncArgs.node;
});
@@ -84,9 +100,11 @@ function MemberEditController($scope, $routeParams, $location, $q, $window, appS
contentEditingHelper.handleSaveError({
redirectOnFailure: false,
err: err,
allNewProps: contentEditingHelper.getAllProps(err.data),
allOrigProps: contentEditingHelper.getAllProps($scope.content)
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
});
//update appState
appState.setGlobalState("editingEntity", umbModelMapper.convertToEntityBasic($scope.content));
});
}

View File

@@ -1,54 +1,67 @@
angular.module("umbraco").controller("Umbraco.Editors.DatepickerController",
function ($scope, notificationsService, assetsService, userService) {
function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService) {
function applyDate(e){
// when a date is changed, update the model
if (e.localDate) {
if ($scope.model.config.format == "yyyy-MM-dd HH:mm:ss") {
$scope.$apply(function(){
$scope.model.value = e.localDate.toIsoDateTimeString();
});
}else{
$scope.model.value = e.localDate.toIsoDateString();
}
//lists the custom language files that we currently support
var customLangs = ["pt-BR"];
//setup the default config
var config = {
pickDate: true,
pickTime: true,
pick12HourFormat: false,
format: "yyyy-MM-dd hh:mm:ss"
};
//map the user config
$scope.model.config = angular.extend(config, $scope.model.config);
//handles the date changing via the api
function applyDate(e) {
angularHelper.safeApply($scope, function() {
// when a date is changed, update the model
if (e.localDate) {
if ($scope.model.config.format == "yyyy-MM-dd hh:mm:ss") {
$scope.model.value = e.localDate.toIsoDateTimeString();
}
}
else {
$scope.model.value = e.localDate.toIsoDateString();
}
}
});
}
function initEditor(){
// Get the id of the datepicker button that was clicked
var pickerId = $scope.model.alias;
// Open the datepicker and add a changeDate eventlistener
$("#" + pickerId)
.datetimepicker($scope.model.config)
.on("changeDate", applyDate)
.on("hide", applyDate);
//get the current user to see if we can localize this picker
userService.getCurrentUser().then(function (user) {
var filesToLoad = ["lib/datetimepicker/bootstrap-datetimepicker.min.js"];
//if we support this custom culture, set it, then we'll need to load in that lang file
if (_.contains(customLangs, user.locale)) {
$scope.model.config.language = user.locale;
filesToLoad.push("lib/datetimepicker/langs/datetimepicker." + user.locale + ".js");
}
userService.getCurrentUser().then(function(user){
assetsService.load(filesToLoad).then(
function() {
//The Datepicker js and css files are available and all components are ready to use.
//setup the default config
var config = {
pickDate: true,
pickTime: true,
language: user.locale,
format: "yyyy-MM-dd HH:mm:ss"
};
// Get the id of the datepicker button that was clicked
var pickerId = $scope.model.alias;
// Open the datepicker and add a changeDate eventlistener
$("#datepicker" + pickerId)
//.datetimepicker(config);
.datetimepicker($scope.model.config)
.on("changeDate", applyDate);
//format:"yyyy-MM-dd HH:mm:ss"
//now assign the date
$("#datepicker" + pickerId).val($scope.model.value);
//map the user config
angular.extend(config, $scope.model.config);
//map back to the model
$scope.model.config = config;
$scope.model.viewvalue = $scope.model.value;
});
assetsService.loadJs(
'views/propertyeditors/datepicker/bootstrap-datetimepicker.js'
).then(initEditor);
});
assetsService.loadCss(
'views/propertyeditors/datepicker/bootstrap-datetimepicker.min.css'
);
});
});
assetsService.loadCss(
'views/propertyeditors/datepicker/bootstrap-datetimepicker.min.css'
);
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.DatepickerController", dateTimePickerController);

View File

@@ -1,7 +1,7 @@
<div class="umb-editor umb-datepicker" ng-controller="Umbraco.Editors.DatepickerController">
<div class="input-append date datepicker" style="position: relative;" id="{{model.alias}}">
<div class="umb-editor umb-datepicker" ng-controller="Umbraco.PropertyEditors.DatepickerController">
<div class="input-append date datepicker" style="position: relative;" id="datepicker{{model.alias}}">
<input name="datepicker" data-format="{{model.config.format}}" type="text"
ng-model="model.viewvalue"
ng-model="model.value"
val-server="value" />
<span class="add-on">
<i data-time-icon="icon-time" data-date-icon="icon-calendar"></i>

View File

@@ -1,213 +1,244 @@
angular.module("umbraco")
.controller("Umbraco.PropertyEditors.ListViewController",
function ($rootScope, $scope, $routeParams, contentResource, contentTypeResource, notificationsService, iconHelper, dialogService) {
function listViewController($rootScope, $scope, $routeParams, $injector, notificationsService, iconHelper, dialogService) {
$scope.actionInProgress = false;
$scope.listViewResultSet = {
totalPages: 0,
items: []
};
//this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content
// that isn't created yet, if we continue this will use the parent id in the route params which isn't what
// we want. NOTE: This is just a safety check since when we scaffold an empty model on the server we remove
// the list view tab entirely when it's new.
if ($routeParams.create) {
$scope.isNew = true;
return;
}
$scope.options = {
pageSize: 10,
pageNumber: 1,
filter: '',
orderBy: 'Id',
orderDirection: "desc"
};
//Now we need to check if this is for media or content because that will depend on the resources we use
var contentResource, contentTypeResource;
if ($scope.model.config.entityType && $scope.model.config.entityType === "media") {
contentResource = $injector.get('mediaResource');
contentTypeResource = $injector.get('mediaTypeResource');
$scope.entityType = "media";
}
else {
contentResource = $injector.get('contentResource');
contentTypeResource = $injector.get('contentTypeResource');
$scope.entityType = "content";
}
$scope.isNew = false;
$scope.actionInProgress = false;
$scope.listViewResultSet = {
totalPages: 0,
items: []
};
$scope.options = {
pageSize: 10,
pageNumber: 1,
filter: '',
orderBy: 'Id',
orderDirection: "desc"
};
$scope.next = function () {
if ($scope.options.pageNumber < $scope.listViewResultSet.totalPages) {
$scope.options.pageNumber++;
$scope.reloadView($scope.contentId);
}
};
$scope.next = function () {
if ($scope.options.pageNumber < $scope.listViewResultSet.totalPages) {
$scope.options.pageNumber++;
$scope.reloadView($scope.contentId);
}
};
$scope.goToPage = function (pageNumber) {
$scope.options.pageNumber = pageNumber + 1;
$scope.reloadView($scope.contentId);
};
$scope.goToPage = function (pageNumber) {
$scope.options.pageNumber = pageNumber + 1;
$scope.reloadView($scope.contentId);
};
$scope.sort = function (field) {
$scope.sort = function (field) {
$scope.options.orderBy = field;
$scope.options.orderBy = field;
if ($scope.options.orderDirection === "desc") {
$scope.options.orderDirection = "asc";
} else {
$scope.options.orderDirection = "desc";
}
if ($scope.options.orderDirection === "desc") {
$scope.options.orderDirection = "asc";
} else {
$scope.options.orderDirection = "desc";
}
$scope.reloadView($scope.contentId);
};
$scope.reloadView($scope.contentId);
};
$scope.prev = function () {
if ($scope.options.pageNumber > 1) {
$scope.options.pageNumber--;
$scope.reloadView($scope.contentId);
}
};
$scope.prev = function () {
if ($scope.options.pageNumber > 1) {
$scope.options.pageNumber--;
$scope.reloadView($scope.contentId);
}
};
/*Loads the search results, based on parameters set in prev,next,sort and so on*/
/*Pagination is done by an array of objects, due angularJS's funky way of monitoring state
with simple values */
/*Loads the search results, based on parameters set in prev,next,sort and so on*/
/*Pagination is done by an array of objects, due angularJS's funky way of monitoring state
with simple values */
$scope.reloadView = function (id) {
contentResource.getChildren(id, $scope.options).then(function (data) {
$scope.reloadView = function (id) {
contentResource.getChildren(id, $scope.options).then(function (data) {
$scope.listViewResultSet = data;
$scope.pagination = [];
$scope.listViewResultSet = data;
$scope.pagination = [];
for (var i = $scope.listViewResultSet.totalPages - 1; i >= 0; i--) {
$scope.pagination[i] = { index: i, name: i + 1 };
}
if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) {
$scope.options.pageNumber = $scope.listViewResultSet.totalPages;
}
});
};
$scope.selectAll = function ($event) {
var checkbox = $event.target;
for (var i = 0; i < $scope.listViewResultSet.items.length; i++) {
var entity = $scope.listViewResultSet.items[i];
entity.selected = checkbox.checked;
}
};
$scope.isSelectedAll = function () {
return _.every($scope.listViewResultSet.items, function (item) {
return item.selected;
});
};
$scope.isAnythingSelected = function () {
return _.some($scope.listViewResultSet.items, function (item) {
return item.selected;
});
};
$scope.getIcon = function (entry) {
return iconHelper.convertFromLegacyIcon(entry.icon);
};
$scope.delete = function () {
var selected = _.filter($scope.listViewResultSet.items, function (item) {
return item.selected;
});
var total = selected.length;
if (total === 0) {
return;
}
if (confirm("Sure you want to delete?") == true) {
$scope.actionInProgress = true;
$scope.bulkStatus = "Starting with delete";
var current = 1;
for (var i = 0; i < selected.length; i++) {
$scope.bulkStatus = "Deleted doc " + current + " out of " + total + " documents";
contentResource.deleteById(selected[i].id).then(function (data) {
if (current === total) {
notificationsService.success("Bulk action", "Deleted " + total + "documents");
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
}
current++;
});
}
}
};
$scope.publish = function () {
var selected = _.filter($scope.listViewResultSet.items, function (item) {
return item.selected;
});
var total = selected.length;
if (total === 0) {
return;
}
$scope.actionInProgress = true;
$scope.bulkStatus = "Starting with publish";
var current = 1;
for (var i = 0; i < selected.length; i++) {
$scope.bulkStatus = "Publishing " + current + " out of " + total + " documents";
contentResource.publishById(selected[i].id)
.then(function (content) {
if (current == total) {
notificationsService.success("Bulk action", "Published " + total + "documents");
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
}
current++;
}, function (err) {
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
//if there are validation errors for publishing then we need to show them
if (err.status === 400 && err.data && err.data.Message) {
notificationsService.error("Publish error", err.data.Message);
}
else {
dialogService.ysodDialog(err);
}
});
}
};
$scope.unpublish = function () {
var selected = _.filter($scope.listViewResultSet.items, function (item) {
return item.selected;
});
var total = selected.length;
if (total === 0) {
return;
}
$scope.actionInProgress = true;
$scope.bulkStatus = "Starting with publish";
var current = 1;
for (var i = 0; i < selected.length; i++) {
$scope.bulkStatus = "Unpublishing " + current + " out of " + total + " documents";
contentResource.unPublish(selected[i].id)
.then(function (content) {
if (current == total) {
notificationsService.success("Bulk action", "Published " + total + "documents");
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
}
current++;
});
}
};
if ($routeParams.id) {
$scope.pagination = new Array(10);
$scope.listViewAllowedTypes = contentTypeResource.getAllowedTypes($routeParams.id);
$scope.reloadView($routeParams.id);
$scope.contentId = $routeParams.id;
for (var i = $scope.listViewResultSet.totalPages - 1; i >= 0; i--) {
$scope.pagination[i] = { index: i, name: i + 1 };
}
if ($scope.options.pageNumber > $scope.listViewResultSet.totalPages) {
$scope.options.pageNumber = $scope.listViewResultSet.totalPages;
}
});
};
$scope.selectAll = function ($event) {
var checkbox = $event.target;
if (!angular.isArray($scope.listViewResultSet.items)) {
return;
}
for (var i = 0; i < $scope.listViewResultSet.items.length; i++) {
var entity = $scope.listViewResultSet.items[i];
entity.selected = checkbox.checked;
}
};
$scope.isSelectedAll = function () {
if (!angular.isArray($scope.listViewResultSet.items)) {
return false;
}
return _.every($scope.listViewResultSet.items, function (item) {
return item.selected;
});
};
$scope.isAnythingSelected = function () {
if (!angular.isArray($scope.listViewResultSet.items)) {
return false;
}
return _.some($scope.listViewResultSet.items, function (item) {
return item.selected;
});
};
$scope.getIcon = function (entry) {
return iconHelper.convertFromLegacyIcon(entry.icon);
};
$scope.delete = function () {
var selected = _.filter($scope.listViewResultSet.items, function (item) {
return item.selected;
});
var total = selected.length;
if (total === 0) {
return;
}
if (confirm("Sure you want to delete?") == true) {
$scope.actionInProgress = true;
$scope.bulkStatus = "Starting with delete";
var current = 1;
for (var i = 0; i < selected.length; i++) {
$scope.bulkStatus = "Deleted doc " + current + " out of " + total + " documents";
contentResource.deleteById(selected[i].id).then(function (data) {
if (current === total) {
notificationsService.success("Bulk action", "Deleted " + total + "documents");
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
}
current++;
});
}
}
};
$scope.publish = function () {
var selected = _.filter($scope.listViewResultSet.items, function (item) {
return item.selected;
});
var total = selected.length;
if (total === 0) {
return;
}
$scope.actionInProgress = true;
$scope.bulkStatus = "Starting with publish";
var current = 1;
for (var i = 0; i < selected.length; i++) {
$scope.bulkStatus = "Publishing " + current + " out of " + total + " documents";
contentResource.publishById(selected[i].id)
.then(function (content) {
if (current == total) {
notificationsService.success("Bulk action", "Published " + total + "documents");
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
}
current++;
}, function (err) {
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
//if there are validation errors for publishing then we need to show them
if (err.status === 400 && err.data && err.data.Message) {
notificationsService.error("Publish error", err.data.Message);
}
else {
dialogService.ysodDialog(err);
}
});
}
};
$scope.unpublish = function () {
var selected = _.filter($scope.listViewResultSet.items, function (item) {
return item.selected;
});
var total = selected.length;
if (total === 0) {
return;
}
$scope.actionInProgress = true;
$scope.bulkStatus = "Starting with publish";
var current = 1;
for (var i = 0; i < selected.length; i++) {
$scope.bulkStatus = "Unpublishing " + current + " out of " + total + " documents";
contentResource.unPublish(selected[i].id)
.then(function (content) {
if (current == total) {
notificationsService.success("Bulk action", "Published " + total + "documents");
$scope.bulkStatus = "";
$scope.reloadView($scope.contentId);
$scope.actionInProgress = false;
}
current++;
});
}
};
if ($routeParams.id) {
$scope.pagination = new Array(10);
$scope.listViewAllowedTypes = contentTypeResource.getAllowedTypes($routeParams.id);
$scope.reloadView($routeParams.id);
$scope.contentId = $routeParams.id;
}
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.ListViewController", listViewController);

View File

@@ -1,103 +1,114 @@
<div class="umb-editor umb-listview" ng-controller="Umbraco.PropertyEditors.ListViewController">
<div class="row-fluid">
<div class="umb-sub-header">
<div class="btn-group" ng-show="listViewAllowedTypes">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<localize key="actions_create">Create</localize>
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li ng-repeat="contentType in listViewAllowedTypes">
<a
href="#/content/content/edit/{{contentId}}?doctype={{contentType.alias}}&create=true">
<i class="icon-{{contentType.cssClass}}"></i>
{{contentType.name}}
</a>
</li>
</ul>
</div>
<div class="btn-group" ng-show="isAnythingSelected()">
<a class="btn btn-success" ng-disabled="actionInProgress" ng-click="publish()" prevent-default>
<localize key="actions_publish">Publish</localize></a>
</div>
<div class="btn-group" ng-show="isAnythingSelected()">
<a class="btn btn-warning" ng-disabled="actionInProgress" ng-click="unpublish()" prevent-default>
<localize key="actions_unpublish">Unpublish</localize>
</a>
</div>
<div class="btn-group" ng-show="isAnythingSelected()">
<a class="btn btn-danger" ng-disabled="actionInProgress" ng-click="delete()" prevent-default>
<localize key="actions_delete">Delete</localize>
</a>
</div>
<span ng-bind="bulkStatus" ng-show="isAnythingSelected()"></span>
</div>
<div class="umb-editor umb-listview" ng-controller="Umbraco.PropertyEditors.ListViewController" ng-switch="isNew">
<div class="row-fluid" ng-switch-when="true">
<table class="table table-striped">
<thead>
<tr>
<td with="20"><input type="checkbox" ng-click="selectAll($event)" ng-checked="isSelectedAll()"></td>
<td><a href="#" ng-click="sort('Name')" prevent-default>
<localize key="general_name">Name</localize>
<i class="icon-sort"></i></a></td>
<td><a href="#" ng-click="sort('UpdateDate')" prevent-default>
<localize key="defaultdialogs_lastEdited">Last edited</localize>
<i class="icon-sort"></i></a></td>
<td><a href="#" ng-click="sort('Owner')" prevent-default>
<localize key="content_updatedBy">Updated by</localize>
<i class="icon-sort"></i></a></td>
<td with="20"><form class="pull-right" novalidate>
<i class="icon-search"></i>
<input type="text" ng-model="options.filter" on-keyup="reloadView(contentId)">
</form></td>
</tr>
</thead>
</div>
<tbody>
<tr ng-repeat="result in listViewResultSet.items"
ng-class="{selected:result.selected}">
<div class="row-fluid" ng-switch-when="false">
<div class="umb-sub-header">
<td>
<i class="icon {{result.icon}}" ng-class="getIcon(result)"></i>
<div class="btn-group" ng-show="listViewAllowedTypes">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<localize key="actions_create">Create</localize>
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li ng-repeat="contentType in listViewAllowedTypes">
<a
href="#/{{entityType}}/{{entityType}}/edit/{{contentId}}?doctype={{contentType.alias}}&create=true">
<i class="icon-{{contentType.cssClass}}"></i>
{{contentType.name}}
</a>
</li>
</ul>
</div>
<div class="btn-group" ng-show="isAnythingSelected()" ng-if="entityType === 'content'">
<a class="btn btn-success" ng-disabled="actionInProgress" ng-click="publish()" prevent-default>
<localize key="actions_publish">Publish</localize>
</a>
</div>
<div class="btn-group" ng-show="isAnythingSelected()" ng-if="entityType === 'content'">
<a class="btn btn-warning" ng-disabled="actionInProgress" ng-click="unpublish()" prevent-default>
<localize key="actions_unpublish">Unpublish</localize>
</a>
</div>
<div class="btn-group" ng-show="isAnythingSelected()">
<a class="btn btn-danger" ng-disabled="actionInProgress" ng-click="delete()" prevent-default>
<localize key="actions_delete">Delete</localize>
</a>
</div>
<span ng-bind="bulkStatus" ng-show="isAnythingSelected()"></span>
</div>
<input type="checkbox" ng-model="result.selected"></td>
<td>
<a ng-class="{inactive:!result.published}" href="#/content/content/edit/{{result.id}}">{{result.name}}</a></td>
<td>{{result.updateDate|date:'medium'}}
<!--<<span class="label label-success">Publish</span>-->
</td>
<td>{{result.owner.name}} <!--<span class="label">Admin</span>--></td>
<td></td>
</tr>
</tbody>
<table class="table table-striped">
<thead>
<tr>
<td>
<input type="checkbox" ng-click="selectAll($event)" ng-checked="isSelectedAll()"></td>
<td><a href="#" ng-click="sort('Name')" prevent-default>
<localize key="general_name">Name</localize>
<i class="icon-sort"></i></a></td>
<td><a href="#" ng-click="sort('UpdateDate')" prevent-default>
<localize key="defaultdialogs_lastEdited">Last edited</localize>
<i class="icon-sort"></i></a></td>
<td><a href="#" ng-click="sort('Owner')" prevent-default>
<localize key="content_updatedBy">Updated by</localize>
<i class="icon-sort"></i></a></td>
<td>
<form class="pull-right" novalidate>
<i class="icon-search"></i>
<input type="text" ng-model="options.filter" on-keyup="reloadView(contentId)">
</form>
</td>
</tr>
</thead>
<tfoot ng-show="pagination.length > 1">
<tr>
<th colspan="5">
<div class="pull-left">
</div>
<div class="pagination pagination-right">
<ul>
<li><a href="#" ng-click="prev()" prevent-default><localize key="general_previous">Previous</localize></a></li>
<tbody>
<tr ng-repeat="result in listViewResultSet.items"
ng-class="{selected:result.selected}">
<li ng-repeat="pgn in pagination track by $index"
ng-class="{active:$index==options.offset}">
<a href="#" ng-click="goToPage($index)" prevent-default>{{$index + 1}}</a>
</li>
<td>
<i class="icon {{result.icon}}" ng-class="getIcon(result)"></i>
<li><a href="#" ng-click="next()" prevent-default>
<localize key="general_next">Next</localize>
</a></li>
</ul>
</div>
<input type="checkbox" ng-model="result.selected"></td>
<td>
<a ng-class="{inactive: entityType === 'content' && !result.published}" href="#/{{entityType}}/{{entityType}}/edit/{{result.id}}">{{result.name}}</a></td>
<td>{{result.updateDate|date:'medium'}}
<!--<<span class="label label-success">Publish</span>-->
</td>
<td>{{result.owner.name}}
<!--<span class="label">Admin</span>-->
</td>
<td></td>
</tr>
</tbody>
</th>
</tr>
</tfoot>
</table>
</div>
<tfoot ng-show="pagination.length > 1">
<tr>
<th colspan="5">
<div class="pull-left">
</div>
<div class="pagination pagination-right">
<ul>
<li><a href="#" ng-click="prev()" prevent-default>
<localize key="general_previous">Previous</localize>
</a></li>
<li ng-repeat="pgn in pagination track by $index"
ng-class="{active:$index==options.offset}">
<a href="#" ng-click="goToPage($index)" prevent-default>{{$index + 1}}</a>
</li>
<li><a href="#" ng-click="next()" prevent-default>
<localize key="general_next">Next</localize>
</a></li>
</ul>
</div>
</th>
</tr>
</tfoot>
</table>
</div>
</div>