OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
openproject/frontend/app/components/api/api-v3/hal-transformed-element.ser...

215 lines
6.9 KiB

//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
function halTransformedElementService(Restangular:restangular.IService, $q:ng.IQService) {
return class HalTransformedElement {
constructor(protected element) {
return this.transform();
}
protected transform() {
if (!this.element._links && !this.element._embedded) return this.element;
if (!this.element.restangularized) {
this.element = Restangular.restangularizeElement(null, this.element, '');
}
const propertiesSet = [];
/**
* The properties added by the transformation should not be enumerable, so that
* Restangular's `.plain()` returns only the relevant properties.
*/
Object.defineProperties(this.element, {
/**
* Linked resources of the element.
*/
links: {value: this.transformLinks()},
/**
* Embedded resources of the element
*/
embedded: {value: this.transformEmbedded()},
/**
* Gets a linked or embedded resource and sets its plain value as a property of the
* element.
* Request the linked resource, if it's not embedded.
* @method
*/
//TODO: Add embedded resource handling (see description).
//TODO: Always return a promise.
setProperty: {
value: (propertyName:string) => {
return !!this.element.links[propertyName] && this.element.links[propertyName]().then(value => {
propertiesSet.push(propertyName);
return this.element[propertyName] = value;
});
}
},
/**
* Set linked or embedded resources as properties of the element.
* @method
*/
//TODO: Return a promise based on $q.all
setProperties: {
value: (propertyNames:string[]) => {
propertyNames.forEach(this.element.setProperty);
}
},
/**
* Write the linked property's value back to the original _links attribute.
* This is useful, if you want to save the resource.
* @method
*/
//TODO: Handle _embedded properties (it it makes any sense - probably not).
//TODO: Maybe delete the linked property, as it has no use.
data: {
value: () => {
var plain = this.element.plain();
plain._links = {};
angular.forEach(this.element.links, (link, name) => {
var property = this.element[name];
var source = link._source;
if (propertiesSet.indexOf(name) !== -1) {
if (property._links) {
property = new HalTransformedElement(property);
}
if (property.links.self) {
source = property.links.self._source;
}
}
plain._links[name] = source;
});
return plain;
}
},
/**
* Indicate whether the element has been transformed.
* @boolean
*/
halTransformed: {value: true}
});
angular.forEach(this.element.links, (link, linkName) => {
const property = {};
angular.extend(property, link._source);
Object.defineProperty(this.element, linkName, {value: property});
});
return this.element;
}
/**
* Transform links
*
* Links are methods that return a promise.
* Collections can be requested by `link[linkName].all()`.
*/
//TODO: Implement handling for link arrays (see schema.priority._links.allowedValues)
protected transformLinks() {
return this.transformHalProperty('_links', (links, link, linkName) => {
var method = (method:string, multiplier?:string = 'oneUrl') => {
return (...params) => {
if (method === 'post') params.unshift('');
if (link.href !== null) {
return this.element[multiplier](linkName, link.href)[method]
.apply(this.element, params)
.then(value => {
if (value) {
angular.extend(this.element[linkName], value);
}
return value;
});
}
return $q.when({});
}
};
if (!link.method) {
links[linkName] = method('get');
links[linkName].list = method('getList', 'allUrl');
}
else {
links[linkName] = method(link.method);
}
});
}
/**
* Transform embedded properties and their children to actual HAL resources,
* if they have links or embedded resources.
*/
protected transformEmbedded() {
return this.transformHalProperty('_embedded', (embedded, element, name) => {
angular.forEach(element, child => child && new HalTransformedElement(element));
embedded[name] = new HalTransformedElement(element);
});
}
/**
*
* @param propertyName
* @param callback
* @returns {{}}
*/
protected transformHalProperty(propertyName:string, callback:(props, prop, name) => any) {
var properties = {};
var _properties = this.element[propertyName];
delete this.element[propertyName];
angular.forEach(_properties, (property, name) => {
callback(properties, property, name);
});
// Add a _source property so the information of the source is accessible
angular.forEach(properties, (property, name) => {
if (_properties[name]) {
Object.defineProperty(
properties[name], '_source', {value: angular.copy(_properties[name])});
}
});
return properties;
}
};
}
angular
.module('openproject.api')
.factory('HalTransformedElement', halTransformedElementService);