release 3.0.0

This commit is contained in:
Mario Romano
2016-08-30 08:28:34 -04:00
489 changed files with 23512 additions and 4303 deletions
+47 -4
View File
@@ -17,8 +17,6 @@ before_install:
- export CHROME_BIN=/usr/bin/google-chrome
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- (cd ng2-components/ng2-alfresco-core; npm version patch; sed -i "s/0\\.0\\.0-PLACEHOLDER/^0.1.0/g" package.json; npm install; npm link)
env:
matrix:
- MODULE=ng2-alfresco-core
@@ -28,11 +26,56 @@ env:
- MODULE=ng2-alfresco-search
- MODULE=ng2-alfresco-upload
- MODULE=ng2-alfresco-viewer
- MODULE=ng2-alfresco-webscript
- MODULE=ng2-activiti-form
- MODULE=ng2-activiti-tasklist
- MODULE=ng2-activiti-processlist
before_script:
- cd ng2-components/$MODULE; npm version patch; sed -i "s/0\\.0\\.0-PLACEHOLDER/^0.1.0/g" package.json; npm install; npm link ng2-alfresco-core; npm run travis
- if ([ "$MODULE" != "ng2-alfresco-core" ]); then
(cd ng2-components/ng2-alfresco-core; npm install; npm link);
fi
- if ([ "$MODULE" == "ng2-alfresco-documentlist" ] || [ "$MODULE" == "ng2-alfresco-webscript" ] || [ "$MODULE" == "ng2-activiti-processlist" ] || [ "$MODULE" == "ng2-activiti-tasklist" ]); then
(cd ng2-components/ng2-alfresco-datatable; npm link ng2-alfresco-core; npm install; npm link);
fi
- if ([ "$MODULE" == "ng2-activiti-tasklist" ] || [ "$MODULE" == "ng2-activiti-processlist" ]); then
(cd ng2-components/ng2-activiti-form; npm link ng2-alfresco-core; npm install; npm link);
fi
- if ([ "$MODULE" == "ng2-activiti-processlist" ]); then
(cd ng2-components/ng2-activiti-tasklist; npm link ng2-alfresco-core; npm link ng2-alfresco-datatable; npm link ng2-activiti-form; npm install; npm link);
fi
- cd ng2-components/$MODULE;
- if ([ "$MODULE" != "ng2-alfresco-core" ]); then
npm link ng2-alfresco-core;
fi
- if ([ "$MODULE" == "ng2-alfresco-documentlist" ] || [ "$MODULE" == "ng2-alfresco-webscript" ] || [ "$MODULE" == "ng2-activiti-processlist" ] || [ "$MODULE" == "ng2-activiti-tasklist" ]); then
npm link ng2-alfresco-datatable;
fi
- if ([ "$MODULE" == "ng2-activiti-tasklist" ]); then
npm link ng2-activiti-form;
fi
- if ([ "$MODULE" == "ng2-activiti-processlist" ]); then
npm link ng2-activiti-tasklist;
fi
- npm install;
- npm run travis
- ls -ltrh ./node_modules/
script: npm run test
# Send coverage data to Coveralls
after_success:
- "cat ./coverage/report/lcov.info | ./node_modules/coveralls/bin/coveralls.js --verbose"
- bash <(curl -s https://codecov.io/bash)
cache:
directories:
- demo-shell-ng2/node_modules
- ng2-components/ng2-activiti-form/node_modules
- ng2-components/ng2-activiti-processlist/node_modules
- ng2-components/ng2-activiti-tasklist/node_modules
- ng2-components/ng2-alfresco-core/node_modules
- ng2-components/ng2-alfresco-datatable/node_modules
- ng2-components/ng2-alfresco-documentlist/node_modules
- ng2-components/ng2-alfresco-login/node_modules
- ng2-components/ng2-alfresco-search/node_modules
- ng2-components/ng2-alfresco-upload/node_modules
- ng2-components/ng2-alfresco-viewer/node_modules
- ng2-components/ng2-alfresco-webscript/node_modules
+16 -3
View File
@@ -2,9 +2,13 @@
The [Angular 2](https://angular.io/) based application development framework requires the following:
- An Alfresco Platform Repository to talk to, which has [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) enabled.
- An Alfresco Platform Repository (version [5.2.a-EA](https://wiki.alfresco.com/wiki/Community_file_list_201606-EA) or newer) to talk to, which has [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) enabled.
- [Node.js](https://nodejs.org/en/) JavaScript runtime.
- [npm](https://www.npmjs.com/) package manager for JavaScript.
**Verify that you are running at least node `v5.x.x` and npm `3.x.x`**
by running `node -v` and `npm -v` in a terminal/console window.
Older versions produce errors.
## Installing Alfresco
@@ -25,9 +29,18 @@ The web client that we are building with the application development framework w
So we need to tell the Alfresco server that any request that comes in from this custom web client should be allowed access
to the Content Repository. This is done by enabling CORS.
To enable CORS in the Alfresco Platform do the following:
To enable CORS in the Alfresco Platform do one of the following:
Modify *tomcat/webapps/alfresco/WEB-INF/web.xml* and uncomment the following section and update
**Download and install the enable CORS module**
This is the easiest way, add the [enablecors](https://artifacts.alfresco.com/nexus/service/local/repositories/releases/content/org/alfresco/enablecors/1.0/enablecors-1.0.jar)
platform module JAR to the *$ALF_INSTALL_DIR/modules/platform* directory and restart the server.
Note. by default the CORS filter that is enabled will allow any orgin.
**Manually update the web.xml file**
Modify *$ALF_INSTALL_DIR/tomcat/webapps/alfresco/WEB-INF/web.xml* and uncomment the following section and update
`cors.allowOrigin` to `http://localhost:3000`:
```
+17 -3
View File
@@ -3,12 +3,16 @@
[![Join the chat at https://gitter.im/Alfresco/alfresco-ng2-components](https://badges.gitter.im/Alfresco/alfresco-ng2-components.svg)](https://gitter.im/Alfresco/alfresco-ng2-components?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
<p>
<a title='Build Status' href="https://travis-ci.org/Alfresco/alfresco-ng2-components">
<a title='Build Status Travis' href="https://travis-ci.org/Alfresco/alfresco-ng2-components">
<img src='https://travis-ci.org/Alfresco/alfresco-ng2-components.svg?branch=master' alt='travis
Status' />
</a>
<a href='https://coveralls.io/github/Alfresco/alfresco-ng2-components'>
<img src='https://coveralls.io/repos/github/Alfresco/alfresco-ng2-components/badge.svg?t=NzxWxh' alt='Coverage Status' />
<a title='Build Status AppVeyor' href="https://ci.appveyor.com/project/alfresco/alfresco-ng2-components">
<img src='https://ci.appveyor.com/api/projects/status/github/Alfresco/alfresco-ng2-components' alt='travis
Status' />
</a>
<a href='https://codecov.io/gh/Alfresco/alfresco-ng2-components'>
<img src='https://img.shields.io/codecov/c/github/Alfresco/alfresco-ng2-components/master.svg?maxAge=2592000' alt='Coverage Status' />
</a>
<a href='https://github.com/Alfresco/alfresco-ng2-components/blob/master/LICENSE'>
<img src='https://img.shields.io/hexpm/l/plug.svg' alt='license' />
@@ -55,6 +59,7 @@ The following is a list of some of the components that you can use when building
- [Viewer](ng2-components/ng2-alfresco-viewer/README.md)
- [Login](ng2-components/ng2-alfresco-login/README.md)
- [Upload](ng2-components/ng2-alfresco-upload/README.md)
- [Webscript viewer](ng2-components/ng2-alfresco-webscript/README.md)
You can browse all the components at the following [page](http://devproducts.alfresco.com/).
@@ -81,4 +86,13 @@ To generate your Alfresco Angular 2 application you can use the following Yeoman
- [Yeoman Generator Angular 2 Alfresco application](https://github.com/Alfresco/generator-ng2-alfresco-app)
## Amazon AWS Elastic Beanstalk fast deploy
<p align="center">
<img alt="AmazonWebservices Logo.svg" src="https://upload.wikimedia.org/wikipedia/commons/1/1d/AmazonWebservices_Logo.svg" width="250" height="94">
</p>
To deploy directly on your AWS instance our demo shell click the button below:
<a title="Deploy to AWS" href="https://console.aws.amazon.com/elasticbeanstalk/home?region=us-west-2#/newApplication?applicationName=Alfresco&solutionStackName=Node.js&tierName=WebServer&sourceBundleUrl=https://s3-us-west-2.amazonaws.com/elasticbeanstalk-us-west-2-677901592050/2016210o6l-Archive-v7.zip" target="_blank"><img src="http://d0.awsstatic.com/product-marketing/Elastic%20Beanstalk/deploy-to-aws.png" height="40"></a>
+53
View File
@@ -0,0 +1,53 @@
# Test against this version of Node.js
branches:
only:
- master
environment:
nodejs_version: "5"
matrix:
- COMPONENT_NAME: ng2-alfresco-core
- COMPONENT_NAME: ng2-alfresco-datatable
- COMPONENT_NAME: ng2-alfresco-documentlist
- COMPONENT_NAME: ng2-alfresco-login
- COMPONENT_NAME: ng2-alfresco-search
- COMPONENT_NAME: ng2-alfresco-upload
- COMPONENT_NAME: ng2-alfresco-viewer
- COMPONENT_NAME: ng2-alfresco-webscript
- COMPONENT_NAME: ng2-activiti-form
- COMPONENT_NAME: ng2-activiti-tasklist
- COMPONENT_NAME: ng2-activiti-processlist
# Install scripts. (runs after repo cloning)
install:
# Get the latest stable version of Node.js or io.js
- ps: Install-Product node $env:nodejs_version
# install module
- IF %COMPONENT_NAME% NEQ ng2-alfresco-core (cd ng2-components/ng2-alfresco-core && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-alfresco-documentlist (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-activiti-processlist (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-activiti-processlist (cd ng2-components/ng2-activiti-form && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-activiti-processlist (cd ng2-components/ng2-activiti-tasklist && npm link ng2-alfresco-core && npm link ng2-alfresco-datatable && npm link ng2-activiti-form && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-activiti-tasklist (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-activiti-tasklist (cd ng2-components/ng2-activiti-form && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
- IF %COMPONENT_NAME% EQU ng2-alfresco-webscript (cd ng2-components/ng2-alfresco-datatable && npm link ng2-alfresco-core && npm install && npm link && cd ../../)
- cd ng2-components/%COMPONENT_NAME%
- IF %COMPONENT_NAME% NEQ ng2-alfresco-core (npm link ng2-alfresco-core)
- IF %COMPONENT_NAME% EQU ng2-alfresco-documentlist (npm link ng2-alfresco-datatable)
- IF %COMPONENT_NAME% EQU ng2-activiti-processlist (npm link ng2-alfresco-datatable)
- IF %COMPONENT_NAME% EQU ng2-activiti-processlist (npm link ng2-activiti-tasklist)
- IF %COMPONENT_NAME% EQU ng2-activiti-tasklist (npm link ng2-alfresco-datatable && npm link ng2-activiti-form)
- IF %COMPONENT_NAME% EQU ng2-alfresco-webscript (npm link ng2-alfresco-datatable)
- npm install
# Post-install test scripts.
test_script:
# Output useful info for debugging.
- node --version
- npm --version
# run tests
- npm test
# Don't actually build.
build: off
@@ -0,0 +1,18 @@
files:
"/opt/elasticbeanstalk/hooks/appdeploy/pre/50npm.sh" :
mode: "000775"
owner: root
group: root
content: |
#!/bin/bash
function error_exit
{
eventHelper.py --msg "$1" --severity ERROR
exit $2
}
export HOME=/home/ec2-user
echo "export home"
sudo curl -sL https://rpm.nodesource.com/setup_6.x | sudo -E bash -
sudo yum install -y nodejs
+5 -1
View File
@@ -4,4 +4,8 @@ bower_components/
app/**/*.js
app/**/*.js.map
.idea
dist/
dist/
# docker files
docker-compose.yml
Dockerfile
+77
View File
@@ -0,0 +1,77 @@
var port = process.env.PORT || 3000,
http = require('http'),
fs = require('fs'),
url = require('url'),
mime = require('mime'),
html = fs.readFileSync('index.html');
var log = function(entry) {
fs.appendFileSync('/tmp/sample-app.log', new Date().toISOString() + ' - ' + entry + '\n');
};
var server = http.createServer(function (req, res) {
// Parse the request containing file name
var pathname = url.parse(req.url).pathname;
// Print the name of the file for which request is made.
console.log("Request for " + pathname + " received.");
if (req.method === 'POST') {
var body = '';
req.on('data', function(chunk) {
body += chunk;
});
req.on('end', function() {
if (req.url === '/') {
log('Received message: ' + body);
} else if (req.url = '/scheduled') {
log('Received task ' + req.headers['x-aws-sqsd-taskname'] + ' scheduled at ' + req.headers['x-aws-sqsd-scheduled-at']);
}
res.writeHead(200, 'OK', {'Content-Type': 'text/plain'});
res.end();
});
} else {
var filename = pathname.substr(1);
if(filename === '' || filename.indexOf('.') === -1){
filename = 'index.html';
}
// Read the requested file content from file system
fs.readFile(filename, function (err, data) {
if (err) {
console.log(err, filename);
// HTTP Status: 404 : NOT FOUND
// Content Type: text/plain
res.writeHead(404, {'Content-Type': 'text/html'});
}else{
//Page found
// HTTP Status: 200 : OK
// Content Type: text/plain
var type = mime.lookup(filename);
console.log('type',type);
if (!res.getHeader('content-type')) {
var charset = mime.charsets.lookup(type);
res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
}
if(type.indexOf('image') > -1 || type.indexOf('font') > -1){
var img = fs.readFileSync(pathname.substr(1));
res.end(img, 'binary');
}else{
res.write(data.toString());
}
// Write the content of the file to response body
}
// Send the response body
res.end();
});
}
});
// Listen on port 3000, IP defaults to 127.0.0.1
server.listen(port);
// Put a friendly message on the terminal
console.log('Server running at http://127.0.0.1:' + port + '/');
+26 -13
View File
@@ -12,11 +12,13 @@
<!-- Navigation. We hide it in small screens. -->
<nav class="mdl-navigation mdl-layout--large-screen-only">
<a class="mdl-navigation__link" data-automation-id="files" href="" [routerLink]="['Files']">DocumentList</a>
<a class="mdl-navigation__link" data-automation-id="datatable" href="" [routerLink]="['DataTable']">DataTable</a>
<a class="mdl-navigation__link" data-automation-id="uploader" href="" [routerLink]="['Uploader']">Uploader</a>
<a class="mdl-navigation__link" data-automation-id="tasks" href="" [routerLink]="['Tasks']">Tasks</a>
<a class="mdl-navigation__link" data-automation-id="login" href="" [routerLink]="['Login']">Login</a>
<a class="mdl-navigation__link" data-automation-id="files" href="" routerLink="/files">DocumentList</a>
<a class="mdl-navigation__link" data-automation-id="datatable" href="" routerLink="/datatable">DataTable</a>
<a class="mdl-navigation__link" data-automation-id="uploader" href="" routerLink="/uploader">Uploader</a>
<a class="mdl-navigation__link" data-automation-id="activiti" href="" routerLink="/activiti">Activiti</a>
<a class="mdl-navigation__link" data-automation-id="webscript" href="" routerLink="/webscript">Webscript</a>
<a class="mdl-navigation__link" data-automation-id="login" href="" routerLink="/login">Login</a>
<a class="mdl-navigation__link" data-automation-id="about" href="" routerLink="/about">About</a>
</nav>
<!-- Right aligned menu below button -->
@@ -30,8 +32,8 @@
<li class="mdl-menu__item " (click)="changeLanguage('en')"><span class="flag-icon flag-icon-gb"></span> English</li>
<li class="mdl-menu__item" (click)="changeLanguage('gr')"><span class="flag-icon flag-icon-gr"></span> Greek</li>
<li class="mdl-menu__item" (click)="changeLanguage('it')"><span class="flag-icon flag-icon-it"></span> Italian</li>
<li *ngIf="!isLoggedIn()" class="mdl-menu__item" >More</li>
<li *ngIf="!isLoggedIn()" class="mdl-menu__item" [routerLink]="['Login']">Login</li>
<li *ngIf="!isLoggedIn()" class="mdl-menu__item">More</li>
<a *ngIf="!isLoggedIn()" class="mdl-menu__item" routerLink="/login">Login</a>
<li *ngIf="isLoggedIn()" class="mdl-menu__item" (click)="onLogout($event)">Logout</li>
</ul>
</div>
@@ -39,18 +41,29 @@
<div class="mdl-layout__drawer">
<span class="mdl-layout-title">Components List</span>
<nav class="mdl-navigation">
<a class="mdl-navigation__link" href="" [routerLink]="['Files']" (click)="hideDrawer()">DocumentList</a>
<a class="mdl-navigation__link" href="" [routerLink]="['DataTable']" (click)="hideDrawer()">DataTable</a>
<a class="mdl-navigation__link" href="" [routerLink]="['Uploader']" (click)="hideDrawer()">Uploader</a>
<a class="mdl-navigation__link" href="" [routerLink]="['Login']" (click)="hideDrawer()">Login</a>
<a class="mdl-navigation__link" href="" [routerLink]="['Tasks']" (click)="hideDrawer()">Tasks</a>
<a class="mdl-navigation__link" href="" routerLink="/files" (click)="hideDrawer()">DocumentList Demo</a>
<a class="mdl-navigation__link" href="" routerLink="/datatable" (click)="hideDrawer()">DataTable Demo</a>
<a class="mdl-navigation__link" href="" routerLink="/uploader" (click)="hideDrawer()">Uploader Demo</a>
<a class="mdl-navigation__link" href="" routerLink="/login" (click)="hideDrawer()">Login Demo</a>
<a class="mdl-navigation__link" href="" routerLink="/activiti" (click)="hideDrawer()">Activiti Components Demo</a>
<a class="mdl-navigation__link" href="" routerLink="/webscript" (click)="hideDrawer()">Webscript</a>
<a class="mdl-navigation__link" href="" routerLink="/about" (click)="hideDrawer()">About</a>
</nav>
<span class="mdl-layout-title">ECM host</span>
<nav class="mdl-navigation">
<input type="text" class="mdl-textfield__input" id="ecmHost" data-automation-id="ecmHost"
tabindex="1" (change)="onChangeECMHost($event)" value="{{ecmHost}}"/>
</nav>
<span class="mdl-layout-title">BPM host</span>
<nav class="mdl-navigation">
<input type="text" class="mdl-textfield__input" id="bpmHost" data-automation-id="bpmHost"
tabindex="1" (change)="onChangeBPMHost($event)" value="{{bpmHost}}"/>
</nav>
</div>
<main class="mdl-layout__content">
<div class="page-content">
<alfresco-login method="{{methodName}}"></alfresco-login>
<router-outlet></router-outlet>
<!--<auth-router-outlet></auth-router-outlet>-->
</div>
</main>
</div>
+40 -23
View File
@@ -16,8 +16,7 @@
*/
import { Component } from '@angular/core';
import { Router, RouteConfig, ROUTER_DIRECTIVES } from '@angular/router-deprecated';
import { FilesComponent } from './components/files/files.component';
import { ROUTER_DIRECTIVES, Router } from '@angular/router';
import {
MDL,
@@ -26,12 +25,8 @@ import {
AlfrescoPipeTranslate,
AlfrescoAuthenticationService
} from 'ng2-alfresco-core';
import { UploadButtonComponent } from 'ng2-alfresco-upload';
import { DataTableDemoComponent } from './components/datatable/datatable-demo.component';
import { SearchComponent } from './components/search/search.component';
import { SearchBarComponent } from './components/search/search-bar.component';
import { LoginDemoComponent } from './components/login/login-demo.component';
import { TasksDemoComponent } from './components/tasks/tasks-demo.component';
import { SearchBarComponent } from './components/index';
declare var document: any;
@@ -42,32 +37,36 @@ declare var document: any;
directives: [SearchBarComponent, ROUTER_DIRECTIVES, MDL],
pipes: [AlfrescoPipeTranslate]
})
@RouteConfig([
{path: '/home', name: 'Home', component: FilesComponent},
{path: '/files', name: 'Files', component: FilesComponent},
{path: '/datatable', name: 'DataTable', component: DataTableDemoComponent},
{path: '/', name: 'Login', component: LoginDemoComponent, useAsDefault: true},
{path: '/uploader', name: 'Uploader', component: UploadButtonComponent},
{path: '/login', name: 'Login', component: LoginDemoComponent},
{path: '/search', name: 'Search', component: SearchComponent},
{path: '/tasks', name: 'Tasks', component: TasksDemoComponent}
])
export class AppComponent {
translate: AlfrescoTranslationService;
searchTerm: string = '';
ecmHost: string = 'http://' + window.location.hostname + ':8080';
bpmHost: string = 'http://' + window.location.hostname + ':9999';
constructor(public auth: AlfrescoAuthenticationService,
public router: Router,
translate: AlfrescoTranslationService,
alfrescoSettingsService: AlfrescoSettingsService) {
alfrescoSettingsService.host = 'http://localhost:8080';
public alfrescoSettingsService: AlfrescoSettingsService) {
this.setEcmHost();
this.setBpmHost();
this.translate = translate;
this.translate.addTranslationFolder();
}
isActive(instruction: any[]): boolean {
return this.router.isRouteActive(this.router.generate(instruction));
public onChangeECMHost(event: KeyboardEvent): void {
console.log((<HTMLInputElement>event.target).value);
this.ecmHost = (<HTMLInputElement>event.target).value;
this.alfrescoSettingsService.ecmHost = this.ecmHost;
localStorage.setItem(`ecmHost`, this.ecmHost);
}
public onChangeBPMHost(event: KeyboardEvent): void {
console.log((<HTMLInputElement>event.target).value);
this.bpmHost = (<HTMLInputElement>event.target).value;
this.alfrescoSettingsService.bpmHost = this.bpmHost;
localStorage.setItem(`bpmHost`, this.bpmHost);
}
isLoggedIn(): boolean {
@@ -78,7 +77,7 @@ export class AppComponent {
event.preventDefault();
this.auth.logout()
.subscribe(
() => this.router.navigate(['Login'])
() => this.router.navigate(['/login'])
);
}
@@ -100,4 +99,22 @@ export class AppComponent {
// todo: workaround for drawer closing
document.querySelector('.mdl-layout').MaterialLayout.toggleDrawer();
}
private setEcmHost() {
if (localStorage.getItem(`ecmHost`)) {
this.alfrescoSettingsService.ecmHost = localStorage.getItem(`ecmHost`);
this.ecmHost = localStorage.getItem(`ecmHost`);
} else {
this.alfrescoSettingsService.ecmHost = this.ecmHost;
}
}
private setBpmHost() {
if (localStorage.getItem(`bpmHost`)) {
this.alfrescoSettingsService.bpmHost = localStorage.getItem(`bpmHost`);
this.bpmHost = localStorage.getItem(`bpmHost`);
} else {
this.alfrescoSettingsService.bpmHost = this.bpmHost;
}
}
}
+50
View File
@@ -0,0 +1,50 @@
/*!
* @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 { provideRouter, RouterConfig } from '@angular/router';
import {
FilesComponent,
UploadButtonComponent,
DataTableDemoComponent,
SearchComponent,
LoginDemoComponent,
ActivitiDemoComponent,
WebscriptComponent,
AboutComponent,
FormViewer
} from './components/index';
import { FormNodeViewer } from './components/activiti/form-node-viewer.component';
export const routes: RouterConfig = [
{ path: 'home', component: FilesComponent },
{ path: 'files', component: FilesComponent },
{ path: 'datatable', component: DataTableDemoComponent },
{ path: '', component: LoginDemoComponent },
{ path: 'uploader', component: UploadButtonComponent },
{ path: 'login', component: LoginDemoComponent },
{ path: 'search', component: SearchComponent },
{ path: 'activiti', component: ActivitiDemoComponent },
{ path: 'activiti/tasks/:id', component: FormViewer },
{ path: 'activiti/tasksnode/:id', component: FormNodeViewer },
{ path: 'webscript', component: WebscriptComponent },
{ path: 'about', component: AboutComponent }
];
export const appRouterProviders = [
provideRouter(routes)
];
@@ -0,0 +1,4 @@
<div style="padding: 10px">
<h3>Packages</h3>
<alfresco-datatable [data]="data"></alfresco-datatable>
</div>
@@ -0,0 +1,55 @@
/*!
* @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 } from '@angular/core';
import { Http } from '@angular/http';
import {
ALFRESCO_DATATABLE_DIRECTIVES,
ObjectDataTableAdapter /*,
DataSorting,
ObjectDataRow,
ObjectDataColumn*/
} from 'ng2-alfresco-datatable';
declare let __moduleName: string;
@Component({
moduleId: __moduleName,
selector: 'about-page',
templateUrl: './about.component.html',
directives: [ALFRESCO_DATATABLE_DIRECTIVES]
})
export class AboutComponent implements OnInit {
data: ObjectDataTableAdapter;
constructor(private http: Http) {}
ngOnInit() {
// this.data = new ObjectDataTableAdapter();
this.http.get('/versions').subscribe(response => {
let data = response.json() || {};
let packages = data.packages || [];
this.data = new ObjectDataTableAdapter(packages, [
{ type: 'text', key: 'name', title: 'Name', sortable: true },
{ type: 'text', key: 'version', title: 'Version', sortable: true }
]);
});
}
}
@@ -0,0 +1,14 @@
.activiti-task-list__item:hover {
cursor: pointer;
font-weight: bold;
}
.activiti {
background-color: #f5f5f5;
}
.task-column {
background-color: #f5f5f5;
padding: 10px 10px 10px 10px;
border: solid 2px rgb(31,188,210);
}
@@ -0,0 +1,65 @@
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
<header class="mdl-layout__header">
<!-- Tabs -->
<div class="mdl-layout__tab-bar mdl-js-ripple-effect">
<a href="#scroll-tab-1" class="mdl-layout__tab is-active">TASK LIST</a>
<a href="#scroll-tab-2" class="mdl-layout__tab">PROCESS LIST</a>
<a href="#scroll-tab-3" class="mdl-layout__tab">REPORT</a>
</div>
</header>
<main class="mdl-layout__content activiti">
<section class="mdl-layout__tab-panel is-active" id="scroll-tab-1">
<div class="page-content">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--2-col task-column mdl-shadow--2dp">
<span>Task Filters</span>
<activiti-filters (filterClick)="onTaskFilterClick($event)"></activiti-filters>
</div>
<div class="mdl-cell mdl-cell--3-col task-column mdl-shadow--2dp">
<span>Task List</span>
<activiti-tasklist *ngIf="isTaskListSelected()" [taskFilter]="taskFilter" [schemaColumn]="taskSchemaColumns"
(rowClick)="onTaskRowClick($event)" #activititasklist></activiti-tasklist>
</div>
<div class="mdl-cell mdl-cell--7-col task-column mdl-shadow--2dp">
<span>Task Details</span>
<activiti-task-details [taskId]="currentTaskId" #activitidetails></activiti-task-details>
</div>
</div>
</div>
</section>
<section class="mdl-layout__tab-panel" id="scroll-tab-2">
<div class="page-content">
<div class="page-content">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--2-col task-column">
<span>Process Filters</span>
<activiti-start-process [appId]="appId"></activiti-start-process>
<activiti-process-filters (filterClick)="onProcessFilterClick($event)"></activiti-process-filters>
</div>
<div class="mdl-cell mdl-cell--3-col task-column">
<span>Process List</span>
<activiti-process-instance-list [filter]="processFilter" [schemaColumn]="processSchemaColumns"
(rowClick)="onProcessRowClick($event)" #activitiprocesslist></activiti-process-instance-list>
</div>
<div class="mdl-cell mdl-cell--7-col task-column">
<span>Process Details</span>
<activiti-process-instance-details [processInstanceId]="currentProcessInstanceId" (taskFormCompleted)="taskFormCompleted()" (processCancelled)="processCancelled()" #activitiprocessdetails></activiti-process-instance-details>
</div>
</div>
</div>
</div>
</section>
<section class="mdl-layout__tab-panel" id="scroll-tab-3">
<div class="page-content"><!-- Your content goes here --></div>
</section>
<section class="mdl-layout__tab-panel" id="scroll-tab-4">
<div class="page-content"><!-- Your content goes here --></div>
</section>
<section class="mdl-layout__tab-panel" id="scroll-tab-5">
<div class="page-content"><!-- Your content goes here --></div>
</section>
<section class="mdl-layout__tab-panel" id="scroll-tab-6">
<div class="page-content"><!-- Your content goes here --></div>
</section>
</main>
</div>
@@ -0,0 +1,119 @@
/*!
* @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, AfterViewChecked, ViewChild, Input } from '@angular/core';
import { ALFRESCO_TASKLIST_DIRECTIVES } from 'ng2-activiti-tasklist';
import { ACTIVITI_PROCESSLIST_DIRECTIVES } from 'ng2-activiti-processlist';
import { ActivitiForm } from 'ng2-activiti-form';
declare let __moduleName: string;
declare var componentHandler;
@Component({
moduleId: __moduleName,
selector: 'activiti-demo',
templateUrl: './activiti-demo.component.html',
styleUrls: ['./activiti-demo.component.css'],
directives: [ALFRESCO_TASKLIST_DIRECTIVES, ACTIVITI_PROCESSLIST_DIRECTIVES, ActivitiForm]
})
export class ActivitiDemoComponent implements AfterViewChecked {
currentChoice: string = 'task-list';
@ViewChild('activitidetails')
activitidetails: any;
@ViewChild('activititasklist')
activititasklist: any;
@ViewChild('activitiprocesslist')
activitiprocesslist: any;
@ViewChild('activitiprocessdetails')
activitiprocessdetails: any;
currentTaskId: string;
currentProcessInstanceId: string;
taskSchemaColumns: any [] = [];
processSchemaColumns: any [] = [];
taskFilter: any;
processFilter: any;
@Input()
appId: string;
setChoice($event) {
this.currentChoice = $event.target.value;
}
isProcessListSelected() {
return this.currentChoice === 'process-list';
}
isTaskListSelected() {
return this.currentChoice === 'task-list';
}
constructor() {
this.taskSchemaColumns = [
{type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}
// {type: 'text', key: 'created', title: 'Created', sortable: true}
];
this.processSchemaColumns = [
{type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}
];
}
onTaskFilterClick(event: any) {
this.taskFilter = event;
this.activititasklist.load(this.taskFilter);
}
onProcessFilterClick(event: any) {
this.processFilter = event.filter;
this.activitiprocesslist.load(this.processFilter);
}
onTaskRowClick(taskId) {
this.currentTaskId = taskId;
this.activitidetails.loadDetails(this.currentTaskId);
}
onProcessRowClick(processInstanceId) {
this.currentProcessInstanceId = processInstanceId;
this.activitiprocessdetails.load(this.currentProcessInstanceId);
}
processCancelled(data: any) {
this.currentProcessInstanceId = null;
this.activitiprocesslist.reload();
}
taskFormCompleted(data: any) {
this.activitiprocesslist.reload();
}
ngAfterViewChecked() {
// workaround for MDL issues with dynamic components
if (componentHandler) {
componentHandler.upgradeAllRegistered();
}
}
}
@@ -0,0 +1,3 @@
.activiti-form-viewer {
margin: 10px;
}
@@ -0,0 +1,6 @@
<div class="activiti-form-viewer" *ngIf="nodeId">
<activiti-form [nodeId]="nodeId"
[saveMetadata]="true"
[path]="'/Sites/swsdp/documentLibrary'">
</activiti-form>
</div>
@@ -0,0 +1,62 @@
/*!
* @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, OnDestroy, AfterViewChecked } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivitiForm, FormService, EcmModelService, NodeService } from 'ng2-activiti-form';
import { Subscription } from 'rxjs/Rx';
declare let __moduleName: string;
declare var componentHandler;
@Component({
moduleId: __moduleName,
selector: 'form-node-viewer',
templateUrl: './form-node-viewer.component.html',
styleUrls: ['./form-node-viewer.component.css'],
directives: [ActivitiForm],
providers: [FormService, EcmModelService, NodeService]
})
export class FormNodeViewer implements OnInit, OnDestroy, AfterViewChecked {
nodeId: string;
private sub: Subscription;
constructor(private formService: FormService,
private route: ActivatedRoute,
private router: Router) {
}
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
this.nodeId = params['id'];
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
ngAfterViewChecked() {
// workaround for MDL issues with dynamic components
if (componentHandler) {
componentHandler.upgradeAllRegistered();
}
}
}
@@ -0,0 +1,3 @@
.activiti-form-viewer {
margin: 10px;
}
@@ -0,0 +1,11 @@
<div class="activiti-form-viewer" *ngIf="taskId">
<activiti-form [taskId]="taskId"></activiti-form>
<!--<activiti-form [formName]="'activitiForms:patientFolder'"-->
<!--[saveMetadata]="true"-->
<!--[path]="'/Sites/swsdp/documentLibrary'"-->
<!--[nameNode]="'test'"></activiti-form>-->
<!--<activiti-form [nodeId]="'e280be3a-6584-45a1-8bb5-89bfe070262e'"-->
<!--[saveMetadata]="true"-->
<!--[path]="'/Sites/swsdp/documentLibrary'">-->
<!--</activiti-form>-->
</div>
@@ -0,0 +1,62 @@
/*!
* @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, OnDestroy, AfterViewChecked } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivitiForm, FormService, EcmModelService, NodeService } from 'ng2-activiti-form';
import { Subscription } from 'rxjs/Rx';
declare let __moduleName: string;
declare var componentHandler;
@Component({
moduleId: __moduleName,
selector: 'form-viewer',
templateUrl: './form-viewer.component.html',
styleUrls: ['./form-viewer.component.css'],
directives: [ActivitiForm],
providers: [FormService, EcmModelService, NodeService]
})
export class FormViewer implements OnInit, OnDestroy, AfterViewChecked {
taskId: string;
private sub: Subscription;
constructor(private formService: FormService,
private route: ActivatedRoute,
private router: Router) {
}
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
this.taskId = params['id'];
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
ngAfterViewChecked() {
// workaround for MDL issues with dynamic components
if (componentHandler) {
componentHandler.upgradeAllRegistered();
}
}
}
@@ -0,0 +1,9 @@
.container {
margin: 10px;
}
@media only screen and (max-width: 640px) {
.container {
margin: 0;
}
}
@@ -1,118 +1,109 @@
<alfresco-upload-drag-area
[showUploadDialog]="true"
[currentFolderPath]="currentPath"
[uploaddirectory]=""
(onSuccess)="documentList.reload()">
<alfresco-document-list-breadcrumb
[currentFolderPath]="currentPath"
[target]="documentList">
</alfresco-document-list-breadcrumb>
<alfresco-document-list
#documentList
<div class="container">
<alfresco-upload-drag-area
[showUploadDialog]="true"
[currentFolderPath]="currentPath"
(preview)="showFile($event)"
(folderChange)="onFolderChanged($event)">
<!--
<empty-folder-content>
<template>
<h1>Sorry, no content here</h1>
</template>
</empty-folder-content>
-->
<content-columns>
<content-column source="$thumbnail" type="image"></content-column>
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.DISPLAY_NAME' | translate}}"
source="name"
class="full-width ellipsis-cell">
</content-column>
[uploaddirectory]=""
[versioning] = "versioning"
(onSuccess)="documentList.reload()">
<alfresco-document-list-breadcrumb
[currentFolderPath]="currentPath"
[target]="documentList">
</alfresco-document-list-breadcrumb>
<alfresco-document-list
#documentList
[currentFolderPath]="currentPath"
[contextMenuActions]="true"
[contentActions]="true"
(preview)="showFile($event)"
(folderChange)="onFolderChanged($event)">
<!--
<content-column
title="Type"
source="content.mimeType">
</content-column>
<empty-folder-content>
<template>
<h1>Sorry, no content here</h1>
</template>
</empty-folder-content>
-->
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.CREATED_BY' | translate}}"
source="createdByUser.displayName"
class="desktop-only">
</content-column>
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.CREATED_ON' | translate}}"
source="createdAt"
type="date"
format="medium"
class="desktop-only">
</content-column>
</content-columns>
<content-columns>
<content-column key="$thumbnail" type="image"></content-column>
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.DISPLAY_NAME' | translate}}"
key="name"
sortable="true"
class="full-width ellipsis-cell">
</content-column>
<!--
<content-column
title="Type"
source="content.mimeType">
</content-column>
-->
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.CREATED_BY' | translate}}"
key="createdByUser.displayName"
sortable="true"
class="desktop-only">
</content-column>
<content-column
title="{{'DOCUMENT_LIST.COLUMNS.CREATED_ON' | translate}}"
key="createdAt"
type="date"
format="medium"
sortable="true"
class="desktop-only">
</content-column>
</content-columns>
<content-actions>
<!-- folder actions -->
<content-action
target="folder"
type="button"
icon="delete"
handler="system1">
</content-action>
<content-action
target="folder"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.SYSTEM_1' | translate}}"
handler="system1">
</content-action>
<content-action
target="folder"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.CUSTOM' | translate}}"
(execute)="myFolderAction1($event)">
</content-action>
<content-action
target="folder"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
handler="delete">
</content-action>
<!-- document actions -->
<content-action
target="document"
type="button"
icon="account_circle"
handler="my-handler">
</content-action>
<content-action
target="document"
type="button"
icon="cloud_download"
handler="download">
</content-action>
<content-action
target="document"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
handler="download">
</content-action>
<content-action
target="document"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.SYSTEM_2' | translate}}"
handler="system2">
</content-action>
<content-action
target="document"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.CUSTOM' | translate}}"
(execute)="myCustomAction1($event)">
</content-action>
<content-action
target="document"
type="menu"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
handler="delete">
</content-action>
</content-actions>
</alfresco-document-list>
</alfresco-upload-drag-area>
<content-actions>
<!-- folder actions -->
<content-action
target="folder"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.SYSTEM_1' | translate}}"
handler="system1">
</content-action>
<content-action
target="folder"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.CUSTOM' | translate}}"
(execute)="myFolderAction1($event)">
</content-action>
<content-action
target="folder"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
handler="delete">
</content-action>
<!-- document actions -->
<content-action
target="document"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
handler="download">
</content-action>
<content-action
target="document"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.SYSTEM_2' | translate}}"
handler="system2">
</content-action>
<content-action
target="document"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.CUSTOM' | translate}}"
(execute)="myCustomAction1($event)">
</content-action>
<content-action
target="document"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
handler="delete">
</content-action>
<content-action
target="folder"
title="Activiti: View Form"
(execute)="viewActivitiForm($event)">
</content-action>
</content-actions>
</alfresco-document-list>
<alfresco-pagination
[provider]="documentList.data"
[supportedPageSizes]="[5, 10, 15, 20]">
</alfresco-pagination>
</alfresco-upload-drag-area>
</div>
<context-menu-holder></context-menu-holder>
@@ -135,37 +126,70 @@
</div>
<p style="width:250px;margin: 20px;">
<label for="switch-multiple-file" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input type="checkbox" id="switch-multiple-file" class="mdl-switch__input" (change)="toggleMultipleFileUpload()" >
<span class="mdl-switch__label">Multiple File Upload</span>
</label>
</p>
<h5>Single file upload</h5>
<alfresco-upload-button data-automation-id="single-file-upload"
[uploaddirectory]="currentPath"
[currentFolderPath]="currentPath"
(onSuccess)="documentList.reload()">
</alfresco-upload-button>
<h5>Folder upload</h5>
<alfresco-upload-button data-automation-id="folder-upload"
[uploaddirectory]="currentPath"
[currentFolderPath]="currentPath"
[uploadFolders]="true"
(onSuccess)="documentList.reload()">
</alfresco-upload-button>
<p style="width:250px;margin: 20px;">
<label for="switch-folder-upload" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input type="checkbox" id="switch-folder-upload" class="mdl-switch__input" (change)="toggleFolder()">
<span class="mdl-switch__label">Folder Upload</span>
</label>
</p>
<h5>Multiple file upload</h5>
<input type="text" data-automation-id="accepted-files-type" [(ngModel)]="acceptedFilesType">
<alfresco-upload-button data-automation-id="multiple-file-upload"
[uploaddirectory]="currentPath"
[currentFolderPath]="currentPath"
acceptedFilesType="{{acceptedFilesType}}"
[multipleFiles]="true"
(onSuccess)="documentList.reload()">
</alfresco-upload-button>
<p style="width:250px;margin: 20px;">
<label for="switch-accepted-file-type" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input type="checkbox" id="switch-accepted-file-type" class="mdl-switch__input" (change)="toggleAcceptedFilesType()">
<span class="mdl-switch__label">Filter extension</span>
</label>
</p>
<p style="width:250px;margin: 20px;">
<label for="switch-versioning" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input type="checkbox" id="switch-versioning" class="mdl-switch__input" (change)="toggleVersioning()">
<span class="mdl-switch__label">Versioning</span>
</label>
</p>
<h5>Upload</h5>
<br>
<div *ngIf="acceptedFilesTypeShow">
<span class="mdl-input__label">Extension accepted</span>
<input type="text" data-automation-id="accepted-files-type" [(ngModel)]="acceptedFilesType">
<br/>
</div>
<div *ngIf="!acceptedFilesTypeShow">
<alfresco-upload-button data-automation-id="multiple-file-upload"
[uploaddirectory]="currentPath"
[currentFolderPath]="currentPath"
[multipleFiles]="multipleFileUpload"
[uploadFolders]="folderUpload"
[versioning] = "versioning"
(onSuccess)="documentList.reload()">
<div class="mdl-spinner mdl-js-spinner is-active"></div>
</alfresco-upload-button>
</div>
<div *ngIf="acceptedFilesTypeShow">
<alfresco-upload-button data-automation-id="multiple-file-upload"
[uploaddirectory]="currentPath"
[currentFolderPath]="currentPath"
acceptedFilesType="{{acceptedFilesType}}"
[multipleFiles]="multipleFileUpload"
[uploadFolders]="folderUpload"
[versioning] = "versioning"
(onSuccess)="documentList.reload()">
<div class="mdl-spinner mdl-js-spinner is-active"></div>
</alfresco-upload-button>
</div>
<alfresco-viewer [(showViewer)]="fileShowed"
[urlFile]="urlFile"
[fileName]="fileName"
[mimeType]="mimeType"
[fileNodeId]="fileNodeId"
[overlayMode]="true">
<div class="mdl-spinner mdl-js-spinner is-active"></div>
</alfresco-viewer>
<file-uploading-dialog #fileDialog></file-uploading-dialog>
@@ -15,11 +15,16 @@
* limitations under the License.
*/
import { Component } from '@angular/core';
import { Component, OnInit, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import {
DOCUMENT_LIST_DIRECTIVES,
DOCUMENT_LIST_PROVIDERS,
DocumentActionsService
DocumentActionsService,
DocumentList,
ContentActionHandler,
DocumentActionModel,
FolderActionModel
} from 'ng2-alfresco-documentlist';
import {
MDL,
@@ -27,8 +32,10 @@ import {
CONTEXT_MENU_DIRECTIVES,
AlfrescoPipeTranslate
} from 'ng2-alfresco-core';
import { PaginationComponent } from 'ng2-alfresco-datatable';
import { ALFRESCO_ULPOAD_COMPONENTS } from 'ng2-alfresco-upload';
import { VIEWERCOMPONENT } from 'ng2-alfresco-viewer';
import { FormService } from 'ng2-activiti-form';
declare let __moduleName: string;
@@ -42,24 +49,31 @@ declare let __moduleName: string;
MDL,
ALFRESCO_ULPOAD_COMPONENTS,
VIEWERCOMPONENT,
CONTEXT_MENU_DIRECTIVES
CONTEXT_MENU_DIRECTIVES,
PaginationComponent
],
providers: [DOCUMENT_LIST_PROVIDERS],
providers: [DOCUMENT_LIST_PROVIDERS, FormService],
pipes: [AlfrescoPipeTranslate]
})
export class FilesComponent {
export class FilesComponent implements OnInit {
currentPath: string = '/Sites/swsdp/documentLibrary';
urlFile: string;
fileName: string;
mimeType: string;
fileNodeId: any;
fileShowed: boolean = false;
multipleFileUpload: boolean = false;
folderUpload: boolean = false;
acceptedFilesTypeShow: boolean = false;
versioning: boolean = false;
acceptedFilesType: string = '.jpg,.pdf,.js';
constructor(
private contentService: AlfrescoContentService,
documentActions: DocumentActionsService) {
@ViewChild(DocumentList)
documentList: DocumentList;
constructor(private contentService: AlfrescoContentService,
private documentActions: DocumentActionsService,
private formService: FormService,
private router: Router) {
documentActions.setHandler('my-handler', this.myDocumentActionHandler.bind(this));
}
@@ -77,9 +91,7 @@ export class FilesComponent {
showFile(event) {
if (event.value.entry.isFile) {
this.fileName = event.value.entry.name;
this.mimeType = event.value.entry.content.mimeType;
this.urlFile = this.contentService.getContentUrl(event.value);
this.fileNodeId = event.value.entry.id;
this.fileShowed = true;
} else {
this.fileShowed = false;
@@ -91,4 +103,56 @@ export class FilesComponent {
this.currentPath = event.path;
}
}
toggleMultipleFileUpload() {
this.multipleFileUpload = !this.multipleFileUpload;
return this.multipleFileUpload;
}
toggleFolder() {
this.multipleFileUpload = false;
this.folderUpload = !this.folderUpload;
return this.folderUpload;
}
toggleAcceptedFilesType() {
this.acceptedFilesTypeShow = !this.acceptedFilesTypeShow;
return this.acceptedFilesTypeShow;
}
toggleVersioning() {
this.versioning = !this.versioning;
return this.versioning;
}
ngOnInit() {
this.formService.getProcessDefinitions().subscribe(
defs => this.setupBpmActions(defs || []),
err => console.log(err)
);
}
viewActivitiForm(event?: any) {
this.router.navigate(['/activiti/tasksnode', event.value.entry.id]);
}
private setupBpmActions(actions: any[]) {
actions.map(def => {
let documentAction = new DocumentActionModel();
documentAction.title = 'Activiti: ' + (def.name || 'Unknown process');
documentAction.handler = this.getBpmActionHandler(def);
this.documentList.actions.push(documentAction);
let folderAction = new FolderActionModel();
folderAction.title = 'Activiti: ' + (def.name || 'Unknown process');
folderAction.handler = this.getBpmActionHandler(def);
this.documentList.actions.push(folderAction);
});
}
private getBpmActionHandler(processDefinition: any): ContentActionHandler {
return function (obj: any, target?: any) {
window.alert(`Starting BPM process: ${processDefinition.id}`);
}.bind(this);
}
}
+27
View File
@@ -0,0 +1,27 @@
/*!
* @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 { UploadButtonComponent } from 'ng2-alfresco-upload';
export { DataTableDemoComponent } from './datatable/datatable-demo.component';
export { SearchComponent } from './search/search.component';
export { SearchBarComponent } from './search/search-bar.component';
export { LoginDemoComponent } from './login/login-demo.component';
export { ActivitiDemoComponent } from './activiti/activiti-demo.component';
export { FormViewer } from './activiti/form-viewer.component';
export { WebscriptComponent } from './webscript/webscript.component';
export { AboutComponent } from './about/about.component';
export { FilesComponent } from './files/files.component';
@@ -1 +1,15 @@
<alfresco-login (onSuccess)="onLogin($event)" (onError)="onError($event)"></alfresco-login>
<div style="border-radius: 8px; position: absolute; background-color: papayawhip; color: cadetblue; left: 10px; top: 10px; z-index: 1;">
<p style="width:120px;margin: 20px;">
<label for="switch1" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input type="checkbox" id="switch1" class="mdl-switch__input" checked (click)="toggleECM(ecm.checked)" #ecm>
<span class="mdl-switch__label">ECM</span>
</label>
</p>
<p style="width:120px;margin: 20px;">
<label for="switch2" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input type="checkbox" id="switch2" class="mdl-switch__input" (click)="toggleBPM(bpm.checked)" #bpm>
<span class="mdl-switch__label">BPM</span>
</label>
</p>
</div>
<alfresco-login [providers]="providers" (onSuccess)="onLogin($event)" (onError)="onError($event)"></alfresco-login>
@@ -15,9 +15,9 @@
* limitations under the License.
*/
import { Component } from '@angular/core';
import { AlfrescoLoginComponent } from 'ng2-alfresco-login';
import { Router, ROUTER_DIRECTIVES } from '@angular/router-deprecated';
import {Component} from '@angular/core';
import {AlfrescoLoginComponent} from 'ng2-alfresco-login';
import {ROUTER_DIRECTIVES, Router} from '@angular/router';
declare let __moduleName: string;
@@ -30,16 +30,37 @@ declare let __moduleName: string;
})
export class LoginDemoComponent {
providers: string = 'ECM';
constructor(public router: Router) {
}
onLogin($event) {
console.log($event);
this.router.navigate(['Home']);
this.router.navigate(['/home']);
}
onError($event) {
console.log($event);
}
toggleECM(checked) {
if (checked && this.providers === 'BPM') {
this.providers = 'ALL';
} else if (checked) {
this.providers = 'ECM';
} else {
this.providers = undefined;
}
}
toggleBPM(checked) {
if (checked && this.providers === 'ECM') {
this.providers = 'ALL';
} else if (checked) {
this.providers = 'BPM';
} else {
this.providers = undefined;
}
}
}
@@ -1,6 +1,8 @@
<alfresco-search-control *ngIf="isLoggedIn()" [searchTerm]="searchTerm" [autocomplete]="false"
(searchChange)="searchTermChange($event);" (expand)="onExpandToggle($event);" (preview)="onFileClicked($event)"></alfresco-search-control>
<alfresco-viewer [(showViewer)]="fileShowed" [urlFile]="urlFile" [fileName]="fileName" [mimeType]="mimeType" [overlayMode]="true">
<alfresco-viewer [(showViewer)]="fileShowed"
[fileNodeId]="fileNodeId"
[overlayMode]="true">
<div class="mdl-spinner mdl-js-spinner is-active"></div>
</alfresco-viewer>
@@ -16,7 +16,7 @@
*/
import { Component, EventEmitter, Output } from '@angular/core';
import { Router } from '@angular/router-deprecated';
import { Router } from '@angular/router';
import { ALFRESCO_SEARCH_DIRECTIVES } from 'ng2-alfresco-search';
import { VIEWERCOMPONENT } from 'ng2-alfresco-viewer';
import {
@@ -56,15 +56,14 @@ export class SearchComponent {
previewName: string;
previewMimeType: string;
previewActive: boolean = false;
fileNodeId: string;
constructor(public contentService: AlfrescoContentService) {
}
onFileClicked(event) {
if (event.value.entry.isFile) {
this.previewName = event.value.entry.name;
this.previewMimeType = event.value.entry.content.mimeType;
this.previewContentUrl = this.contentService.getContentUrl(event.value);
this.fileNodeId = event.value.entry.id;
this.previewActive = true;
}
}
@@ -1,85 +0,0 @@
/*!
* @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 { Http, Headers, RequestOptions, Response } from '@angular/http';
// import { Observable } from 'rxjs/Rx';
@Injectable()
export class ActivitiService {
constructor(private http: Http) {}
login(username: string, password: string) {
let url = 'http://localhost:9999/activiti-app/app/authentication';
let headers = new Headers({
'Content-Type': 'application/x-www-form-urlencoded',
'Cache-Control': 'no-cache'
});
let options = new RequestOptions({ headers: headers });
let data = 'j_username='
+ encodeURIComponent(username)
+ '&j_password='
+ encodeURIComponent(password)
+ '&_spring_security_remember_me=true&submit=Login';
return this.http
.post(url, data, options)
.toPromise()
// .then(res => console.log(res))
.catch(this.handleError);
}
getTasks() {
// emulate filter value
let data = JSON.stringify({
'page': 0,
'filterId': 3,
'filter': {
'sort': 'created-desc',
'name': '',
'state': 'open',
'assignment': 'involved'
},
'appDefinitionId': null
});
let url = 'http://localhost:9999/activiti-app/app/rest/filter/tasks';
let headers = new Headers({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
});
let options = new RequestOptions({ headers: headers });
return this.http
.post(url, data, options)
.toPromise()
.then(this.parseJSON)
.catch(this.handleError);
}
private parseJSON(res: Response) {
let body = res.json();
return body.data || { };
}
private handleError(error: any) {
console.error('An error occurred', error);
return Promise.reject(error.message || error);
}
}
@@ -1,74 +0,0 @@
/*!
* @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 } from '@angular/core';
import { ActivitiService } from './activiti.service';
import {
ALFRESCO_DATATABLE_DIRECTIVES,
ObjectDataTableAdapter
} from 'ng2-alfresco-datatable';
@Component({
selector: 'tasks-demo',
template: `
<div class="container">
<alfresco-datatable [data]="tasks"></alfresco-datatable>
</div>
`,
directives: [ALFRESCO_DATATABLE_DIRECTIVES],
providers: [ActivitiService],
styles: [':host > .container { padding: 10px; }']
})
export class TasksDemoComponent implements OnInit {
tasks: ObjectDataTableAdapter;
constructor(
private activitiService: ActivitiService) {}
ngOnInit() {
this.activitiService
.login('denys.vuika@alfresco.com', 'test')
.then(() => {
this.activitiService
.getTasks()
.then((data) => {
let tasks = data || [];
console.log(tasks);
this.loadTasks(tasks);
});
});
}
private loadTasks(tasks: any[]) {
tasks = tasks.map(t => {
t.name = t.name || 'Nameless task';
if (t.name.length > 50) {
t.name = t.name.substring(0, 50) + '...';
}
return t;
});
this.tasks = new ObjectDataTableAdapter(tasks, [
{ type: 'text', key: 'id', title: 'Id'},
{ type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true },
{ type: 'text', key: 'formKey', title: 'Form Key', sortable: true },
{ type: 'text', key: 'created', title: 'Created', sortable: true }
]);
}
}
@@ -0,0 +1,62 @@
/*!
* @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 } from '@angular/core';
import {
CONTEXT_MENU_DIRECTIVES
} from 'ng2-alfresco-core';
import { WEBSCRIPTCOMPONENT } from 'ng2-alfresco-webscript';
@Component({
selector: 'alfresco-webscript-demo',
template: `
<label for="token"><b>Insert a scriptPath</b></label><br>
<input id="token" type="text" size="48" [(ngModel)]="scriptPath"><br>
<label for="token"><b>Insert a contextRoot</b></label><br>
<input id="token" type="text" size="48" [(ngModel)]="contextRoot"><br>
<label for="token"><b>Insert a servicePath</b></label><br>
<input id="token" type="text" size="48" [(ngModel)]="servicePath"><br>
<alfresco-webscript-get [scriptPath]="scriptPath"
[scriptArgs]="scriptArgs"
[contextRoot]="contextRoot"
[servicePath]="servicePath"
[contentType]="'HTML'"
(onSuccess)= "logData($event)"></alfresco-webscript-get>
`,
directives: [WEBSCRIPTCOMPONENT, CONTEXT_MENU_DIRECTIVES]
})
export class WebscriptComponent {
currentPath: string = '/';
authenticated: boolean;
host: string = 'http://127.0.0.1:8080';
scriptPath: string = 'sample/folder/Company%20Home';
contextRoot: string = 'alfresco';
servicePath: string = 'service';
scriptArgs: string = '';
logData(data) {
console.log(data);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

+782
View File
@@ -0,0 +1,782 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="110px"
height="110px" viewBox="0 0 110 110" style="enable-background:new 0 0 110 110;" xml:space="preserve">
<g id="Artboard" style="display:none;">
<rect x="-443" y="-79" style="display:inline;fill:#8C8C8C;" width="1412" height="743"/>
</g>
<g id="R-Multicolor" style="display:none;">
<circle style="display:inline;fill:#E04F5F;" cx="55" cy="55" r="55"/>
<g style="display:inline;">
<path style="fill:#FFFFFF;" d="M23,26.84c0-2.121,1.719-3.84,3.84-3.84H55l16,13.44v46.72c0,2.121-1.719,3.84-3.84,3.84H26.84
C24.719,87,23,85.281,23,83.16V26.84z"/>
<path style="fill:#F0F1F1;" d="M55,23v9.6c0,2.121,1.719,3.84,3.84,3.84H71L55,23z"/>
<polygon style="fill:#F4D0A1;" points="77,41.5 82,31 87,41.5 87,80 77,80 "/>
<polygon style="fill:#EDBC7C;" points="87,47 87,41.5 82,31 82,47 "/>
<path style="fill:#F79392;" d="M87,84c0,1.657-1.343,3-3,3h-4c-1.657,0-3-1.343-3-3v-2h10V84z"/>
<path style="fill:#E2E4E5;" d="M62,45c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15C61.55,44,62,44.45,62,45
L62,45z"/>
<path style="fill:#E2E4E5;" d="M56,50c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,49,56,49.45,56,50
L56,50z"/>
<path style="fill:#E2E4E5;" d="M62,58c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15C61.55,57,62,57.45,62,58
L62,58z"/>
<path style="fill:#E2E4E5;" d="M56,63c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,62,56,62.45,56,63
L56,63z"/>
<path style="fill:#E2E4E5;" d="M62,71c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15C61.55,70,62,70.45,62,71
L62,71z"/>
<path style="fill:#E2E4E5;" d="M56,76c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,75,56,75.45,56,76
L56,76z"/>
<g>
<path style="fill:#17B198;" d="M35.067,52l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.024-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.425,0.984-0.482,1.408-0.128c0.424,0.354,0.482,0.984,0.128,1.408L35.067,52z"/>
</g>
<g>
<path style="fill:#17B198;" d="M35.067,65l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.024-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.425,0.984-0.482,1.408-0.128c0.424,0.354,0.482,0.984,0.128,1.408L35.067,65z"/>
</g>
<g>
<path style="fill:#17B198;" d="M35.067,78l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.024-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.425,0.984-0.482,1.408-0.128c0.424,0.354,0.482,0.984,0.128,1.408L35.067,78z"/>
</g>
<rect x="77" y="79" style="fill:#FACB1B;" width="10" height="3"/>
<rect x="77" y="80" style="fill:#FBE158;" width="5" height="2"/>
<path style="fill:#3E3E3F;" d="M82.013,30.984l-1.857,3.899c0.512,0.678,1.152,1.101,1.857,1.101s1.345-0.422,1.857-1.101
L82.013,30.984z"/>
<path style="fill:#0484AB;" d="M84.5,43c-1.154,0-2.126-0.782-2.413-1.845c-0.026-0.097-0.147-0.097-0.173,0
C81.626,42.218,80.654,43,79.5,43c-1.052,0-1.953-0.65-2.322-1.571C77.139,41.332,77,41.359,77,41.464V80h10V41.464
c0-0.105-0.139-0.131-0.178-0.034C86.453,42.35,85.552,43,84.5,43z"/>
<path style="fill:#21B2D1;" d="M77,80V41.493c0-0.111,0.147-0.139,0.19-0.036C77.565,42.363,78.458,43,79.5,43
c1.159,0,2.134-0.789,2.417-1.859C81.931,41.089,82,41.096,82,41.15V80H77z"/>
<path style="fill:#F7B4B4;" d="M77,82v2c0,1.657,1.343,3,3,3h2v-5H77z"/>
</g>
</g>
<g id="Multicolor">
<g>
<path style="fill:#FFFFFF;" d="M23,26.84c0-2.121,1.719-3.84,3.84-3.84H55l16,13.44v46.72c0,2.121-1.719,3.84-3.84,3.84H26.84
C24.719,87,23,85.281,23,83.16V26.84z"/>
<path style="fill:#F0F1F1;" d="M55,23v9.6c0,2.121,1.719,3.84,3.84,3.84H71L55,23z"/>
<polygon style="fill:#F4D0A1;" points="77,41.5 82,31 87,41.5 87,80 77,80 "/>
<polygon style="fill:#EDBC7C;" points="87,47 87,41.5 82,31 82,47 "/>
<path style="fill:#F79392;" d="M87,84c0,1.657-1.343,3-3,3h-4c-1.657,0-3-1.343-3-3v-2h10V84z"/>
<path style="fill:#E2E4E5;" d="M62,45c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15C61.55,44,62,44.45,62,45
L62,45z"/>
<path style="fill:#E2E4E5;" d="M56,50c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,49,56,49.45,56,50
L56,50z"/>
<path style="fill:#E2E4E5;" d="M62,58c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15C61.55,57,62,57.45,62,58
L62,58z"/>
<path style="fill:#E2E4E5;" d="M56,63c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,62,56,62.45,56,63
L56,63z"/>
<path style="fill:#E2E4E5;" d="M62,71c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15C61.55,70,62,70.45,62,71
L62,71z"/>
<path style="fill:#E2E4E5;" d="M56,76c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,75,56,75.45,56,76
L56,76z"/>
<g>
<path style="fill:#17B198;" d="M35.067,52l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.024-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.425,0.984-0.482,1.408-0.128c0.424,0.354,0.482,0.984,0.128,1.408L35.067,52z"/>
</g>
<g>
<path style="fill:#17B198;" d="M35.067,65l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.024-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.425,0.984-0.482,1.408-0.128c0.424,0.354,0.482,0.984,0.128,1.408L35.067,65z"/>
</g>
<g>
<path style="fill:#17B198;" d="M35.067,78l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.024-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.425,0.984-0.482,1.408-0.128c0.424,0.354,0.482,0.984,0.128,1.408L35.067,78z"/>
</g>
<rect x="77" y="79" style="fill:#FACB1B;" width="10" height="3"/>
<rect x="77" y="80" style="fill:#FBE158;" width="5" height="2"/>
<path style="fill:#3E3E3F;" d="M82.013,30.984l-1.857,3.899c0.512,0.678,1.152,1.101,1.857,1.101s1.345-0.422,1.857-1.101
L82.013,30.984z"/>
<path style="fill:#0484AB;" d="M84.5,43c-1.154,0-2.126-0.782-2.413-1.845c-0.026-0.097-0.147-0.097-0.173,0
C81.626,42.218,80.654,43,79.5,43c-1.052,0-1.953-0.65-2.322-1.571C77.139,41.332,77,41.359,77,41.464V80h10V41.464
c0-0.105-0.139-0.131-0.178-0.034C86.453,42.35,85.552,43,84.5,43z"/>
<path style="fill:#21B2D1;" d="M77,80V41.493c0-0.111,0.147-0.139,0.19-0.036C77.565,42.363,78.458,43,79.5,43
c1.159,0,2.134-0.789,2.417-1.859C81.931,41.089,82,41.096,82,41.15V80H77z"/>
<path style="fill:#F7B4B4;" d="M77,82v2c0,1.657,1.343,3,3,3h2v-5H77z"/>
</g>
</g>
<g id="Blue" style="display:none;">
<g style="display:inline;">
<g>
<path style="fill:#FFFFFF;" d="M23,26.84c0-2.121,1.719-3.84,3.84-3.84H55l16,13.44v46.72c0,2.121-1.719,3.84-3.84,3.84H26.84
C24.719,87,23,85.281,23,83.16V26.84z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M55,23v9.6c0,2.121,1.719,3.84,3.84,3.84H71L55,23z"/>
</g>
<g>
<polygon style="fill:#FFFFFF;" points="77,41.5 82,31 87,41.5 87,80 77,80 "/>
</g>
<g>
<polygon style="fill:#E1F5FA;" points="87,47 87,41.5 82,31 82,47 "/>
</g>
<g>
<path style="fill:#E1F5FA;" d="M87,84c0,1.657-1.343,3-3,3h-4c-1.657,0-3-1.343-3-3v-2h10V84z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M62,45c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,44,62,44.45,62,45L62,45z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M56,50c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,49,56,49.45,56,50
L56,50z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M62,58c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,57,62,57.45,62,58L62,58z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M56,63c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,62,56,62.45,56,63
L56,63z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M62,71c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,70,62,70.45,62,71L62,71z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M56,76c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,75,56,75.45,56,76
L56,76z"/>
</g>
<g>
<g>
<path style="fill:#009FC7;" d="M35.067,52l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L35.067,52z"/>
</g>
</g>
<g>
<g>
<path style="fill:#009FC7;" d="M35.067,65l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L35.067,65z"/>
</g>
</g>
<g>
<g>
<path style="fill:#009FC7;" d="M35.067,78l-3.774-3.774c-0.391-0.39-0.391-1.023,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L35.067,78z"/>
</g>
</g>
<g>
<rect x="77" y="79" style="fill:#0F6175;" width="10" height="3"/>
</g>
<g>
<rect x="77" y="80" style="fill:#33899E;" width="5" height="2"/>
</g>
<g>
<path style="fill:#0C333D;" d="M82.013,30.984l-1.857,3.899c0.512,0.678,1.152,1.101,1.857,1.101s1.345-0.423,1.857-1.101
L82.013,30.984z"/>
</g>
<g>
<path style="fill:#0081A1;" d="M84.5,43c-1.154,0-2.126-0.782-2.414-1.845c-0.026-0.097-0.147-0.097-0.173,0
C81.626,42.218,80.654,43,79.5,43c-1.052,0-1.953-0.65-2.322-1.57C77.139,41.332,77,41.359,77,41.464V80h10V41.464
c0-0.105-0.139-0.132-0.178-0.034C86.453,42.35,85.552,43,84.5,43z"/>
</g>
<g>
<path style="fill:#009FC7;" d="M77,80V41.494c0-0.112,0.146-0.14,0.19-0.037C77.565,42.363,78.458,43,79.5,43
c1.159,0,2.134-0.789,2.416-1.858C81.931,41.089,82,41.096,82,41.149V80H77z"/>
</g>
<g>
<path style="fill:#FFFFFF;" d="M77,82v2c0,1.657,1.343,3,3,3h2v-5H77z"/>
</g>
</g>
</g>
<g id="R-Blue" style="display:none;">
<g style="display:inline;">
<circle style="fill:#81D2EB;" cx="55" cy="55" r="55"/>
</g>
<g style="display:inline;">
<g>
<path style="fill:#FFFFFF;" d="M23,26.84c0-2.121,1.719-3.84,3.84-3.84H55l16,13.44v46.72c0,2.121-1.719,3.84-3.84,3.84H26.84
C24.719,87,23,85.281,23,83.16V26.84z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M55,23v9.6c0,2.121,1.719,3.84,3.84,3.84H71L55,23z"/>
</g>
<g>
<polygon style="fill:#FFFFFF;" points="77,41.5 82,31 87,41.5 87,80 77,80 "/>
</g>
<g>
<polygon style="fill:#E1F5FA;" points="87,47 87,41.5 82,31 82,47 "/>
</g>
<g>
<path style="fill:#E1F5FA;" d="M87,84c0,1.657-1.343,3-3,3h-4c-1.657,0-3-1.343-3-3v-2h10V84z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M62,45c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,44,62,44.45,62,45L62,45z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M56,50c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,49,56,49.45,56,50
L56,50z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M62,58c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,57,62,57.45,62,58L62,58z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M56,63c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,62,56,62.45,56,63
L56,63z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M62,71c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,70,62,70.45,62,71L62,71z"/>
</g>
<g>
<path style="fill:#BBE7F2;" d="M56,76c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,75,56,75.45,56,76
L56,76z"/>
</g>
<g>
<g>
<path style="fill:#009FC7;" d="M35.067,52l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L35.067,52z"/>
</g>
</g>
<g>
<g>
<path style="fill:#009FC7;" d="M35.067,65l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L35.067,65z"/>
</g>
</g>
<g>
<g>
<path style="fill:#009FC7;" d="M35.067,78l-3.774-3.774c-0.391-0.39-0.391-1.023,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L35.067,78z"/>
</g>
</g>
<g>
<rect x="77" y="79" style="fill:#0F6175;" width="10" height="3"/>
</g>
<g>
<rect x="77" y="80" style="fill:#33899E;" width="5" height="2"/>
</g>
<g>
<path style="fill:#0C333D;" d="M82.013,30.984l-1.857,3.899c0.512,0.678,1.152,1.101,1.857,1.101s1.345-0.423,1.857-1.101
L82.013,30.984z"/>
</g>
<g>
<path style="fill:#0081A1;" d="M84.5,43c-1.154,0-2.126-0.782-2.414-1.845c-0.026-0.097-0.147-0.097-0.173,0
C81.626,42.218,80.654,43,79.5,43c-1.052,0-1.953-0.65-2.322-1.57C77.139,41.332,77,41.359,77,41.464V80h10V41.464
c0-0.105-0.139-0.132-0.178-0.034C86.453,42.35,85.552,43,84.5,43z"/>
</g>
<g>
<path style="fill:#009FC7;" d="M77,80V41.494c0-0.112,0.146-0.14,0.19-0.037C77.565,42.363,78.458,43,79.5,43
c1.159,0,2.134-0.789,2.416-1.858C81.931,41.089,82,41.096,82,41.149V80H77z"/>
</g>
<g>
<path style="fill:#FFFFFF;" d="M77,82v2c0,1.657,1.343,3,3,3h2v-5H77z"/>
</g>
</g>
</g>
<g id="Green" style="display:none;">
<g style="display:inline;">
<g>
<path style="fill:#FFFFFF;" d="M23,26.84c0-2.121,1.719-3.84,3.84-3.84H55l16,13.44v46.72c0,2.121-1.719,3.84-3.84,3.84H26.84
C24.719,87,23,85.281,23,83.16V26.84z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M55,23v9.6c0,2.121,1.719,3.84,3.84,3.84H71L55,23z"/>
</g>
<g>
<polygon style="fill:#FFFFFF;" points="77,41.5 82,31 87,41.5 87,80 77,80 "/>
</g>
<g>
<polygon style="fill:#D7FCF9;" points="87,47 87,41.5 82,31 82,47 "/>
</g>
<g>
<path style="fill:#D7FCF9;" d="M87,84c0,1.657-1.343,3-3,3h-4c-1.657,0-3-1.343-3-3v-2h10V84z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M62,45c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,44,62,44.45,62,45L62,45z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M56,50c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,49,56,49.45,56,50
L56,50z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M62,58c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,57,62,57.45,62,58L62,58z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M56,63c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,62,56,62.45,56,63
L56,63z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M62,71c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,70,62,70.45,62,71L62,71z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M56,76c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,75,56,75.45,56,76
L56,76z"/>
</g>
<g>
<g>
<path style="fill:#00B8A5;" d="M35.067,52l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.481,0.984,0.128,1.408L35.067,52z"/>
</g>
</g>
<g>
<g>
<path style="fill:#00B8A5;" d="M35.067,65l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.481,0.984,0.128,1.408L35.067,65z"/>
</g>
</g>
<g>
<g>
<path style="fill:#00B8A5;" d="M35.067,78l-3.774-3.774c-0.391-0.39-0.391-1.023,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.481,0.984,0.128,1.408L35.067,78z"/>
</g>
</g>
<g>
<rect x="77" y="79" style="fill:#14635B;" width="10" height="3"/>
</g>
<g>
<rect x="77" y="80" style="fill:#3E948B;" width="5" height="2"/>
</g>
<g>
<path style="fill:#0A3631;" d="M82.013,30.984l-1.857,3.899c0.512,0.678,1.152,1.101,1.857,1.101s1.345-0.423,1.857-1.101
L82.013,30.984z"/>
</g>
<g>
<path style="fill:#009687;" d="M84.5,43c-1.154,0-2.126-0.782-2.414-1.845c-0.026-0.097-0.147-0.097-0.173,0
C81.625,42.218,80.654,43,79.5,43c-1.052,0-1.953-0.65-2.322-1.57C77.139,41.332,77,41.359,77,41.464V80h10V41.464
c0-0.105-0.139-0.132-0.178-0.034C86.453,42.35,85.552,43,84.5,43z"/>
</g>
<g>
<path style="fill:#00B8A5;" d="M77,80V41.494c0-0.112,0.146-0.14,0.19-0.037C77.565,42.363,78.458,43,79.5,43
c1.159,0,2.134-0.789,2.416-1.858C81.931,41.089,82,41.096,82,41.149V80H77z"/>
</g>
<g>
<path style="fill:#FFFFFF;" d="M77,82v2c0,1.657,1.343,3,3,3h2v-5H77z"/>
</g>
</g>
</g>
<g id="R-Green" style="display:none;">
<g style="display:inline;">
<circle style="fill:#87E0C8;" cx="55" cy="55" r="55"/>
</g>
<g style="display:inline;">
<g>
<path style="fill:#FFFFFF;" d="M23,26.84c0-2.121,1.719-3.84,3.84-3.84H55l16,13.44v46.72C71,85.281,69.28,87,67.16,87H26.84
C24.719,87,23,85.281,23,83.16V26.84z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M55,23v9.6c0,2.121,1.719,3.84,3.84,3.84H71L55,23z"/>
</g>
<g>
<polygon style="fill:#FFFFFF;" points="77,41.5 82,31 87,41.5 87,80 77,80 "/>
</g>
<g>
<polygon style="fill:#D7FCF9;" points="87,47 87,41.5 82,31 82,47 "/>
</g>
<g>
<path style="fill:#D7FCF9;" d="M87,84c0,1.657-1.343,3-3,3h-4c-1.657,0-3-1.343-3-3v-2h10V84z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M62,45c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,44,62,44.45,62,45L62,45z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M56,50c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,49,56,49.45,56,50
L56,50z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M62,58c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,57,62,57.45,62,58L62,58z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M56,63c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,62,56,62.45,56,63
L56,63z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M62,71c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,70,62,70.45,62,71L62,71z"/>
</g>
<g>
<path style="fill:#AAF0E9;" d="M56,76c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,75,56,75.45,56,76
L56,76z"/>
</g>
<g>
<g>
<path style="fill:#00B8A5;" d="M35.067,52l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.481,0.984,0.128,1.408L35.067,52z"/>
</g>
</g>
<g>
<g>
<path style="fill:#00B8A5;" d="M35.067,65l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.481,0.984,0.128,1.408L35.067,65z"/>
</g>
</g>
<g>
<g>
<path style="fill:#00B8A5;" d="M35.067,78l-3.774-3.774c-0.391-0.39-0.391-1.023,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.481,0.984,0.128,1.408L35.067,78z"/>
</g>
</g>
<g>
<rect x="77" y="79" style="fill:#14635B;" width="10" height="3"/>
</g>
<g>
<rect x="77" y="80" style="fill:#3E948B;" width="5" height="2"/>
</g>
<g>
<path style="fill:#0A3631;" d="M82.013,30.984l-1.857,3.899c0.512,0.678,1.152,1.101,1.857,1.101
c0.705,0,1.345-0.423,1.857-1.101L82.013,30.984z"/>
</g>
<g>
<path style="fill:#009687;" d="M84.5,43c-1.154,0-2.125-0.782-2.414-1.845c-0.026-0.097-0.147-0.097-0.173,0
C81.625,42.218,80.654,43,79.5,43c-1.052,0-1.953-0.65-2.322-1.57C77.139,41.332,77,41.359,77,41.464V80h10V41.464
c0-0.105-0.139-0.132-0.178-0.034C86.452,42.35,85.552,43,84.5,43z"/>
</g>
<g>
<path style="fill:#00B8A5;" d="M77,80V41.494c0-0.112,0.146-0.14,0.19-0.037C77.565,42.363,78.458,43,79.5,43
c1.159,0,2.134-0.789,2.417-1.858C81.93,41.089,82,41.096,82,41.149V80H77z"/>
</g>
<g>
<path style="fill:#FFFFFF;" d="M77,82v2c0,1.657,1.343,3,3,3h2v-5H77z"/>
</g>
</g>
</g>
<g id="Red" style="display:none;">
<g style="display:inline;">
<g>
<path style="fill:#FFFFFF;" d="M22.896,26.84c0-2.121,1.719-3.84,3.84-3.84h28.16l16,13.44v46.72c0,2.121-1.719,3.84-3.84,3.84
h-40.32c-2.121,0-3.84-1.719-3.84-3.84V26.84z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M54.896,23v9.6c0,2.121,1.719,3.84,3.84,3.84h12.16L54.896,23z"/>
</g>
<g>
<polygon style="fill:#FFFFFF;" points="76.896,41.5 81.896,31 86.896,41.5 86.896,80 76.896,80 "/>
</g>
<g>
<polygon style="fill:#FFEFED;" points="86.896,47 86.896,41.5 81.896,31 81.896,47 "/>
</g>
<g>
<path style="fill:#FFEFED;" d="M86.896,84c0,1.657-1.343,3-3,3h-4c-1.657,0-3-1.343-3-3v-2h10V84z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M61.896,45c0,0.55-0.45,1-1,1h-15c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.446,44,61.896,44.45,61.896,45L61.896,45z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M55.896,50c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9
C55.446,49,55.896,49.45,55.896,50L55.896,50z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M61.896,58c0,0.55-0.45,1-1,1h-15c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.446,57,61.896,57.45,61.896,58L61.896,58z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M55.896,63c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9
C55.446,62,55.896,62.45,55.896,63L55.896,63z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M61.896,71c0,0.55-0.45,1-1,1h-15c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.446,70,61.896,70.45,61.896,71L61.896,71z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M55.896,76c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9
C55.446,75,55.896,75.45,55.896,76L55.896,76z"/>
</g>
<g>
<g>
<path style="fill:#E54B44;" d="M34.963,52l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L34.963,52z"/>
</g>
</g>
<g>
<g>
<path style="fill:#E54B44;" d="M34.963,65l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L34.963,65z"/>
</g>
</g>
<g>
<g>
<path style="fill:#E54B44;" d="M34.963,78l-3.774-3.774c-0.391-0.39-0.391-1.023,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L34.963,78z"/>
</g>
</g>
<g>
<rect x="76.896" y="79" style="fill:#7D261E;" width="10" height="3"/>
</g>
<g>
<rect x="76.896" y="80" style="fill:#B84E44;" width="5" height="2"/>
</g>
<g>
<path style="fill:#4A1410;" d="M81.909,30.984l-1.857,3.899c0.512,0.678,1.152,1.101,1.857,1.101s1.345-0.423,1.857-1.101
L81.909,30.984z"/>
</g>
<g>
<path style="fill:#C23023;" d="M84.396,43c-1.154,0-2.126-0.782-2.414-1.845c-0.026-0.097-0.147-0.097-0.173,0
C81.521,42.218,80.55,43,79.396,43c-1.052,0-1.953-0.65-2.322-1.57c-0.039-0.098-0.178-0.071-0.178,0.034V80h10V41.464
c0-0.105-0.139-0.132-0.178-0.034C86.349,42.35,85.448,43,84.396,43z"/>
</g>
<g>
<path style="fill:#E54B44;" d="M76.896,80V41.494c0-0.112,0.146-0.14,0.19-0.037C77.461,42.363,78.354,43,79.396,43
c1.159,0,2.134-0.789,2.416-1.858c0.014-0.052,0.084-0.046,0.084,0.008V80H76.896z"/>
</g>
<g>
<path style="fill:#FFFFFF;" d="M76.896,82v2c0,1.657,1.343,3,3,3h2v-5H76.896z"/>
</g>
</g>
</g>
<g id="R-Red" style="display:none;">
<g style="display:inline;">
<circle style="fill:#FABBAF;" cx="55" cy="55" r="55"/>
</g>
<g style="display:inline;">
<g>
<path style="fill:#FFFFFF;" d="M23,26.84c0-2.121,1.719-3.84,3.84-3.84H55l16,13.44v46.72c0,2.121-1.719,3.84-3.84,3.84H26.84
C24.719,87,23,85.281,23,83.16V26.84z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M55,23v9.6c0,2.121,1.719,3.84,3.84,3.84H71L55,23z"/>
</g>
<g>
<polygon style="fill:#FFFFFF;" points="77,41.5 82,31 87,41.5 87,80 77,80 "/>
</g>
<g>
<polygon style="fill:#FFEFED;" points="87,47 87,41.5 82,31 82,47 "/>
</g>
<g>
<path style="fill:#FFEFED;" d="M87,84c0,1.657-1.343,3-3,3h-4c-1.657,0-3-1.343-3-3v-2h10V84z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M62,45c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,44,62,44.45,62,45L62,45z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M56,50c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,49,56,49.45,56,50
L56,50z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M62,58c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,57,62,57.45,62,58L62,58z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M56,63c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,62,56,62.45,56,63
L56,63z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M62,71c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,70,62,70.45,62,71L62,71z"/>
</g>
<g>
<path style="fill:#FFD7D4;" d="M56,76c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,75,56,75.45,56,76
L56,76z"/>
</g>
<g>
<g>
<path style="fill:#E54B44;" d="M35.067,52l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L35.067,52z"/>
</g>
</g>
<g>
<g>
<path style="fill:#E54B44;" d="M35.067,65l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L35.067,65z"/>
</g>
</g>
<g>
<g>
<path style="fill:#E54B44;" d="M35.067,78l-3.774-3.774c-0.391-0.39-0.391-1.023,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L35.067,78z"/>
</g>
</g>
<g>
<rect x="77" y="79" style="fill:#7D261E;" width="10" height="3"/>
</g>
<g>
<rect x="77" y="80" style="fill:#B84E44;" width="5" height="2"/>
</g>
<g>
<path style="fill:#4A1410;" d="M82.013,30.984l-1.857,3.899c0.512,0.678,1.152,1.101,1.857,1.101s1.345-0.423,1.857-1.101
L82.013,30.984z"/>
</g>
<g>
<path style="fill:#C23023;" d="M84.5,43c-1.154,0-2.126-0.782-2.414-1.845c-0.026-0.097-0.147-0.097-0.173,0
C81.626,42.218,80.654,43,79.5,43c-1.052,0-1.953-0.65-2.322-1.57C77.139,41.332,77,41.359,77,41.464V80h10V41.464
c0-0.105-0.139-0.132-0.178-0.034C86.453,42.35,85.552,43,84.5,43z"/>
</g>
<g>
<path style="fill:#E54B44;" d="M77,80V41.494c0-0.112,0.146-0.14,0.19-0.037C77.565,42.363,78.458,43,79.5,43
c1.159,0,2.134-0.789,2.416-1.858C81.931,41.089,82,41.096,82,41.149V80H77z"/>
</g>
<g>
<path style="fill:#FFFFFF;" d="M77,82v2c0,1.657,1.343,3,3,3h2v-5H77z"/>
</g>
</g>
</g>
<g id="Yellow" style="display:none;">
<g style="display:inline;">
<g>
<path style="fill:#FFFFFF;" d="M22.896,26.84c0-2.121,1.719-3.84,3.84-3.84h28.16l16,13.44v46.72c0,2.121-1.719,3.84-3.84,3.84
h-40.32c-2.121,0-3.84-1.719-3.84-3.84V26.84z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M54.896,23v9.6c0,2.121,1.719,3.84,3.84,3.84h12.16L54.896,23z"/>
</g>
<g>
<polygon style="fill:#FFFFFF;" points="76.896,41.5 81.896,31 86.896,41.5 86.896,80 76.896,80 "/>
</g>
<g>
<polygon style="fill:#FFF5D9;" points="86.896,47 86.896,41.5 81.896,31 81.896,47 "/>
</g>
<g>
<path style="fill:#FFF5D9;" d="M86.896,84c0,1.657-1.343,3-3,3h-4c-1.657,0-3-1.343-3-3v-2h10V84z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M61.896,45c0,0.55-0.45,1-1,1h-15c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.446,44,61.896,44.45,61.896,45L61.896,45z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M55.896,50c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9
C55.446,49,55.896,49.45,55.896,50L55.896,50z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M61.896,58c0,0.55-0.45,1-1,1h-15c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.446,57,61.896,57.45,61.896,58L61.896,58z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M55.896,63c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9
C55.446,62,55.896,62.45,55.896,63L55.896,63z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M61.896,71c0,0.55-0.45,1-1,1h-15c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.446,70,61.896,70.45,61.896,71L61.896,71z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M55.896,76c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9
C55.446,75,55.896,75.45,55.896,76L55.896,76z"/>
</g>
<g>
<g>
<path style="fill:#FA9200;" d="M34.963,52l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.481,0.984,0.128,1.408L34.963,52z"/>
</g>
</g>
<g>
<g>
<path style="fill:#FA9200;" d="M34.963,65l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.481,0.984,0.128,1.408L34.963,65z"/>
</g>
</g>
<g>
<g>
<path style="fill:#FA9200;" d="M34.963,78l-3.774-3.774c-0.391-0.39-0.391-1.023,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.481,0.984,0.128,1.408L34.963,78z"/>
</g>
</g>
<g>
<rect x="76.896" y="79" style="fill:#66510D;" width="10" height="3"/>
</g>
<g>
<rect x="76.896" y="80" style="fill:#947615;" width="5" height="2"/>
</g>
<g>
<path style="fill:#3D320C;" d="M81.909,30.984l-1.857,3.899c0.512,0.678,1.152,1.101,1.857,1.101s1.345-0.423,1.857-1.101
L81.909,30.984z"/>
</g>
<g>
<path style="fill:#E07000;" d="M84.396,43c-1.154,0-2.126-0.782-2.414-1.845c-0.026-0.097-0.147-0.097-0.173,0
C81.521,42.218,80.55,43,79.396,43c-1.052,0-1.953-0.65-2.322-1.57c-0.039-0.098-0.178-0.071-0.178,0.034V80h10V41.464
c0-0.105-0.139-0.132-0.178-0.034C86.349,42.35,85.448,43,84.396,43z"/>
</g>
<g>
<path style="fill:#FA9200;" d="M76.896,80V41.494c0-0.112,0.146-0.14,0.19-0.037C77.461,42.363,78.354,43,79.396,43
c1.159,0,2.134-0.789,2.416-1.858c0.014-0.052,0.084-0.046,0.084,0.008V80H76.896z"/>
</g>
<g>
<path style="fill:#FFFFFF;" d="M76.896,82v2c0,1.657,1.343,3,3,3h2v-5H76.896z"/>
</g>
</g>
</g>
<g id="R-Yellow" style="display:none;">
<g style="display:inline;">
<circle style="fill:#FFD75E;" cx="55" cy="55" r="55"/>
</g>
<g style="display:inline;">
<g>
<path style="fill:#FFFFFF;" d="M23,26.84c0-2.121,1.719-3.84,3.84-3.84H55l16,13.44v46.72c0,2.121-1.719,3.84-3.84,3.84H26.84
C24.719,87,23,85.281,23,83.16V26.84z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M55,23v9.6c0,2.121,1.719,3.84,3.84,3.84H71L55,23z"/>
</g>
<g>
<polygon style="fill:#FFFFFF;" points="77,41.5 82,31 87,41.5 87,80 77,80 "/>
</g>
<g>
<polygon style="fill:#FFF5D9;" points="87,47 87,41.5 82,31 82,47 "/>
</g>
<g>
<path style="fill:#FFF5D9;" d="M87,84c0,1.657-1.343,3-3,3h-4c-1.657,0-3-1.343-3-3v-2h10V84z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M62,45c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,44,62,44.45,62,45L62,45z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M56,50c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,49,56,49.45,56,50
L56,50z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M62,58c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,57,62,57.45,62,58L62,58z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M56,63c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,62,56,62.45,56,63
L56,63z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M62,71c0,0.55-0.45,1-1,1H46c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h15
C61.55,70,62,70.45,62,71L62,71z"/>
</g>
<g>
<path style="fill:#FFE9A1;" d="M56,76c0,0.55-0.45,1-1,1h-9c-0.55,0-1-0.45-1-1l0,0c0-0.55,0.45-1,1-1h9C55.55,75,56,75.45,56,76
L56,76z"/>
</g>
<g>
<g>
<path style="fill:#FA9200;" d="M35.067,52l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L35.067,52z"/>
</g>
</g>
<g>
<g>
<path style="fill:#FA9200;" d="M35.067,65l-3.774-3.774c-0.391-0.39-0.391-1.024,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L35.067,65z"/>
</g>
</g>
<g>
<g>
<path style="fill:#FA9200;" d="M35.067,78l-3.774-3.774c-0.391-0.39-0.391-1.023,0-1.414c0.391-0.391,1.023-0.391,1.414,0
l2.226,2.226l4.299-5.159c0.354-0.424,0.984-0.481,1.408-0.128c0.424,0.353,0.482,0.984,0.128,1.408L35.067,78z"/>
</g>
</g>
<g>
<rect x="77" y="79" style="fill:#66510D;" width="10" height="3"/>
</g>
<g>
<rect x="77" y="80" style="fill:#947615;" width="5" height="2"/>
</g>
<g>
<path style="fill:#3D320C;" d="M82.013,30.984l-1.857,3.899c0.512,0.678,1.152,1.101,1.857,1.101s1.345-0.423,1.857-1.101
L82.013,30.984z"/>
</g>
<g>
<path style="fill:#E07000;" d="M84.5,43c-1.154,0-2.126-0.782-2.414-1.845c-0.026-0.097-0.147-0.097-0.173,0
C81.626,42.218,80.654,43,79.5,43c-1.052,0-1.953-0.65-2.322-1.57C77.139,41.332,77,41.359,77,41.464V80h10V41.464
c0-0.105-0.139-0.132-0.178-0.034C86.453,42.35,85.552,43,84.5,43z"/>
</g>
<g>
<path style="fill:#FA9200;" d="M77,80V41.494c0-0.112,0.146-0.14,0.19-0.037C77.565,42.363,78.458,43,79.5,43
c1.159,0,2.134-0.789,2.416-1.858C81.931,41.089,82,41.096,82,41.149V80H77z"/>
</g>
<g>
<path style="fill:#FFFFFF;" d="M77,82v2c0,1.657,1.343,3,3,3h2v-5H77z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 34 KiB

+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 80 80" style="enable-background:new 0 0 80 80;" xml:space="preserve">
<style type="text/css">
.st0{fill:#4B4B4B;}
</style>
<g id="XMLID_52_">
<path id="XMLID_1171_" class="st0" d="M46.5,78.9c-7.7,0-15.5,0-23.2,0c-5.5,0-9.2-3.7-9.2-9.2c0-15.5,0-31,0-46.5
c0-5.5,3.7-9.2,9.1-9.2c15.6,0,31.2,0,46.7,0c5.2,0,9,3.8,9,9c0,15.5,0,31.1,0,46.6c0,5.5-3.7,9.2-9.2,9.2
C62,78.9,54.2,78.9,46.5,78.9z M20.5,46.5c0,7.4,0,14.9,0,22.3c0,2.4,1.3,3.7,3.7,3.7c14.8,0,29.7,0,44.5,0c2.5,0,3.7-1.3,3.7-3.7
c0-14.8,0-29.7,0-44.5c0-2.5-1.3-3.7-3.8-3.7c-14.8,0-29.7,0-44.5,0c-2.4,0-3.7,1.3-3.7,3.7C20.5,31.6,20.5,39.1,20.5,46.5z"/>
<path id="XMLID_1170_" class="st0" d="M10.8,65.9c-5.6,0.4-9.7-3.5-9.7-8.9c0-15.6,0-31.3,0-46.9c0-5.2,3.8-8.9,9-8.9
c15.7,0,31.4,0,47,0c5.3,0,9.2,4.2,8.8,9.7c-0.4,0-0.8,0-1.2,0c-15.1,0-30.3,0-45.4,0c-5.1,0-8.5,3.5-8.5,8.5c0,15.1,0,30.2,0,45.3
C10.8,65,10.8,65.4,10.8,65.9z"/>
<path id="XMLID_899_" class="st0" d="M37.9,50.5c4.7-4.7,9.3-9.3,13.8-13.9c1.8-1.8,3.5-3.5,5.3-5.3c2.1-2.1,4.8-2,6.9,0.1
c2.3,2.3,2.5,5,0.4,7.1c-7.7,7.7-15.3,15.3-23,23c-2.1,2.1-4.8,2.1-6.8,0c-2.2-2.2-4.5-4.5-6.7-6.8c-1.9-2-1.9-4.6,0-6.6
c1.4-1.5,2.9-2.5,5-1.9c0.8,0.2,1.6,0.7,2.2,1.3C36.1,48.5,36.9,49.5,37.9,50.5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

+7 -4
View File
@@ -16,17 +16,20 @@
*/
import { bootstrap } from '@angular/platform-browser-dynamic';
import { ROUTER_PROVIDERS } from '@angular/router-deprecated';
import { HTTP_PROVIDERS } from '@angular/http';
import { ALFRESCO_SEARCH_PROVIDERS } from 'ng2-alfresco-search';
import { ALFRESCO_CORE_PROVIDERS } from 'ng2-alfresco-core';
import { ATIVITI_FORM_PROVIDERS } from 'ng2-activiti-form';
import { UploadService } from 'ng2-alfresco-upload';
import { AppComponent } from './app.component';
import { appRouterProviders } from './app.routes';
bootstrap(AppComponent, [
ROUTER_PROVIDERS,
appRouterProviders,
HTTP_PROVIDERS,
ALFRESCO_CORE_PROVIDERS,
ALFRESCO_SEARCH_PROVIDERS,
UploadService
]);
UploadService,
ATIVITI_FORM_PROVIDERS
]).catch(err => console.error(err));
@@ -16,23 +16,16 @@
*/
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class AlfrescoSettingsServiceMock {
export class NotificationService {
static DEFAULT_HOST_ADDRESS: string = 'fakehost';
static DEFAULT_CONTEXT_PATH: string = '/fake-path-alfresco';
static DEFAULT_BASE_API_PATH: string = '/fake-api/fake-public/fake-alfresco/fake-versions/1';
notificationsdSource = new Subject<string>();
private _host: string = AlfrescoSettingsServiceMock.DEFAULT_HOST_ADDRESS;
private _contextPath = AlfrescoSettingsServiceMock.DEFAULT_CONTEXT_PATH;
private _apiBasePath: string = AlfrescoSettingsServiceMock.DEFAULT_BASE_API_PATH;
notifications = this.notificationsdSource.asObservable();
public get host(): string {
return this._host;
}
getApiBaseUrl(): string {
return this._host + this._contextPath + this._apiBasePath;
sendNotification(message: string) {
this.notificationsdSource.next(message);
}
}
-24
View File
@@ -1,24 +0,0 @@
var browserSync = require("browser-sync").create();
var historyApiFallback = require('connect-history-api-fallback');
browserSync.init({
server: {
baseDir: './',
middleware: [ historyApiFallback() ]
},
files: [
'index.html',
'app/**/*.{html,htm,css,js}',
'dist/**/*.{html,htm,css,js}',
'node_modules/ng2-alfresco-core/dist/**/*.{html,htm,css,js}',
'node_modules/ng2-alfresco-datatable/dist/**/*.{html,htm,css,js}',
'node_modules/ng2-alfresco-documentlist/dist/**/*.{html,htm,css,js}',
'node_modules/ng2-alfresco-login/dist/**/*.{html,htm,css,js}',
'node_modules/ng2-alfresco-search/dist/**/*.{html,htm,css,js}',
'node_modules/ng2-alfresco-upload/dist/**/*.{html,htm,css,js}',
'node_modules/ng2-alfresco-viewer/dist/**/*.{html,htm,css,js}'],
reloadDelay: 1000
});
+25 -1
View File
@@ -4,7 +4,7 @@
"DOCUMENT_LIST": {
"COLUMNS": {
"DISPLAY_NAME": "Display name",
"CREATED_BY": "Created by",
"CREATED_BY": "mario",
"CREATED_ON": "Created on"
},
"ACTIONS": {
@@ -39,5 +39,29 @@
"CUSTOM": "Custom action"
}
}
},
"LOGIN": {
"LOGO": "Alfresco",
"LABEL": {
"LOGIN": "Login",
"USERNAME": "Username",
"PASSWORD": "Password",
"REMEMBER": "Remember"
},
"MESSAGES": {
"USERNAME-REQUIRED": "Required",
"USERNAME-MIN": "Your username needs to be at least 4 characters.",
"PASSWORD-REQUIRED": "Enter your password to sign in",
"LOGIN-ERROR": "You have entered an invalid username or password",
"LOGIN-SUCCESS": "Login successful"
},
"BUTTON": {
"LOGIN": "MARIO"
},
"ACTION": {
"HELP": "NEED HELP?",
"REGISTER": "REGISTER"
}
}
}
+1 -1
View File
@@ -24,7 +24,7 @@
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="/app/js/xml2json.js"></script>
<script src="node_modules/alfresco-js-api/bundle.js"></script>
<script src="node_modules/alfresco-js-api/dist/alfresco-js-api.js"></script>
<script src="node_modules/pdfjs-dist/build/pdf.js"></script>
<script src="node_modules/pdfjs-dist/build/pdf.worker.js"></script>
+24 -27
View File
@@ -1,18 +1,19 @@
{
"name": "Alfresco-Angular2-Demo",
"description": "Demo shell for Alfresco Angular2 components",
"version": "0.2.0",
"version": "0.3.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"postinstall": "typings install",
"clean": "rimraf node_modules typings",
"start": "npm run tslint && typings install && concurrently \"npm run tsc:w\" \"license-check\" \"npm run serve\" ",
"build": "npm run tslint && npm run tsc && npm run licensecheck",
"start": "npm run build && concurrently \"npm run tsc:w\" \"npm run serve\" ",
"aws": "node app.js",
"tsc": "tsc",
"tsc:w": "tsc -w",
"serve": "node browser-sync-config.js",
"serve": "wsrv -o -s -l -p 3000 -a 0.0.0.0 -x ./server/versions.js",
"typings": "typings install",
"tslint": "npm run tslint-src && npm run tslint-root",
"tslint-src": "tslint -c tslint.json app/{,**/}**.ts",
"tslint-root": "tslint -c tslint.json *.ts",
"tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json 'app/{,**/}**.ts'",
"licensecheck": "license-check"
},
"repository": {
@@ -62,47 +63,43 @@
"@angular/router": "3.0.0-alpha.7",
"@angular/router-deprecated": "2.0.0-rc.2",
"@angular/upgrade": "2.0.0-rc.3",
"alfresco-js-api": "^0.3.0",
"systemjs": "0.19.27",
"core-js": "2.4.0",
"reflect-metadata": "0.1.3",
"rxjs": "5.0.0-beta.6",
"zone.js": "0.6.12",
"rimraf": "2.5.2",
"material-design-icons": "2.2.3",
"material-design-lite": "1.1.3",
"ng2-translate": "2.2.0",
"pdfjs-dist": "1.5.258",
"flag-icon-css": "2.3.0",
"alfresco-js-api": "0.1.0",
"ng2-alfresco-core": "0.2.0",
"ng2-alfresco-datatable": "0.2.0",
"ng2-alfresco-documentlist": "0.2.0",
"ng2-alfresco-login": "0.2.0",
"ng2-alfresco-search": "0.2.0",
"ng2-alfresco-upload": "0.2.0",
"ng2-alfresco-viewer": "0.2.0"
"ng2-alfresco-core": "0.3.0",
"ng2-alfresco-datatable": "0.3.0",
"ng2-alfresco-documentlist": "0.3.0",
"ng2-alfresco-login": "0.3.0",
"ng2-alfresco-search": "0.3.0",
"ng2-alfresco-upload": "0.3.0",
"ng2-alfresco-viewer": "0.3.0",
"ng2-activiti-form": "0.3.0",
"ng2-activiti-tasklist": "0.3.0",
"ng2-activiti-processlist": "0.3.0",
"ng2-alfresco-webscript": "0.3.0"
},
"devDependencies": {
"browser-sync": "2.10.0",
"concurrently": "2.0.0",
"connect-history-api-fallback": "1.2.0",
"license-check": "1.1.5",
"mime": "^1.3.4",
"tslint": "3.8.1",
"typescript": "1.8.10",
"typings": "1.0.4"
"typings": "1.0.4",
"wsrv": "0.1.4"
},
"license-check-config": {
"src": [
"**/*.js",
"**/*.ts",
"!/**/coverage/**/*",
"!/**/demo/**/*",
"!/**/node_modules/**/*",
"!/**/typings/**/*",
"!*.js",
"!app/js/xml2json.js"
"./app/**/*.js",
"!./app/js/xml2json.js"
],
"path": "assets/license_header.txt",
"blocking": true,
+42
View File
@@ -0,0 +1,42 @@
'use strict';
// wsrv extension that provides dynamic '/versions' route
exports.register = function (server, options, next) {
var packages = [
'ng2-activiti-form',
'ng2-alfresco-core',
'ng2-alfresco-datatable',
'ng2-alfresco-documentlist',
'ng2-alfresco-login',
'ng2-alfresco-search',
'ng2-alfresco-upload',
'ng2-alfresco-viewer',
'ng2-alfresco-webscript'
];
server.route({
method: 'GET',
path: '/versions',
handler: function (request, reply) {
var result = {
packages: packages.map(function (packageName) {
return {
name: packageName,
version: require('./../node_modules/' + packageName + '/package.json').version
}
})
};
return reply(result).type('application/json');
}
});
next();
};
exports.register.attributes = {
name: 'ng2-module-versions',
version: '1.0.0'
};
+15 -2
View File
@@ -17,7 +17,11 @@
'ng2-alfresco-login': 'node_modules/ng2-alfresco-login/dist',
'ng2-alfresco-search': 'node_modules/ng2-alfresco-search/dist',
'ng2-alfresco-upload': 'node_modules/ng2-alfresco-upload/dist',
'ng2-alfresco-viewer': 'node_modules/ng2-alfresco-viewer/dist'
'ng2-activiti-form': 'node_modules/ng2-activiti-form/dist',
'ng2-alfresco-viewer': 'node_modules/ng2-alfresco-viewer/dist',
'ng2-alfresco-webscript': 'node_modules/ng2-alfresco-webscript/dist',
'ng2-activiti-processlist': 'node_modules/ng2-activiti-processlist/dist',
'ng2-activiti-tasklist': 'node_modules/ng2-activiti-tasklist/dist'
};
// packages tells the System loader how to load when no filename and/or no extension
var packages = {
@@ -33,7 +37,11 @@
'ng2-alfresco-login': { main: 'index.js', defaultExtension: 'js'},
'ng2-alfresco-search': { main: 'index.js', defaultExtension: 'js'},
'ng2-alfresco-upload': { main: 'index.js', defaultExtension: 'js'},
'ng2-alfresco-viewer': { main: 'index.js', defaultExtension: 'js'}
'ng2-alfresco-viewer': { main: 'index.js', defaultExtension: 'js'},
'ng2-activiti-form': { main: 'index.js', defaultExtension: 'js'},
'ng2-activiti-processlist': { main: 'index.js', defaultExtension: 'js'},
'ng2-activiti-tasklist': { main: 'index.js', defaultExtension: 'js'},
'ng2-alfresco-webscript': { main: 'index.js', defaultExtension: 'js'}
};
var ngPackageNames = [
'common',
@@ -56,8 +64,13 @@
}
// Most environments should use UMD; some (Karma) need the individual index files
var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;
// Add package entries for angular packages
ngPackageNames.forEach(setPackageConfig);
// No umd for router yet
packages['@angular/router'] = { main: 'index.js', defaultExtension: 'js' };
var config = {
map: map,
packages: packages
+1 -1
View File
@@ -26,7 +26,7 @@
"label-undefined": true,
"max-line-length": [
true,
140
180
],
"member-ordering": [
true,
+13
View File
@@ -0,0 +1,13 @@
{
"watch": [
"node_modules/ng2-alfresco-core/dist/**/*.{html,css,js}",
"node_modules/ng2-alfresco-datatable/dist/**/*.{html,css,js}",
"node_modules/ng2-alfresco-documentlist/dist/**/*.{html,css,js}",
"node_modules/ng2-alfresco-login/dist/**/*.{html,css,js}",
"node_modules/ng2-alfresco-search/dist/**/*.{html,css,js}",
"node_modules/ng2-alfresco-upload/dist/**/*.{html,css,js}",
"node_modules/ng2-alfresco-viewer/dist/**/*.{html,css,js}",
"node_modules/ng2-alfresco-webscript/dist/**/*.{html,css,js}",
"node_modules/ng2-activiti-form/dist/**/*.{html,css,js}"
]
}
+21 -4
View File
@@ -1,12 +1,16 @@
# Alfresco Angular 2 Components
<p>
<a title='Build Status' href="https://travis-ci.org/Alfresco/alfresco-ng2-components">
<a title='Build Status Travis' href="https://travis-ci.org/Alfresco/alfresco-ng2-components">
<img src='https://travis-ci.org/Alfresco/alfresco-ng2-components.svg?branch=master' alt='travis
Status' />
</a>
<a href='https://coveralls.io/github/Alfresco/alfresco-ng2-components'>
<img src='https://coveralls.io/repos/github/Alfresco/alfresco-ng2-components/badge.svg?t=NzxWxh' alt='Coverage Status' />
<a title='Build Status AppVeyor' href="https://ci.appveyor.com/project/alfresco/alfresco-ng2-components">
<img src='https://ci.appveyor.com/api/projects/status/github/Alfresco/alfresco-ng2-components' alt='travis
Status' />
</a>
<a href='https://codecov.io/gh/Alfresco/alfresco-ng2-components'>
<img src='https://img.shields.io/codecov/c/github/Alfresco/alfresco-ng2-components/master.svg?maxAge=2592000' alt='Coverage Status' />
</a>
<a href='https://github.com/Alfresco/alfresco-ng2-components/blob/master/LICENSE'>
<img src='https://img.shields.io/hexpm/l/plug.svg' alt='license' />
@@ -25,12 +29,25 @@
</a>
</p>
## Base components
- [Core library](ng2-alfresco-core/README.md)
* Material Design elements
* Context Menu component
## ECM components
- [DataTable](ng2-alfresco-datatable/README.md)
- [Document List](ng2-alfresco-documentlist/README.md)
- [DocumentList](ng2-alfresco-documentlist/README.md)
- [Viewer](ng2-alfresco-viewer/README.md)
- [Login](ng2-alfresco-login/README.md)
- [Upload](ng2-alfresco-upload/README.md)
- [Webscript Viewer](ng2-components/ng2-alfresco-webscript/README.md)
## BPM components
- [ProcessList](ng2-activiti-processlist/README.md)
- [Form](ng2-activiti-form/README.md)
You can browse all the components at the following address:
@@ -0,0 +1,23 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[package.json]
indent_style = space
indent_size = 2
[karma.conf.js]
indent_style = space
indent_size = 2
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
@@ -0,0 +1,11 @@
npm-debug.log
node_modules/
.idea/
typings
coverage/
dist/
src/**/*.js
src/**/*.js.map
index.js
index.js.map
!systemjs.config.js
@@ -0,0 +1,16 @@
npm-debug.log
.idea
assets/
coverage/
node_modules
typings/
fonts/
/.editorconfig
/.travis.yml
/*.js
/*.json
/*.ts
/*.js.map
/.npmignore
+177
View File
@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+283
View File
@@ -0,0 +1,283 @@
# Alfresco Bpm Form component for Angular 2
## Prerequisites
Before you start using this development framework, make sure you have installed all required software and done all the
necessary configuration, see this [page](https://github.com/Alfresco/alfresco-ng2-components/blob/master/PREREQUISITES.md).
## Install
```sh
npm install --save ng2-activiti-form
```
### Dependencies
Add the following dependency to your index.html:
```html
<script src="node_modules/alfresco-js-api/dist/alfresco-js-api.js"></script>
```
You must separately install the following libraries for your application:
- [ng2-translate](https://github.com/ocombe/ng2-translate)
- [ng2-alfresco-core](https://www.npmjs.com/package/ng2-alfresco-core)
```sh
npm install --save ng2-translate ng2-alfresco-core
```
#### Material Design Lite
The style of this component is based on [material design](https://getmdl.io/), so if you want to visualize it correctly you have to add the material
design dependency to your project:
```sh
npm install --save material-design-icons material-design-lite
```
Also make sure you include these dependencies in your `index.html` file:
```html
<!-- Google Material Design Lite -->
<link rel="stylesheet" href="node_modules/material-design-lite/material.min.css">
<script src="node_modules/material-design-lite/material.min.js"></script>
<link rel="stylesheet" href="node_modules/material-design-icons/iconfont/material-icons.css">
```
## Basic usage examples
### Display form instance by task id
```html
<activiti-form
[taskId]="selectedTask?.id">
</activiti-form>
```
For an existing Task both form and values will be fetched and displayed.
### Display form definition by form id
```html
<activiti-form
[formId]="selectedFormDefinition?.id"
[data]="customData">
</activiti-form>
```
Only form definition will be fetched
### Display form definition by form name
```html
<activiti-form
[formName]="selectedFormDefinition?.name"
[data]="customData">
</activiti-form>
```
### Display form definition by ECM nodeId, in this case the metadata of the node are showed in an activiti Form. If there are no form
definied in activiti for the type of the node, a new form will be automaticaly created in activiti.
```html
<activiti-form
[nodeId]="'e280be3a-6584-45a1-8bb5-89bfe070262e'">
</activiti-form>
```
### Display form definition by form name, and store the form field as metadata. The param nameNode is optional.
```html
<activiti-form
[formName]="'activitiForms:patientFolder'"
[saveMetadata]="true"
[path]="'/Sites/swsdp/documentLibrary'"
[nameNode]="'test'">
</activiti-form>
```
### Display form definition by ECM nodeId, in this case the metadata of the node are showed in an activiti Form, and store the form field
as metadata. The param nameNode is optional.
```html
<activiti-form
[nodeId]="'e280be3a-6584-45a1-8bb5-89bfe070262e'"
[saveMetadata]="true"
[path]="'/Sites/swsdp/documentLibrary'"
[nameNode]="'test'">
</activiti-form>
```
## Configuration
### Properties
The recommended set of properties can be found in the following table:
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| taskId | string | | Task id to fetch corresponding form and values. |
| formId | string | | The id of the form definition to load and display with custom values. |
| formName | string | | Name of hte form definition to load and display with custom values. |
| data | `FormValues` | | Custom form values map to be used with the rendered form. |
| showTitle | boolean | true | Toggle rendering of the form title. |
| showCompleteButton | boolean | true | Toggle rendering of the `Complete` outcome button. |
| showSaveButton | boolean | true | Toggle rendering of the `Save` outcome button. |
| readOnly | boolean | false | Toggle readonly state of the form. Enforces all form widgets render readonly if enabled. |
| showRefreshButton | boolean | true | Toggle rendering of the `Refresh` button. |
| saveMetadata | boolean | false | Store the value of the form as metadata. |
| path | string | | Path of the folder where to store the metadata. |
| nameNode (optional) | string | true | Name to assign to the new node where the metadata are stored. |
* {path} string - path of the folder where the to store the metadata
*
* {nameNode} string (optional) - name of the node stored, if not defined the node will be sotred with an uuid as name
#### Advanced properties
The following properties are for complex customisation purposes:
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| form | `FormModel` | | Underlying form model instance. |
| debugMode | boolean | false | Toggle debug mode, allows displaying additional data for development and debugging purposes. |
### Events
| Name | Description |
| --- | --- |
| formLoaded | Invoked when form is loaded or reloaded. |
| formSaved | Invoked when form is submitted with `Save` or custom outcomes. |
| formCompleted | Invoked when form is submitted with `Complete` outcome. |
| executeOutcome | Invoked when any outcome is executed, default behaviour can be prevented via `event.preventDefault()` |
All `form*` events receive an instance of the `FormModel` as event argument for ease of development:
**MyView.component.html**
```html
<activiti-form
[taskId]="selectedTask?.id"
(formSaved)="onFormSaved($event)">
</activiti-form>
```
**MyView.component.ts**
```ts
onFormSaved(form: FormModel) {
console.log(form);
}
```
#### Controlling outcome execution behaviour
If absolutely needed it is possible taking full control over form outcome execution by means of `executeOutcome` event.
This event is fired upon each outcome execution, both system and custom ones.
You can prevent default behaviour by calling `event.preventDefault()`.
This allows for example having custom form validation scenarios and/or additional validation summary presentation.
Alternatively you may want just running additional code on outcome execution without suppressing default one.
**MyView.component.html**
```html
<activiti-form
[taskId]="selectedTask?.id"
executeOutcome="validateForm($event)">
</activiti-form>
```
**MyView.component.ts**
```ts
import { FormOutcomeEvent } from 'ng2-activiti-form';
export class MyView {
validateForm(event: FormOutcomeEvent) {
let outcome = event.outcome;
// you can also get additional properties of outcomes
// if you defined them within outcome definition
if (outcome) {
let form = outcome.form;
if (form) {
// check/update the form here
event.preventDefault();
}
}
}
}
```
There are two additional functions that can be of a great value when controlling outcomes:
- `saveTaskForm()` - saves current form
- `completeTaskForm(outcome?: string)` - save and complete form with a given outcome name
**Please note that if `event.preventDefault()` is not called then default outcome behaviour
will also be executed after your custom code.**
## Supported form widgets
- [x] Tabs
- [x] Text
- [x] Multiline Text
- [x] Number
- [x] Checkbox
- [ ] Date
- Dropdown
* [x] Manual
* [x] REST service
* [ ] Data source
- [ ] Typeahead
- [ ] Amount
- [x] Radio buttons
- [ ] People
- [ ] Group of People
- [ ] Dynamic Table
- [x] Hyperlink
- Header
* [x] Plain header
* [x] Collapsible header
- [ ] Attach
- [x] Display value
- [x] Display text
## Build from sources
Alternatively you can build component from sources with the following commands:
```sh
npm install
npm run build
```
### Build the files and keep watching for changes
```sh
$ npm run build:w
```
### Running unit tests
```sh
npm test
```
### Running unit tests in browser
```sh
npm test-browser
```
This task rebuilds all the code, runs tslint, license checks and other quality check tools
before performing unit testing.
### Code coverage
```sh
npm run coverage
```
@@ -0,0 +1,16 @@
/*!
* @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.
*/
+33
View File
@@ -0,0 +1,33 @@
/*!
* @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 { FormService } from './src/services/form.service';
import { EcmModelService } from './src/services/ecm-model.service';
import { NodeService } from './src/services/node.service';
export * from './src/components/activiti-form.component';
export * from './src/services/form.service';
export * from './src/components/widgets/index';
export * from './src/services/ecm-model.service';
export * from './src/services/node.service';
export const ATIVITI_FORM_PROVIDERS: [any] = [
FormService,
EcmModelService,
NodeService
];
@@ -0,0 +1,107 @@
// Tun on full stack traces in errors to help debugging
Error.stackTraceLimit = Infinity;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
__karma__.loaded = function() {};
var map = {
'app': 'base/dist',
'rxjs': 'base/node_modules/rxjs',
'@angular': 'base/node_modules/@angular',
'ng2-alfresco-core': '/base/node_modules/ng2-alfresco-core/dist',
'ng2-translate' : '/base/node_modules/ng2-translate'
};
var packages = {
'app': { main: 'main.js', defaultExtension: 'js' },
'rxjs': { defaultExtension: 'js' },
'ng2-alfresco-core': { main: 'index.js', defaultExtension: 'js' },
'ng2-translate': { defaultExtension: 'js' }
};
var packageNames = [
'@angular/common',
'@angular/compiler',
'@angular/core',
'@angular/http',
'@angular/platform-browser',
'@angular/platform-browser-dynamic',
'@angular/router',
'@angular/router-deprecated',
'@angular/testing',
'@angular/upgrade'
];
packageNames.forEach(function(pkgName) {
packages[pkgName] = { main: 'index.js', defaultExtension: 'js' };
});
packages['base/dist'] = {
defaultExtension: 'js',
format: 'register',
map: Object.keys(window.__karma__.files).filter(onlyAppFiles).reduce(createPathRecords, {})
};
var config = {
map: map,
packages: packages
};
System.config(config);
System.import('@angular/platform-browser/src/browser/browser_adapter')
.then(function(browser_adapter) { browser_adapter.BrowserDomAdapter.makeCurrent(); })
.then(function () {
return Promise.all([
System.import('@angular/core/testing'),
System.import('@angular/platform-browser-dynamic/testing')
])
})
.then(function (providers) {
var testing = providers[0];
var testingBrowser = providers[1];
testing.setBaseTestProviders(
testingBrowser.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
testingBrowser.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
})
.then(function() { return Promise.all(resolveTestFiles()); })
.then(
function() {
__karma__.start();
},
function(error) {
if(typeof __karma__.error == 'function') {
__karma__.error(error.stack || error);
}else{
console.error(error);
}
}
);
function createPathRecords(pathsMapping, appPath) {
var pathParts = appPath.split('/');
var moduleName = './' + pathParts.slice(Math.max(pathParts.length - 2, 1)).join('/');
moduleName = moduleName.replace(/\.js$/, '');
pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath];
return pathsMapping;
}
function onlyAppFiles(filePath) {
return /\/base\/dist\/(?!.*\.spec\.js$).*\.js$/.test(filePath);
}
function onlySpecFiles(path) {
return /\.spec\.js$/.test(path);
}
function resolveTestFiles() {
return Object.keys(window.__karma__.files) // All files served by Karma.
.filter(onlySpecFiles)
.map(function(moduleName) {
// loads all spec files via their global module names (e.g.
// 'base/dist/vg-player/vg-player.spec')
return System.import(moduleName);
});
}
@@ -0,0 +1,99 @@
'use strict';
module.exports = function (config) {
var configuration = {
basePath: '.',
frameworks: ['jasmine-ajax', 'jasmine'],
files: [
// paths loaded by Karma
{pattern: 'node_modules/reflect-metadata/Reflect.js', included: true, watched: true},
{pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: false},
{pattern: 'node_modules/zone.js/dist/zone.js', included: true, watched: true},
{pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false},
{pattern: 'node_modules/rxjs/**/*.map', included: false, watched: false},
{pattern: 'node_modules/@angular/**/*.js', included: false, watched: false},
{pattern: 'node_modules/@angular/**/*.map', included: false, watched: false},
{pattern: 'node_modules/ng2-alfresco-core/dist/**/*.js', included: false, served: true, watched: false},
{pattern: 'node_modules/ng2-translate/**/*.js', included: false, served: true, watched: false},
{pattern: 'node_modules/alfresco-js-api/dist/alfresco-js-api.js', included: true, watched: false},
{pattern: 'karma-test-shim.js', included: true, watched: true},
// paths loaded via module imports
{pattern: 'dist/**/*.js', included: false, watched: true},
{pattern: 'dist/**/*.html', included: true, served: true, watched: true},
{pattern: 'dist/**/*.css', included: true, served: true, watched: true},
// paths to support debugging with source maps in dev tools
{pattern: 'src/**/*.ts', included: false, watched: false},
{pattern: 'dist/**/*.js.map', included: false, watched: false}
],
exclude: [
'node_modules/**/*spec.js'
],
// proxied base paths
proxies: {
// required for component assets fetched by Angular's compiler
'/src/': '/base/src/'
},
port: 9876,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
colors: true,
autoWatch: true,
browsers: ['Chrome'],
customLaunchers: {
Chrome_travis_ci: {
base: 'Chrome',
flags: ['--no-sandbox']
}
},
// Karma plugins loaded
plugins: [
'karma-jasmine',
'karma-coverage',
'karma-jasmine-ajax',
'karma-chrome-launcher',
'karma-mocha-reporter',
'karma-jasmine-html-reporter'
],
// Coverage reporter generates the coverage
reporters: ['mocha', 'coverage', 'kjhtml'],
// Source files that you wanna generate coverage for.
// Do not include tests or libraries (these files will be instrumented by Istanbul)
preprocessors: {
'dist/**/!(*spec).js': ['coverage']
},
coverageReporter: {
dir: 'coverage/',
subdir: 'report',
reporters: [
{type: 'text'},
{type: 'json', file: 'coverage-final.json'},
{type: 'html'},
{type: 'lcov'}
]
}
};
if (process.env.TRAVIS) {
configuration.browsers = ['Chrome_travis_ci'];
}
config.set(configuration)
};
@@ -0,0 +1,106 @@
{
"name": "ng2-activiti-form",
"description": "Alfresco Activiti Form Component for Angular 2",
"version": "0.3.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"postinstall": "typings install",
"clean": "rimraf dist node_modules typings",
"typings": "typings install",
"server": "wsrv -o -p 9875",
"build": "npm run tslint && rimraf dist && tsc && npm run copy-dist && license-check",
"build:w": "npm run tslint && rimraf dist && npm run watch-task",
"watch-task": "concurrently \"npm run tsc:w\" \"npm run copy-dist:w\" \"license-check\"",
"tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json 'src/{,**/}**.ts'",
"copy-dist": "cpx \"./src/**/*.{html,css,json,png,jpg,gif,svg}\" ./dist/src",
"copy-dist:w": "cpx \"./src/**/*.{html,css,json,png,jpg,gif,svg}\" ./dist/src -w",
"tsc": "tsc",
"tsc:w": "tsc -w",
"pretest": "npm run build",
"test": "karma start karma.conf.js --reporters mocha,coverage --single-run",
"test-browser": "concurrently \"karma start karma.conf.js --reporters kjhtml\" \"npm run watch-task\"",
"posttest": "remap-istanbul -i coverage/report/coverage-final.json -o coverage/report -t html && remap-istanbul -i coverage/report/coverage-final.json -o coverage/report/coverage-final.json",
"coverage": "npm run test && wsrv -o -p 9875 ./coverage/report",
"prepublish": "npm run build",
"travis": "npm link ng2-alfresco-core"
},
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/Alfresco/alfresco-ng2-components.git"
},
"bugs": {
"url": "https://github.com/Alfresco/alfresco-ng2-components/issues"
},
"license": "Apache-2.0",
"contributors": [
{
"name": "Denys Vuika",
"email": "denys.vuika@gmail.com"
}
],
"keywords": [
"ng2",
"angular",
"angular2",
"alfresco",
"activiti"
],
"dependencies": {
"@angular/common": "2.0.0-rc.3",
"@angular/compiler": "2.0.0-rc.3",
"@angular/core": "2.0.0-rc.3",
"@angular/forms": "0.1.1",
"@angular/http": "2.0.0-rc.3",
"@angular/platform-browser": "2.0.0-rc.3",
"@angular/platform-browser-dynamic": "2.0.0-rc.3",
"@angular/router": "3.0.0-alpha.7",
"@angular/router-deprecated": "2.0.0-rc.2",
"@angular/upgrade": "2.0.0-rc.3",
"alfresco-js-api": "^0.3.0",
"systemjs": "0.19.27",
"core-js": "2.4.0",
"reflect-metadata": "0.1.3",
"rxjs": "5.0.0-beta.6",
"zone.js": "0.6.12",
"ng2-translate": "2.2.2",
"ng2-alfresco-core": "0.3.0"
},
"peerDependencies": {
"material-design-icons": "^2.2.3",
"material-design-lite": "^1.1.3"
},
"devDependencies": {
"concurrently": "2.1.0",
"coveralls": "2.11.9",
"cpx": "1.3.1",
"jasmine-core": "2.4.1",
"karma": "0.13.22",
"karma-chrome-launcher": "1.0.1",
"karma-coverage": "1.0.0",
"karma-coveralls": "1.1.2",
"karma-jasmine": "1.0.2",
"karma-jasmine-ajax": "0.1.13",
"karma-jasmine-html-reporter": "0.2.0",
"karma-jasmine-ajax": "0.1.13",
"karma-mocha-reporter": "2.0.3",
"license-check": "1.1.5",
"remap-istanbul": "0.6.3",
"rimraf": "2.5.2",
"traceur": "0.0.91",
"tslint": "3.8.1",
"typescript": "1.8.10",
"typings": "1.0.4",
"wsrv": "0.1.3"
},
"license-check-config": {
"src": [
"./dist/**/*.js"
],
"path": "assets/license_header.txt",
"blocking": true,
"logInfo": false,
"logError": true
}
}
@@ -0,0 +1,25 @@
.activiti-form-container {
width: 100%;
min-height: 100px;
}
.activiti-form-container > .mdl-card__media {
background-color: #fff;
}
.activiti-form-debug-container {
padding: 10px;
}
.activiti-form-debug-container .debug-toggle-text {
padding-left: 15px;
cursor: pointer;
}
.activiti-form-debug-container .debug-toggle-text:hover {
font-weight: bold;
}
.activiti-form-hide-button {
display: none;
}
@@ -0,0 +1,56 @@
<div>
<div *ngIf="!hasForm()">
<h3 style="text-align: center">Please select a Task</h3>
</div>
<div *ngIf="hasForm()">
<div class="mdl-card mdl-shadow--2dp activiti-form-container">
<div *ngIf="isTitleEnabled()" class="mdl-card__title">
<h2 class="mdl-card__title-text">{{form.taskName}}</h2>
</div>
<div class="mdl-card__media">
<div *ngIf="form.hasTabs()">
<tabs-widget [tabs]="form.tabs"></tabs-widget>
</div>
<div *ngIf="!form.hasTabs() && form.hasFields()">
<container-widget *ngFor="let field of form.fields" [content]="field" (formValueChanged)="checkVisibility($event);"></container-widget>
</div>
</div>
<div *ngIf="form.hasOutcomes()" class="mdl-card__actions mdl-card--border">
<button *ngFor="let outcome of form.outcomes"
alfresco-mdl-button
[class.mdl-button--colored]="!outcome.isSystem"
[class.activiti-form-hide-button]="!isOutcomeButtonEnabled(outcome)"
(click)="onOutcomeClicked(outcome, $event)">
{{outcome.name}}
</button>
</div>
<div *ngIf="showRefreshButton" class="mdl-card__menu" >
<button (click)="onRefreshClicked()"
class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect">
<i class="material-icons">refresh</i>
</button>
</div>
</div>
</div>
</div>
<!--
For debugging and data visualisation purposes,
will be removed during future revisions
-->
<div class="activiti-form-debug-container">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="switch-1" [class.is-checked]="debugMode">
<input type="checkbox" id="switch-1" class="mdl-switch__input" [(ngModel)]="debugMode">
<span class="mdl-switch__label"></span>
<span class="debug-toggle-text">Debug mode</span>
</label>
<div *ngIf="debugMode && hasForm()">
<h4>Values</h4>
<pre>{{form.values | json}}</pre>
<h4>Form</h4>
<pre>{{form.json | json}}</pre>
</div>
</div>
@@ -0,0 +1,637 @@
/*!
* @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 { it, describe, expect } from '@angular/core/testing';
import { Observable } from 'rxjs/Rx';
import { SimpleChange } from '@angular/core';
import { ActivitiForm } from './activiti-form.component';
import { FormModel, FormOutcomeModel, FormFieldModel, FormOutcomeEvent } from './widgets/index';
import { FormService } from './../services/form.service';
import { WidgetVisibilityService } from './../services/widget-visibility.service';
import { ContainerWidget } from './widgets/container/container.widget';
describe('ActivitiForm', () => {
let componentHandler: any;
let formService: FormService;
let formComponent: ActivitiForm;
let visibilityService: WidgetVisibilityService;
beforeEach(() => {
componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered'
]);
visibilityService = jasmine.createSpyObj('WidgetVisibilityService', [
'updateVisibilityForForm', 'getTaskProcessVariableModelsForTask'
]);
window['componentHandler'] = componentHandler;
formService = new FormService(null, null);
formComponent = new ActivitiForm(formService, visibilityService, null, null, null);
});
it('should upgrade MDL content on view checked', () => {
formComponent.ngAfterViewChecked();
expect(componentHandler.upgradeAllRegistered).toHaveBeenCalled();
});
it('should setup MDL content only if component handler available', () => {
expect(formComponent.setupMaterialComponents()).toBeTruthy();
window['componentHandler'] = null;
expect(formComponent.setupMaterialComponents()).toBeFalsy();
});
it('should start loading form on init', () => {
spyOn(formComponent, 'loadForm').and.stub();
formComponent.ngOnInit();
expect(formComponent.loadForm).toHaveBeenCalled();
});
it('should check form', () => {
expect(formComponent.hasForm()).toBeFalsy();
formComponent.form = new FormModel();
expect(formComponent.hasForm()).toBeTruthy();
});
it('should allow title if task name available', () => {
let formModel = new FormModel();
formComponent.form = formModel;
expect(formComponent.showTitle).toBeTruthy();
expect(formModel.taskName).toBe(FormModel.UNSET_TASK_NAME);
expect(formComponent.isTitleEnabled()).toBeTruthy();
// override property as it's the readonly one
Object.defineProperty(formModel, 'taskName', {
enumerable: false,
configurable: false,
writable: false,
value: null
});
expect(formComponent.isTitleEnabled()).toBeFalsy();
});
it('should not allow title', () => {
let formModel = new FormModel();
formComponent.form = formModel;
formComponent.showTitle = false;
expect(formModel.taskName).toBe(FormModel.UNSET_TASK_NAME);
expect(formComponent.isTitleEnabled()).toBeFalsy();
});
it('should not enable outcome button when model missing', () => {
expect(formComponent.isOutcomeButtonEnabled(null)).toBeFalsy();
});
it('should enable custom outcome buttons', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, { id: 'action1', name: 'Action 1' });
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy();
});
it('should allow controlling [complete] button visibility', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION });
formComponent.showSaveButton = true;
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy();
formComponent.showSaveButton = false;
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy();
});
it('should allow controlling [save] button visibility', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.COMPLETE_ACTION });
formComponent.showCompleteButton = true;
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeTruthy();
formComponent.showCompleteButton = false;
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy();
});
it('should load form on refresh', () => {
spyOn(formComponent, 'loadForm').and.stub();
formComponent.onRefreshClicked();
expect(formComponent.loadForm).toHaveBeenCalled();
});
it('should get form by task id on load', () => {
spyOn(formComponent, 'getFormByTaskId').and.stub();
const taskId = '123';
formComponent.taskId = taskId;
formComponent.loadForm();
expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId);
expect(visibilityService.getTaskProcessVariableModelsForTask).toHaveBeenCalledWith(taskId);
});
it('should get form definition by form id on load', () => {
spyOn(formComponent, 'getFormDefinitionByFormId').and.stub();
const formId = '123';
formComponent.formId = formId;
formComponent.loadForm();
expect(formComponent.getFormDefinitionByFormId).toHaveBeenCalledWith(formId);
});
it('should get form definition by form name on load', () => {
spyOn(formComponent, 'getFormDefinitionByFormName').and.stub();
const formName = '<form>';
formComponent.formName = formName;
formComponent.loadForm();
expect(formComponent.getFormDefinitionByFormName).toHaveBeenCalledWith(formName);
});
it('should reload form by task id on binding changes', () => {
spyOn(formComponent, 'getFormByTaskId').and.stub();
const taskId = '<task id>';
let change = new SimpleChange(null, taskId);
formComponent.ngOnChanges({ 'taskId': change });
expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId);
});
it('should reload form definition by form id on binding changes', () => {
spyOn(formComponent, 'getFormDefinitionByFormId').and.stub();
const formId = '123';
let change = new SimpleChange(null, formId);
formComponent.ngOnChanges({ 'formId': change });
expect(formComponent.getFormDefinitionByFormId).toHaveBeenCalledWith(formId);
});
it('should reload form definition by name on binding changes', () => {
spyOn(formComponent, 'getFormDefinitionByFormName').and.stub();
const formName = '<form>';
let change = new SimpleChange(null, formName);
formComponent.ngOnChanges({ 'formName': change });
expect(formComponent.getFormDefinitionByFormName).toHaveBeenCalledWith(formName);
});
it('should not get form on load', () => {
spyOn(formComponent, 'getFormByTaskId').and.stub();
spyOn(formComponent, 'getFormDefinitionByFormId').and.stub();
spyOn(formComponent, 'getFormDefinitionByFormName').and.stub();
formComponent.taskId = null;
formComponent.formId = null;
formComponent.formName = null;
formComponent.loadForm();
expect(formComponent.getFormByTaskId).not.toHaveBeenCalled();
expect(formComponent.getFormDefinitionByFormId).not.toHaveBeenCalled();
expect(formComponent.getFormDefinitionByFormName).not.toHaveBeenCalled();
});
it('should not reload form on binding changes', () => {
spyOn(formComponent, 'getFormByTaskId').and.stub();
spyOn(formComponent, 'getFormDefinitionByFormId').and.stub();
spyOn(formComponent, 'getFormDefinitionByFormName').and.stub();
formComponent.ngOnChanges({ 'tag': new SimpleChange(null, 'hello world')});
expect(formComponent.getFormByTaskId).not.toHaveBeenCalled();
expect(formComponent.getFormDefinitionByFormId).not.toHaveBeenCalled();
expect(formComponent.getFormDefinitionByFormName).not.toHaveBeenCalled();
});
it('should complete form on custom outcome click', () => {
let formModel = new FormModel();
let outcomeName = 'Custom Action';
let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName });
let saved = false;
formComponent.form = formModel;
formComponent.formSaved.subscribe(v => saved = true);
spyOn(formComponent, 'completeTaskForm').and.stub();
let result = formComponent.onOutcomeClicked(outcome);
expect(result).toBeTruthy();
expect(saved).toBeTruthy();
expect(formComponent.completeTaskForm).toHaveBeenCalledWith(outcomeName);
});
it('should save form on [save] outcome click', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, {
id: ActivitiForm.SAVE_OUTCOME_ID,
name: 'Save',
isSystem: true
});
formComponent.form = formModel;
spyOn(formComponent, 'saveTaskForm').and.stub();
let result = formComponent.onOutcomeClicked(outcome);
expect(result).toBeTruthy();
expect(formComponent.saveTaskForm).toHaveBeenCalled();
});
it('should complete form on [complete] outcome click', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, {
id: ActivitiForm.COMPLETE_OUTCOME_ID,
name: 'Complete',
isSystem: true
});
formComponent.form = formModel;
spyOn(formComponent, 'completeTaskForm').and.stub();
let result = formComponent.onOutcomeClicked(outcome);
expect(result).toBeTruthy();
expect(formComponent.completeTaskForm).toHaveBeenCalled();
});
it('should emit form saved event on custom outcome click', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, {
id: ActivitiForm.CUSTOM_OUTCOME_ID,
name: 'Custom',
isSystem: true
});
let saved = false;
formComponent.form = formModel;
formComponent.formSaved.subscribe(v => saved = true);
let result = formComponent.onOutcomeClicked(outcome);
expect(result).toBeTruthy();
expect(saved).toBeTruthy();
});
it('should do nothing when clicking outcome for readonly form', () => {
let formModel = new FormModel();
const outcomeName = 'Custom Action';
let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName });
formComponent.form = formModel;
spyOn(formComponent, 'completeTaskForm').and.stub();
expect(formComponent.onOutcomeClicked(outcome)).toBeTruthy();
formComponent.readOnly = true;
expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
});
it('should require outcome model when clicking outcome', () => {
formComponent.form = new FormModel();
formComponent.readOnly = false;
expect(formComponent.onOutcomeClicked(null)).toBeFalsy();
});
it('should require loaded form when clicking outcome', () => {
let formModel = new FormModel();
const outcomeName = 'Custom Action';
let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName });
formComponent.readOnly = false;
formComponent.form = null;
expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
});
it('should not execute unknown system outcome', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, { id: 'unknown', name: 'Unknown', isSystem: true });
formComponent.form = formModel;
expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
});
it('should require custom action name to complete form', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, { id: 'custom' });
formComponent.form = formModel;
expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
outcome = new FormOutcomeModel(formModel, { id: 'custom', name: 'Custom' });
spyOn(formComponent, 'completeTaskForm').and.stub();
expect(formComponent.onOutcomeClicked(outcome)).toBeTruthy();
});
it('should fetch and parse form by task id', () => {
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({ taskId: taskId });
observer.complete();
});
});
const taskId = '456';
let loaded = false;
formComponent.formLoaded.subscribe(() => loaded = true);
expect(formComponent.form).toBeUndefined();
formComponent.getFormByTaskId(taskId);
expect(loaded).toBeTruthy();
expect(formService.getTaskForm).toHaveBeenCalledWith(taskId);
expect(formComponent.form).toBeDefined();
expect(formComponent.form.taskId).toBe(taskId);
});
it('should handle error when getting form by task id', () => {
const error = 'Some error';
spyOn(formComponent, 'handleError').and.stub();
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.throw(error);
});
formComponent.getFormByTaskId('123');
expect(formComponent.handleError).toHaveBeenCalledWith(error);
});
it('should apply readonly state when getting form by task id', () => {
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({ taskId: taskId });
observer.complete();
});
});
formComponent.readOnly = true;
formComponent.getFormByTaskId('123');
expect(formComponent.form).toBeDefined();
expect(formComponent.form.readOnly).toBe(true);
});
it('should fetch and parse form definition by id', () => {
spyOn(formService, 'getFormDefinitionById').and.callFake((formId) => {
return Observable.create(observer => {
observer.next({ id: formId });
observer.complete();
});
});
const formId = '456';
let loaded = false;
formComponent.formLoaded.subscribe(() => loaded = true);
expect(formComponent.form).toBeUndefined();
formComponent.getFormDefinitionByFormId(formId);
expect(loaded).toBeTruthy();
expect(formService.getFormDefinitionById).toHaveBeenCalledWith(formId);
expect(formComponent.form).toBeDefined();
expect(formComponent.form.id).toBe(formId);
});
it('should handle error when getting form by definition id', () => {
const error = 'Some error';
spyOn(formComponent, 'handleError').and.stub();
spyOn(formService, 'getFormDefinitionById').and.callFake(() => Observable.throw(error));
formComponent.getFormDefinitionByFormId('123');
expect(formService.getFormDefinitionById).toHaveBeenCalledWith('123');
expect(formComponent.handleError).toHaveBeenCalledWith(error);
});
it('should fetch and parse form definition by form name', () => {
spyOn(formService, 'getFormDefinitionByName').and.callFake((formName) => {
return Observable.create(observer => {
observer.next(formName);
observer.complete();
});
});
spyOn(formService, 'getFormDefinitionById').and.callFake((formName) => {
return Observable.create(observer => {
observer.next({ name: formName });
observer.complete();
});
});
const formName = '<form>';
let loaded = false;
formComponent.formLoaded.subscribe(() => loaded = true);
expect(formComponent.form).toBeUndefined();
formComponent.getFormDefinitionByFormName(formName);
expect(loaded).toBeTruthy();
expect(formService.getFormDefinitionByName).toHaveBeenCalledWith(formName);
expect(formComponent.form).toBeDefined();
expect(formComponent.form.name).toBe(formName);
});
it('should save task form and raise corresponding event', () => {
spyOn(formService, 'saveTaskForm').and.callFake(() => {
return Observable.create(observer => {
observer.next();
observer.complete();
});
});
let saved = false;
let savedForm = null;
formComponent.formSaved.subscribe(form => {
saved = true;
savedForm = form;
});
let formModel = new FormModel({
taskId: '123',
fields: [
{ id: 'field1' },
{ id: 'field2' }
]
});
formComponent.form = formModel;
formComponent.saveTaskForm();
expect(formService.saveTaskForm).toHaveBeenCalledWith(formModel.taskId, formModel.values);
expect(saved).toBeTruthy();
expect(savedForm).toEqual(formModel);
});
it('should handle error during form save', () => {
const error = 'Error';
spyOn(formService, 'saveTaskForm').and.callFake(() => Observable.throw(error));
spyOn(formComponent, 'handleError').and.stub();
formComponent.form = new FormModel({ taskId: '123' });
formComponent.saveTaskForm();
expect(formComponent.handleError).toHaveBeenCalledWith(error);
});
it('should require form with task id to save', () => {
spyOn(formService, 'saveTaskForm').and.stub();
formComponent.form = null;
formComponent.saveTaskForm();
formComponent.form = new FormModel();
formComponent.saveTaskForm();
expect(formService.saveTaskForm).not.toHaveBeenCalled();
});
it('should require form with task id to complete', () => {
spyOn(formService, 'completeTaskForm').and.stub();
formComponent.form = null;
formComponent.completeTaskForm('save');
formComponent.form = new FormModel();
formComponent.completeTaskForm('complete');
expect(formService.completeTaskForm).not.toHaveBeenCalled();
});
it('should log error to console by default', () => {
const error = 'Error';
spyOn(console, 'log').and.stub();
formComponent.handleError(error);
expect(console.log).toHaveBeenCalledWith(error);
});
it('should complete form form and raise corresponding event', () => {
spyOn(formService, 'completeTaskForm').and.callFake(() => {
return Observable.create(observer => {
observer.next();
observer.complete();
});
});
const outcome = 'complete';
let completed = false;
formComponent.formCompleted.subscribe(() => completed = true);
let formModel = new FormModel({
taskId: '123',
fields: [
{ id: 'field1' },
{ id: 'field2' }
]
});
formComponent.form = formModel;
formComponent.completeTaskForm(outcome);
expect(formService.completeTaskForm).toHaveBeenCalledWith(formModel.taskId, formModel.values, outcome);
expect(completed).toBeTruthy();
});
it('should require json to parse form', () => {
expect(formComponent.parseForm(null)).toBeNull();
});
it('should parse form from json', () => {
let form = formComponent.parseForm({
id: '<id>',
fields: [
{ id: 'field1' }
]
});
expect(form).toBeDefined();
expect(form.id).toBe('<id>');
expect(form.fields.length).toBe(1);
expect(form.fields[0].id).toBe('field1');
});
it('should provide outcomes for form definition', () => {
spyOn(formComponent, 'getFormDefinitionOutcomes').and.callThrough();
let form = formComponent.parseForm({ id: '<id>' });
expect(formComponent.getFormDefinitionOutcomes).toHaveBeenCalledWith(form);
});
it('should update the visibility when the container raise the change event', (valueChanged) => {
spyOn(formComponent, 'checkVisibility').and.callThrough();
let widget = new ContainerWidget();
let fakeForm = new FormModel();
let fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'});
widget.formValueChanged.subscribe(field => { valueChanged(); });
widget.fieldChanged(fakeField);
expect(formComponent.checkVisibility).toHaveBeenCalledWith(fakeField);
});
it('should prevent default outcome execution', () => {
let outcome = new FormOutcomeModel(new FormModel(), {
id: ActivitiForm.CUSTOM_OUTCOME_ID,
name: 'Custom'
});
formComponent.form = new FormModel();
formComponent.executeOutcome.subscribe((event: FormOutcomeEvent) => {
expect(event.outcome).toBe(outcome);
event.preventDefault();
expect(event.defaultPrevented).toBeTruthy();
});
let result = formComponent.onOutcomeClicked(outcome);
expect(result).toBeFalsy();
});
it('should not prevent default outcome execution', () => {
let outcome = new FormOutcomeModel(new FormModel(), {
id: ActivitiForm.CUSTOM_OUTCOME_ID,
name: 'Custom'
});
formComponent.form = new FormModel();
formComponent.executeOutcome.subscribe((event: FormOutcomeEvent) => {
expect(event.outcome).toBe(outcome);
expect(event.defaultPrevented).toBeFalsy();
});
spyOn(formComponent, 'completeTaskForm').and.callThrough();
let result = formComponent.onOutcomeClicked(outcome);
expect(result).toBeTruthy();
expect(formComponent.completeTaskForm).toHaveBeenCalledWith(outcome.name);
});
it('should check visibility only if field with form provided', () => {
formComponent.checkVisibility(null);
expect(visibilityService.updateVisibilityForForm).not.toHaveBeenCalled();
let field = new FormFieldModel(null);
formComponent.checkVisibility(field);
expect(visibilityService.updateVisibilityForForm).not.toHaveBeenCalled();
field = new FormFieldModel(new FormModel());
formComponent.checkVisibility(field);
expect(visibilityService.updateVisibilityForForm).toHaveBeenCalledWith(field.form);
});
});
@@ -0,0 +1,431 @@
/*!
* @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, AfterViewChecked, OnChanges,
SimpleChanges,
Input,
Output,
EventEmitter
} from '@angular/core';
import { MATERIAL_DESIGN_DIRECTIVES, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
import { EcmModelService } from './../services/ecm-model.service';
import { FormService } from './../services/form.service';
import { NodeService } from './../services/node.service';
import { FormModel, FormOutcomeModel, FormValues, FormFieldModel, FormOutcomeEvent } from './widgets/core/index';
import { TabsWidget } from './widgets/tabs/tabs.widget';
import { ContainerWidget } from './widgets/container/container.widget';
declare let __moduleName: string;
declare var componentHandler;
import { WidgetVisibilityService } from './../services/widget-visibility.service';
/**
* @Input
* ActivitiForm can show 4 types of forms searching by 4 type of params:
* 1) Form attached to a task passing the {taskId}.
*
* 2) Form that are only defined with the {formId} (in this case you receive only the form definition and the form will not be
* attached to any process, useful in case you want to use ActivitiForm as form designer), in this case you can pass also other 2
* parameters:
* - {saveOption} as parameter to tell what is the function to call on the save action.
* - {data} to fill the form field with some data, the id of the form must to match the name of the field of the provided data object.
*
* 3) Form that are only defined with the {formName} (in this case you receive only the form definition and the form will not be
* attached to any process, useful in case you want to use ActivitiForm as form designer),
* in this case you can pass also other 2 parameters:
* - {saveOption} as parameter to tell what is the function to call on the save action.
* - {data} to fill the form field with some data, the id of the form must to match the name of the field of the provided data object.
*
* 4) Form that show the metadata of a {nodeId}
*
* {showTitle} boolean - to hide the title of the form pass false, default true;
*
* {showRefreshButton} boolean - to hide the refresh button of the form pass false, default true;
*
* {showCompleteButton} boolean - to hide the complete button of the form pass false, default true;
*
* {showSaveButton} boolean - to hide the save button of the form pass false, default true;
*
* {saveMetadata} boolean - store the value of the form as metadata, default false;
*
* {path} string - path of the folder where to store the metadata;
*
* {nameNode} string (optional) - Name to assign to the new node where the metadata are stored;
*
* @Output
* {formLoaded} EventEmitter - This event is fired when the form is loaded, it pass all the value in the form.
* {formSaved} EventEmitter - This event is fired when the form is saved, it pass all the value in the form.
* {formCompleted} EventEmitter - This event is fired when the form is completed, it pass all the value in the form.
*
* @returns {ActivitiForm} .
*/
@Component({
moduleId: __moduleName,
selector: 'activiti-form',
templateUrl: './activiti-form.component.html',
styleUrls: ['./activiti-form.component.css'],
directives: [MATERIAL_DESIGN_DIRECTIVES, ContainerWidget, TabsWidget],
providers: [EcmModelService, FormService, WidgetVisibilityService, NodeService]
})
export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
static SAVE_OUTCOME_ID: string = '$save';
static COMPLETE_OUTCOME_ID: string = '$complete';
static CUSTOM_OUTCOME_ID: string = '$custom';
@Input()
taskId: string;
@Input()
nodeId: string;
@Input()
formId: string;
@Input()
formName: string;
@Input()
saveMetadata: boolean = false;
@Input()
data: FormValues;
@Input()
path: string;
@Input()
nameNode: string;
@Input()
showTitle: boolean = true;
@Input()
showCompleteButton: boolean = true;
@Input()
showSaveButton: boolean = true;
@Input()
readOnly: boolean = false;
@Input()
showRefreshButton: boolean = true;
@Output()
formSaved: EventEmitter<FormModel> = new EventEmitter<FormModel>();
@Output()
formCompleted: EventEmitter<FormModel> = new EventEmitter<FormModel>();
@Output()
formLoaded: EventEmitter<FormModel> = new EventEmitter<FormModel>();
@Output()
executeOutcome: EventEmitter<FormOutcomeEvent> = new EventEmitter<FormOutcomeEvent>();
form: FormModel;
debugMode: boolean = false;
constructor(private formService: FormService,
private visibilityService: WidgetVisibilityService,
private authService: AlfrescoAuthenticationService,
private ecmModelService: EcmModelService,
private nodeService: NodeService) {
}
hasForm(): boolean {
return this.form ? true : false;
}
isTitleEnabled(): boolean {
if (this.showTitle) {
if (this.form && this.form.taskName) {
return true;
}
}
return false;
}
isOutcomeButtonEnabled(outcome: FormOutcomeModel): boolean {
if (outcome && outcome.name) {
if (outcome.name === FormOutcomeModel.COMPLETE_ACTION) {
return this.showCompleteButton;
}
if (outcome.name === FormOutcomeModel.SAVE_ACTION) {
return this.showSaveButton;
}
return true;
}
return false;
}
ngOnInit() {
if (this.nodeId) {
this.loadActivitiFormForEcmNode();
} else {
this.loadForm();
}
}
ngAfterViewChecked() {
this.setupMaterialComponents();
}
ngOnChanges(changes: SimpleChanges) {
let taskId = changes['taskId'];
if (taskId && taskId.currentValue) {
this.getFormByTaskId(taskId.currentValue);
return;
}
let formId = changes['formId'];
if (formId && formId.currentValue) {
this.getFormDefinitionByFormId(formId.currentValue);
return;
}
let formName = changes['formName'];
if (formName && formName.currentValue) {
this.getFormDefinitionByFormName(formName.currentValue);
return;
}
}
/**
* Invoked when user clicks outcome button.
* @param outcome Form outcome model
* @returns {boolean} True if outcome action was executed, otherwise false.
*/
onOutcomeClicked(outcome: FormOutcomeModel): boolean {
if (!this.readOnly && outcome && this.form) {
let args = new FormOutcomeEvent(outcome);
this.executeOutcome.emit(args);
if (args.defaultPrevented) {
return false;
}
if (outcome.isSystem) {
if (outcome.id === ActivitiForm.SAVE_OUTCOME_ID) {
this.saveTaskForm();
return true;
}
if (outcome.id === ActivitiForm.COMPLETE_OUTCOME_ID) {
this.completeTaskForm();
return true;
}
if (outcome.id === ActivitiForm.CUSTOM_OUTCOME_ID) {
this.formSaved.emit(this.form);
this.storeFormAsMetadata();
return true;
}
} else {
// Note: Activiti is using NAME field rather than ID for outcomes
if (outcome.name) {
this.formSaved.emit(this.form);
this.completeTaskForm(outcome.name);
return true;
}
}
}
return false;
}
/**
* Invoked when user clicks form refresh button.
*/
onRefreshClicked() {
this.loadForm();
}
loadForm() {
if (this.taskId) {
this.getFormByTaskId(this.taskId);
this.visibilityService.getTaskProcessVariableModelsForTask(this.taskId);
return;
}
if (this.formId) {
this.getFormDefinitionByFormId(this.formId);
return;
}
if (this.formName) {
this.getFormDefinitionByFormName(this.formName);
return;
}
}
setupMaterialComponents(): boolean {
// workaround for MDL issues with dynamic components
if (componentHandler) {
componentHandler.upgradeAllRegistered();
return true;
}
return false;
}
getFormByTaskId(taskId: string) {
let data = this.data;
this.formService
.getTaskForm(taskId)
.subscribe(
form => {
this.form = new FormModel(form, data, this.readOnly);
this.formLoaded.emit(this.form);
},
this.handleError
);
}
getFormDefinitionByFormId(formId: string) {
this.formService
.getFormDefinitionById(formId)
.subscribe(
form => {
this.formName = form.name;
this.form = this.parseForm(form);
this.formLoaded.emit(this.form);
},
this.handleError
);
}
getFormDefinitionByFormName(formName: string) {
this.formService
.getFormDefinitionByName(formName)
.subscribe(
id => {
this.formService.getFormDefinitionById(id).subscribe(
form => {
// console.log('Get Form By Form definition Name', form);
this.form = this.parseForm(form);
this.formLoaded.emit(this.form);
},
this.handleError
);
},
this.handleError
);
}
saveTaskForm() {
if (this.form && this.form.taskId) {
this.formService
.saveTaskForm(this.form.taskId, this.form.values)
.subscribe(
() => {
this.formSaved.emit(this.form);
this.storeFormAsMetadata();
},
this.handleError
);
}
}
completeTaskForm(outcome?: string) {
if (this.form && this.form.taskId) {
this.formService
.completeTaskForm(this.form.taskId, this.form.values, outcome)
.subscribe(
() => {
this.formCompleted.emit(this.form);
this.storeFormAsMetadata();
},
this.handleError
);
}
}
handleError(err: any): any {
console.log(err);
}
parseForm(json: any): FormModel {
if (json) {
let form = new FormModel(json, this.data, this.readOnly);
if (!json.fields) {
form.outcomes = this.getFormDefinitionOutcomes(form);
}
return form;
}
return null;
}
/**
* Get custom set of outcomes for a Form Definition.
* @param form Form definition model.
* @returns {FormOutcomeModel[]} Outcomes for a given form definition.
*/
getFormDefinitionOutcomes(form: FormModel): FormOutcomeModel[] {
return [
new FormOutcomeModel(form, {id: '$custom', name: FormOutcomeModel.SAVE_ACTION, isSystem: true})
];
}
checkVisibility(field: FormFieldModel) {
if (field && field.form) {
this.visibilityService.updateVisibilityForForm(field.form);
}
}
private loadActivitiFormForEcmNode(): void {
this.nodeService.getNodeMetadata(this.nodeId).subscribe(data => {
this.data = data.metadata;
this.loadFormFromActiviti(data.nodeType);
},
this.handleError);
}
public loadFormFromActiviti(nodeType: string): any {
this.formService.searchFrom(nodeType).subscribe(
form => {
if (!form) {
this.formService.createFormFromNodeType(nodeType).subscribe(formMetadata => {
this.loadFormFromFormId(formMetadata.id);
});
} else {
this.loadFormFromFormId(form.id);
}
},
this.handleError
);
}
private loadFormFromFormId(formId: string) {
this.formId = formId;
this.loadForm();
}
private storeFormAsMetadata() {
if (this.saveMetadata) {
this.ecmModelService.createEcmTypeForActivitiForm(this.formName, this.form).subscribe(type => {
this.nodeService.createNodeMetadata(type.nodeType || type.entry.prefixedName, EcmModelService.MODEL_NAMESPACE, this.form.values, this.path, this.nameNode);
}, this.handleError
);
}
}
}
@@ -0,0 +1 @@
/widget.model.ts
@@ -0,0 +1,9 @@
<label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect" [attr.for]="field.id">
<input type="checkbox"
[attr.id]="field.id"
class="mdl-checkbox__input"
[(ngModel)]="field.value"
(ngModelChange)="checkVisibility(field)"
[disabled]="field.readOnly">
<span class="mdl-checkbox__label">{{field.name}}</span>
</label>
@@ -0,0 +1,31 @@
/*!
* @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 } from '@angular/core';
import { WidgetComponent } from './../widget.component';
declare let __moduleName: string;
declare var componentHandler;
@Component({
moduleId: __moduleName,
selector: 'checkbox-widget',
templateUrl: './checkbox.widget.html'
})
export class CheckboxWidget extends WidgetComponent {
}
@@ -0,0 +1,20 @@
.container-widget {}
.container-widget__header {}
.container-widget__header-text {
border-bottom: 1px solid rgba(0,0,0,.87);
margin-left: 10px;
margin-right: 10px;
cursor: default;
user-select: none;
-webkit-user-select: none; /* Chrome/Safari/Opera */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
-webkit-touch-callout: none; /* iOS Safari */
}
.container-widget__header-text.collapsible {
cursor: pointer;
}
@@ -0,0 +1,57 @@
<div class="container-widget">
<div *ngIf="content?.isGroup()" class="container-widget__header">
<h4 class="container-widget__header-text"
[class.collapsible]="content?.isCollapsible()">
<button *ngIf="content?.isCollapsible()"
alfresco-mdl-button
class="mdl-button--icon"
(click)="onExpanderClicked()">
<i class="material-icons">{{ content?.isExpanded ? 'expand_less' : 'expand_more' }}</i>
</button>
<span (click)="onExpanderClicked()">{{content.name}}</span>
</h4>
</div>
<div class="mdl-grid" *ngIf="content?.isExpanded">
<div *ngFor="let col of content.columns" class="mdl-cell mdl-cell--{{col.size}}-col">
<div class="mdl-grid" *ngIf="col.hasFields()">
<div *ngFor="let field of col.fields" class="mdl-cell mdl-cell--12-col">
<div [ngSwitch]="field.type" [hidden]="!field.isVisible">
<div *ngSwitchCase="'integer'">
<number-widget [field]="field" (fieldChanged)="fieldChanged($event);"></number-widget>
</div>
<div *ngSwitchCase="'text'">
<text-widget [field]="field" (fieldChanged)="fieldChanged($event);"></text-widget>
</div>
<div *ngSwitchCase="'multi-line-text'">
<multiline-text-widget [field]="field" (fieldChanged)="fieldChanged($event);"></multiline-text-widget>
</div>
<div *ngSwitchCase="'boolean'">
<checkbox-widget [field]="field" (fieldChanged)="fieldChanged($event);"></checkbox-widget>
</div>
<div *ngSwitchCase="'dropdown'">
<dropdown-widget [field]="field" (fieldChanged)="fieldChanged($event);"></dropdown-widget>
</div>
<div *ngSwitchCase="'hyperlink'">
<hyperlink-widget [field]="field" (fieldChanged)="fieldChanged($event);"></hyperlink-widget>
</div>
<div *ngSwitchCase="'radio-buttons'">
<radio-buttons-widget [field]="field" (fieldChanged)="fieldChanged($event);"></radio-buttons-widget>
</div>
<div *ngSwitchCase="'readonly'">
<display-value-widget [field]="field" (fieldChanged)="fieldChanged($event);"></display-value-widget>
</div>
<div *ngSwitchCase="'readonly-text'">
<display-text-widget [field]="field" (fieldChanged)="fieldChanged($event);"></display-text-widget>
</div>
<div *ngSwitchCase="'upload'">
<upload-widget [field]="field" (fieldChanged)="fieldChanged($event);"></upload-widget>
</div>
<div *ngSwitchDefault>
<span>UNKNOWN WIDGET TYPE: {{field.type}}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,112 @@
/*!
* @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 { it, describe, expect, beforeEach } from '@angular/core/testing';
import { ContainerWidget } from './container.widget';
import { FormModel } from './../core/form.model';
import { ContainerModel } from './../core/container.model';
import { FormFieldTypes } from './../core/form-field-types';
import { FormFieldModel } from './../core/form-field.model';
describe('ContainerWidget', () => {
let componentHandler;
beforeEach(() => {
componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered'
]);
window['componentHandler'] = componentHandler;
});
it('should upgrade MDL content on view init', () => {
let container = new ContainerWidget();
container.ngAfterViewInit();
expect(componentHandler.upgradeAllRegistered).toHaveBeenCalled();
});
it('should setup MDL content only if component handler available', () => {
let container = new ContainerWidget();
expect(container.setupMaterialComponents()).toBeTruthy();
window['componentHandler'] = null;
expect(container.setupMaterialComponents()).toBeFalsy();
});
it('should toggle underlying group container', () => {
let container = new ContainerModel(new FormModel(), {
type: FormFieldTypes.GROUP,
params: {
allowCollapse: true
}
});
let widget = new ContainerWidget();
widget.content = container;
expect(container.isExpanded).toBeTruthy();
widget.onExpanderClicked();
expect(container.isExpanded).toBeFalsy();
widget.onExpanderClicked();
expect(container.isExpanded).toBeTruthy();
});
it('should toggle only collapsible container', () => {
let container = new ContainerModel(new FormModel(), {
type: FormFieldTypes.GROUP
});
let widget = new ContainerWidget();
widget.content = container;
expect(container.isExpanded).toBeTruthy();
widget.onExpanderClicked();
expect(container.isExpanded).toBeTruthy();
});
it('should toggle only group container', () => {
let container = new ContainerModel(new FormModel(), {
type: FormFieldTypes.CONTAINER,
params: {
allowCollapse: true
}
});
let widget = new ContainerWidget();
widget.content = container;
expect(container.isExpanded).toBeTruthy();
widget.onExpanderClicked();
expect(container.isExpanded).toBeTruthy();
});
it('should send an event when a value is changed in the form', (done) => {
let widget = new ContainerWidget();
let fakeForm = new FormModel();
let fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'});
widget.formValueChanged.subscribe(field => {
expect(field).not.toBe(null);
expect(field.id).toBe('fakeField');
expect(field.value).toBe('fakeValue');
done();
});
widget.fieldChanged(fakeField);
});
});
@@ -0,0 +1,69 @@
/*!
* @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, Input, AfterViewInit, Output, EventEmitter } from '@angular/core';
import { ContainerModel } from './../core/index';
import { MATERIAL_DESIGN_DIRECTIVES } from 'ng2-alfresco-core';
import { PRIMITIVE_WIDGET_DIRECTIVES } from './../index';
import { FormFieldModel } from '../core/index';
declare let __moduleName: string;
declare var componentHandler;
@Component({
moduleId: __moduleName,
selector: 'container-widget',
templateUrl: './container.widget.html',
styleUrls: ['./container.widget.css'],
directives: [
MATERIAL_DESIGN_DIRECTIVES,
PRIMITIVE_WIDGET_DIRECTIVES
]
})
export class ContainerWidget implements AfterViewInit {
@Input()
content: ContainerModel;
@Output()
formValueChanged: EventEmitter<FormFieldModel> = new EventEmitter<FormFieldModel>();
onExpanderClicked() {
if (this.content && this.content.isCollapsible()) {
this.content.isExpanded = !this.content.isExpanded;
}
}
ngAfterViewInit() {
this.setupMaterialComponents();
}
setupMaterialComponents(): boolean {
// workaround for MDL issues with dynamic components
if (componentHandler) {
componentHandler.upgradeAllRegistered();
return true;
}
return false;
}
fieldChanged(field: FormFieldModel) {
this.formValueChanged.emit(field);
}
}
@@ -0,0 +1,42 @@
/*!
* @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 { it, describe, expect } from '@angular/core/testing';
import { ContainerColumnModel } from './container-column.model';
import { FormFieldModel } from './form-field.model';
describe('ContainerColumnModel', () => {
it('should have max size by default', () => {
let column = new ContainerColumnModel();
expect(column.size).toBe(12);
});
it('should check fields', () => {
let column = new ContainerColumnModel();
column.fields = null;
expect(column.hasFields()).toBeFalsy();
column.fields = [];
expect(column.hasFields()).toBeFalsy();
column.fields = [new FormFieldModel(null, null)];
expect(column.hasFields()).toBeTruthy();
});
});
@@ -15,17 +15,14 @@
* limitations under the License.
*/
export class ColumnSortingModel {
import { FormFieldModel } from './form-field.model';
static DEFAULT_DIRECTION: string = 'asc';
export class ContainerColumnModel {
key: string;
direction: string = ColumnSortingModel.DEFAULT_DIRECTION;
size: number = 12;
fields: FormFieldModel[] = [];
constructor(opts?: any) {
if (opts) {
this.key = opts.key;
this.direction = opts.direction || ColumnSortingModel.DEFAULT_DIRECTION;
}
hasFields(): boolean {
return this.fields && this.fields.length > 0;
}
}
@@ -0,0 +1,140 @@
/*!
* @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 { it, describe, expect } from '@angular/core/testing';
import { ContainerModel } from './container.model';
import { FormModel } from './form.model';
import { FormFieldTypes } from './form-field-types';
describe('ContainerModel', () => {
it('should store the form reference', () => {
let form = new FormModel();
let model = new ContainerModel(form);
expect(model.form).toBe(form);
});
it('should store original json', () => {
let json = {};
let model = new ContainerModel(null, json);
expect(model.json).toBe(json);
});
it('should have 1 column layout by default', () => {
let container = new ContainerModel(null, null);
expect(container.numberOfColumns).toBe(1);
});
it('should be expanded by default', () => {
let container = new ContainerModel(null, null);
expect(container.isExpanded).toBeTruthy();
});
it('should setup with json config', () => {
let json = {
fieldType: '<type>',
id: '<id>',
name: '<name>',
type: '<type>',
tab: '<tab>',
numberOfColumns: 2,
params: {}
};
let container = new ContainerModel(null, json);
Object.keys(json).forEach(key => {
expect(container[key]).toEqual(json[key]);
});
});
it('should wrap fields into columns on setup', () => {
let form = new FormModel();
let json = {
fieldType: '<type>',
id: '<id>',
name: '<name>',
type: '<type>',
tab: '<tab>',
numberOfColumns: 3,
params: {},
fields: {
'1': [
{ id: 'field-1' },
{ id: 'field-3' }
],
'2': [
{ id: 'field-2' }
],
'3': null
}
};
let container = new ContainerModel(form, json);
expect(container.columns.length).toBe(3);
let col1 = container.columns[0];
expect(col1.fields.length).toBe(2);
expect(col1.fields[0].id).toBe('field-1');
expect(col1.fields[1].id).toBe('field-3');
let col2 = container.columns[1];
expect(col2.fields.length).toBe(1);
expect(col2.fields[0].id).toBe('field-2');
let col3 = container.columns[2];
expect(col3.fields.length).toBe(0);
});
it('should allow collapsing only when of a group type', () => {
let container = new ContainerModel(new FormModel(), {
type: FormFieldTypes.CONTAINER,
params: {
allowCollapse: true
}
});
expect(container.isCollapsible()).toBeFalsy();
container.type = FormFieldTypes.GROUP;
expect(container.isCollapsible()).toBeTruthy();
});
it('should allow collapsing only when explicitly defined in params', () => {
let container = new ContainerModel(new FormModel(), {
type: FormFieldTypes.GROUP,
params: {}
});
expect(container.isCollapsible()).toBeFalsy();
container = new ContainerModel(new FormModel(), {
type: FormFieldTypes.GROUP,
params: {
allowCollapse: true
}
});
expect(container.isCollapsible()).toBeTruthy();
});
it('should be collapsed by default', () => {
let container = new ContainerModel(new FormModel(), {
type: FormFieldTypes.GROUP,
params: {
allowCollapse: true,
collapseByDefault: true
}
});
expect(container.isCollapsedByDefault()).toBeTruthy();
});
});
@@ -0,0 +1,97 @@
/*!
* @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 { FormWidgetModel } from './form-widget.model';
import { FormFieldMetadata } from './form-field-metadata';
import { ContainerColumnModel } from './container-column.model';
import { FormFieldTypes } from './form-field-types';
import { FormModel } from './form.model';
import { FormFieldModel } from './form-field.model';
// TODO: inherit FormFieldModel
export class ContainerModel extends FormWidgetModel {
fieldType: string;
id: string;
name: string;
type: string;
tab: string;
numberOfColumns: number = 1;
params: FormFieldMetadata = {};
columns: ContainerColumnModel[] = [];
isExpanded: boolean = true;
isGroup(): boolean {
return this.type === FormFieldTypes.GROUP;
}
isCollapsible(): boolean {
let allowCollapse = false;
if (this.isGroup() && this.params['allowCollapse']) {
allowCollapse = <boolean> this.params['allowCollapse'];
}
return allowCollapse;
}
isCollapsedByDefault(): boolean {
let collapseByDefault = false;
if (this.isCollapsible() && this.params['collapseByDefault']) {
collapseByDefault = <boolean> this.params['collapseByDefault'];
}
return collapseByDefault;
}
constructor(form: FormModel, json?: any) {
super(form, json);
if (json) {
this.fieldType = json.fieldType;
this.id = json.id;
this.name = json.name;
this.type = json.type;
this.tab = json.tab;
this.numberOfColumns = <number> json.numberOfColumns;
this.params = <FormFieldMetadata> json.params || {};
let columnSize: number = 12;
if (this.numberOfColumns > 1) {
columnSize = 12 / this.numberOfColumns;
}
for (let i = 0; i < this.numberOfColumns; i++) {
let col = new ContainerColumnModel();
col.size = columnSize;
this.columns.push(col);
}
if (json.fields) {
Object.keys(json.fields).map(key => {
let fields = (json.fields[key] || []).map(f => new FormFieldModel(form, f));
let col = this.columns[parseInt(key, 10) - 1];
col.fields = fields;
});
}
this.isExpanded = !this.isCollapsedByDefault();
}
}
}
@@ -0,0 +1,20 @@
/*!
* @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 FormFieldMetadata {
[key: string]: any;
}
@@ -0,0 +1,21 @@
/*!
* @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 FormFieldOption {
id: string;
name: string;
}
@@ -0,0 +1,37 @@
/*!
* @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 class FormFieldTypes {
static CONTAINER: string = 'container';
static GROUP: string = 'group';
static DROPDOWN: string = 'dropdown';
static HYPERLINK: string = 'hyperlink';
static RADIO_BUTTONS: string = 'radio-buttons';
static DISPLAY_VALUE: string = 'readonly';
static READONLY_TEXT: string = 'readonly-text';
static UPLOAD: string = 'upload';
static READONLY_TYPES: string[] = [
FormFieldTypes.HYPERLINK,
FormFieldTypes.DISPLAY_VALUE,
FormFieldTypes.READONLY_TEXT
];
static isReadOnlyType(type: string) {
return FormFieldTypes.READONLY_TYPES.indexOf(type) > -1;
}
}
@@ -0,0 +1,243 @@
/*!
* @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 { it, describe, expect } from '@angular/core/testing';
import { FormFieldModel } from './form-field.model';
import { FormFieldTypes } from './form-field-types';
import { FormModel } from './form.model';
describe('FormFieldModel', () => {
it('should store the form reference', () => {
let form = new FormModel();
let model = new FormFieldModel(form);
expect(model.form).toBe(form);
});
it('should store original json', () => {
let json = {};
let model = new FormFieldModel(new FormModel(), json);
expect(model.json).toBe(json);
});
it('should setup with json config', () => {
let json = {
fieldType: '<fieldType>',
id: '<id>',
name: '<name>',
type: '<type>',
required: true,
readOnly: true,
overrideId: true,
tab: '<tab>',
restUrl: '<rest-url>',
restResponsePath: '<rest-path>',
restIdProperty: '<rest-id>',
restLabelProperty: '<rest-label>',
colspan: 1,
options: [],
hasEmptyValue: true,
className: '<class>',
optionType: '<type>',
params: {},
hyperlinkUrl: '<url>',
displayText: '<text>',
value: '<value>'
};
let field = new FormFieldModel(new FormModel(), json);
Object.keys(json).forEach(key => {
expect(field[key]).toBe(json[key]);
});
});
it('should setup empty options collection', () => {
let field = new FormFieldModel(new FormModel(), null);
expect(field.options).toBeDefined();
expect(field.options.length).toBe(0);
field = new FormFieldModel(new FormModel(), { options: null });
expect(field.options).toBeDefined();
expect(field.options.length).toBe(0);
});
it('should setup empty params', () => {
let field = new FormFieldModel(new FormModel(), null);
expect(field.params).toEqual({});
field = new FormFieldModel(new FormModel(), { params: null });
expect(field.params).toEqual({});
});
it('should update form on every value change', () => {
let form = new FormModel();
let field = new FormFieldModel(form, { id: 'field1' });
let value = 10;
spyOn(field, 'updateForm').and.callThrough();
field.value = value;
expect(field.value).toBe(value);
expect(field.updateForm).toHaveBeenCalled();
expect(form.values['field1']).toBe(value);
});
it('should get form readonly state', () => {
let form = new FormModel();
let field = new FormFieldModel(form, null);
expect(field.readOnly).toBeFalsy();
form.readOnly = true;
expect(field.readOnly).toBeTruthy();
});
it('should take own readonly state if form is writable', () => {
let form = new FormModel();
let field = new FormFieldModel(form, { readOnly: true });
expect(form.readOnly).toBeFalsy();
expect(field.readOnly).toBeTruthy();
});
it('should parse and convert empty dropdown value', () => {
let field = new FormFieldModel(new FormModel(), {
type: FormFieldTypes.DROPDOWN,
value: ''
});
expect(field.value).toBe('empty');
});
it('should parse and leave dropdown value as is', () => {
let field = new FormFieldModel(new FormModel(), {
type: FormFieldTypes.DROPDOWN,
options: [],
value: 'deferred'
});
expect(field.value).toBe('deferred');
});
it('should parse and resolve radio button value', () => {
let field = new FormFieldModel(new FormModel(), {
type: FormFieldTypes.RADIO_BUTTONS,
options: [
{ id: 'opt1', value: 'Option 1' },
{ id: 'opt2', value: 'Option 2' }
],
value: 'opt2'
});
expect(field.value).toBe('opt2');
});
it('should parse and fall back to first radio button value', () => {
let field = new FormFieldModel(new FormModel(), {
type: FormFieldTypes.RADIO_BUTTONS,
options: [
{ id: 'opt1', value: 'Option 1' },
{ id: 'opt2', value: 'Option 2' }
],
value: 'opt3'
});
expect(field.value).toBe('opt1');
});
it('should parse and leave radio button value as is', () => {
let field = new FormFieldModel(new FormModel(), {
type: FormFieldTypes.RADIO_BUTTONS,
options: [],
value: 'deferred-radio'
});
expect(field.value).toBe('deferred-radio');
});
it('should update form with empty dropdown value', () => {
let form = new FormModel();
let field = new FormFieldModel(form, {
id: 'dropdown-1',
type: FormFieldTypes.DROPDOWN
});
field.value = 'empty';
expect(form.values['dropdown-1']).toEqual({});
field.value = '';
expect(form.values['dropdown-1']).toEqual({});
});
it('should update form with dropdown value', () => {
let form = new FormModel();
let field = new FormFieldModel(form, {
id: 'dropdown-2',
type: FormFieldTypes.DROPDOWN,
options: [
{ id: 'opt1', value: 'Option 1' },
{ id: 'opt2', value: 'Option 2' }
]
});
field.value = 'opt2';
expect(form.values['dropdown-2']).toEqual(field.options[1]);
});
it('should update form with radio button value', () => {
let form = new FormModel();
let field = new FormFieldModel(form, {
id: 'radio-1',
type: FormFieldTypes.RADIO_BUTTONS,
options: [
{ id: 'opt1', value: 'Option 1' },
{ id: 'opt2', value: 'Option 2' }
]
});
field.value = 'opt2';
expect(form.values['radio-1']).toEqual(field.options[1]);
});
it('should update form with the first radio button value', () => {
let form = new FormModel();
let field = new FormFieldModel(form, {
id: 'radio-2',
type: FormFieldTypes.RADIO_BUTTONS,
options: [
{ id: 'opt1', value: 'Option 1' },
{ id: 'opt2', value: 'Option 2' }
]
});
field.value = 'missing';
expect(form.values['radio-2']).toEqual(field.options[0]);
});
it('should not update form with display-only field value', () => {
let form = new FormModel();
FormFieldTypes.READONLY_TYPES.forEach(typeName => {
let field = new FormFieldModel(form, {
id: typeName,
type: typeName
});
field.value = '<some value>';
expect(form.values[field.id]).toBeUndefined();
});
});
});
@@ -0,0 +1,173 @@
/*!
* @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 { FormWidgetModel } from './form-widget.model';
import { FormFieldOption } from './form-field-option';
import { FormFieldTypes } from './form-field-types';
import { FormFieldMetadata } from './form-field-metadata';
import { FormModel } from './form.model';
import { WidgetVisibilityModel } from '../../../models/widget-visibility.model';
export class FormFieldModel extends FormWidgetModel {
private _value: string;
private _readOnly: boolean = false;
fieldType: string;
id: string;
name: string;
type: string;
required: boolean;
overrideId: boolean;
tab: string;
colspan: number = 1;
options: FormFieldOption[] = [];
restUrl: string;
restResponsePath: string;
restIdProperty: string;
restLabelProperty: string;
hasEmptyValue: boolean;
className: string;
optionType: string;
params: FormFieldMetadata = {};
hyperlinkUrl: string;
displayText: string;
isVisible: boolean = true;
visibilityCondition: WidgetVisibilityModel = null;
get value(): any {
return this._value;
}
set value(v: any) {
this._value = v;
this.updateForm();
}
get readOnly(): boolean {
if (this.form && this.form.readOnly) {
return true;
}
return this._readOnly;
}
constructor(form: FormModel, json?: any) {
super(form, json);
if (json) {
this.fieldType = json.fieldType;
this.id = json.id;
this.name = json.name;
this.type = json.type;
this.required = <boolean> json.required;
this._readOnly = <boolean> json.readOnly;
this.overrideId = <boolean> json.overrideId;
this.tab = json.tab;
this.restUrl = json.restUrl;
this.restResponsePath = json.restResponsePath;
this.restIdProperty = json.restIdProperty;
this.restLabelProperty = json.restLabelProperty;
this.colspan = <number> json.colspan;
this.options = <FormFieldOption[]> json.options || [];
this.hasEmptyValue = <boolean> json.hasEmptyValue;
this.className = json.className;
this.optionType = json.optionType;
this.params = <FormFieldMetadata> json.params || {};
this.hyperlinkUrl = json.hyperlinkUrl;
this.displayText = json.displayText;
this.visibilityCondition = <WidgetVisibilityModel> json.visibilityCondition;
this._value = this.parseValue(json);
this.updateForm();
}
}
parseValue(json: any): any {
let value = json.value;
/*
This is needed due to Activiti issue related to reading dropdown values as value string
but saving back as object: { id: <id>, name: <name> }
*/
// TODO: needs review
if (json.type === FormFieldTypes.DROPDOWN) {
if (value === '') {
value = 'empty';
}
}
/*
This is needed due to Activiti issue related to reading radio button values as value string
but saving back as object: { id: <id>, name: <name> }
*/
if (json.type === FormFieldTypes.RADIO_BUTTONS) {
// Activiti has a bug with default radio button value,
// so try resolving current one with a fallback to first entry
let entry: FormFieldOption[] = this.options.filter(opt => opt.id === value);
if (entry.length > 0) {
value = entry[0].id;
} else if (this.options.length > 0) {
value = this.options[0].id;
}
}
return value;
}
updateForm() {
switch (this.type) {
case FormFieldTypes.DROPDOWN:
/*
This is needed due to Activiti reading dropdown values as string
but saving back as object: { id: <id>, name: <name> }
*/
if (this.value === 'empty' || this.value === '') {
this.form.values[this.id] = {};
} else {
let entry: FormFieldOption[] = this.options.filter(opt => opt.id === this.value);
if (entry.length > 0) {
this.form.values[this.id] = entry[0];
}
}
break;
case FormFieldTypes.RADIO_BUTTONS:
/*
This is needed due to Activiti issue related to reading radio button values as value string
but saving back as object: { id: <id>, name: <name> }
*/
let entry: FormFieldOption[] = this.options.filter(opt => opt.id === this.value);
if (entry.length > 0) {
this.form.values[this.id] = entry[0];
} else if (this.options.length > 0) {
this.form.values[this.id] = this.options[0];
}
break;
case FormFieldTypes.UPLOAD:
if (this.value && this.value.length > 0) {
this.form.values[this.id] = `${this.value[0].id}`;
} else {
this.form.values[this.id] = null;
}
break;
default:
if (!FormFieldTypes.isReadOnlyType(this.type)) {
this.form.values[this.id] = this.value;
}
}
}
}
@@ -0,0 +1,41 @@
/*!
* @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 { FormOutcomeModel } from './form-outcome.model';
export class FormOutcomeEvent {
private _outcome: FormOutcomeModel;
private _defaultPrevented: boolean = false;
get outcome(): FormOutcomeModel {
return this._outcome;
}
get defaultPrevented() {
return this._defaultPrevented;
}
constructor(outcome: FormOutcomeModel) {
this._outcome = outcome;
}
preventDefault() {
this._defaultPrevented = true;
}
}
@@ -0,0 +1,46 @@
/*!
* @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 { it, describe, expect } from '@angular/core/testing';
import { FormModel } from './form.model';
import { FormOutcomeModel } from './form-outcome.model';
describe('FormOutcomeModel', () => {
it('should setup with json config', () => {
let json = {
id: '<id>',
name: '<name>'
};
let model = new FormOutcomeModel(null, json);
expect(model.id).toBe(json.id);
expect(model.name).toBe(json.name);
});
it('should store the form reference', () => {
let form = new FormModel();
let model = new FormOutcomeModel(form);
expect(model.form).toBe(form);
});
it('should store original json', () => {
let json = {};
let model = new FormOutcomeModel(null, json);
expect(model.json).toBe(json);
});
});
@@ -0,0 +1,48 @@
/*!
* @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 { FormWidgetModel } from './form-widget.model';
import { FormModel } from './form.model';
export class FormOutcomeModel extends FormWidgetModel {
static SAVE_ACTION: string = 'Save'; // Activiti 'Save' action name
static COMPLETE_ACTION: string = 'Complete'; // Activiti 'Complete' action name
private _id: string;
private _name: string;
isSystem: boolean = false;
get id() {
return this._id;
}
get name() {
return this._name;
}
constructor(form: FormModel, json?: any) {
super(form, json);
if (json) {
this._id = json.id;
this._name = json.name;
this.isSystem = json.isSystem ? true : false;
}
}
}
@@ -0,0 +1,21 @@
/*!
* @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 { FormFieldMetadata } from './form-field-metadata';
export interface FormValues extends FormFieldMetadata {
}
@@ -0,0 +1,36 @@
/*!
* @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 { it, describe, expect } from '@angular/core/testing';
import { FormModel } from './form.model';
import { FormWidgetModel } from './form-widget.model';
describe('FormWidgetModel', () => {
it('should store the form reference', () => {
let form = new FormModel();
let model = new FormWidgetModel(form, null);
expect(model.form).toBe(form);
});
it('should store original json', () => {
let json = {};
let model = new FormWidgetModel(null, json);
expect(model.json).toBe(json);
});
});
@@ -0,0 +1,41 @@
/*!
* @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 { FormModel } from './form.model';
export class FormWidgetModel {
private _form: FormModel;
private _json: any;
get form(): FormModel {
return this._form;
}
get json(): any {
return this._json;
}
constructor(form: FormModel, json: any) {
this._form = form;
this._json = json;
}
}
export interface FormWidgetModelCache<T extends FormWidgetModel> {
[key: string]: T;
}
@@ -0,0 +1,295 @@
/*!
* @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 { it, describe, expect } from '@angular/core/testing';
import { FormModel } from './form.model';
import { TabModel } from './tab.model';
import { ContainerModel } from './container.model';
import { FormOutcomeModel } from './form-outcome.model';
import { FormValues } from './form-values';
describe('FormModel', () => {
it('should store original json', () => {
let json = {};
let form = new FormModel(json);
expect(form.json).toBe(json);
});
it('should setup properties with json', () => {
let json = {
id: '<id>',
name: '<name>',
taskId: '<task-id>',
taskName: '<task-name>'
};
let form = new FormModel(json);
Object.keys(json).forEach(key => {
expect(form[key]).toEqual(form[key]);
});
});
it('should take form name when task name is missing', () => {
let json = {
id: '<id>',
name: '<name>'
};
let form = new FormModel(json);
expect(form.taskName).toBe(json.name);
});
it('should use fallback value for task name', () => {
let form = new FormModel({});
expect(form.taskName).toBe(FormModel.UNSET_TASK_NAME);
});
it('should set readonly state from params', () => {
let form = new FormModel({}, null, true);
expect(form.readOnly).toBeTruthy();
});
it('should check tabs', () => {
let form = new FormModel();
form.tabs = null;
expect(form.hasTabs()).toBeFalsy();
form.tabs = [];
expect(form.hasTabs()).toBeFalsy();
form.tabs = [new TabModel(null)];
expect(form.hasTabs()).toBeTruthy();
});
it('should check fields', () => {
let form = new FormModel();
form.fields = null;
expect(form.hasFields()).toBeFalsy();
form.fields = [];
expect(form.hasFields()).toBeFalsy();
form.fields = [new ContainerModel(null)];
expect(form.hasFields()).toBeTruthy();
});
it('should check outcomes', () => {
let form = new FormModel();
form.outcomes = null;
expect(form.hasOutcomes()).toBeFalsy();
form.outcomes = [];
expect(form.hasOutcomes()).toBeFalsy();
form.outcomes = [new FormOutcomeModel(null)];
expect(form.hasOutcomes()).toBeTruthy();
});
it('should parse tabs', () => {
let json = {
tabs: [
{ id: 'tab1' },
{ id: 'tab2' }
]
};
let form = new FormModel(json);
expect(form.tabs.length).toBe(2);
expect(form.tabs[0].id).toBe('tab1');
expect(form.tabs[1].id).toBe('tab2');
});
it('should parse fields', () => {
let json = {
fields: [
{ id: 'field1' },
{ id: 'field2' }
]
};
let form = new FormModel(json);
expect(form.fields.length).toBe(2);
expect(form.fields[0].id).toBe('field1');
expect(form.fields[1].id).toBe('field2');
});
it('should parse fields from the definition', () => {
let json = {
fields: null,
formDefinition: {
fields: [
{ id: 'field1' },
{ id: 'field2' }
]
}
};
let form = new FormModel(json);
expect(form.fields.length).toBe(2);
expect(form.fields[0].id).toBe('field1');
expect(form.fields[1].id).toBe('field2');
});
it('should convert missing fields to empty collection', () => {
let json = {
fields: null
};
let form = new FormModel(json);
expect(form.fields).toBeDefined();
expect(form.fields.length).toBe(0);
});
it('should put fields into corresponding tabs', () => {
let json = {
tabs: [
{ id: 'tab1' },
{ id: 'tab2' }
],
fields: [
{ id: 'field1', tab: 'tab1' },
{ id: 'field2', tab: 'tab2' },
{ id: 'field3', tab: 'tab1' },
{ id: 'field4', tab: 'missing-tab' }
]
};
let form = new FormModel(json);
expect(form.tabs.length).toBe(2);
expect(form.fields.length).toBe(4);
let tab1 = form.tabs[0];
expect(tab1.fields.length).toBe(2);
expect(tab1.fields[0].id).toBe('field1');
expect(tab1.fields[1].id).toBe('field3');
let tab2 = form.tabs[1];
expect(tab2.fields.length).toBe(1);
expect(tab2.fields[0].id).toBe('field2');
});
it('should apply external data', () => {
let data: FormValues = {
field1: 'one',
field2: 'two'
};
let json = {
fields: [
{
fieldType: 'ContainerRepresentation',
id: 'container1',
type: 'container',
numberOfColumns: 2,
fields: {
'1': [
{
fieldType: 'FormFieldRepresentation',
type: 'text',
id: 'field1'
}
],
'2': [
{
fieldType: 'FormFieldRepresentation',
type: 'text',
id: 'field2'
},
{
fieldType: 'FormFieldRepresentation',
type: 'text',
id: 'field3',
value: 'original-value'
}
]
}
}
]
};
let form = new FormModel(json, data);
expect(form.fields.length).toBe(1);
let container = form.fields[0];
expect(container.columns.length).toBe(2);
let column1 = container.columns[0];
let column2 = container.columns[1];
expect(column1.fields.length).toBe(1);
expect(column2.fields.length).toBe(2);
let field1 = column1.fields[0];
expect(field1.id).toBe('field1');
expect(field1.value).toBe('one');
let field2 = column2.fields[0];
expect(field2.id).toBe('field2');
expect(field2.value).toBe('two');
let field3 = column2.fields[1];
expect(field3.id).toBe('field3');
expect(field3.value).toBe('original-value');
});
it('should create standard form outcomes', () => {
let json = {
fields: [
{ id: 'container1' }
]
};
let form = new FormModel(json);
expect(form.outcomes.length).toBe(2);
expect(form.outcomes[0].id).toBe(FormModel.SAVE_OUTCOME);
expect(form.outcomes[0].isSystem).toBeTruthy();
expect(form.outcomes[1].id).toBe(FormModel.COMPLETE_OUTCOME);
expect(form.outcomes[1].isSystem).toBeTruthy();
});
it('should create outcomes only when fields available', () => {
let json = {
fields: null
};
let form = new FormModel(json);
expect(form.outcomes.length).toBe(0);
});
it('should use custom form outcomes', () => {
let json = {
fields: [
{ id: 'container1' }
],
outcomes: [
{ id: 'custom-1', name: 'custom 1' }
]
};
let form = new FormModel(json);
expect(form.outcomes.length).toBe(2);
expect(form.outcomes[0].id).toBe(FormModel.SAVE_OUTCOME);
expect(form.outcomes[0].isSystem).toBeTruthy();
expect(form.outcomes[1].id).toBe('custom-1');
expect(form.outcomes[1].isSystem).toBeFalsy();
});
});
@@ -0,0 +1,151 @@
/*!
* @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 { FormWidgetModelCache } from './form-widget.model';
import { FormValues } from './form-values';
import { ContainerModel } from './container.model';
import { TabModel } from './tab.model';
import { FormOutcomeModel } from './form-outcome.model';
export class FormModel {
static UNSET_TASK_NAME: string = 'Nameless task';
static SAVE_OUTCOME: string = '$save';
static COMPLETE_OUTCOME: string = '$complete';
private _id: string;
private _name: string;
private _taskId: string;
private _taskName: string = FormModel.UNSET_TASK_NAME;
get id(): string {
return this._id;
}
get name(): string {
return this._name;
}
get taskId(): string {
return this._taskId;
}
get taskName(): string {
return this._taskName;
}
readOnly: boolean = false;
tabs: TabModel[] = [];
fields: ContainerModel[] = [];
outcomes: FormOutcomeModel[] = [];
values: FormValues = {};
private _json: any;
get json() {
return this._json;
}
hasTabs(): boolean {
return this.tabs && this.tabs.length > 0;
}
hasFields(): boolean {
return this.fields && this.fields.length > 0;
}
hasOutcomes(): boolean {
return this.outcomes && this.outcomes.length > 0;
}
constructor(json?: any, data?: FormValues, readOnly: boolean = false) {
this.readOnly = readOnly;
if (json) {
this._json = json;
this._id = json.id;
this._name = json.name;
this._taskId = json.taskId;
this._taskName = json.taskName || json.name || FormModel.UNSET_TASK_NAME;
let tabCache: FormWidgetModelCache<TabModel> = {};
this.tabs = (json.tabs || []).map(t => {
let model = new TabModel(this, t);
tabCache[model.id] = model;
return model;
});
this.fields = this.parseContainerFields(json);
if (data) {
this.loadData(data);
}
for (let i = 0; i < this.fields.length; i++) {
let field = this.fields[i];
if (field.tab) {
let tab = tabCache[field.tab];
if (tab) {
tab.fields.push(new ContainerModel(this, field.json));
}
}
}
if (json.fields) {
let saveOutcome = new FormOutcomeModel(this, { id: FormModel.SAVE_OUTCOME, name: 'Save', isSystem: true });
let completeOutcome = new FormOutcomeModel(this, {id: FormModel.COMPLETE_OUTCOME, name: 'Complete', isSystem: true });
let customOutcomes = (json.outcomes || []).map(obj => new FormOutcomeModel(this, obj));
this.outcomes = [saveOutcome].concat(
customOutcomes.length > 0 ? customOutcomes : [completeOutcome]
);
}
}
}
private parseContainerFields(json: any): ContainerModel[] {
let fields = [];
if (json.fields) {
fields = json.fields;
} else if (json.formDefinition && json.formDefinition.fields) {
fields = json.formDefinition.fields;
}
return fields.map(obj => new ContainerModel(this, obj));
}
// Loads external data and overrides field values
// Typically used when form definition and form data coming from different sources
private loadData(data: FormValues) {
for (let i = 0; i < this.fields.length; i++) {
let container = this.fields[i];
for (let i = 0; i < container.columns.length; i++) {
let column = container.columns[i];
for (let i = 0; i < column.fields.length; i++) {
let field = column.fields[i];
if (data[field.id]) {
field.json.value = data[field.id];
field.value = data[field.id];
}
}
}
}
}
}
@@ -0,0 +1,29 @@
/*!
* @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 * from './form-field-metadata';
export * from './form-values';
export * from './form-field-types';
export * from './form-field-option';
export * from './form-widget.model';
export * from './form-field.model';
export * from './form.model';
export * from './container-column.model';
export * from './container.model';
export * from './tab.model';
export * from './form-outcome.model';
export * from './form-outcome-event.model';
@@ -0,0 +1,70 @@
/*!
* @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 { it, describe, expect } from '@angular/core/testing';
import { FormModel } from './form.model';
import { TabModel } from './tab.model';
import { ContainerModel } from './container.model';
describe('TabModel', () => {
it('should setup with json config', () => {
let json = {
id: '<id>',
title: '<title>',
visibilityCondition: '<condition>'
};
let model = new TabModel(null, json);
expect(model.id).toBe(json.id);
expect(model.title).toBe(json.title);
expect(model.visibilityCondition).toBe(json.visibilityCondition);
});
it('should not setup with json config', () => {
let model = new TabModel(null, null);
expect(model.id).toBeUndefined();
expect(model.title).toBeUndefined();
expect(model.visibilityCondition).toBeUndefined();
});
it('should evaluate content based on fields', () => {
let model = new TabModel(null, null);
model.fields = null;
expect(model.hasContent()).toBeFalsy();
model.fields = [];
expect(model.hasContent()).toBeFalsy();
model.fields = [new ContainerModel(null, null)];
expect(model.hasContent()).toBeTruthy();
});
it('should store the form reference', () => {
let form = new FormModel();
let model = new TabModel(form);
expect(model.form).toBe(form);
});
it('should store original json', () => {
let json = {};
let model = new TabModel(null, json);
expect(model.json).toBe(json);
});
});
@@ -0,0 +1,43 @@
/*!
* @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 { FormWidgetModel } from './form-widget.model';
import { ContainerModel } from './container.model';
import { FormModel } from './form.model';
export class TabModel extends FormWidgetModel {
id: string;
title: string;
visibilityCondition: any;
fields: ContainerModel[] = [];
hasContent(): boolean {
return this.fields && this.fields.length > 0;
}
constructor(form: FormModel, json?: any) {
super(form, json);
if (json) {
this.id = json.id;
this.title = json.title;
this.visibilityCondition = json.visibilityCondition;
}
}
}
@@ -0,0 +1 @@
.display-text-widget {}
@@ -0,0 +1,3 @@
<div class="display-text-widget">
<span>{{field.value}}</span>
</div>
@@ -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.
*/
import { Component } from '@angular/core';
import { WidgetComponent } from './../widget.component';
declare let __moduleName: string;
declare var componentHandler;
@Component({
moduleId: __moduleName,
selector: 'display-text-widget',
templateUrl: './display-text.widget.html',
styleUrls: ['./display-text.widget.css']
})
export class DisplayTextWidget extends WidgetComponent {
}
@@ -0,0 +1,3 @@
.display-value-widget {
width: 100%;
}
@@ -0,0 +1,10 @@
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label display-value-widget">
<input class="mdl-textfield__input"
type="text"
[attr.id]="field.id"
[(ngModel)]="field.value"
(ngModelChange)="checkVisibility(field)"
disabled>
<label class="mdl-textfield__label" [attr.for]="field.id">{{field.name}}</label>
</div>
@@ -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.
*/
import { Component } from '@angular/core';
import { WidgetComponent } from './../widget.component';
declare let __moduleName: string;
declare var componentHandler;
@Component({
moduleId: __moduleName,
selector: 'display-value-widget',
templateUrl: './display-value.widget.html',
styleUrls: ['./display-value.widget.css']
})
export class DisplayValueWidget extends WidgetComponent {
}

Some files were not shown because too many files have changed in this diff Show More