Closes #67: Relations between document (client-side)

This commit is contained in:
jendib 2016-03-12 20:29:02 +01:00
parent 0525754337
commit ff91521a67
10 changed files with 125 additions and 4 deletions

View File

@ -28,7 +28,7 @@ public class RelationDao {
@SuppressWarnings("unchecked")
public List<RelationDto> getByDocumentId(String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select d.DOC_ID_C, d.DOC_TITLE_C ");
StringBuilder sb = new StringBuilder("select d.DOC_ID_C, d.DOC_TITLE_C, r.REL_IDDOCFROM_C ");
sb.append(" from T_RELATION r ");
sb.append(" join T_DOCUMENT d on d.DOC_ID_C = r.REL_IDDOCFROM_C and r.REL_IDDOCFROM_C != :documentId or d.DOC_ID_C = r.REL_IDDOCTO_C and r.REL_IDDOCTO_C != :documentId ");
sb.append(" where (r.REL_IDDOCFROM_C = :documentId or r.REL_IDDOCTO_C = :documentId) ");
@ -47,6 +47,8 @@ public class RelationDao {
RelationDto relationDto = new RelationDto();
relationDto.setId((String) o[i++]);
relationDto.setTitle((String) o[i++]);
String fromDocId = (String) o[i++];
relationDto.setSource(documentId.equals(fromDocId));
relationDtoList.add(relationDto);
}
return relationDtoList;

View File

@ -16,6 +16,11 @@ public class RelationDto {
*/
private String title;
/**
* True if the document is the source of the relation.
*/
private boolean source;
public String getId() {
return id;
}
@ -31,4 +36,12 @@ public class RelationDto {
public void setTitle(String title) {
this.title = title;
}
public boolean isSource() {
return source;
}
public void setSource(boolean source) {
this.source = source;
}
}

View File

@ -181,7 +181,8 @@ public class DocumentResource extends BaseResource {
for (RelationDto relationDto : relationDtoList) {
relationList.add(Json.createObjectBuilder()
.add("id", relationDto.getId())
.add("title", relationDto.getTitle()));
.add("title", relationDto.getTitle())
.add("source", relationDto.isSource()));
}
document.add("relations", relationList);
@ -669,7 +670,8 @@ public class DocumentResource extends BaseResource {
RelationDao relationDao = new RelationDao();
Set<String> documentIdSet = new HashSet<>();
for (String targetDocId : relationList) {
Document document = documentDao.getDocument(targetDocId, PermType.READ, principal.getId());
// ACL are not checked, because the editing user is not forced to view the target document
Document document = documentDao.getById(targetDocId);
if (document != null && !documentId.equals(targetDocId)) {
documentIdSet.add(targetDocId);
}

View File

@ -50,6 +50,7 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $
$scope.resetForm = function() {
$scope.document = {
tags: [],
relations: [],
language: 'fra'
};
$scope.newFiles = [];
@ -71,6 +72,9 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $
// Extract ids from tags
document.tags = _.pluck(document.tags, 'id');
// Extract ids from relations (only when our document is the source)
document.relations = _.pluck(_.where(document.relations, { source: true }), 'id');
if ($scope.isEdit()) {
promise = Restangular.one('document', $stateParams.id).post('', document);

View File

@ -0,0 +1,68 @@
'use strict';
/**
* Relation selection directive.
*/
angular.module('docs').directive('selectRelation', function() {
return {
restrict: 'E',
templateUrl: 'partial/docs/directive.selectrelation.html',
replace: true,
scope: {
relations: '=',
ref: '@',
ngDisabled: '='
},
controller: function($scope, $q, Restangular) {
/**
* Add a relation.
*/
$scope.addRelation = function($item) {
// Does the new relation is already in the model
var duplicate = _.find($scope.relations, function(relation) {
if ($item.id == relation.id) {
return relation;
}
});
// Add the new relation
if (!duplicate) {
$scope.relations.push({
id: $item.id,
title: $item.title,
source: true
});
}
$scope.input = '';
};
/**
* Remove a relation.
*/
$scope.deleteRelation = function(deleteRelation) {
$scope.relations = _.reject($scope.relations, function(relation) {
return relation.id == deleteRelation.id;
})
};
/**
* Returns a promise for typeahead title.
*/
$scope.getDocumentTypeahead = function($viewValue) {
var deferred = $q.defer();
Restangular.one('document')
.getList('list', {
limit: 5,
sort_column: 1,
asc: true,
search: $viewValue
}).then(function(data) {
deferred.resolve(data.documents);
});
return deferred.promise;
};
},
link: function(scope, element, attr, ctrl) {
}
}
});

View File

@ -70,6 +70,7 @@
<script src="app/docs/filter/Filesize.js" type="text/javascript"></script>
<script src="app/docs/directive/File.js" type="text/javascript"></script>
<script src="app/docs/directive/SelectTag.js" type="text/javascript"></script>
<script src="app/docs/directive/SelectRelation.js" type="text/javascript"></script>
<script src="app/docs/directive/AuditLog.js" type="text/javascript"></script>
<script src="app/docs/directive/InlineEdit.js" type="text/javascript"></script>
<script src="app/docs/directive/ImgError.js" type="text/javascript"></script>

View File

@ -0,0 +1,13 @@
<div>
<ul class="list-inline">
<li ng-repeat="relation in relations | filter: { source: true }">
<span class="label label-info">{{ relation.title }}
<span class="glyphicon glyphicon-remove" ng-click="deleteRelation(relation)" ng-show="!ngDisabled"></span>
</span>
</li>
</ul>
<input class="form-control" type="text" id="{{ ref }}" placeholder="Type a document title" ng-model="input" ng-disabled="ngDisabled"
autocomplete="off"
typeahead="document.title for document in getDocumentTypeahead($viewValue) | filter: $viewValue"
typeahead-wait-ms="200" typeahead-on-select="addRelation($item)" />
</div>

View File

@ -118,6 +118,12 @@
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="inputRelations">Relations</label>
<div class="col-sm-10">
<select-relation relations="document.relations" ref="inputRelations" ng-disabled="fileIsUploading"></select-relation>
</div>
</div>
</fieldset>
<div class="form-group">

View File

@ -19,13 +19,23 @@
<dt>Contributors</dt>
<dd>
<span ng-repeat="contributor in document.contributors">
<span class="btn btn-default btn-xs">
<span class="btn btn-link btn-xs">
<a href="#/user/{{ contributor.username }}">
{{ contributor.username }}
</a>
</span>
</span>
</dd>
<dt>Relations</dt>
<dd>
<span ng-repeat="relation in document.relations">
<span class="btn btn-link btn-xs">
<a href="#/document/view/{{ relation.id }}">
{{ relation.title }}
</a>
</span>
</span>
</dd>
</dl>
<div ng-file-drop drag-over-class="bg-success" ng-multiple="true" allow-dir="false" ng-model="dropFiles"

View File

@ -238,6 +238,7 @@ public class TestDocumentResource extends BaseJerseyTest {
JsonArray relations = json.getJsonArray("relations");
Assert.assertEquals(1, relations.size());
Assert.assertEquals(document2Id, relations.getJsonObject(0).getString("id"));
Assert.assertFalse(relations.getJsonObject(0).getBoolean("source"));
Assert.assertEquals("My super title document 2", relations.getJsonObject(0).getString("title"));
// Get document 2
@ -248,6 +249,7 @@ public class TestDocumentResource extends BaseJerseyTest {
relations = json.getJsonArray("relations");
Assert.assertEquals(1, relations.size());
Assert.assertEquals(document1Id, relations.getJsonObject(0).getString("id"));
Assert.assertTrue(relations.getJsonObject(0).getBoolean("source"));
Assert.assertEquals("My super title document 1", relations.getJsonObject(0).getString("title"));
// Export a document in PDF format