* fix upload area snackbar behaviour

* SASS support for components

ability to use '.scss' files from within components

* [ADF-610] Upload button and DnD area should not upload hidden files and folders (#1908)

[ADF-610]  upload cleanup
- more strongly typing
- api improvements

* Upload cleanup and api improvements

- remove old unused settings (formFields variable)
- individual options for uploaded files (i.e. versioning)
- upload button and drag-and-drop area now set individual settings for file versioning

* exclude hidden files from upload

* [ADF-640] reload document list on folder upload (#1895)

* reload document list on folder upload

- extend UploadService with 'folderCreated' event to be able reacting on folder uploads globally
- extend Demo Shell to reload document list on UploadService events (folderCreated)

* readme updates

* [ADF-621] show drop effect on folders only (#1897)

* show drop effect on folders only

- fix `hasValue` api for data rows (avoid 'false' value to be evaluated as missing value)
- support for evaluating drop support for rows
- document list enables upload zones for folders only

* api improvements as per code review

* [ADF-242] Fixed behaviour for saving/deleting reports (#1905)

* [ADF-242] - fix for deleting - saving a report

* [ADF - 242] added test for fixed feature on reports save - delete

* Added translation key

* [ADF-604] Upgrade @angular/material to latest version (#1909)

* update dependencies and module imports

* fix template warnings and fix import issues

* migrate Activiti Form to MdTabsModule

* fix unit tests

* fix tests

* fix unit tests

* fix unit tests

* disable test that fails only on travis

* upgrade activiti form component to angular/material

* fix test (remove MDL class check)

* [ADF-613] Add plain text viewer (#1873)

* add plain text viewer

* different devices optimizations

* returns types

* [ADF-573] support for toggling enabled state (#1912)

* [ADF-602] Accordion component - Add basic documentation (#1913)

* Add basic documentation Accordion component

* Update README.md

* [ADF-680] Added previous page check when page has no more elements (#1911)

* [ADF-242] - fix for deleting - saving a report

* [ADF - 242] added test for fixed feature on reports save - delete

* [ADF-680] - Fixed behaviour when deleting all files on last page of document list

* Start adding test for documentlist check

* Added test for check double page load

* [ADF-680] - removed commented test code

* [ADF-680] Added changes from peer review

* [ADF-680] added return type

* [ADF-667] selection mode and row styles (#1914)

* selection mode and row styles

- single/multiple/none selection modes for DataTable component (and Document List)
- support for custom row styles (inline and classname values)
- fix karma config (material themes)
- readme updates
- package-lock.json files for NPM5 support
- updated DataTable demo to demonstrate selection modes and row styles

* remove package lock files

* move demo projects to webpack (#1915)

* wav and Mp3 enabling viewer (#1916)

* add option only demo shell version change for update version script

* ADF-402 add the show diagram button (#1917)

* [ADF-707] Ability to select a row on a dynamic table (#1921)

* [ADF-710] Create an Process Attachment List component (#1919)

* added new component to list the process attachments with view, download and delete functionality

* added unit test cases for activit-process-attachment-list component

* exported new process attachment list component

* added documentation for process-attachment-list component

* [ADF-712] Task Attachment - Provide a way to attach a new content (#1898)

* create button, download, view functionality added in task attachment list component

* created sevice to attach document to task

* added new component to create/uplaod attachment to task

* added new component to create/uplaod attachment to task

* added test case for create task attachment component

* added test case for create task attachment component

* added input to block upload document  to ECM

* fixed create task attachment spec file issue

* changed alfresco-upload to alfresco-core upload directive

* removed attachCreate button and emitter from task-attachment-list

* removed uploadToEcm input and checkValidity method from alfresco-upload

* added documentation for task-attachment-list and create-task-attachment components

* [ADF-696] Entire accordion group header should be clickable (#1918)

* #ADF-696 Added new input to show/hide expand icon, click event is activated for the complete heading

* #ADF-696 tslint fix

* #ADF-696 Added documentation for new input and removed unwanted div

* [ADF-721] Fix translation reference for dev task (#1923)

* move translation files in the bundles folder

* fix after review
ripristinate tslint and remove override tsconfig

* [ADF-709] add autofocus when a new row is added on dynamic table widget (#1927)

* [ADF-709] add autofocus when a new row is added on dynamic table widget

* [ADF-709] removed wrong reference for template

* [ADF-713] Process Attachment - Provide a way to attach a new content (#1920)

* added service to get all the related content of the process instance

* added new component to create/upload attachment for process instance

* added unit test cases for create-process-attachment component

* exported create-process-attachment component

* added documentation for create-process-attachment component

* Add data-automation-id to multi select checkbox (#1924)

* [ADF-571] upload feature rework (#1922)

* upload feature rework

lots of improvements for upload dialog and underlying services

* readme update

- readme cleanup
- remove some old comments from code
- update readme with new events for Upload Service

* restore prerequisites section in readme

* fix i18n issue with webpack

* exported report and chart models (#1925)

* fix file upload bug (#1928)

- proper extraction of File objects from the FileList

* lock files for npm 5 (#1930)

add lock files for npm v5; does nothing for earlier versions, so is not harmful

* Source Mapping is not working on test debugging (#1931)

* coverage single components run fix

* remove spec.ts from coverage

* make the coverage and the istanbul-instrumenter-loader works only over the console test because a problem on the remapping for the browser test

* move tslint on the main folder of any component

* remove build:w from readme

* stop build tslint error also in spec files

* clear karma file from unnecessary files

* add set -f for build all script in order to accept *

* fix lint problem and failing tests

* fix failing test search component

* add loader test for viewer

* fix tslint error userinfo

* --max_old_space_size=2048 remove

* fix tslint error uploader unused EventEmitter

* remove spec|index|.*mock|.*model|.*event from coverage

* move coverage separate file and get component to calculate coverage as input

* remove old 'banned' demo from login screen (#1929)

* add sleep time flag in publish script

* rollback demo tag

* fix pacakge.json tag

* [ADF-686] add blobFile as input (#1933)

* coverage fix (#1934)

* [ADF-702] Task/Process Filter - Provide a way to change the default filter (#1932)

* [ADF-702] Task/Process Filter - Provide a way to change the default filter

* Provide a way to select a custom menu filter

* Improve activiti process filter

* Add internal link

* Change link name

* add link

* [ADF-744] Attachment List is not displayed within Processes. (#1937)

* Use the adf process attachment list indise demo shell

* Change documentation

* support for healdess chrome (#1939)

* #ADF-696 Now accordion opens/closes on click of group header along with emitting heading click event (#1936)

* add info and link on current last git commit (#1940)

* [ADF-754] toolbar component (#1938)

* toolbar component

- simple toolbar component (core lib)
- readme updates (core lib)
- update demo shell with toolbar component demo (document list)

* update unit tests

* [ADF-763] Add Chrome default browser for karma chrome launcher for Chrome versi… (#1941)

* Add Chrome default browser for karma chrome launcher for Chrome version<59

* Fixing intermittently failing test in ng2-activiti-analytics component

* Adding new icon (sent) for Bootstrap to Material icon mapping (#1943)

* fix blob input in text viewer (#1942)

* GitHub issue & pull request template change (#1945)

* Update ISSUE_TEMPLATE.md

* Update PULL_REQUEST_TEMPLATE.md

* [ADF-689] Fix alfresco-document-menu-action styling (z-index) (#1944)

* fix translation wrong folder creation issue

* [ADF-773] Fix datatable custom template render (#1947)

* [ADF-780] centralised call for process filters api (#1950)

* [ADF-780] centralised call for process filters api

* [ADF-780] updated conversion to string

* [ADF-741] Add the create task attachment component to the demo shell (#1946)

* Add the create task attachment component to the demo shell

* Add translation keys

* Add return to methods

* fix thumbnail task process list (#1951)

* [ADF-643] upload enhancements (#1949)

* rework folder uploading

- flatterns hierarchy on folder upload
- performs a single traversal for the entire folder heirarchy and ends with a comple file list
- allows now dropping folders on existing folders
- overall code improvements

* fix unit tests

* readme updates

* clean old and unused code

* code cleanup

* limit concurrent uploads

* update code as per review

* fix upload button for Safari

* fixes for Safari

- Safari compatibility
- code updates based on review

* fix code

* fix unit tests

* [ADF-589] Login component different bug fixes (#1953)

* Basic style changes

* Further design changes

* Responsive design fixes

* Different sign in button style for the different login steps

* #ADF-780 Fixed getProcessFilterByName to get the correct filter for the given appId and name (#1952)

* fix issues with the require keyword (angular cli) (#1954)

* [ADF-799] add HappyPack to webpack conf (#1956)

* update npm5 lock files

* [ADF-740] Add button for process attachment list (#1955)

* [ADF-740] adding button to allow user to upload related content on process instance

* [ADF-740] add button for attachment content list for process

* changed locatin for translation

* [ADF-740] added test for add button for process attach

* [ADF-740] added PR request changes

* [ADF-802] fix error on uploading file to attachment list (#1957)

* [ADF-802] fix error on uploading file to attachment list

* [ADF-802] improved for loop

* [ADF-797] remove dist folder from npm distributed package , leave src and bundles (#1961)

* [ADF-804] webpack proxy setup to avoid CORS problem (#1960)

* package lock update

* update travis to node 8 (#1965)

* upload service exposes created nodes (#1964)

* [ADF-591] documentation refinements (#1959)

* refine ng2-activiti-analytics

* refine ng2-activiti-diagrams docs

* refine ng2-activiti-form

* refine ng2-activiti-processlist

* refine ng2-activiti-tasklist

* refine ng2-alfresco-core

* refine ng2-alfresco-datatable

* refine ng2-alfresco-datatable

* refine gn2-alfresco-login

* refine ng2-alfresco-search

* refine ng2-alfresco-social

* refine ng2-alfresco-tag

* refine ng2-alfresco-upload

* refine ng2-alfresco-userinfo

* refine ng2-alfresco-viewer

* refine ng2-alfresco-webscript

* various readme cleanups

* fix builds related to node-sass library (#1966)

* update dependencies and remove old lock files

* update sass loader

* updated lock files

* [ADF-578] Remember me functionality (#1962)

* Remember me functionality

* Happy pack to ng2-components' package.json

* Build fix

* Adding tabindices to viewer control elements (#1968)

* karma conf all single browser

* Fix current page number issue (#1970)

* [ADF-524] Datatable loading state (#1958)

* loading state datatable

* modify readme after review

* [ADF-78]  Update CORS help (#1973)

* Fix host configuration in demo-shell when no port is present (#1971)

* remove brachet

* [ADF-494] fixed readonly rendering for forms (#1972)

* [ADF-494] improved disabling for form

* [ADF-494] fixed readonly rendering for forms

* [ADF-814] application configuration service (#1969)

* app configuration service

* api improvements and readme update

* extend readme and add defaults

* unit tests for app config service

* [ADF-716] Task Header - Use a custom view inside the component (#1967)

* Use a generic custom view inside the task header

* Move the component into core component and change name

* Missing file

* Fix unit test

* fix unit test component name

* fix issue with shared Code settings

- remove obsolete rules for .js/.ts
- hide .happypack folder in the project tree

* [ADF-810] fix default value radio button (#1975)

* [ADF-510] Drag&Drop check permission to allow user to upload a file (#1948)

* [ADF-510] added permission check for drag&drop

* Improved code for drag and drop side

* Added test for drag and drop upload area changes

* Added test for document list permissions check

* [ADF-510] rebased branch after changes applied to upload

* [ADF-510] rebased branch and fixed tests

* [ADF-717] upgrade i18n and charting dependencies (#1976)

* remove app-specific polyfill dependencies

remove polyfill dependencies never used by component libraries

* upgrade i18n dependencies

* upgrade ng2-charts dependency

* fix unit tests

* update demo projects

* [ADF-524] Fix empty state after the loading introduction (#1980)

* fix empty state after the loading introduction

* Update document-list.component.spec.ts

remove typo

* [ADF-838] Table of content automatic creation (#1981)

* readonly value set

* Table of content automatic creation (#1982)

* add missing intl dependency for demo shell (#1984)

* [ADF-833] DataTable -  improve the single and double click event (#1979)

* Improve the single and double click event

* Fix unit test

* Task header basic documentation (#1985)

* Disable upload attachment when the task is completed (#1987)

* [ADF-847] upgrade to use application configuration service (#1986)

* migrate core lib to use server-side app config

* fix unit tests

* update Search tests

- update tests
- upgrade tests to use TestBed

* update UserInfo tests

* update Social tests

* update tests

* update unit tests

* cleanup old code

* update about page

* update demo shell readme

* dev and prod configurations

* updated package-lock file and removed duplicates in package.json

* [ADF-851] execute-outcome event for form service (#1989)

* execute-outcome event for form service

* readme updates

* fix loading state excluding other state during the loading (#1991)

* Fix compilation error (#1993)

* [ADF-883] Fix build errors (#1992)

* [ADF-793] Ability to create PDF renditions in case of non supported formats (#1994)

* Style changes and button

* Convert to PDF button

* Convert to PDF button part II.

* Convert within the Not Supported Format component

* Rendition loading skeleton

* Conversion is working.

* Convert button behaviour tests

* Rebasing fix.

* app settings page (#1997)

- custom app setttings service to use isolated storage (demo shell)
- restore settings UI
- redirect angular and rxjs to the same version as components use.

* [ADF-822] Added the npm-prepublish script (#1978)

* added the npm prepublish script

* changed permissions to prepublish script

* changed to npm run prepublish

* prepare the pr

* removed useless code to the script

* remove flags lib from demo shell (#1983)

* remove flags lib from demo shell

greatly reduce demo shell webpack resources by switching off flags (only 3 icons were displayed in the past)

* merge package.json

* add icons

* Fix typo error

* [ADF-794] Add people assignment component (#1977)

* Add people component

* exported people service

* added people-list component to show the involved user list

* changed people-search component layout

* changed people-list usage in people component

* changed people-list data table from custom template to data adapter

* changes people-search component related to people-list

* changes in activiti-people related to people-list and people-search component

* changed data adapter to direct data column setting to data-table

* removed ngChanges and added User and UserEvent models

* added User and UserEvent model in emitter and other emitter handler

* added user event model

* changed activiti-people component with latest UX changes

* addedand changed translate keys to the components

* added hasUser method to check the condition in html

* fixed tslint issue and test case issue in activiti-people component

* added test case for actviti-people-list component

* test case added for activiti-people-search component

* changed activiti-people test cases according to latest UX changes

* added description for activiti-people component

* changed test case to fix component.upgradeElement issue

* changes requested by Vito Albano #1

* splitted getDisplayUser into getDisplayUser and getInitialUsername

* introduce check type definition

* [ADF-897] - ActivitiPeopleList - use the adf prefix (#2001)

* Use the adf as prexif instead of activiti

* Fix typo

* Fix wrong import

* support binding [form] data directly (#1996)

- ability to bind [form] data directly inside `<activiti-form>` component
- ability to parse forms with FormService
- demo of the custom form in demo shell

* [ADF-778] cancel window for upload dialog shows only on complete (#2003)

* [ADF-778] Added new behaviour to upload dialog

* [ADF-778] cancel window for upload dialog shows only on complete

* [ADF-778] changed variable name to showCloseButton

* Create task/process attachment Compilation error (#2004)

* fix tslint errors

minor fix for "Unnecessary semicolon" TSLint rule

* [ADF-842] Fixed type for taskdetails (#2009)

* fix type definition (#2002)

* Use the activiti people with the new look and feel inside the demo shell (#2008)

* add rxjs and @angular in tsconfig.json

* [ADF-843] Form events bus (#1990)

* form events bus

* event test bus fix

* fix test after code review

* fix types errors

* change to public formservice

* make optional formservice

* [ADF-915] Add option to change the JS-API with different version in the update package

* Missing keys (#2011)

* [ADF-845] breadcrumb root option added and style review (#1999)

* breadcrumb root option added and style review

* new breadcrumbs

* split onchange in a method

* update readme with a note for old pefix tag

* fix tslint errors

* fix breadcrumb test

* [ADF-922] Regenerate package-lock.json files for every package and create script for doing that in the future (#2012)

* Updated package-lock.json files

* npm-relock-pkgs.sh

* Update README.md

* Fix ng2-alfresco-search sass problem

* SASS version update (#2013)

* sass update

* update sass loader

* vjsapi option prepublish

* prepublish script deprecation in favour of prepublishOnly node 8 (#2010)

* modify prePublish script with preoPublishOnly

* install rimraf globally

* fix clean scripts demo folders

* move appveyor to node 8

* Appveyor test (#1998)

* reduce memory

* remove max-old-space

* remove increase memory

* create new TaskDetailsModel in loadNextTask (#2017)

* Fix readme document list

* [ADF-907] - Form reacts to data added in input (#2016)

* [ADF-907] Enable activiti form to react on value data changes

* [ADF-907] - Form reacts to data added in input]

* [ADF - 907] added mock json for form

* [ADF-907] added new event of the form to the event list

* [ADF - 907] Added return column to README

* [ADF - 907] Added return column to README

* Script add pkg and clean update

* install globally pkg pre build

* Fix upload related content (#2019)

* regeneration TOC and add automatic list component generator (#2022)

* Fix upload process attachment (#2024)

* update typescript (#2026)

* update viewer readme

* fix type definition variables

* NgZone type passed parameter

* fix tslint error in tasklist

* Add screenshot (#2028)

* fix search miss typing

* bump version 1.6.0 (#2027)
This commit is contained in:
Eugenio Romano
2017-06-29 15:57:37 +01:00
committed by GitHub
parent c6f6227da7
commit b15ab3b988
822 changed files with 168022 additions and 13865 deletions

View File

@@ -20,6 +20,7 @@ import { DebugElement, SimpleChange } from '@angular/core';
import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs/Rx';
import { CoreModule, AlfrescoTranslationService, ContentService } from 'ng2-alfresco-core';
import { MdTabsModule } from '@angular/material';
import { ActivitiContent } from './activiti-content.component';
import { FormService } from '../services/form.service';
@@ -66,6 +67,7 @@ describe('ActivitiContent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MdTabsModule,
CoreModule.forRoot()
],
declarations: [

View File

@@ -53,7 +53,7 @@ export class ActivitiContent implements OnChanges {
private logService: LogService,
private contentService: ContentService) {
if (this.translate) {
this.translate.addTranslationFolder('ng2-activiti-form', 'node_modules/ng2-activiti-form/src');
this.translate.addTranslationFolder('ng2-activiti-form', 'assets/ng2-activiti-form');
}
}

View File

@@ -21,12 +21,14 @@
font-weight: bold;
}
.activiti-form-hide-button {
display: none;
.activiti-form-reload-button {
position: absolute;
right: 0;
top: 0;
}
.activiti-debug-button {
float: right;
.activiti-form-hide-button {
display: none;
}
.activiti-task-title {

View File

@@ -2,13 +2,23 @@
<div *ngIf="!hasForm()">
<h3 class="activiti-task-title">Please select a Task</h3>
</div>
<div *ngIf="hasForm()">
<div class="mdl-card mdl-shadow--2dp activiti-form-container {{form.className}}">
<div class="mdl-card__title">
<i *ngIf="showValidationIcon" class="material-icons">{{ form.isValid ? 'event_available' : 'event_busy' }}</i>
<h2 *ngIf="isTitleEnabled()" class="mdl-card__title-text">{{form.taskName}}</h2>
</div>
<div class="mdl-card__media">
<div *ngIf="hasForm()" class="{{form.className}}">
<md-card>
<md-card-header>
<md-card-title>
<h4 *ngIf="isTitleEnabled()">
<div *ngIf="showRefreshButton" class="activiti-form-reload-button">
<button md-icon-button (click)="onRefreshClicked()">
<md-icon>refresh</md-icon>
</button>
</div>
<md-icon>{{ form.isValid ? 'event_available' : 'event_busy' }}</md-icon>
<span>{{form.taskName}}</span>
</h4>
</md-card-title>
</md-card-header>
<md-card-content>
<div *ngIf="form.hasTabs()">
<tabs-widget [tabs]="form.tabs" (formTabChanged)="checkVisibility($event);"></tabs-widget>
</div>
@@ -18,40 +28,26 @@
<form-field [field]="field.field"></form-field>
</div>
</div>
</div>
<div *ngIf="form.hasOutcomes()" class="mdl-card__actions mdl-card--border">
</md-card-content>
<md-card-actions *ngIf="form.hasOutcomes()">
<!--[class.mdl-button--colored]="!outcome.isSystem"-->
<button *ngFor="let outcome of form.outcomes"
alfresco-mdl-button
md-button
[disabled]="!isOutcomeButtonEnabled(outcome)"
[class.mdl-button--colored]="!outcome.isSystem"
[class.activiti-form-hide-button]="!isOutcomeButtonVisible(outcome, form.readOnly)"
(click)="onOutcomeClicked(outcome, $event)">
{{outcome.name}}
{{outcome.name | uppercase}}
</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>
</md-card-actions>
</md-card>
</div>
</div>
<!--
For debugging and data visualisation purposes,
will be removed during future revisions
-->
<div *ngIf="showDebugButton" class="activiti-form-debug-container">
<div class="activiti-debug-button">
<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>
<div *ngIf="showDebugButton" class="activiti-form-debug-container">
<md-slide-toggle [(ngModel)]="debugMode">Debug mode</md-slide-toggle>
<div *ngIf="debugMode && hasForm()">
<h4>Values</h4>
<pre>{{form.values | json}}</pre>

View File

@@ -23,6 +23,7 @@ import { FormModel, FormOutcomeModel, FormFieldModel, FormOutcomeEvent, FormFiel
import { FormService } from './../services/form.service';
import { WidgetVisibilityService } from './../services/widget-visibility.service';
import { NodeService } from './../services/node.service';
import { fakeForm } from '../assets/activiti-form.component.mock';
describe('ActivitiForm', () => {
@@ -107,14 +108,14 @@ describe('ActivitiForm', () => {
it('should enable custom outcome buttons', () => {
let formModel = new FormModel();
formComponent.form = formModel;
let outcome = new FormOutcomeModel(formModel, {id: 'action1', name: 'Action 1'});
let outcome = new FormOutcomeModel(formModel, { id: 'action1', name: 'Action 1' });
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
});
it('should allow controlling [complete] button visibility', () => {
let formModel = new FormModel();
formComponent.form = formModel;
let outcome = new FormOutcomeModel(formModel, {id: '$save', name: FormOutcomeModel.SAVE_ACTION});
let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION });
formComponent.showSaveButton = true;
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
@@ -127,7 +128,7 @@ describe('ActivitiForm', () => {
let formModel = new FormModel();
formModel.readOnly = true;
formComponent.form = formModel;
let outcome = new FormOutcomeModel(formModel, {id: '$complete', name: FormOutcomeModel.COMPLETE_ACTION});
let outcome = new FormOutcomeModel(formModel, { id: '$complete', name: FormOutcomeModel.COMPLETE_ACTION });
formComponent.showCompleteButton = true;
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
@@ -137,23 +138,23 @@ describe('ActivitiForm', () => {
let formModel = new FormModel();
formModel.readOnly = true;
formComponent.form = formModel;
let outcome = new FormOutcomeModel(formModel, {id: '$save', name: FormOutcomeModel.SAVE_ACTION});
let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION });
formComponent.showSaveButton = true;
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeFalsy();
});
it('should show [custom-outcome] button with readOnly form and selected custom-outcome', () => {
let formModel = new FormModel({selectedOutcome: 'custom-outcome'});
let formModel = new FormModel({ selectedOutcome: 'custom-outcome' });
formModel.readOnly = true;
formComponent.form = formModel;
let outcome = new FormOutcomeModel(formModel, {id: '$customoutome', name: 'custom-outcome'});
let outcome = new FormOutcomeModel(formModel, { id: '$customoutome', name: 'custom-outcome' });
formComponent.showCompleteButton = true;
formComponent.showSaveButton = true;
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
outcome = new FormOutcomeModel(formModel, {id: '$customoutome2', name: 'custom-outcome2'});
outcome = new FormOutcomeModel(formModel, { id: '$customoutome2', name: 'custom-outcome2' });
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeFalsy();
});
@@ -161,7 +162,7 @@ describe('ActivitiForm', () => {
let formModel = new FormModel();
formModel.readOnly = false;
formComponent.form = formModel;
let outcome = new FormOutcomeModel(formModel, {id: '$save', name: FormOutcomeModel.COMPLETE_ACTION});
let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.COMPLETE_ACTION });
formComponent.showCompleteButton = true;
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
@@ -191,7 +192,7 @@ describe('ActivitiForm', () => {
it('should get process variable if is a process task', () => {
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({taskId: taskId});
observer.next({ taskId: taskId });
observer.complete();
});
});
@@ -199,7 +200,7 @@ describe('ActivitiForm', () => {
spyOn(visibilityService, 'getTaskProcessVariable').and.returnValue(Observable.of({}));
spyOn(formService, 'getTask').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({taskId: taskId, processDefinitionId: '10201'});
observer.next({ taskId: taskId, processDefinitionId: '10201' });
observer.complete();
});
});
@@ -214,7 +215,7 @@ describe('ActivitiForm', () => {
it('should not get process variable if is not a process task', () => {
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({taskId: taskId});
observer.next({ taskId: taskId });
observer.complete();
});
});
@@ -222,7 +223,7 @@ describe('ActivitiForm', () => {
spyOn(visibilityService, 'getTaskProcessVariable').and.returnValue(Observable.of({}));
spyOn(formService, 'getTask').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({taskId: taskId, processDefinitionId: 'null'});
observer.next({ taskId: taskId, processDefinitionId: 'null' });
observer.complete();
});
});
@@ -259,7 +260,7 @@ describe('ActivitiForm', () => {
const taskId = '<task id>';
let change = new SimpleChange(null, taskId, true);
formComponent.ngOnChanges({'taskId': change});
formComponent.ngOnChanges({ 'taskId': change });
expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId);
});
@@ -269,7 +270,7 @@ describe('ActivitiForm', () => {
const formId = '123';
let change = new SimpleChange(null, formId, true);
formComponent.ngOnChanges({'formId': change});
formComponent.ngOnChanges({ 'formId': change });
expect(formComponent.getFormDefinitionByFormId).toHaveBeenCalledWith(formId);
});
@@ -279,7 +280,7 @@ describe('ActivitiForm', () => {
const formName = '<form>';
let change = new SimpleChange(null, formName, true);
formComponent.ngOnChanges({'formName': change});
formComponent.ngOnChanges({ 'formName': change });
expect(formComponent.getFormDefinitionByFormName).toHaveBeenCalledWith(formName);
});
@@ -304,7 +305,7 @@ describe('ActivitiForm', () => {
spyOn(formComponent, 'getFormDefinitionByFormId').and.stub();
spyOn(formComponent, 'getFormDefinitionByFormName').and.stub();
formComponent.ngOnChanges({'tag': new SimpleChange(null, 'hello world', true)});
formComponent.ngOnChanges({ 'tag': new SimpleChange(null, 'hello world', true) });
expect(formComponent.getFormByTaskId).not.toHaveBeenCalled();
expect(formComponent.getFormDefinitionByFormId).not.toHaveBeenCalled();
@@ -314,7 +315,7 @@ describe('ActivitiForm', () => {
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 outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName });
let saved = false;
formComponent.form = formModel;
@@ -379,7 +380,7 @@ describe('ActivitiForm', () => {
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});
let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName });
formComponent.form = formModel;
spyOn(formComponent, 'completeTaskForm').and.stub();
@@ -398,7 +399,7 @@ describe('ActivitiForm', () => {
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});
let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName });
formComponent.readOnly = false;
formComponent.form = null;
@@ -407,7 +408,7 @@ describe('ActivitiForm', () => {
it('should not execute unknown system outcome', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, {id: 'unknown', name: 'Unknown', isSystem: true});
let outcome = new FormOutcomeModel(formModel, { id: 'unknown', name: 'Unknown', isSystem: true });
formComponent.form = formModel;
expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
@@ -415,12 +416,12 @@ describe('ActivitiForm', () => {
it('should require custom action name to complete form', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, {id: 'custom'});
let outcome = new FormOutcomeModel(formModel, { id: 'custom' });
formComponent.form = formModel;
expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
outcome = new FormOutcomeModel(formModel, {id: 'custom', name: 'Custom'});
outcome = new FormOutcomeModel(formModel, { id: 'custom', name: 'Custom' });
spyOn(formComponent, 'completeTaskForm').and.stub();
expect(formComponent.onOutcomeClicked(outcome)).toBeTruthy();
});
@@ -429,7 +430,7 @@ describe('ActivitiForm', () => {
spyOn(formService, 'getTask').and.returnValue(Observable.of({}));
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({taskId: taskId});
observer.next({ taskId: taskId });
observer.complete();
});
});
@@ -465,7 +466,7 @@ describe('ActivitiForm', () => {
spyOn(formService, 'getTask').and.returnValue(Observable.of({}));
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({taskId: taskId});
observer.next({ taskId: taskId });
observer.complete();
});
});
@@ -481,7 +482,7 @@ describe('ActivitiForm', () => {
it('should fetch and parse form definition by id', () => {
spyOn(formService, 'getFormDefinitionById').and.callFake((formId) => {
return Observable.create(observer => {
observer.next({id: formId});
observer.next({ id: formId });
observer.complete();
});
});
@@ -520,7 +521,7 @@ describe('ActivitiForm', () => {
spyOn(formService, 'getFormDefinitionById').and.callFake((formName) => {
return Observable.create(observer => {
observer.next({name: formName});
observer.next({ name: formName });
observer.complete();
});
});
@@ -556,8 +557,8 @@ describe('ActivitiForm', () => {
let formModel = new FormModel({
taskId: '123',
fields: [
{id: 'field1'},
{id: 'field2'}
{ id: 'field1' },
{ id: 'field2' }
]
});
formComponent.form = formModel;
@@ -573,7 +574,7 @@ describe('ActivitiForm', () => {
spyOn(formService, 'saveTaskForm').and.callFake(() => Observable.throw(error));
spyOn(formComponent, 'handleError').and.stub();
formComponent.form = new FormModel({taskId: '123'});
formComponent.form = new FormModel({ taskId: '123' });
formComponent.saveTaskForm();
expect(formComponent.handleError).toHaveBeenCalledWith(error);
@@ -618,8 +619,8 @@ describe('ActivitiForm', () => {
let formModel = new FormModel({
taskId: '123',
fields: [
{id: 'field1'},
{id: 'field2'}
{ id: 'field1' },
{ id: 'field2' }
]
});
@@ -638,7 +639,7 @@ describe('ActivitiForm', () => {
let form = formComponent.parseForm({
id: '<id>',
fields: [
{id: 'field1', type: FormFieldTypes.CONTAINER}
{ id: 'field1', type: FormFieldTypes.CONTAINER }
]
});
@@ -651,7 +652,7 @@ describe('ActivitiForm', () => {
it('should provide outcomes for form definition', () => {
spyOn(formComponent, 'getFormDefinitionOutcomes').and.callThrough();
let form = formComponent.parseForm({id: '<id>'});
let form = formComponent.parseForm({ id: '<id>' });
expect(formComponent.getFormDefinitionOutcomes).toHaveBeenCalledWith(form);
});
@@ -724,7 +725,7 @@ describe('ActivitiForm', () => {
let metadata = {};
spyOn(nodeService, 'getNodeMetadata').and.returnValue(
Observable.create(observer => {
observer.next({metadata: metadata});
observer.next({ metadata: metadata });
observer.complete();
})
);
@@ -800,4 +801,44 @@ describe('ActivitiForm', () => {
expect(formComponent.isOutcomeButtonEnabled(outcome)).toBeFalsy();
});
it('should raise [executeOutcome] event for formService', (done) => {
formService.executeOutcome.subscribe(() => {
done();
});
let outcome = new FormOutcomeModel(new FormModel(), {
id: ActivitiForm.CUSTOM_OUTCOME_ID,
name: 'Custom'
});
formComponent.form = new FormModel();
formComponent.onOutcomeClicked(outcome);
});
it('should refresh form values when data is changed', () => {
formComponent.form = new FormModel(fakeForm);
let formFields = formComponent.form.getFormFields();
let labelField = formFields.find(field => field.id === 'label');
let radioField = formFields.find(field => field.id === 'raduio');
expect(labelField.value).toBe('empty');
expect(radioField.value).toBeNull();
let formValues: any = {};
formValues.label = {
id: 'option_1',
name: 'test1'
};
formValues.raduio = { id: 'option_1', name: 'Option 1' };
let change = new SimpleChange(null, formValues, false);
formComponent.data = formValues;
formComponent.ngOnChanges({ 'data': change });
formFields = formComponent.form.getFormFields();
labelField = formFields.find(field => field.id === 'label');
radioField = formFields.find(field => field.id === 'raduio');
expect(labelField.value).toBe('option_1');
expect(radioField.value).toBe('option_1');
});
});

View File

@@ -28,48 +28,6 @@ import { WidgetVisibilityService } from './../services/widget-visibility.servic
declare var componentHandler: any;
/**
* @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;
*
* {showDebugButton} boolean - to show the debug options, default false;
*
* {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({
selector: 'activiti-form',
templateUrl: './activiti-form.component.html',
@@ -82,6 +40,9 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
static START_PROCESS_OUTCOME_ID: string = '$startProcess';
static CUSTOM_OUTCOME_ID: string = '$custom';
@Input()
form: FormModel;
@Input()
taskId: string;
@@ -139,14 +100,15 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
@Output()
formLoaded: EventEmitter<FormModel> = new EventEmitter<FormModel>();
@Output()
formDataRefreshed: EventEmitter<FormModel> = new EventEmitter<FormModel>();
@Output()
executeOutcome: EventEmitter<FormOutcomeEvent> = new EventEmitter<FormOutcomeEvent>();
@Output()
onError: EventEmitter<any> = new EventEmitter<any>();
form: FormModel;
debugMode: boolean = false;
constructor(protected formService: FormService,
@@ -243,6 +205,12 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
this.loadFormForEcmNode();
return;
}
let data = changes['data'];
if (data && data.currentValue) {
this.refreshFormData();
return;
}
}
/**
@@ -253,9 +221,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
onOutcomeClicked(outcome: FormOutcomeModel): boolean {
if (!this.readOnly && outcome && this.form) {
let args = new FormOutcomeEvent(outcome);
this.executeOutcome.emit(args);
if (args.defaultPrevented) {
if (!this.onExecuteOutcome(outcome)) {
return false;
}
@@ -467,6 +433,12 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
}
}
private refreshFormData() {
this.form = new FormModel(this.form.json, this.data, this.readOnly, this.formService);
this.onFormLoaded(this.form);
this.onFormDataRefreshed(this.form);
}
private loadFormForEcmNode(): void {
this.nodeService.getNodeMetadata(this.nodeId).subscribe(data => {
this.data = data.metadata;
@@ -514,6 +486,11 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
this.formService.formLoaded.next(new FormEvent(form));
}
protected onFormDataRefreshed(form: FormModel) {
this.formDataRefreshed.emit(form);
this.formService.formDataRefreshed.next(new FormEvent(form));
}
protected onTaskSaved(form: FormModel) {
this.formSaved.emit(form);
this.formService.taskSaved.next(new FormEvent(form));
@@ -533,4 +510,20 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
this.handleError(error);
this.formService.taskCompletedError.next(new FormErrorEvent(form, error));
}
protected onExecuteOutcome(outcome: FormOutcomeModel): boolean {
let args = new FormOutcomeEvent(outcome);
this.formService.executeOutcome.next(args);
if (args.defaultPrevented) {
return false;
}
this.executeOutcome.emit(args);
if (args.defaultPrevented) {
return false;
}
return true;
}
}

View File

@@ -18,6 +18,7 @@
import { SimpleChange } from '@angular/core';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { Observable } from 'rxjs/Rx';
import { MdTabsModule } from '@angular/material';
import { ActivitiStartForm } from './activiti-start-form.component';
import { FormFieldComponent } from './form-field/form-field.component';
@@ -43,7 +44,9 @@ describe('ActivitiStartForm', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [CoreModule.forRoot()],
imports: [
MdTabsModule,
CoreModule.forRoot()],
declarations: [
ActivitiStartForm,
FormFieldComponent,

View File

@@ -88,7 +88,7 @@ export class ActivitiStartForm extends ActivitiForm implements AfterViewChecked,
super(formService, visibilityService, null, null, logService);
if (this.translate) {
this.translate.addTranslationFolder('ng2-activiti-form', 'node_modules/ng2-activiti-form/src');
this.translate.addTranslationFolder('ng2-activiti-form', 'assets/ng2-activiti-form');
}
this.showTitle = false;

View File

@@ -17,13 +17,37 @@
import { AmountWidget } from './amount.widget';
import { FormFieldModel } from './../core/form-field.model';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { FormService } from './../../../services/form.service';
import { EcmModelService } from './../../../services/ecm-model.service';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
describe('AmountWidget', () => {
let widget: AmountWidget;
let fixture: ComponentFixture<AmountWidget>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
],
declarations: [
AmountWidget
],
providers: [
FormService,
EcmModelService,
ActivitiAlfrescoContentService
]
}).compileComponents();
}));
beforeEach(() => {
widget = new AmountWidget();
fixture = TestBed.createComponent(AmountWidget);
widget = fixture.componentInstance;
});
it('should setup currentcy from field', () => {

View File

@@ -16,12 +16,14 @@
*/
import { Component, OnInit } from '@angular/core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormService } from './../../../services/form.service';
@Component({
selector: 'amount-widget',
templateUrl: './amount.widget.html',
styleUrls: ['./amount.widget.css']
styleUrls: ['./amount.widget.css'],
host: baseHost
})
export class AmountWidget extends WidgetComponent implements OnInit {
@@ -29,6 +31,10 @@ export class AmountWidget extends WidgetComponent implements OnInit {
currency: string = AmountWidget.DEFAULT_CURRENCY;
constructor(public formService: FormService) {
super(formService);
}
ngOnInit() {
if (this.field && this.field.currency) {
this.currency = this.field.currency;

View File

@@ -2,11 +2,11 @@
<label [attr.for]="field.id">{{field.name}}<span *ngIf="isRequired()">*</span></label>
<div>
<span *ngIf="hasFile()" class="attach-widget__file mdl-chip"><span class="mdl-chip__text">{{getLinkedFileName()}}</span></span>
<button #browseFile (click)="showDialog();" class="mdl-button mdl-js-button mdl-js-ripple-effect attach-widget__browser">
<button #browseFile [disabled]="field.readOnly" (click)="showDialog();" class="mdl-button mdl-js-button mdl-js-ripple-effect attach-widget__browser">
<i class="material-icons">image</i>
Browse {{selectedFolderSiteName}}
</button>
<button *ngIf="hasFile" (click)="reset(file);" class="mdl-button mdl-js-button mdl-js-ripple-effect attach-widget__reset">Clear</button>
<button *ngIf="hasFile" [disabled]="field.readOnly" (click)="reset(file);" class="mdl-button mdl-js-button mdl-js-ripple-effect attach-widget__reset">Clear</button>
</div>
</div>

View File

@@ -16,32 +16,55 @@
*/
import { Observable } from 'rxjs/Rx';
import { LogServiceMock } from 'ng2-alfresco-core';
import { AttachWidget } from './attach.widget';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
import { FormFieldModel } from './../core/form-field.model';
import { FormFieldTypes } from '../core/form-field-types';
import { ExternalContent } from '../core/external-content';
import { ExternalContentLink } from '../core/external-content-link';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { FormService } from './../../../services/form.service';
import { EcmModelService } from './../../../services/ecm-model.service';
describe('AttachWidget', () => {
let widget: AttachWidget;
let fixture: ComponentFixture<AttachWidget>;
let element: HTMLElement;
let contentService: ActivitiAlfrescoContentService;
let dialogPolyfill: any;
let logService: LogServiceMock;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
],
declarations: [
AttachWidget
],
providers: [
FormService,
EcmModelService,
ActivitiAlfrescoContentService
]
}).compileComponents();
}));
beforeEach(() => {
logService = new LogServiceMock();
contentService = new ActivitiAlfrescoContentService(null, logService);
widget = new AttachWidget(contentService, logService);
fixture = TestBed.createComponent(AttachWidget);
contentService = TestBed.get(ActivitiAlfrescoContentService);
element = fixture.nativeElement;
widget = fixture.componentInstance;
dialogPolyfill = {
registerDialog(obj: any) {
obj.showModal = function () {
obj.showModal = () => {
};
}
};
window['dialogPolyfill'] = dialogPolyfill;
});
@@ -98,7 +121,7 @@ describe('AttachWidget', () => {
expect(widget.selectedFolderNodes).toEqual(nodes);
});
it('should link file on select', () => {
xit('should link file on select', () => {
let link = <ExternalContentLink> {};
spyOn(contentService, 'linkAlfrescoNode').and.returnValue(
Observable.create(observer => {

View File

@@ -17,18 +17,19 @@
import { Component, OnInit, Input, Output, EventEmitter, ViewChild } from '@angular/core';
import { LogService } from 'ng2-alfresco-core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
import { ExternalContent } from '../core/external-content';
import { ExternalContentLink } from '../core/external-content-link';
import { FormFieldModel } from '../core/form-field.model';
import { FormService } from './../../../services/form.service';
declare let dialogPolyfill: any;
@Component({
selector: 'attach-widget',
templateUrl: './attach.widget.html',
styleUrls: ['./attach.widget.css']
styleUrls: ['./attach.widget.css'], host: baseHost
})
export class AttachWidget extends WidgetComponent implements OnInit {
@@ -52,9 +53,10 @@ export class AttachWidget extends WidgetComponent implements OnInit {
@ViewChild('dialog')
dialog: any;
constructor(private contentService: ActivitiAlfrescoContentService,
constructor(public formService: FormService,
private contentService: ActivitiAlfrescoContentService,
private logService: LogService) {
super();
super(formService);
}
ngOnInit() {

View File

@@ -16,17 +16,19 @@
*/
import { Component } from '@angular/core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost} from './../widget.component';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
import { FormService } from './../../../services/form.service';
@Component({
selector: 'checkbox-widget',
templateUrl: './checkbox.widget.html'
templateUrl: './checkbox.widget.html',
host: baseHost
})
export class CheckboxWidget extends WidgetComponent {
constructor(private visibilityService: WidgetVisibilityService) {
super();
constructor(private visibilityService: WidgetVisibilityService, public formService: FormService) {
super(formService);
}
onChange() {

View File

@@ -20,19 +20,55 @@ import { ContainerWidgetModel } from './container.widget.model';
import { FormModel } from './../core/form.model';
import { FormFieldTypes } from './../core/form-field-types';
import { FormFieldModel } from './../core/form-field.model';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { WIDGET_DIRECTIVES } from '../index';
import { MASK_DIRECTIVE } from '../index';
import { FormFieldComponent } from './../../form-field/form-field.component';
import { ActivitiContent } from './../../activiti-content.component';
import { fakeFormJson } from '../../../services/assets/widget-visibility.service.mock';
import { MdTabsModule } from '@angular/material';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { FormService } from './../../../services/form.service';
import { EcmModelService } from './../../../services/ecm-model.service';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
describe('ContainerWidget', () => {
let widget: ContainerWidget;
let fixture: ComponentFixture<ContainerWidget>;
let element: HTMLElement;
let contentService: ActivitiAlfrescoContentService;
let componentHandler;
let dialogPolyfill;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot(),
MdTabsModule
],
declarations: [FormFieldComponent, ActivitiContent, WIDGET_DIRECTIVES, MASK_DIRECTIVE],
providers: [
FormService,
EcmModelService,
ActivitiAlfrescoContentService
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ContainerWidget);
contentService = TestBed.get(ActivitiAlfrescoContentService);
element = fixture.nativeElement;
widget = fixture.componentInstance;
dialogPolyfill = {
registerDialog(obj: any) {
obj.showModal = function () {
};
}
};
componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered'
]);
@@ -42,25 +78,22 @@ describe('ContainerWidget', () => {
it('should wrap field with model instance', () => {
let field = new FormFieldModel(null);
let container = new ContainerWidget();
container.field = field;
container.ngOnInit();
expect(container.content).toBeDefined();
expect(container.content.field).toBe(field);
widget.field = field;
widget.ngOnInit();
expect(widget.content).toBeDefined();
expect(widget.content.field).toBe(field);
});
it('should upgrade MDL content on view init', () => {
let container = new ContainerWidget();
container.ngAfterViewInit();
widget.ngAfterViewInit();
expect(componentHandler.upgradeAllRegistered).toHaveBeenCalled();
});
it('should setup MDL content only if component handler available', () => {
let container = new ContainerWidget();
expect(container.setupMaterialComponents()).toBeTruthy();
expect(widget.setupMaterialComponents()).toBeTruthy();
window['componentHandler'] = null;
expect(container.setupMaterialComponents()).toBeFalsy();
expect(widget.setupMaterialComponents()).toBeFalsy();
});
it('should toggle underlying group container', () => {
@@ -71,7 +104,6 @@ describe('ContainerWidget', () => {
}
}));
let widget = new ContainerWidget();
widget.content = container;
expect(container.isExpanded).toBeTruthy();
@@ -86,7 +118,6 @@ describe('ContainerWidget', () => {
type: FormFieldTypes.GROUP
}));
let widget = new ContainerWidget();
widget.content = container;
expect(container.isExpanded).toBeTruthy();
@@ -95,6 +126,7 @@ describe('ContainerWidget', () => {
});
it('should toggle only group container', () => {
let container = new ContainerWidgetModel(new FormFieldModel(new FormModel(), {
type: FormFieldTypes.CONTAINER,
params: {
@@ -102,7 +134,6 @@ describe('ContainerWidget', () => {
}
}));
let widget = new ContainerWidget();
widget.content = container;
expect(container.isExpanded).toBeTruthy();
@@ -111,7 +142,6 @@ describe('ContainerWidget', () => {
});
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.fieldChanged.subscribe(field => {
@@ -125,22 +155,8 @@ describe('ContainerWidget', () => {
});
describe('when template is ready', () => {
let containerWidgetComponent: ContainerWidget;
let fixture: ComponentFixture<ContainerWidget>;
let element: HTMLElement;
let fakeContainerVisible: ContainerWidgetModel;
let fakeContainerInvisible: ContainerWidgetModel;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [CoreModule],
declarations: [FormFieldComponent, ActivitiContent, WIDGET_DIRECTIVES, MASK_DIRECTIVE]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(ContainerWidget);
containerWidgetComponent = fixture.componentInstance;
element = fixture.nativeElement;
});
}));
let fakeContainerVisible;
let fakeContainerInvisible;
beforeEach(() => {
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
@@ -167,7 +183,7 @@ describe('ContainerWidget', () => {
});
it('should show the container header when it is visible', () => {
containerWidgetComponent.content = fakeContainerVisible;
widget.content = fakeContainerVisible;
fixture.detectChanges();
fixture.whenStable()
.then(() => {
@@ -178,7 +194,7 @@ describe('ContainerWidget', () => {
});
it('should not show the container header when it is not visible', () => {
containerWidgetComponent.content = fakeContainerInvisible;
widget.content = fakeContainerInvisible;
fixture.detectChanges();
fixture.whenStable()
.then(() => {
@@ -187,23 +203,23 @@ describe('ContainerWidget', () => {
});
it('should hide header when it becomes not visible', async(() => {
containerWidgetComponent.content = fakeContainerVisible;
widget.content = fakeContainerVisible;
fixture.detectChanges();
containerWidgetComponent.fieldChanged.subscribe((res) => {
containerWidgetComponent.content.field.isVisible = false;
widget.fieldChanged.subscribe((res) => {
widget.content.field.isVisible = false;
fixture.detectChanges();
fixture.whenStable()
.then(() => {
expect(element.querySelector('.container-widget__header').classList.contains('hidden')).toBe(true);
});
});
containerWidgetComponent.onFieldChanged(null);
widget.onFieldChanged(null);
}));
it('should show header when it becomes visible', async(() => {
containerWidgetComponent.content = fakeContainerInvisible;
containerWidgetComponent.fieldChanged.subscribe((res) => {
containerWidgetComponent.content.field.isVisible = true;
widget.content = fakeContainerInvisible;
widget.fieldChanged.subscribe((res) => {
widget.content.field.isVisible = true;
fixture.detectChanges();
fixture.whenStable()
.then(() => {
@@ -213,7 +229,7 @@ describe('ContainerWidget', () => {
expect(element.querySelector('#container-header-label').innerHTML).toContain('fake-cont-2-name');
});
});
containerWidgetComponent.onFieldChanged(null);
widget.onFieldChanged(null);
}));
});

View File

@@ -17,19 +17,25 @@
import { Component, AfterViewInit, OnInit } from '@angular/core';
import { ContainerWidgetModel } from './container.widget.model';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormService } from './../../../services/form.service';
declare var componentHandler: any;
@Component({
selector: 'container-widget',
templateUrl: './container.widget.html',
styleUrls: ['./container.widget.css']
styleUrls: ['./container.widget.css'],
host: baseHost
})
export class ContainerWidget extends WidgetComponent implements OnInit, AfterViewInit {
content: ContainerWidgetModel;
constructor(public formService: FormService) {
super(formService);
}
onExpanderClicked() {
if (this.content && this.content.isCollapsible()) {
this.content.isExpanded = !this.content.isExpanded;

View File

@@ -68,7 +68,7 @@ describe('FormFieldModel', () => {
expect(field.options).toBeDefined();
expect(field.options.length).toBe(0);
field = new FormFieldModel(new FormModel(), { options: null });
field = new FormFieldModel(new FormModel(), {options: null});
expect(field.options).toBeDefined();
expect(field.options.length).toBe(0);
});
@@ -77,13 +77,13 @@ describe('FormFieldModel', () => {
let field = new FormFieldModel(new FormModel(), null);
expect(field.params).toEqual({});
field = new FormFieldModel(new FormModel(), { params: null });
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 field = new FormFieldModel(form, {id: 'field1'});
let value = 10;
spyOn(field, 'updateForm').and.callThrough();
@@ -105,7 +105,7 @@ describe('FormFieldModel', () => {
it('should take own readonly state if form is writable', () => {
let form = new FormModel();
let field = new FormFieldModel(form, { readOnly: true });
let field = new FormFieldModel(form, {readOnly: true});
expect(form.readOnly).toBeFalsy();
expect(field.readOnly).toBeTruthy();
@@ -241,8 +241,8 @@ describe('FormFieldModel', () => {
let field = new FormFieldModel(new FormModel(), {
type: FormFieldTypes.RADIO_BUTTONS,
options: [
{ id: 'opt1', value: 'Option 1' },
{ id: 'opt2', value: 'Option 2' }
{id: 'opt1', value: 'Option 1'},
{id: 'opt2', value: 'Option 2'}
],
value: 'opt2'
});
@@ -279,8 +279,8 @@ describe('FormFieldModel', () => {
id: 'dropdown-2',
type: FormFieldTypes.DROPDOWN,
options: [
{ id: 'opt1', name: 'Option 1' },
{ id: 'opt2', name: 'Option 2' }
{id: 'opt1', name: 'Option 1'},
{id: 'opt2', name: 'Option 2'}
]
});
@@ -294,8 +294,8 @@ describe('FormFieldModel', () => {
id: 'radio-1',
type: FormFieldTypes.RADIO_BUTTONS,
options: [
{ id: 'opt1', value: 'Option 1' },
{ id: 'opt2', value: 'Option 2' }
{id: 'opt1', value: 'Option 1'},
{id: 'opt2', value: 'Option 2'}
]
});
@@ -309,8 +309,8 @@ describe('FormFieldModel', () => {
id: 'radio-2',
type: FormFieldTypes.RADIO_BUTTONS,
options: [
{ id: 'opt1', value: 'Option 1' },
{ id: 'opt2', value: 'Option 2' }
{id: 'opt1', value: 'Option 1'},
{id: 'opt2', value: 'Option 2'}
]
});
@@ -332,21 +332,21 @@ describe('FormFieldModel', () => {
});
});
it('should be able to check if the field has options available', () =>{
it('should be able to check if the field has options available', () => {
let form = new FormModel();
let field = new FormFieldModel(form, {
id: 'dropdown-happy',
type: FormFieldTypes.DROPDOWN,
options: [
{ id: 'opt1', name: 'Option 1' },
{ id: 'opt2', name: 'Option 2' }
{id: 'opt1', name: 'Option 1'},
{id: 'opt2', name: 'Option 2'}
]
});
expect(field.hasOptions()).toBeTruthy();
});
it('should return false if field has no options', () =>{
it('should return false if field has no options', () => {
let form = new FormModel();
let field = new FormFieldModel(form, {
id: 'dropdown-sad',

View File

@@ -105,6 +105,11 @@ export class FormFieldModel extends FormWidgetModel {
return this._readOnly;
}
set readOnly(readOnly: boolean) {
this._readOnly = readOnly;
this.updateForm();
}
get isValid(): boolean {
return this._isValid;
}
@@ -292,8 +297,6 @@ export class FormFieldModel extends FormWidgetModel {
let rbEntry: FormFieldOption[] = this.options.filter(opt => opt.id === this.value);
if (rbEntry.length > 0) {
this.form.values[this.id] = rbEntry[0];
} else if (this.options.length > 0) {
this.form.values[this.id] = this.options[0];
}
break;
case FormFieldTypes.UPLOAD:

View File

@@ -109,7 +109,7 @@ export class FormModel {
}
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 completeOutcome = new FormOutcomeModel(this, { id: FormModel.COMPLETE_OUTCOME, name: 'Complete', isSystem: true });
let startProcessOutcome = new FormOutcomeModel(this, { id: FormModel.START_PROCESS_OUTCOME, name: 'Start Process', isSystem: true });
let customOutcomes = (json.outcomes || []).map(obj => new FormOutcomeModel(this, obj));
@@ -137,7 +137,7 @@ export class FormModel {
let field = this.fields[i];
if (field instanceof ContainerModel) {
let container = <ContainerModel> field;
let container = <ContainerModel>field;
result.push(container.field);
result.push(...container.field.fields);
}
@@ -203,7 +203,11 @@ export class FormModel {
for (let field of this.getFormFields()) {
if (data[field.id]) {
field.json.value = data[field.id];
field.value = data[field.id];
field.value = field.parseValue(field.json);
if (field.type === FormFieldTypes.DROPDOWN ||
field.type === FormFieldTypes.RADIO_BUTTONS) {
field.value = data[field.id].id;
}
}
}
}

View File

@@ -15,19 +15,40 @@
* limitations under the License.
*/
import { ElementRef } from '@angular/core';
import { DateWidget } from './date.widget';
import { FormFieldModel } from './../core/form-field.model';
import { FormModel } from './../core/form.model';
import { CoreModule } from 'ng2-alfresco-core';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import * as moment from 'moment';
import { CoreModule } from 'ng2-alfresco-core';
import { FormService } from './../../../services/form.service';
import { EcmModelService } from './../../../services/ecm-model.service';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
import { ElementRef } from '@angular/core';
describe('DateWidget', () => {
let widget: DateWidget;
let fixture: ComponentFixture<DateWidget>;
let componentHandler;
let nativeElement: any;
let elementRef: ElementRef;
let element: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
],
declarations: [
DateWidget
],
providers: [
FormService,
ActivitiAlfrescoContentService,
EcmModelService
]
}).compileComponents();
}));
beforeEach(() => {
nativeElement = {
@@ -35,9 +56,12 @@ describe('DateWidget', () => {
return null;
}
};
elementRef = new ElementRef(nativeElement);
widget = new DateWidget(elementRef);
let componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
fixture = TestBed.createComponent(DateWidget);
element = fixture.nativeElement;
widget = fixture.componentInstance;
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
});
@@ -82,6 +106,7 @@ describe('DateWidget', () => {
});
it('should setup trigger element', () => {
widget.elementRef = new ElementRef(nativeElement);
let el = {};
spyOn(nativeElement, 'querySelector').and.returnValue(el);
widget.field = new FormFieldModel(null, {id: 'fake-id'});
@@ -91,9 +116,8 @@ describe('DateWidget', () => {
});
it('should not setup trigger element', () => {
let w = new DateWidget(null);
w.ngOnInit();
expect(w.datePicker.trigger).toBeFalsy();
widget.ngOnInit();
expect(widget.datePicker.trigger).toBeFalsy();
});
it('should eval visibility on date changed', () => {
@@ -120,6 +144,7 @@ describe('DateWidget', () => {
});
it('should update field value on date selected', () => {
widget.elementRef = new ElementRef(nativeElement);
widget.field = new FormFieldModel(null, {type: 'date'});
widget.ngOnInit();
@@ -141,77 +166,58 @@ describe('DateWidget', () => {
});
it('should not update material textfield on date selected', () => {
let w = new DateWidget(null);
spyOn(w, 'setupMaterialTextField').and.callThrough();
widget.elementRef = undefined;
spyOn(widget, 'setupMaterialTextField').and.callThrough();
w.field = new FormFieldModel(null, {type: 'date'});
w.ngOnInit();
widget.field = new FormFieldModel(null, {type: 'date'});
widget.ngOnInit();
w.datePicker.time = moment();
w.onDateSelected();
expect(w.setupMaterialTextField).not.toHaveBeenCalled();
widget.datePicker.time = moment();
widget.onDateSelected();
expect(widget.setupMaterialTextField).not.toHaveBeenCalled();
});
it('should send field change event when a new date is picked from data picker', (done) => {
let w = new DateWidget(null);
spyOn(w, 'setupMaterialTextField').and.callThrough();
w.field = new FormFieldModel(null, {value: '9-9-9999', type: 'date'});
w.ngOnInit();
w.datePicker.time = moment('9-9-9999', w.field.dateDisplayFormat);
w.fieldChanged.subscribe((field) => {
spyOn(widget, 'setupMaterialTextField').and.callThrough();
widget.field = new FormFieldModel(null, {value: '9-9-9999', type: 'date'});
widget.ngOnInit();
widget.datePicker.time = moment('9-9-9999', widget.field.dateDisplayFormat);
widget.fieldChanged.subscribe((field) => {
expect(field).toBeDefined();
expect(field).not.toBeNull();
expect(field.value).toEqual('9-9-9999');
done();
});
w.onDateSelected();
widget.onDateSelected();
});
it('should send field change event when date is changed in input text', (done) => {
let w = new DateWidget(null);
spyOn(w, 'setupMaterialTextField').and.callThrough();
w.field = new FormFieldModel(null, {value: '9-9-9999', type: 'date'});
w.ngOnInit();
w.datePicker.time = moment('9-9-9999', w.field.dateDisplayFormat);
w.fieldChanged.subscribe((field) => {
spyOn(widget, 'setupMaterialTextField').and.callThrough();
widget.field = new FormFieldModel(null, {value: '9-9-9999', type: 'date'});
widget.ngOnInit();
widget.datePicker.time = moment('9-9-9999', widget.field.dateDisplayFormat);
widget.fieldChanged.subscribe((field) => {
expect(field).toBeDefined();
expect(field).not.toBeNull();
expect(field.value).toEqual('9-9-9999');
done();
});
w.onDateChanged();
widget.onDateChanged();
});
describe('template check', () => {
let dateWidget: DateWidget;
let fixture: ComponentFixture<DateWidget>;
let element: HTMLElement;
let componentHandler;
beforeEach(async(() => {
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
TestBed.configureTestingModule({
imports: [CoreModule],
declarations: [DateWidget]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(DateWidget);
dateWidget = fixture.componentInstance;
element = fixture.nativeElement;
});
}));
beforeEach(() => {
spyOn(dateWidget, 'setupMaterialTextField').and.stub();
dateWidget.field = new FormFieldModel(new FormModel(), {
spyOn(widget, 'setupMaterialTextField').and.stub();
widget.field = new FormFieldModel(new FormModel(), {
id: 'date-field-id',
name: 'date-name',
value: '9-9-9999',
type: 'date',
readOnly: 'false'
});
dateWidget.field.isVisible = true;
widget.field.isVisible = true;
fixture.detectChanges();
});
@@ -231,7 +237,7 @@ describe('DateWidget', () => {
}));
it('should hide not visible date widget', async(() => {
dateWidget.field.isVisible = false;
widget.field.isVisible = false;
fixture.detectChanges();
fixture.whenStable()
.then(() => {
@@ -241,9 +247,9 @@ describe('DateWidget', () => {
}));
it('should become visibile if the visibility change to true', async(() => {
dateWidget.field.isVisible = false;
widget.field.isVisible = false;
fixture.detectChanges();
dateWidget.fieldChanged.subscribe((field) => {
widget.fieldChanged.subscribe((field) => {
field.isVisible = true;
fixture.detectChanges();
fixture.whenStable()
@@ -254,11 +260,11 @@ describe('DateWidget', () => {
expect(dateElement.value).toEqual('9-9-9999');
});
});
dateWidget.checkVisibility(dateWidget.field);
widget.checkVisibility(widget.field);
}));
it('should be hided if the visibility change to false', async(() => {
dateWidget.fieldChanged.subscribe((field) => {
widget.fieldChanged.subscribe((field) => {
field.isVisible = false;
fixture.detectChanges();
fixture.whenStable()
@@ -266,7 +272,7 @@ describe('DateWidget', () => {
expect(element.querySelector('#data-widget')).toBeNull();
});
});
dateWidget.checkVisibility(dateWidget.field);
widget.checkVisibility(widget.field);
}));
});
});

View File

@@ -16,8 +16,9 @@
*/
import { Component, ElementRef, OnInit, AfterViewChecked } from '@angular/core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import * as moment from 'moment';
import { FormService } from './../../../services/form.service';
declare let mdDateTimePicker: any;
declare var componentHandler: any;
@@ -25,14 +26,16 @@ declare var componentHandler: any;
@Component({
selector: 'date-widget',
templateUrl: './date.widget.html',
styleUrls: ['./date.widget.css']
styleUrls: ['./date.widget.css'],
host: baseHost
})
export class DateWidget extends WidgetComponent implements OnInit, AfterViewChecked {
datePicker: any;
constructor(private elementRef: ElementRef) {
super();
constructor(public formService: FormService,
public elementRef: ElementRef) {
super(formService);
}
ngOnInit() {

View File

@@ -16,13 +16,19 @@
*/
import { Component } from '@angular/core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormService } from './../../../services/form.service';
@Component({
selector: 'display-text-widget',
templateUrl: './display-text.widget.html',
styleUrls: ['./display-text.widget.css']
styleUrls: ['./display-text.widget.css'],
host: baseHost
})
export class DisplayTextWidget extends WidgetComponent {
constructor(public formService: FormService) {
super(formService);
}
}

View File

@@ -16,30 +16,49 @@
*/
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { CoreModule, LogServiceMock } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { DisplayValueWidget } from './display-value.widget';
import { FormService } from '../../../services/form.service';
import { ActivitiContent } from '../../activiti-content.component';
import { EcmModelService } from '../../../services/ecm-model.service';
import { FormFieldModel } from './../core/form-field.model';
import { FormFieldTypes } from '../core/form-field-types';
import { FormModel } from '../core/form.model';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { FormService } from './../../../services/form.service';
import { EcmModelService } from './../../../services/ecm-model.service';
describe('DisplayValueWidget', () => {
let widget: DisplayValueWidget;
let fixture: ComponentFixture<DisplayValueWidget>;
let element: HTMLElement;
let formService: FormService;
let visibilityService: WidgetVisibilityService;
let logService: LogServiceMock;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
],
declarations: [
DisplayValueWidget,
ActivitiContent
],
providers: [
FormService,
EcmModelService,
WidgetVisibilityService
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
}));
beforeEach(() => {
logService = new LogServiceMock();
formService = new FormService(null, null, logService);
visibilityService = new WidgetVisibilityService(null, logService);
widget = new DisplayValueWidget(formService, visibilityService);
fixture = TestBed.createComponent(DisplayValueWidget);
formService = TestBed.get(FormService);
element = fixture.nativeElement;
widget = fixture.componentInstance;
});
it('should require field to setup default value', () => {
@@ -609,35 +628,15 @@ describe('DisplayValueWidget', () => {
});
describe('UI check', () => {
let widgetUI: DisplayValueWidget;
let fixture: ComponentFixture<DisplayValueWidget>;
let element: HTMLElement;
let componentHandler;
beforeEach(async(() => {
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
TestBed.configureTestingModule({
imports: [CoreModule],
declarations: [
DisplayValueWidget,
ActivitiContent
],
providers: [
EcmModelService,
FormService,
WidgetVisibilityService
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(DisplayValueWidget);
widgetUI = fixture.componentInstance;
element = fixture.nativeElement;
});
}));
beforeEach(() => {
spyOn(widgetUI, 'setupMaterialTextField').and.stub();
spyOn(widget, 'setupMaterialTextField').and.stub();
});
afterEach(() => {
@@ -646,7 +645,7 @@ describe('DisplayValueWidget', () => {
});
it('should show the checkbox on when [BOOLEAN] field is true', async(() => {
widgetUI.field = new FormFieldModel(null, {
widget.field = new FormFieldModel(null, {
id: 'fake-checkbox-id',
type: FormFieldTypes.DISPLAY_VALUE,
value: 'true',
@@ -667,7 +666,7 @@ describe('DisplayValueWidget', () => {
}));
it('should show the checkbox off when [BOOLEAN] field is false', async(() => {
widgetUI.field = new FormFieldModel(null, {
widget.field = new FormFieldModel(null, {
id: 'fake-checkbox-id',
type: FormFieldTypes.DISPLAY_VALUE,
value: 'false',
@@ -688,7 +687,7 @@ describe('DisplayValueWidget', () => {
}));
it('should show the dropdown value taken from options when field has options', async(() => {
widgetUI.field = new FormFieldModel(null, {
widget.field = new FormFieldModel(null, {
id: 'fake-dropdown-id',
type: FormFieldTypes.DISPLAY_VALUE,
value: '1',
@@ -713,7 +712,7 @@ describe('DisplayValueWidget', () => {
}));
it('should show the dropdown value taken from value when field has no options', async(() => {
widgetUI.field = new FormFieldModel(null, {
widget.field = new FormFieldModel(null, {
id: 'fake-dropdown-id',
type: FormFieldTypes.DISPLAY_VALUE,
value: 'FAKE',

View File

@@ -17,7 +17,7 @@
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
import * as moment from 'moment';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormFieldTypes } from '../core/form-field-types';
import { FormService } from '../../../services/form.service';
import { FormFieldOption } from './../core/form-field-option';
@@ -27,7 +27,8 @@ import { NumberFieldValidator } from '../core/form-field-validator';
@Component({
selector: 'display-value-widget',
templateUrl: './display-value.widget.html',
styleUrls: ['./display-value.widget.css']
styleUrls: ['./display-value.widget.css'],
host: baseHost
})
export class DisplayValueWidget extends WidgetComponent implements OnInit {
@@ -49,9 +50,9 @@ export class DisplayValueWidget extends WidgetComponent implements OnInit {
hasFile: boolean = false;
showDocumentContent: boolean = true;
constructor(private formService: FormService,
constructor(public formService: FormService,
private visibilityService: WidgetVisibilityService) {
super();
super(formService);
}
ngOnInit() {

View File

@@ -4,6 +4,7 @@
<select class="dropdown-widget__select"
[attr.id]="field.id"
[(ngModel)]="field.value"
[disabled]="field.readOnly"
(ngModelChange)="checkVisibility(field)">
<option *ngFor="let opt of field.options" [value]="getOptionValue(opt, field.value)" [id]="opt.id">{{opt.name}}</option>
</select>

View File

@@ -253,6 +253,24 @@ describe('DropdownWidget', () => {
expect(dropDownElement.selectedOptions[0].textContent).toBe('Choose one...');
});
}));
it('should be disabled when the field is readonly', async(() => {
dropDownWidget.field = new FormFieldModel(new FormModel({ processDefinitionId: 'fake-process-id' }), {
id: 'dropdown-id',
name: 'date-name',
type: 'dropdown',
readOnly: 'true',
restUrl: 'fake-rest-url'
});
fixture.detectChanges();
fixture.whenStable()
.then(() => {
let dropDownElement: HTMLSelectElement = <HTMLSelectElement> element.querySelector('#dropdown-id');
expect(dropDownElement).not.toBeNull();
expect(dropDownElement.disabled).toBeTruthy();
});
}));
});
});
});

View File

@@ -18,21 +18,22 @@
import { Component, OnInit } from '@angular/core';
import { LogService } from 'ng2-alfresco-core';
import { FormService } from '../../../services/form.service';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormFieldOption } from './../core/form-field-option';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
@Component({
selector: 'dropdown-widget',
templateUrl: './dropdown.widget.html',
styleUrls: ['./dropdown.widget.css']
styleUrls: ['./dropdown.widget.css'],
host: baseHost
})
export class DropdownWidget extends WidgetComponent implements OnInit {
constructor(private formService: FormService,
constructor(public formService: FormService,
private visibilityService: WidgetVisibilityService,
private logService: LogService) {
super();
super(formService);
}
ngOnInit() {

View File

@@ -4,7 +4,7 @@
<div *ngIf="!editMode">
<div class="dynamic-table-widget__table-container">
<table class="mdl-data-table mdl-js-data-table dynamic-table-widget__table">
<table class="mdl-data-table mdl-js-data-table dynamic-table-widget__table" id="dynamic-table-{{content.id}}">
<thead>
<tr>
<th *ngFor="let column of content.visibleColumns"
@@ -14,8 +14,8 @@
</tr>
</thead>
<tbody>
<tr *ngFor="let row of content.rows"
[class.dynamic-table-widget__row-selected]="row.selected">
<tr *ngFor="let row of content.rows; let idx = index" tabindex="0" id="{{content.id}}-row-{{idx}}"
[class.dynamic-table-widget__row-selected]="row.selected" (keyup)="onKeyPressed($event, row)">
<td *ngFor="let column of content.visibleColumns"
class="mdl-data-table__cell--non-numeric"
(click)="onRowClicked(row)">
@@ -38,6 +38,7 @@
<i class="material-icons">arrow_downward</i>
</button>
<button class="mdl-button mdl-js-button mdl-button--icon"
id="{{content.id}}-add-row"
(click)="addNewRow()">
<i class="material-icons">add_circle_outline</i>
</button>

View File

@@ -19,26 +19,104 @@ import { LogServiceMock } from 'ng2-alfresco-core';
import { DynamicTableWidget } from './dynamic-table.widget';
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './dynamic-table.widget.model';
import { FormModel, FormFieldTypes, FormFieldModel } from './../core/index';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { RowEditorComponent } from './editors/row.editor';
import { DropdownEditorComponent } from './editors/dropdown/dropdown.editor';
import { DateEditorComponent } from './editors/date/date.editor';
import { BooleanEditorComponent } from './editors/boolean/boolean.editor';
import { TextEditorComponent } from './editors/text/text.editor';
import { CoreModule, LogService } from 'ng2-alfresco-core';
import { FormService } from './../../../services/form.service';
import { EcmModelService } from './../../../services/ecm-model.service';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
let fakeFormField = {
id: 'fake-dynamic-table',
name: 'fake-label',
value: [{1: 1, 2: 2, 3: 4}],
required: false,
readOnly: false,
overrideId: false,
colspan: 1,
placeholder: null,
minLength: 0,
maxLength: 0,
params: {
existingColspan: 1,
maxColspan: 1
},
sizeX: 2,
sizeY: 2,
row: -1,
col: -1,
columnDefinitions: [
{
id: 1,
name: 1,
type: 'String',
visible: true
},
{
id: 2,
name: 2,
type: 'String',
visible: true
},
{
id: 3,
name: 3,
type: 'String',
visible: true
}
]
};
describe('DynamicTableWidget', () => {
let widget: DynamicTableWidget;
let fixture: ComponentFixture<DynamicTableWidget>;
let element: HTMLElement;
let table: DynamicTableModel;
let visibilityService: WidgetVisibilityService;
let logService: LogServiceMock;
let logService: LogService;
let componentHandler: any;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
],
declarations: [DynamicTableWidget, RowEditorComponent,
DropdownEditorComponent, DateEditorComponent, BooleanEditorComponent, TextEditorComponent],
providers: [
FormService,
{provide: LogService, useClass: LogServiceMock},
ActivitiAlfrescoContentService,
EcmModelService,
WidgetVisibilityService
]
}).compileComponents();
}));
beforeEach(() => {
logService = new LogServiceMock();
const field = new FormFieldModel(new FormModel());
logService = TestBed.get(LogService);
table = new DynamicTableModel(field);
visibilityService = new WidgetVisibilityService(null, logService);
widget = new DynamicTableWidget(null, visibilityService, logService);
let changeDetectorSpy = jasmine.createSpyObj('cd', ['detectChanges']);
let nativeElementSpy = jasmine.createSpyObj('nativeElement', ['querySelector']);
changeDetectorSpy.nativeElement = nativeElementSpy;
let elementRefSpy = jasmine.createSpyObj('elementRef', ['']);
elementRefSpy.nativeElement = nativeElementSpy;
fixture = TestBed.createComponent(DynamicTableWidget);
element = fixture.nativeElement;
widget = fixture.componentInstance;
widget.content = table;
});
it('should select row on click', () => {
let row = <DynamicTableRow> { selected: false };
let row = <DynamicTableRow> {selected: false};
widget.onRowClicked(row);
expect(row.selected).toBeTruthy();
@@ -46,7 +124,7 @@ describe('DynamicTableWidget', () => {
});
it('should requre table to select clicked row', () => {
let row = <DynamicTableRow> { selected: false };
let row = <DynamicTableRow> {selected: false};
widget.content = null;
widget.onRowClicked(row);
@@ -54,7 +132,7 @@ describe('DynamicTableWidget', () => {
});
it('should reset selected row', () => {
let row = <DynamicTableRow> { selected: false };
let row = <DynamicTableRow> {selected: false};
widget.content.rows.push(row);
widget.content.selectedRow = row;
expect(widget.content.selectedRow).toBe(row);
@@ -66,7 +144,7 @@ describe('DynamicTableWidget', () => {
});
it('should check selection', () => {
let row = <DynamicTableRow> { selected: false };
let row = <DynamicTableRow> {selected: false};
widget.content.rows.push(row);
widget.content.selectedRow = row;
expect(widget.hasSelection()).toBeTruthy();
@@ -144,7 +222,7 @@ describe('DynamicTableWidget', () => {
expect(widget.editMode).toBeFalsy();
expect(widget.editRow).toBeFalsy();
let row = <DynamicTableRow> { value: true };
let row = <DynamicTableRow> {value: true};
widget.content.selectedRow = row;
expect(widget.editSelection()).toBeTruthy();
@@ -154,7 +232,7 @@ describe('DynamicTableWidget', () => {
});
it('should copy row', () => {
let row = <DynamicTableRow> { value: { opt: { key: '1', value: 1 } } };
let row = <DynamicTableRow> {value: {opt: {key: '1', value: 1}}};
let copy = widget.copyRow(row);
expect(copy.value).toEqual(row.value);
});
@@ -166,14 +244,14 @@ describe('DynamicTableWidget', () => {
it('should retrieve cell value', () => {
const value = '<value>';
let row = <DynamicTableRow> { value: { key: value } };
let column = <DynamicTableColumn> { id: 'key' };
let row = <DynamicTableRow> {value: {key: value}};
let column = <DynamicTableColumn> {id: 'key'};
expect(widget.getCellValue(row, column)).toBe(value);
});
it('should save changes and add new row', () => {
let row = <DynamicTableRow> { isNew: true, value: { key: 'value' } };
let row = <DynamicTableRow> {isNew: true, value: {key: 'value'}};
widget.editMode = true;
widget.editRow = row;
@@ -186,7 +264,7 @@ describe('DynamicTableWidget', () => {
});
it('should save changes and update row', () => {
let row = <DynamicTableRow> { isNew: false, value: { key: 'value' } };
let row = <DynamicTableRow> {isNew: false, value: {key: 'value'}};
widget.editMode = true;
widget.editRow = row;
widget.content.selectedRow = row;
@@ -244,16 +322,69 @@ describe('DynamicTableWidget', () => {
});
it('should prepend default currency for amount columns', () => {
let row = <DynamicTableRow> { value: { key: '100' } };
let column = <DynamicTableColumn> { id: 'key', type: 'Amount' };
let row = <DynamicTableRow> {value: {key: '100'}};
let column = <DynamicTableColumn> {id: 'key', type: 'Amount'};
let actual = widget.getCellValue(row, column);
expect(actual).toBe('$ 100');
});
it('should prepend custom currency for amount columns', () => {
let row = <DynamicTableRow> { value: { key: '100' } };
let column = <DynamicTableColumn> { id: 'key', type: 'Amount', amountCurrency: 'GBP' };
let row = <DynamicTableRow> {value: {key: '100'}};
let column = <DynamicTableColumn> {id: 'key', type: 'Amount', amountCurrency: 'GBP'};
let actual = widget.getCellValue(row, column);
expect(actual).toBe('GBP 100');
});
describe('when template is ready', () => {
beforeEach(async(() => {
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
}));
beforeEach(() => {
widget.field = new FormFieldModel(new FormModel({taskId: 'fake-task-id'}), fakeFormField);
widget.field.type = FormFieldTypes.DYNAMIC_TABLE;
fixture.detectChanges();
});
afterEach(() => {
fixture.destroy();
TestBed.resetTestingModule();
});
it('should select a row when press space bar', async(() => {
let rowElement = element.querySelector('#fake-dynamic-table-row-0');
expect(element.querySelector('#dynamic-table-fake-dynamic-table')).not.toBeNull();
expect(rowElement).not.toBeNull();
expect(rowElement.className).toBeFalsy();
let event: any = new Event('keyup');
event.keyCode = 32;
rowElement.dispatchEvent(event);
fixture.detectChanges();
fixture.whenStable().then(() => {
let selectedRow = element.querySelector('#fake-dynamic-table-row-0');
expect(selectedRow.className).toBe('dynamic-table-widget__row-selected');
});
}));
it('should focus on add button when a new row is saved', async(() => {
let addNewRowButton: HTMLButtonElement = <HTMLButtonElement> element.querySelector('#fake-dynamic-table-add-row');
expect(element.querySelector('#dynamic-table-fake-dynamic-table')).not.toBeNull();
expect(addNewRowButton).not.toBeNull();
widget.addNewRow();
widget.onSaveChanges();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(document.activeElement.id).toBe('fake-dynamic-table-add-row');
});
}));
});
});

View File

@@ -15,17 +15,19 @@
* limitations under the License.
*/
import { Component, ElementRef, OnInit, Input } from '@angular/core';
import { Component, ElementRef, OnInit, Input, ChangeDetectorRef } from '@angular/core';
import { LogService } from 'ng2-alfresco-core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { DynamicTableModel, DynamicTableRow, DynamicTableColumn } from './dynamic-table.widget.model';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
import { FormFieldModel } from '../core/form-field.model';
import { FormService } from './../../../services/form.service';
@Component({
selector: 'dynamic-table-widget',
templateUrl: './dynamic-table.widget.html',
styleUrls: ['./dynamic-table.widget.css']
styleUrls: ['./dynamic-table.widget.css'],
host: baseHost
})
export class DynamicTableWidget extends WidgetComponent implements OnInit {
@@ -42,10 +44,14 @@ export class DynamicTableWidget extends WidgetComponent implements OnInit {
editMode: boolean = false;
editRow: DynamicTableRow = null;
constructor(private elementRef: ElementRef,
private selectArrayCode = [32, 0, 13];
constructor(public formService: FormService,
public elementRef: ElementRef,
private visibilityService: WidgetVisibilityService,
private logService: LogService) {
super();
private logService: LogService,
private cd: ChangeDetectorRef) {
super(formService);
}
ngOnInit() {
@@ -55,6 +61,20 @@ export class DynamicTableWidget extends WidgetComponent implements OnInit {
}
}
forceFocusOnAddButton() {
if (this.content) {
this.cd.detectChanges();
let buttonAddRow = <HTMLButtonElement>this.elementRef.nativeElement.querySelector('#' + this.content.id + '-add-row');
if (this.isDynamicTableReady(buttonAddRow)) {
buttonAddRow.focus();
}
}
}
private isDynamicTableReady(buttonAddRow) {
return this.field && !this.editMode && buttonAddRow;
}
isValid() {
let result = true;
@@ -71,6 +91,16 @@ export class DynamicTableWidget extends WidgetComponent implements OnInit {
}
}
onKeyPressed($event: KeyboardEvent, row: DynamicTableRow) {
if (this.content && this.isEnterOrSpacePressed($event.keyCode)) {
this.content.selectedRow = row;
}
}
private isEnterOrSpacePressed(keycode) {
return this.selectArrayCode.indexOf(keycode) !== -1;
}
hasSelection(): boolean {
return !!(this.content && this.content.selectedRow);
}
@@ -147,11 +177,13 @@ export class DynamicTableWidget extends WidgetComponent implements OnInit {
this.logService.error(this.ERROR_MODEL_NOT_FOUND);
}
this.editMode = false;
this.forceFocusOnAddButton();
}
onCancelChanges() {
this.editMode = false;
this.editRow = null;
this.forceFocusOnAddButton();
}
copyRow(row: DynamicTableRow): DynamicTableRow {

View File

@@ -43,7 +43,7 @@ export class DateEditorComponent implements OnInit {
@Input()
column: DynamicTableColumn;
constructor(private elementRef: ElementRef) {}
constructor(public elementRef: ElementRef) {}
ngOnInit() {
this.settings = {

View File

@@ -39,7 +39,7 @@ export class DropdownEditorComponent implements OnInit {
@Input()
column: DynamicTableColumn;
constructor(private formService: FormService,
constructor(public formService: FormService,
private logService: LogService) {
}

View File

@@ -16,14 +16,15 @@
*/
import { Component, OnInit, ElementRef } from '@angular/core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormService } from '../../../services/form.service';
import { GroupModel } from './../core/group.model';
@Component({
selector: 'functional-group-widget',
templateUrl: './functional-group.widget.html',
styleUrls: ['./functional-group.widget.css']
styleUrls: ['./functional-group.widget.css'],
host: baseHost
})
export class FunctionalGroupWidget extends WidgetComponent implements OnInit {
@@ -33,9 +34,9 @@ export class FunctionalGroupWidget extends WidgetComponent implements OnInit {
minTermLength: number = 1;
groupId: string;
constructor(private formService: FormService,
private elementRef: ElementRef) {
super();
constructor(public formService: FormService,
public elementRef: ElementRef) {
super(formService);
}
// TODO: investigate, called 2 times

View File

@@ -25,7 +25,7 @@ describe('HyperlinkWidget', () => {
let widget: HyperlinkWidget;
beforeEach(() => {
widget = new HyperlinkWidget();
widget = new HyperlinkWidget(null);
});
it('should get link text from field display text', () => {

View File

@@ -16,18 +16,24 @@
*/
import { Component, OnInit } from '@angular/core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormService } from './../../../services/form.service';
@Component({
selector: 'hyperlink-widget',
templateUrl: './hyperlink.widget.html',
styleUrls: ['./hyperlink.widget.css']
styleUrls: ['./hyperlink.widget.css'],
host: baseHost
})
export class HyperlinkWidget extends WidgetComponent implements OnInit {
linkUrl: string = WidgetComponent.DEFAULT_HYPERLINK_URL;
linkText: string = null;
constructor(public formService: FormService) {
super(formService);
}
ngOnInit() {
if (this.field) {
this.linkUrl = this.getHyperlinkUrl(this.field);

View File

@@ -22,7 +22,7 @@ describe('MultilineTextWidget', () => {
let widget: MultilineTextWidget;
beforeEach(() => {
widget = new MultilineTextWidget();
widget = new MultilineTextWidget(null);
});
it('should exist', () => {

View File

@@ -16,12 +16,18 @@
*/
import { Component } from '@angular/core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormService } from './../../../services/form.service';
@Component({
selector: 'multiline-text-widget',
templateUrl: './multiline-text.widget.html',
styleUrls: ['./multiline-text.widget.css']
styleUrls: ['./multiline-text.widget.css'],
host: baseHost
})
export class MultilineTextWidget extends WidgetComponent {
constructor(public formService: FormService) {
super(formService);
}
}

View File

@@ -22,7 +22,7 @@ describe('NumberWidget', () => {
let widget: NumberWidget;
beforeEach(() => {
widget = new NumberWidget();
widget = new NumberWidget(null);
});
it('should exist', () => {

View File

@@ -16,12 +16,19 @@
*/
import { Component } from '@angular/core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormService } from './../../../services/form.service';
@Component({
selector: 'number-widget',
templateUrl: './number.widget.html',
styleUrls: ['./number.widget.css']
styleUrls: ['./number.widget.css'],
host: baseHost
})
export class NumberWidget extends WidgetComponent {
constructor(public formService: FormService) {
super(formService);
}
}

View File

@@ -16,7 +16,7 @@
*/
import { Component, OnInit, ElementRef } from '@angular/core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormService } from '../../../services/form.service';
import { GroupModel } from '../core/group.model';
import { GroupUserModel } from '../core/group-user.model';
@@ -24,7 +24,8 @@ import { GroupUserModel } from '../core/group-user.model';
@Component({
selector: 'people-widget',
templateUrl: './people.widget.html',
styleUrls: ['./people.widget.css']
styleUrls: ['./people.widget.css'],
host: baseHost
})
export class PeopleWidget extends WidgetComponent implements OnInit {
@@ -34,9 +35,9 @@ export class PeopleWidget extends WidgetComponent implements OnInit {
users: GroupUserModel[] = [];
groupId: string;
constructor(private formService: FormService,
private elementRef: ElementRef) {
super();
constructor(public formService: FormService,
public elementRef: ElementRef) {
super(formService);
}
// TODO: investigate, called 2 times

View File

@@ -17,7 +17,7 @@
import { Component, OnInit } from '@angular/core';
import { LogService } from 'ng2-alfresco-core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormService } from '../../../services/form.service';
import { FormFieldOption } from './../core/form-field-option';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
@@ -25,14 +25,15 @@ import { WidgetVisibilityService } from '../../../services/widget-visibility.ser
@Component({
selector: 'radio-buttons-widget',
templateUrl: './radio-buttons.widget.html',
styleUrls: ['./radio-buttons.widget.css']
styleUrls: ['./radio-buttons.widget.css'],
host: baseHost
})
export class RadioButtonsWidget extends WidgetComponent implements OnInit {
constructor(private formService: FormService,
constructor(public formService: FormService,
private visibilityService: WidgetVisibilityService,
private logService: LogService) {
super();
super(formService);
}
ngOnInit() {

View File

@@ -1,20 +1,9 @@
<div *ngIf="hasTabs()" class="alfresco-tabs-widget">
<div alfresco-mdl-tabs>
<div class="mdl-tabs__tab-bar">
<a *ngFor="let tab of visibleTabs; let isFirst = first"
id="title-{{tab.id}}"
[href]="'#' + tab.id"
class="mdl-tabs__tab" [class.is-active]="isFirst">
{{tab.title}}
</a>
</div>
<div *ngFor="let tab of visibleTabs; let isFirst = first"
class="mdl-tabs__panel"
[class.is-active]="isFirst"
[attr.id]="tab.id">
<div *ngFor="let field of tab.fields">
<md-tab-group>
<md-tab *ngFor="let tab of visibleTabs" [label]="tab.title">
<div *ngFor="let field of tab.fields">
<form-field [field]="field.field"></form-field>
</div>
</div>
</div>
</md-tab>
</md-tab-group>
</div>

View File

@@ -26,6 +26,7 @@ import { MASK_DIRECTIVE } from '../index';
import { FormFieldComponent } from './../../form-field/form-field.component';
import { ActivitiContent } from './../../activiti-content.component';
import { CoreModule } from 'ng2-alfresco-core';
import { MdTabsModule } from '@angular/material';
describe('TabsWidget', () => {
@@ -104,7 +105,7 @@ describe('TabsWidget', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [CoreModule],
imports: [CoreModule, MdTabsModule],
declarations: [FormFieldComponent, ActivitiContent, WIDGET_DIRECTIVES, MASK_DIRECTIVE]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(TabsWidget);
@@ -140,22 +141,17 @@ describe('TabsWidget', () => {
fixture.detectChanges();
fixture.whenStable()
.then(() => {
expect(element.querySelector('#tab-id-visible')).toBeDefined();
expect(element.querySelector('#tab-id-visible')).not.toBeNull();
expect(element.querySelector('#tab-id-invisible')).toBeNull();
expect(element.querySelector('#title-tab-id-visible')).toBeDefined();
expect(element.querySelector('#title-tab-id-visible').innerHTML).toContain('tab-title-visible');
});
});
it('should show tab when it became visible', async(() => {
xit('should show tab when it became visible', async(() => {
fixture.detectChanges();
tabWidgetComponent.formTabChanged.subscribe((res) => {
tabWidgetComponent.tabs[1].isVisible = true;
fixture.detectChanges();
fixture.whenStable()
.then(() => {
expect(element.querySelector('#tab-id-invisible')).not.toBeNull();
expect(element.querySelector('#title-tab-id-invisible').innerHTML).toContain('tab-title-invisible');
});
});

View File

@@ -8,7 +8,7 @@
[value]="field.value"
[(ngModel)]="field.value"
(ngModelChange)="onFieldChanged(field)"
[disabled]="field.readOnly"
[attr.disabled]="field.readOnly ? true : null"
[textMask]="{mask: mask, isReversed: isMaskReversed}"
placeholder="{{field.placeholder}}">
<span *ngIf="field.validationSummary" class="mdl-textfield__error">{{field.validationSummary}}</span>

View File

@@ -17,60 +17,57 @@
import { TextWidget } from './text.widget';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { InputMaskDirective } from './text-mask.component';
import { FormFieldModel } from '../core/form-field.model';
import { FormModel } from '../core/form.model';
import { FormFieldTypes } from '../core/form-field-types';
import { CoreModule } from 'ng2-alfresco-core';
import { FormService } from './../../../services/form.service';
import { EcmModelService } from './../../../services/ecm-model.service';
import { ActivitiAlfrescoContentService } from '../../../services/activiti-alfresco.service';
describe('TextWidget', () => {
let widget: TextWidget;
let fixture: ComponentFixture<TextWidget>;
let componentHandler;
let element: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
],
declarations: [
TextWidget,
InputMaskDirective
],
providers: [
FormService,
EcmModelService,
ActivitiAlfrescoContentService
]
}).compileComponents();
}));
beforeEach(() => {
widget = new TextWidget();
componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered'
]);
fixture = TestBed.createComponent(TextWidget);
widget = fixture.componentInstance;
element = fixture.nativeElement;
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
});
describe('when template is ready', () => {
let textWidget: TextWidget;
let fixture: ComponentFixture<TextWidget>;
let element: HTMLInputElement;
let componentHandler;
beforeEach(async(() => {
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
}));
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [CoreModule],
declarations: [TextWidget, InputMaskDirective]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(TextWidget);
textWidget = fixture.componentInstance;
element = fixture.nativeElement;
});
}));
afterEach(() => {
fixture.destroy();
TestBed.resetTestingModule();
});
describe('and no mask is configured on text element', () => {
let inputElement: HTMLInputElement;
beforeEach(() => {
textWidget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
id: 'text-id',
name: 'text-name',
value: '',
@@ -84,13 +81,24 @@ describe('TextWidget', () => {
it('should raise ngModelChange event', async(() => {
inputElement.value = 'TEXT';
expect(textWidget.field.value).toBe('');
expect(widget.field.value).toBe('');
inputElement.dispatchEvent(new Event('input'));
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(textWidget.field).not.toBeNull();
expect(textWidget.field.value).not.toBeNull();
expect(textWidget.field.value).toBe('TEXT');
expect(widget.field).not.toBeNull();
expect(widget.field.value).not.toBeNull();
expect(widget.field.value).toBe('TEXT');
});
}));
it('should be disabled on readonly forms', async(() => {
widget.field.form.readOnly = true;
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(inputElement).toBeDefined();
expect(inputElement).not.toBeNull();
expect(inputElement.disabled).toBeTruthy();
});
}));
});
@@ -100,7 +108,7 @@ describe('TextWidget', () => {
let inputElement: HTMLInputElement;
beforeEach(() => {
textWidget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
id: 'text-id',
name: 'text-name',
value: '',
@@ -122,7 +130,7 @@ describe('TextWidget', () => {
expect(element.querySelector('#text-id')).not.toBeNull();
inputElement.value = 'F';
textWidget.field.value = 'F';
widget.field.value = 'F';
let event: any = new Event('keyup');
event.keyCode = '70';
inputElement.dispatchEvent(event);
@@ -139,7 +147,7 @@ describe('TextWidget', () => {
expect(element.querySelector('#text-id')).not.toBeNull();
inputElement.value = 'F';
textWidget.field.value = 'F';
widget.field.value = 'F';
inputElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
@@ -154,15 +162,15 @@ describe('TextWidget', () => {
expect(element.querySelector('#text-id')).not.toBeNull();
inputElement.value = '1';
textWidget.field.value = '1';
widget.field.value = '1';
let event: any = new Event('keyup');
event.keyCode = '49';
inputElement.dispatchEvent(event);
fixture.whenStable().then(() => {
fixture.detectChanges();
let inputElement: HTMLInputElement = <HTMLInputElement>element.querySelector('#text-id');
expect(inputElement.value).toBe('1');
let textEle: HTMLInputElement = <HTMLInputElement>element.querySelector('#text-id');
expect(textEle.value).toBe('1');
});
}));
@@ -170,15 +178,15 @@ describe('TextWidget', () => {
expect(element.querySelector('#text-id')).not.toBeNull();
inputElement.value = '12345678';
textWidget.field.value = '12345678';
widget.field.value = '12345678';
let event: any = new Event('keyup');
event.keyCode = '49';
inputElement.dispatchEvent(event);
fixture.whenStable().then(() => {
fixture.detectChanges();
let inputElement: HTMLInputElement = <HTMLInputElement>element.querySelector('#text-id');
expect(inputElement.value).toBe('12-345,67%');
let textEle: HTMLInputElement = <HTMLInputElement>element.querySelector('#text-id');
expect(textEle.value).toBe('12-345,67%');
});
}));
});
@@ -188,11 +196,11 @@ describe('TextWidget', () => {
let inputElement: HTMLInputElement;
beforeEach(() => {
textWidget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
id: 'text-id',
name: 'text-name',
value: '',
params: { existingColspan: 1, maxColspan: 2, inputMask: "#.##0,00%", inputMaskReversed: true },
params: { existingColspan: 1, maxColspan: 2, inputMask: '#.##0,00%', inputMaskReversed: true },
type: FormFieldTypes.TEXT,
readOnly: false
});
@@ -210,18 +218,17 @@ describe('TextWidget', () => {
expect(element.querySelector('#text-id')).not.toBeNull();
inputElement.value = '1234';
textWidget.field.value = '1234';
widget.field.value = '1234';
let event: any = new Event('keyup');
event.keyCode = '49';
inputElement.dispatchEvent(event);
fixture.whenStable().then(() => {
fixture.detectChanges();
let inputElement: HTMLInputElement = <HTMLInputElement>element.querySelector('#text-id');
expect(inputElement.value).toBe('12,34%');
let textEle: HTMLInputElement = <HTMLInputElement>element.querySelector('#text-id');
expect(textEle.value).toBe('12,34%');
});
}));
});
});
});

View File

@@ -16,18 +16,24 @@
*/
import { Component, OnInit } from '@angular/core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormService } from './../../../services/form.service';
@Component({
selector: 'text-widget',
templateUrl: './text.widget.html',
styleUrls: ['./text.widget.css']
styleUrls: ['./text.widget.css'],
host: baseHost
})
export class TextWidget extends WidgetComponent implements OnInit {
private mask;
private isMaskReversed;
constructor(public formService: FormService) {
super(formService);
}
ngOnInit() {
if (this.field.params && this.field.params['inputMask']) {
this.mask = this.field.params['inputMask'];

View File

@@ -18,14 +18,15 @@
import { Component, OnInit } from '@angular/core';
import { LogService } from 'ng2-alfresco-core';
import { FormService } from './../../../services/form.service';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormFieldOption } from './../core/form-field-option';
import { WidgetVisibilityService } from '../../../services/widget-visibility.service';
@Component({
selector: 'typeahead-widget',
templateUrl: './typeahead.widget.html',
styleUrls: ['./typeahead.widget.css']
styleUrls: ['./typeahead.widget.css'],
host: baseHost
})
export class TypeaheadWidget extends WidgetComponent implements OnInit {
@@ -34,10 +35,10 @@ export class TypeaheadWidget extends WidgetComponent implements OnInit {
value: string;
options: FormFieldOption[] = [];
constructor(private formService: FormService,
constructor(public formService: FormService,
private visibilityService: WidgetVisibilityService,
private logService: LogService) {
super();
super(formService);
}
ngOnInit() {

View File

@@ -16,7 +16,8 @@
*/
import { Component } from '@angular/core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormService } from './../../../services/form.service';
@Component({
selector: 'unknown-widget',
@@ -25,7 +26,12 @@ import { WidgetComponent } from './../widget.component';
<i class="material-icons">error_outline</i>
<span style="color: red">Unknown type: {{field.type}}</span>
</div>
`
`,
host: baseHost
})
export class UnknownWidget extends WidgetComponent {
constructor(public formService: FormService) {
super(formService);
}
}

View File

@@ -8,6 +8,7 @@
#file
type="file"
[attr.id]="field.id"
[disabled]="field.readOnly"
class="upload-widget__file"
(change)="onFileChanged($event)">
<button *ngIf="hasFile" (click)="reset(file);"

View File

@@ -19,9 +19,14 @@ import { UploadWidget } from './upload.widget';
import { FormFieldModel } from './../core/form-field.model';
import { FormFieldTypes } from '../core/form-field-types';
import { FormService } from '../../../services/form.service';
import { EcmModelService } from '../../../services/ecm-model.service';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { FormModel } from '../core/form.model';
describe('UploadWidget', () => {
let componentHandler;
let widget: UploadWidget;
let formService: FormService;
@@ -37,7 +42,7 @@ describe('UploadWidget', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.UPLOAD,
value: [
{name: encodedFileName}
{ name: encodedFileName }
]
});
@@ -71,7 +76,7 @@ describe('UploadWidget', () => {
widget.field = new FormFieldModel(null, {
type: FormFieldTypes.UPLOAD,
value: [
{name: 'filename'}
{ name: 'filename' }
]
});
widget.reset();
@@ -79,4 +84,58 @@ describe('UploadWidget', () => {
expect(widget.field.json.value).toBeNull();
});
describe('when template is ready', () => {
let uploadWidget: UploadWidget;
let fixture: ComponentFixture<UploadWidget>;
let element: HTMLInputElement;
let inputElement: HTMLInputElement;
beforeEach(async(() => {
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
}));
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [CoreModule],
declarations: [UploadWidget],
providers: [FormService, EcmModelService]
}).compileComponents().then(() => {
fixture = TestBed.createComponent(UploadWidget);
uploadWidget = fixture.componentInstance;
element = fixture.nativeElement;
});
}));
afterEach(() => {
fixture.destroy();
TestBed.resetTestingModule();
});
beforeEach(() => {
uploadWidget.field = new FormFieldModel(new FormModel({ taskId: 'fake-upload-id' }), {
id: 'upload-id',
name: 'upload-name',
value: '',
type: FormFieldTypes.UPLOAD,
readOnly: false
});
fixture.detectChanges();
inputElement = <HTMLInputElement>element.querySelector('#upload-id');
});
it('should be disabled on readonly forms', async(() => {
uploadWidget.field.form.readOnly = true;
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(inputElement).toBeDefined();
expect(inputElement).not.toBeNull();
expect(inputElement.disabled).toBeTruthy();
});
}));
});
});

View File

@@ -17,13 +17,14 @@
import { Component, OnInit } from '@angular/core';
import { LogService } from 'ng2-alfresco-core';
import { WidgetComponent } from './../widget.component';
import { WidgetComponent , baseHost } from './../widget.component';
import { FormService } from '../../../services/form.service';
@Component({
selector: 'upload-widget',
templateUrl: './upload.widget.html',
styleUrls: ['./upload.widget.css']
styleUrls: ['./upload.widget.css'],
host: baseHost
})
export class UploadWidget extends WidgetComponent implements OnInit {
@@ -31,9 +32,9 @@ export class UploadWidget extends WidgetComponent implements OnInit {
fileName: string;
displayText: string;
constructor(private formService: FormService,
constructor(public formService: FormService,
private logService: LogService) {
super();
super(formService);
}
ngOnInit() {

View File

@@ -15,101 +15,130 @@
* limitations under the License.
*/
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { ElementRef } from '@angular/core';
import { WidgetComponent } from './widget.component';
import { FormFieldModel } from './core/form-field.model';
import { FormModel } from './core/form.model';
import { FormService } from './../../services/form.service';
import { CoreModule } from 'ng2-alfresco-core';
import { EcmModelService } from './../../services/ecm-model.service';
import { ActivitiAlfrescoContentService } from '../../services/activiti-alfresco.service';
describe('WidgetComponent', () => {
let widget: WidgetComponent;
let fixture: ComponentFixture<WidgetComponent>;
let element: HTMLElement;
let componentHandler;
let formService: FormService;
beforeEach(async(() => {
componentHandler = jasmine.createSpyObj('componentHandler', ['upgradeAllRegistered', 'upgradeElement']);
window['componentHandler'] = componentHandler;
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
],
declarations: [WidgetComponent],
providers: [
FormService,
EcmModelService,
ActivitiAlfrescoContentService
]
}).compileComponents();
}));
beforeEach(() => {
componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered'
]);
fixture = TestBed.createComponent(WidgetComponent);
formService = TestBed.get(FormService);
element = fixture.nativeElement;
widget = fixture.componentInstance;
fixture.detectChanges();
});
describe('Events', () => {
it('should click event be redirect on the form event service', (done) => {
widget.formService.formEvents.subscribe(() => {
done();
});
element.click();
});
window['componentHandler'] = componentHandler;
});
it('should upgrade MDL content on view init', () => {
let component = new WidgetComponent();
component.ngAfterViewInit();
widget.ngAfterViewInit();
expect(componentHandler.upgradeAllRegistered).toHaveBeenCalled();
});
it('should setup MDL content only if component handler available', () => {
let component = new WidgetComponent();
expect(component.setupMaterialComponents(componentHandler)).toBeTruthy();
expect(component.setupMaterialComponents()).toBeFalsy();
expect(widget.setupMaterialComponents(componentHandler)).toBeTruthy();
expect(widget.setupMaterialComponents()).toBeFalsy();
});
it('should check field', () => {
let component = new WidgetComponent();
expect(component.hasField()).toBeFalsy();
component.field = new FormFieldModel(new FormModel());
expect(component.hasField()).toBeTruthy();
expect(widget.hasField()).toBeFalsy();
widget.field = new FormFieldModel(new FormModel());
expect(widget.hasField()).toBeTruthy();
});
it('should send an event after view init', (done) => {
let component = new WidgetComponent();
let fakeForm = new FormModel();
let fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'});
component.field = fakeField;
widget.field = fakeField;
component.fieldChanged.subscribe(field => {
widget.fieldChanged.subscribe(field => {
expect(field).not.toBe(null);
expect(field.id).toBe('fakeField');
expect(field.value).toBe('fakeValue');
done();
});
component.ngAfterViewInit();
widget.ngAfterViewInit();
});
it('should send an event when a field is changed', (done) => {
let component = new WidgetComponent();
let fakeForm = new FormModel();
let fakeField = new FormFieldModel(fakeForm, {id: 'fakeField', value: 'fakeValue'});
component.fieldChanged.subscribe(field => {
widget.fieldChanged.subscribe(field => {
expect(field).not.toBe(null);
expect(field.id).toBe('fakeField');
expect(field.value).toBe('fakeValue');
done();
});
component.checkVisibility(fakeField);
widget.checkVisibility(fakeField);
});
it('should eval isRequired state of the field', () => {
let widget = new WidgetComponent();
expect(widget.isRequired()).toBeFalsy();
widget.field = new FormFieldModel(null);
expect(widget.isRequired()).toBeFalsy();
widget.field = new FormFieldModel(null, { required: false });
widget.field = new FormFieldModel(null, {required: false});
expect(widget.isRequired()).toBeFalsy();
widget.field = new FormFieldModel(null, { required: true });
widget.field = new FormFieldModel(null, {required: true});
expect(widget.isRequired()).toBeTruthy();
});
it('should require element reference to setup textfield', () => {
let widget = new WidgetComponent();
expect(widget.setupMaterialTextField(null, {}, 'value')).toBeFalsy();
});
it('should require component handler to setup textfield', () => {
let elementRef = new ElementRef(null);
let widget = new WidgetComponent();
expect(widget.setupMaterialTextField(elementRef, null, 'value')).toBeFalsy();
});
it('should require field value to setup textfield', () => {
let elementRef = new ElementRef(null);
let widget = new WidgetComponent();
expect(widget.setupMaterialTextField(elementRef, {}, null)).toBeFalsy();
});
@@ -119,15 +148,15 @@ describe('WidgetComponent', () => {
querySelector: function () {
return {
MaterialTextfield: {
change: function() {
change: function () {
changeCalled = true;
}
}
};
}
});
let widget = new WidgetComponent();
expect(widget.setupMaterialTextField(elementRef, {}, 'value')).toBeTruthy();
expect(changeCalled).toBeTruthy();
});
});

View File

@@ -15,14 +15,32 @@
* limitations under the License.
*/
import { Input, AfterViewInit, Output, EventEmitter, ElementRef } from '@angular/core';
import { Component, Input, AfterViewInit, Output, EventEmitter, ElementRef } from '@angular/core';
import { FormFieldModel } from './core/index';
import { FormService } from './../../services/form.service';
declare var componentHandler: any;
declare let componentHandler: any;
export const baseHost = {
'(click)': 'event($event)',
'(blur)': 'event($event)',
'(change)': 'event($event)',
'(focus)': 'event($event)',
'(focusin)': 'event($event)',
'(focusout)': 'event($event)',
'(input)': 'event($event)',
'(invalid)': 'event($event)',
'(select)': 'event($event)'
};
/**
* Base widget component.
*/
@Component({
selector: 'base-widget',
template: '',
host: baseHost
})
export class WidgetComponent implements AfterViewInit {
static DEFAULT_HYPERLINK_URL: string = '#';
@@ -35,6 +53,9 @@ export class WidgetComponent implements AfterViewInit {
@Output()
fieldChanged: EventEmitter<FormFieldModel> = new EventEmitter<FormFieldModel>();
constructor(public formService?: FormService) {
}
hasField() {
return this.field ? true : false;
}
@@ -111,4 +132,8 @@ export class WidgetComponent implements AfterViewInit {
}
return null;
}
protected event(event: Event): void {
this.formService.formEvents.next(event);
}
}