diff --git a/UI/Common/UIxObjectActions.m b/UI/Common/UIxObjectActions.m index ba76d2a0e..b50133919 100644 --- a/UI/Common/UIxObjectActions.m +++ b/UI/Common/UIxObjectActions.m @@ -23,12 +23,16 @@ #import #import +#import #import #import #import + #import #import +#import + #import "WODirectAction+SOGo.h" #import "UIxObjectActions.h" @@ -74,10 +78,19 @@ - (WOResponse *) deleteAction { WOResponse *response; + NSDictionary *data; response = (WOResponse *) [[self clientObject] delete]; - if (!response) - response = [self responseWithStatus: 204]; + if (response) + { + data = [NSDictionary dictionaryWithObjectsAndKeys: [(NSException *) response reason], @"error", nil]; + response = [self responseWithStatus: 403 + andString: [data jsonRepresentation]]; + } + else + { + response = [self responseWithStatus: 204]; + } return response; } diff --git a/UI/Templates/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/ContactsUI/UIxContactFoldersView.wox index 5a608c115..e48e0fb79 100644 --- a/UI/Templates/ContactsUI/UIxContactFoldersView.wox +++ b/UI/Templates/ContactsUI/UIxContactFoldersView.wox @@ -148,7 +148,7 @@
- +
diff --git a/UI/Templates/Themes/mobile/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/Themes/mobile/ContactsUI/UIxContactFoldersView.wox index ec5b35d54..f07dd83ee 100644 --- a/UI/Templates/Themes/mobile/ContactsUI/UIxContactFoldersView.wox +++ b/UI/Templates/Themes/mobile/ContactsUI/UIxContactFoldersView.wox @@ -51,15 +51,19 @@ + + + - - {{folder.name}} + + + {{addressbook.name}} + ng-click="edit(addressbook)"> diff --git a/UI/WebServerResources/js/Common/resource.js b/UI/WebServerResources/js/Common/resource.js index 5c6288dda..bfdbe6668 100644 --- a/UI/WebServerResources/js/Common/resource.js +++ b/UI/WebServerResources/js/Common/resource.js @@ -64,7 +64,25 @@ return deferred.promise; }; - Resource.prototype.set = function(uid, newValue, options) { + /** + * @function create + * @desc Create a new resource using a specific action + * @param {string} action - the action to be used in the URL + * @param {string} name - the new resource's name + */ + Resource.prototype.create = function(action, name) { + var deferred = this._q.defer(); + var path = this._path + '/' + action; + + this._http + .post(path, { 'name': name }) + .success(deferred.resolve) + .error(deferred.reject); + + return deferred.promise; + }; + + Resource.prototype.save = function(uid, newValue, options) { var deferred = this._q.defer(); var action = (options && options.action)? options.action : 'save'; var path = this._path + '/' + uid + '/' + action; diff --git a/UI/WebServerResources/js/Common/ui-desktop.js b/UI/WebServerResources/js/Common/ui-desktop.js index f49581265..866b1eb9b 100644 --- a/UI/WebServerResources/js/Common/ui-desktop.js +++ b/UI/WebServerResources/js/Common/ui-desktop.js @@ -5,8 +5,8 @@ 'use strict'; /** - * @name Dialog (sgDialog factory in SOGo.UIDesktop) - * @desc Dialog object constructor + * @name Dialog + * @constructor */ function Dialog() { } @@ -19,7 +19,7 @@ */ Dialog.alert = function(title, content) { var modal = this.$modal.open({ - template: + template: '

' + '

' + '' + l('OK') + '' + @@ -46,7 +46,7 @@ Dialog.confirm = function(title, content) { var d = this.$q.defer(); var modal = this.$modal.open({ - template: + template: '

' + '

' + '' + l('OK') + '' + @@ -69,7 +69,39 @@ return d.promise; }; - /* The factory we'll use to register with Angular */ + Dialog.prompt = function(title, inputPlaceholder, options) { + var o = options || {}; + var d = this.$q.defer(); + var modal = this.$modal.open({ + template: + '

' + + '
' + + '' + l('OK') + '' + + '' + l('Cancel') + '' + + '', + windowClass: 'small', + + controller: function($scope, $modalInstance) { + $scope.title = title; + $scope.inputValue = o.inputValue || ''; + $scope.closeModal = function() { + $modalInstance.close(); + d.resolve(false); + }; + $scope.confirm = function(value) { + $modalInstance.close(); + d.resolve(value); + }; + } + }); + return d.promise; + }; + + /** + * @memberof Dialog + * @desc The factory we'll register as sgDialog in the Angular module SOGo.UIDesktop + */ Dialog.$factory = ['$modal', '$q', function($modal, $q) { angular.extend(Dialog, { $modal: $modal, $q: $q }); diff --git a/UI/WebServerResources/js/Common/ui-mobile.js b/UI/WebServerResources/js/Common/ui-mobile.js index 3e3e4000b..ca0dd1337 100644 --- a/UI/WebServerResources/js/Common/ui-mobile.js +++ b/UI/WebServerResources/js/Common/ui-mobile.js @@ -4,7 +4,10 @@ (function() { 'use strict'; - /* Dialog */ + /** + * @name Dialog + * @constructor + */ function Dialog() { } @@ -17,14 +20,25 @@ }; Dialog.confirm = function(title, content) { - var alertPopup = this.$ionicPopup.confirm({ + var confirmPopup = this.$ionicPopup.confirm({ title: title, template: content }); - return alertPopup; + return confirmPopup; }; - /* The factory we'll use to register with Angular */ + Dialog.prompt = function(title, content) { + var promptPopup = this.$ionicPopup.prompt({ + title: title, + inputPlaceholder: content + }); + return promptPopup; + }; + + /** + * @memberof Dialog + * @desc The factory we'll register as sgDialog in the Angular module SOGo.UIMobile + */ Dialog.$factory = ['$ionicPopup', function($ionicPopup) { angular.extend(Dialog, { $ionicPopup: $ionicPopup }); diff --git a/UI/WebServerResources/js/Contacts/addressbook-model.js b/UI/WebServerResources/js/Contacts/addressbook-model.js index d639d5c38..fb466a702 100644 --- a/UI/WebServerResources/js/Contacts/addressbook-model.js +++ b/UI/WebServerResources/js/Contacts/addressbook-model.js @@ -1,23 +1,33 @@ (function() { 'use strict'; - /* Constructor */ + /** + * @name AddressBook + * @constructor + * @param {object} futureAddressBookData + */ function AddressBook(futureAddressBookData) { // Data is immediately available if (typeof futureAddressBookData.then !== 'function') { angular.extend(this, futureAddressBookData); - return; + if (this.name && !this.id) { + // Create a new addressbook on the server + var newAddressBookData = AddressBook.$$resource.create('createFolder', this.name); + this.$unwrap(newAddressBookData); + } + } + else { + // The promise will be unwrapped first + this.$unwrap(futureAddressBookData); } - - // The promise will be unwrapped first - this.$unwrap(futureAddressBookData); } /* The factory we'll use to register with Angular */ - AddressBook.$factory = ['$timeout', 'sgSettings', 'sgResource', 'sgCard', function($timeout, Settings, Resource, Card) { + AddressBook.$factory = ['$q', '$timeout', 'sgSettings', 'sgResource', 'sgCard', function($q, $timeout, Settings, Resource, Card) { angular.extend(AddressBook, { - $$resource: new Resource(Settings.baseURL), + $q: $q, $timeout: $timeout, + $$resource: new Resource(Settings.baseURL), $Card: Card }); @@ -27,14 +37,38 @@ /* Factory registration in Angular module */ angular.module('SOGo.ContactsUI').factory('sgAddressBook', AddressBook.$factory); - /* Set or get the list of addressbooks */ + /** + * @memberof AddressBook + * @desc Set or get the list of addressbooks. Will instanciate a new AddressBook object for each item. + * @param {array} [data] - the metadata of the addressbooks + * @returns the list of addressbooks + */ AddressBook.$all = function(data) { + var self = this; if (data) { this.$addressbooks = data; + // Instanciate AddressBook objects + angular.forEach(this.$addressbooks, function(o, i) { + self.$addressbooks[i] = new AddressBook(o); + }); } return this.$addressbooks; }; + /** + * @memberof AddressBook + * @desc Add a new addressbook to the static list of addressbooks + * @param {AddressBook} addressbook - an Addressbook object instance + */ + AddressBook.$add = function(addressbook) { + // Insert new addressbook at proper index + var sibling = _.find(this.$addressbooks, function(o) { + return (o.isRemote || (o.id != 'personal' && o.name.localeCompare(addressbook.name) === 1)); + }); + var i = sibling? _.indexOf(_.pluck(this.$addressbooks, 'id'), sibling.id) : 1; + this.$addressbooks.splice(i, 0, addressbook); + }; + /* Fetch list of cards and return an AddressBook instance */ AddressBook.$find = function(addressbook_id) { var futureAddressBookData = AddressBook.$$resource.find(addressbook_id); @@ -51,7 +85,12 @@ }; /** - * @param {} [options] + * @function $filter + * @memberof AddressBook.prototype + * @desc Search for cards matching some criterias + * @param {string} search - the search string to match + * @param {hash} [options] - additional options to the query + * @returns a collection of Cards instances */ AddressBook.prototype.$filter = function(search, options) { var self = this; @@ -68,6 +107,8 @@ return futureAddressBookData.then(function(data) { var cards; if (options && options.dry) { + // Don't keep a copy of the resulting cards. + // This is usefull when doing autocompletion. cards = data.cards; } else { @@ -83,12 +124,37 @@ }); }; + /** + * @function $rename + * @memberof AddressBook.prototype + * @desc Rename the addressbook + * @param {string} name - the new name + * @returns a promise of the HTTP operation + */ + AddressBook.prototype.$rename = function(name) { + var i = _.indexOf(_.pluck(AddressBook.$addressbooks, 'id'), this.id); + this.name = name; + AddressBook.$addressbooks.splice(i, 1); + AddressBook.$add(this); + return this.$save(); + }; + AddressBook.prototype.$delete = function() { - return AddressBook.$$resource.remove(this.id); + var self = this; + var d = AddressBook.$q.defer(); + AddressBook.$$resource.remove(this.id) + .then(function() { + var i = _.indexOf(_.pluck(AddressBook.$addressbooks, 'id'), self.id); + AddressBook.$addressbooks.splice(i, 1); + d.resolve(true); + }, function(data, status) { + d.reject(data); + }); + return d.promise; }; AddressBook.prototype.$save = function() { - return AddressBook.$$resource.set(this.id, this.$omit()).then(function (data) { + return AddressBook.$$resource.save(this.id, this.$omit()).then(function (data) { return data; }); }; @@ -129,6 +195,9 @@ self.cards[i] = new AddressBook.$Card(o); }); }); + }, function(data) { + angular.extend(self, data); + self.isError = true; }); }; diff --git a/UI/WebServerResources/js/Contacts/card-model.js b/UI/WebServerResources/js/Contacts/card-model.js index 21f012ae4..58bd22d7d 100644 --- a/UI/WebServerResources/js/Contacts/card-model.js +++ b/UI/WebServerResources/js/Contacts/card-model.js @@ -1,7 +1,11 @@ (function() { 'use strict'; - /* Constructor */ + /** + * @name Card + * @constructor + * @param {object} futureCardData + */ function Card(futureCardData) { // Data is immediately available @@ -14,9 +18,10 @@ this.$unwrap(newCardData); } } - else + else { // The promise will be unwrapped first this.$unwrap(futureCardData); + } } Card.$tel_types = ['work', 'home', 'cell', 'fax', 'pager']; @@ -62,7 +67,13 @@ } }); - /* Fetch a card */ + /** + * @memberof Card + * @desc Fetch a card from a specific addressbook + * @param {string} addressbook_id - the addressbook ID + * @param {string} card_id - the card ID + * @see {@link AddressBook.$getCard} + */ Card.$find = function(addressbook_id, card_id) { var futureCardData = this.$$resource.find([addressbook_id, card_id].join('/')); @@ -71,7 +82,11 @@ return Card.$unwrapCollection(futureCardData); // a collection of cards }; - /* Unwrap to a collection of Card instances */ + /** + * @memberof Card + * @desc Unwrap to a collection of Card instances + * @param {Object} futureCardData + */ Card.$unwrapCollection = function(futureCardData) { var collection = {}; @@ -88,21 +103,30 @@ return collection; }; - /* Instance methods */ - + /** + * @function $id + * @memberof Card.prototype + * @desc Return the card ID + * @returns the card ID + */ Card.prototype.$id = function() { return this.$futureCardData.then(function(data) { return data.id; }); }; + /** + * @function $save + * @memberof Card.prototype + * @desc Save the card to the server + */ Card.prototype.$save = function() { var action = 'saveAsContact'; if (this.tag == 'vlist') action = 'saveAsList'; //var action = 'saveAs' + this.tag.substring(1).capitalize(); - return Card.$$resource.set([this.pid, this.id || '_new_'].join('/'), - this.$omit(), - { 'action': action }) + return Card.$$resource.save([this.pid, this.id || '_new_'].join('/'), + this.$omit(), + { 'action': action }) .then(function (data) { return data; }); @@ -162,9 +186,11 @@ }; /** - * @name $preferredEmail - * @desc Returns the first email address of type "pref" or the first address if none found. + * @function $preferredEmail + * @memberof Card.prototype + * @desc Get the preferred email address * @param {string} [partial] - a partial string that the email must match + * @returns the first email address of type "pref" or the first address if none found */ Card.prototype.$preferredEmail = function(partial) { var email; @@ -196,7 +222,10 @@ }; /** - * + * @function $shortFormat + * @memberof Card.prototype + * @param {string} [partial] - a partial string that the email must match + * @returns the fullname along with a matching email address in parentheses */ Card.prototype.$shortFormat = function(partial) { var fullname = this.$fullname(); @@ -313,12 +342,16 @@ }; /** - * @name $updateMember + * @function $updateMember + * @memberof Card.prototype * @desc Update an existing list member from a Card instance. * A list member has the following attribtues: * - email * - reference * - fn + * @param {number} index + * @param {string} email + * @param {Card} card */ Card.prototype.$updateMember = function(index, email, card) { var ref = {'email': email, 'reference': card.c_name, 'fn': card.$fullname()}; diff --git a/UI/WebServerResources/js/ContactsUI.js b/UI/WebServerResources/js/ContactsUI.js index fca766332..4a98c49cb 100644 --- a/UI/WebServerResources/js/ContactsUI.js +++ b/UI/WebServerResources/js/ContactsUI.js @@ -96,6 +96,19 @@ $scope.select = function(rowIndex) { $scope.editMode = false; }; + $scope.newAddressbook = function() { + $scope.editMode = false; + Dialog.prompt(l('New addressbook'), + l('Name of new addressbook')) + .then(function(name) { + if (name && name.length > 0) { + var addressbook = new AddressBook({ 'name': name, + 'isEditable': true, + 'isRemote': false }); + AddressBook.$add(addressbook); + } + }); + }; $scope.edit = function(i) { if (!$rootScope.addressbook.isRemote) { if (angular.isUndefined(i)) { @@ -116,18 +129,19 @@ }; $scope.confirmDelete = function() { Dialog.confirm(l('Warning'), l('Are you sure you want to delete the addressbook "%{0}"?', - $rootScope.addressbook.name), function() { - $rootScope.addressbook.$delete() - .then(function() { - $rootScope.addressbooks = _.reject($rootScope.addressbooks, function(o) { - return o.id == $rootScope.addressbook.id; - }); - $rootScope.addressbook = null; - }, function(data, status) { - Dialog.alert(l('Warning'), l('An error occured while deleting the addressbook "%{0}".', - $rootScope.addressbook.name)); - }); - }); + $rootScope.addressbook.name)) + .then(function(res) { + if (res) { + $rootScope.addressbook.$delete() + .then(function() { + $rootScope.addressbook = null; + }, function(data, status) { + Dialog.alert(l('An error occured while deleting the addressbook "%{0}".', + $rootScope.addressbook.name), + l(data.error)); + }); + } + }); }; $scope.share = function() { var modal = $modal.open({ @@ -238,9 +252,16 @@ }; $scope.cancel = function() { $scope.reset(); - //$scope.editMode = false; delete $rootScope.master_card; - $state.go('addressbook.card', { card_id: $scope.addressbook.card.id }); + if ($scope.addressbook.card.id) { + // Cancelling the edition of an existing card + $state.go('addressbook.card', { card_id: $scope.addressbook.card.id }); + } + else { + // Cancelling the creation of a card + delete $rootScope.addressbook.card; + $state.go('addressbook', { addressbook_id: $scope.addressbook.id }); + } }; $scope.reset = function() { $rootScope.addressbook.card = angular.copy($rootScope.master_card); diff --git a/UI/WebServerResources/js/mobile/ContactsUI.js b/UI/WebServerResources/js/mobile/ContactsUI.js index cc72a58f3..f8455f4bb 100644 --- a/UI/WebServerResources/js/mobile/ContactsUI.js +++ b/UI/WebServerResources/js/mobile/ContactsUI.js @@ -115,14 +115,56 @@ // }; }]) -.controller('AddressBooksCtrl', ['$scope', '$rootScope', '$timeout', 'sgAddressBook', function($scope, $rootScope, $timeout, AddressBook) { +.controller('AddressBooksCtrl', ['$scope', '$rootScope', '$ionicModal', '$ionicListDelegate', '$ionicActionSheet', 'sgDialog', 'sgAddressBook', function($scope, $rootScope, $ionicModal, $ionicListDelegate, $ionicActionSheet, Dialog, AddressBook) { // Initialize with data from template $scope.addressbooks = AddressBook.$all(contactFolders); - $scope.edit = function(i) { - + $scope.newAddressbook = function() { + Dialog.prompt(l('Create addressbook'), + l('Name of new addressbook')) + .then(function(res) { + if (res && res.length > 0) { + var addressbook = new AddressBook({ 'name': res, + 'isEditable': true, + 'isRemote': false }); + AddressBook.$add(addressbook); + } + }); }; - $scope.save = function(i) { - + $scope.edit = function(addressbook) { + $ionicActionSheet.show({ + titleText: l('Modify your addressbook %{0}', addressbook.name), + buttons: [ + { text: l('Rename') } + ], + destructiveText: l('Delete'), + cancelText: l('Cancel'), + buttonClicked: function(index) { + // Rename addressbook + Dialog.prompt(l('Rename addressbook'), + addressbook.name) + .then(function(name) { + if (name && name.length > 0) { + addressbook.$rename(name); + } + }); + return true; + }, + destructiveButtonClicked: function() { + // Delete addressbook + addressbook.$delete() + .then(function() { + addressbook = null; + }, function(data) { + Dialog.alert(l('An error occured while deleting the addressbook "%{0}".', + addressbook.name), + l(data.error)); + }); + return true; + }, + // cancel: function() { + // }, + }); + $ionicListDelegate.closeOptionButtons(); }; }])