Merge pull request #622 from Alfresco/dev-wabson-621

Add new attached-file widget for linking to external files
This commit is contained in:
Denys Vuika
2016-09-08 11:04:47 +01:00
committed by GitHub
14 changed files with 408 additions and 4 deletions

View File

@@ -244,10 +244,17 @@ will also be executed after your custom code.**
- Header - Header
* [x] Plain header * [x] Plain header
* [x] Collapsible header * [x] Collapsible header
- [ ] Attach - [x] Attach file **
- [x] Display value - [x] Display value
- [x] Display text - [x] Display text
** Files may be uploaded from a user's device if the file source selected is
'Local file' or 'All sources' and 'link to files' is not selected. Alternatively
you can link to files in a configured Alfresco repository by selecting this source
explicitly from the list and making sure that 'link to files' is selected. Copying
files from Alfresco into Activiti via the control (no linking) is not currently
supported, nor is allowing the user to choose between more than one source.
## Build from sources ## Build from sources
Alternatively you can build component from sources with the following commands: Alternatively you can build component from sources with the following commands:

View File

@@ -26,6 +26,7 @@ import {
import { MATERIAL_DESIGN_DIRECTIVES } from 'ng2-alfresco-core'; import { MATERIAL_DESIGN_DIRECTIVES } from 'ng2-alfresco-core';
import { EcmModelService } from './../services/ecm-model.service'; import { EcmModelService } from './../services/ecm-model.service';
import { FormService } from './../services/form.service'; import { FormService } from './../services/form.service';
import { ActivitiAlfrescoContentService } from './../services/activiti-alfresco.service';
import { NodeService } from './../services/node.service'; import { NodeService } from './../services/node.service';
import { FormModel, FormOutcomeModel, FormValues, FormFieldModel, FormOutcomeEvent } from './widgets/core/index'; import { FormModel, FormOutcomeModel, FormValues, FormFieldModel, FormOutcomeEvent } from './widgets/core/index';
@@ -85,7 +86,7 @@ import { WidgetVisibilityService } from './../services/widget-visibility.servic
templateUrl: './activiti-form.component.html', templateUrl: './activiti-form.component.html',
styleUrls: ['./activiti-form.component.css'], styleUrls: ['./activiti-form.component.css'],
directives: [MATERIAL_DESIGN_DIRECTIVES, ContainerWidget, TabsWidget], directives: [MATERIAL_DESIGN_DIRECTIVES, ContainerWidget, TabsWidget],
providers: [EcmModelService, FormService, WidgetVisibilityService, NodeService] providers: [EcmModelService, FormService, ActivitiAlfrescoContentService, WidgetVisibilityService, NodeService]
}) })
export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges { export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {

View File

@@ -0,0 +1,15 @@
.attach-widget {
width:100%
}
.attach-widget__icon {
float: left;
}
.attach-widget__file {
margin-top: 4px;
}
.attach-widget__reset {
margin-top: 4px;
}

View File

@@ -0,0 +1,33 @@
<div class="attach-widget">
<label [attr.for]="field.id">{{field.name}}</label>
<div>
<span *ngIf="hasFile" class="attach-widget__file">{{getLinkedFileName()}}</span>
<button #browseFile (click)="showDialog();" class="mdl-button mdl-js-button mdl-js-ripple-effect attach-widget__browser">
<i class="material-icons">image</i>
Browse {{selectedFolderSiteName}}
</button>
<button *ngIf="hasFile" (click)="reset(file);" class="mdl-button mdl-js-button mdl-js-ripple-effect attach-widget__reset">Clear</button>
</div>
</div>
<dialog class="mdl-dialog" #dialog>
<h4 class="mdl-dialog__title">Select content</h4>
<div class="mdl-dialog__content">
<ul class='mdl-list'>
<li class="mdl-list__item" *ngFor="let node of selectedFolderNodes">
<span class="mdl-list__item-primary-content" *ngIf="node.folder">
<i class="material-icons mdl-list__item-icon">folder</i>
<a (click)="selectFolder(node, $event)">{{node.title}}</a>
</span>
<span class="mdl-list__item-primary-content" *ngIf="!node.folder">
<i class="material-icons mdl-list__item-icon">description</i>
<a (click)="selectFile(node, $event)">{{node.title}}</a>
</span>
</li>
</ul>
</div>
<div class="mdl-dialog__actions">
<button type="button" (click)="cancel()" class="mdl-button close">Cancel</button>
</div>
</dialog>

View File

@@ -0,0 +1,146 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, OnInit, Input, Output, EventEmitter, ViewChild } from '@angular/core';
import { WidgetComponent } from './../widget.component';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
import { ExternalContent } from '../core/external-content';
import { ExternalContentLink } from '../core/external-content-link';
import { FormFieldModel } from '../core/form-field.model';
declare let __moduleName: string;
declare var componentHandler;
@Component({
moduleId: __moduleName,
selector: 'attach-widget',
templateUrl: './attach.widget.html',
styleUrls: ['./attach.widget.css']
})
export class AttachWidget extends WidgetComponent implements OnInit {
selectedFolderPathId: string;
selectedFolderSiteId: string;
selectedFolderSiteName: string;
selectedFolderAccountId: string;
fileName: string;
hasFile: boolean;
selectedFolderNodes: [ExternalContent];
selectedFile: ExternalContent;
@Input()
field: FormFieldModel;
@Output()
fieldChanged: EventEmitter<FormFieldModel> = new EventEmitter<FormFieldModel>();
@ViewChild('dialog')
dialog: any;
constructor(private contentService: ActivitiAlfrescoContentService) {
super();
}
ngOnInit() {
if (this.field &&
this.field.value) {
this.hasFile = true;
}
if (this.field &&
this.field.params &&
this.field.params.fileSource &&
this.field.params.fileSource.selectedFolder) {
this.selectedFolderSiteId = this.field.params.fileSource.selectedFolder.siteId;
this.selectedFolderSiteName = this.field.params.fileSource.selectedFolder.site;
this.setupFileBrowser();
this.getExternalContentNodes();
}
}
private setupFileBrowser() {
this.selectedFolderPathId = this.field.params.fileSource.selectedFolder.pathId;
this.selectedFolderAccountId = this.field.params.fileSource.selectedFolder.accountId;
}
getLinkedFileName(): string {
let result = this.fileName;
if (this.selectedFile &&
this.selectedFile.title) {
result = this.selectedFile.title;
}
if (this.field.value &&
this.field.value.length > 0 &&
this.field.value[0].name) {
result = this.field.value[0].name;
}
return result;
}
private getExternalContentNodes() {
this.contentService.getAlfrescoNodes(this.selectedFolderAccountId, this.selectedFolderPathId)
.subscribe(
(nodes) => {
this.selectedFolderNodes = nodes;
},
error => console.error(error));
}
selectFile(node: ExternalContent, $event: any) {
this.contentService.linkAlfrescoNode(this.selectedFolderAccountId, node, this.selectedFolderSiteId).subscribe(
(link: ExternalContentLink) => {
this.selectedFile = node;
this.field.value = [link];
this.field.json.value = [link];
this.closeDialog();
this.fieldChanged.emit(this.field);
}
);
}
selectFolder(node: ExternalContent, $event: any) {
this.selectedFolderPathId = node.id;
this.getExternalContentNodes();
}
public showDialog() {
this.setupFileBrowser();
this.getExternalContentNodes();
if (this.dialog) {
this.dialog.nativeElement.showModal();
}
}
private closeDialog() {
if (this.dialog) {
this.dialog.nativeElement.close();
}
}
public cancel() {
this.closeDialog();
}
reset() {
this.field.value = null;
this.field.json.value = null;
this.hasFile = false;
}
}

View File

@@ -44,7 +44,8 @@
<display-text-widget [field]="field" (fieldChanged)="fieldChanged($event);"></display-text-widget> <display-text-widget [field]="field" (fieldChanged)="fieldChanged($event);"></display-text-widget>
</div> </div>
<div *ngSwitchCase="'upload'"> <div *ngSwitchCase="'upload'">
<upload-widget [field]="field" (fieldChanged)="fieldChanged($event);"></upload-widget> <upload-widget *ngIf="!field.params.link" [field]="field" (fieldChanged)="fieldChanged($event);"></upload-widget>
<attach-widget *ngIf="field.params.link" [field]="field" (fieldChanged)="fieldChanged($event);"></attach-widget>
</div> </div>
<div *ngSwitchCase="'typeahead'"> <div *ngSwitchCase="'typeahead'">
<typeahead-widget [field]="field" (fieldChanged)="fieldChanged($event);"></typeahead-widget> <typeahead-widget [field]="field" (fieldChanged)="fieldChanged($event);"></typeahead-widget>

View File

@@ -0,0 +1,32 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface ExternalContentLink {
contentAvailable: boolean;
created: string;
createdBy: any;
id: number;
link: boolean;
mimeType: string;
name: string;
previewStatus: string;
relatedContent: boolean;
simpleType: string;
source: string;
sourceId: string;
thumbnailStatus: string;
}

View File

@@ -0,0 +1,23 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface ExternalContent {
folder: boolean;
id: string;
simpleType: string;
title: string;
}

View File

@@ -0,0 +1,25 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {FormFieldSelectedFolder} from './form-field-selected-folder';
export interface FormFieldFileSource {
metadataAllowed: boolean;
name: string;
selectedFolder: FormFieldSelectedFolder;
serviceId: string;
}

View File

@@ -15,6 +15,10 @@
* limitations under the License. * limitations under the License.
*/ */
import {FormFieldFileSource} from './form-field-file-source';
export interface FormFieldMetadata { export interface FormFieldMetadata {
[key: string]: any; [key: string]: any;
fileSource?: FormFieldFileSource;
link?: boolean;
} }

View File

@@ -0,0 +1,25 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface FormFieldSelectedFolder {
accountId: string;
folderTree: [any];
path: string;
pathId: string;
site: string;
siteId: string;
}

View File

@@ -28,6 +28,7 @@ import { RadioButtonsWidget } from './radio-buttons/radio-buttons.widget';
import { DisplayValueWidget } from './display-value/display-value.widget'; import { DisplayValueWidget } from './display-value/display-value.widget';
import { DisplayTextWidget } from './display-text/display-text.widget'; import { DisplayTextWidget } from './display-text/display-text.widget';
import { UploadWidget } from './upload/upload.widget'; import { UploadWidget } from './upload/upload.widget';
import { AttachWidget } from './attach/attach.widget';
import { TypeaheadWidget } from './typeahead/typeahead.widget'; import { TypeaheadWidget } from './typeahead/typeahead.widget';
import { FunctionalGroupWidget } from './functional-group/functional-group.widget'; import { FunctionalGroupWidget } from './functional-group/functional-group.widget';
import { PeopleWidget } from './people/people.widget'; import { PeopleWidget } from './people/people.widget';
@@ -51,6 +52,7 @@ export * from './radio-buttons/radio-buttons.widget';
export * from './display-value/display-value.widget'; export * from './display-value/display-value.widget';
export * from './display-text/display-text.widget'; export * from './display-text/display-text.widget';
export * from './upload/upload.widget'; export * from './upload/upload.widget';
export * from './attach/attach.widget';
export * from './typeahead/typeahead.widget'; export * from './typeahead/typeahead.widget';
export * from './functional-group/functional-group.widget'; export * from './functional-group/functional-group.widget';
export * from './people/people.widget'; export * from './people/people.widget';
@@ -71,6 +73,7 @@ export const PRIMITIVE_WIDGET_DIRECTIVES: [any] = [
DisplayValueWidget, DisplayValueWidget,
DisplayTextWidget, DisplayTextWidget,
UploadWidget, UploadWidget,
AttachWidget,
TypeaheadWidget, TypeaheadWidget,
FunctionalGroupWidget, FunctionalGroupWidget,
PeopleWidget PeopleWidget

View File

@@ -0,0 +1,90 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { AlfrescoAuthenticationService } from 'ng2-alfresco-core';
import { ExternalContent } from '../components/widgets/core/external-content';
import { ExternalContentLink } from '../components/widgets/core/external-content-link';
@Injectable()
export class ActivitiAlfrescoContentService {
static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error';
static GENERIC_ERROR_MESSAGE: string = 'Server error';
constructor(private authService: AlfrescoAuthenticationService) {
}
/**
* Returns a list of child nodes below the specified folder
*
* @param accountId
* @param folderId
* @returns {null}
*/
getAlfrescoNodes(accountId: string, folderId: string): Observable<[ExternalContent]> {
let apiService: any = this.authService.getAlfrescoApi();
let accountShortId = accountId.replace('alfresco-', '');
return Observable.fromPromise(apiService.activiti.alfrescoApi.getContentInFolder(accountShortId, folderId))
.map(this.toJsonArray)
.catch(this.handleError);
}
/**
* Returns a list of child nodes below the specified folder
*
* @param accountId
* @param node
* @param siteId
* @returns {null}
*/
linkAlfrescoNode(accountId: string, node: ExternalContent, siteId: string): Observable<ExternalContentLink> {
let apiService: any = this.authService.getAlfrescoApi();
return Observable.fromPromise(apiService.activiti.contentApi.createTemporaryRelatedContent({
link: true,
name: node.title,
simpleType: node.simpleType,
source: accountId,
sourceId: node.id + '@' + siteId
})).map(this.toJson).catch(this.handleError);
}
toJson(res: any) {
if (res) {
return res || {};
}
return {};
}
toJsonArray(res: any) {
if (res) {
return res.data || [];
}
return [];
}
handleError(error: any): Observable<any> {
let errMsg = ActivitiAlfrescoContentService.UNKNOWN_ERROR_MESSAGE;
if (error) {
errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : ActivitiAlfrescoContentService.GENERIC_ERROR_MESSAGE;
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}

View File

@@ -273,7 +273,6 @@ export class FormService {
}); });
} }
getFormId(res: any) { getFormId(res: any) {
let result = null; let result = null;