Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Angular fields #374

Merged
merged 1 commit into from
Oct 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ gulp.task('copy-resource:manifest', function copyResourceManifest() {
* 9. Copy README.md from / to /dist
*/
gulp.task('copy:readme', function cleanReadme() {
return gulp.src([path.join(rootFolder, 'README.MD')])
return gulp.src([path.join(rootFolder, 'README.md')])
.pipe(gulp.dest(distFolder));
});

Expand Down
5,221 changes: 2,565 additions & 2,656 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"@angular/compiler": "^8.2.10",
"@angular/compiler-cli": "^8.2.10",
"@angular/core": "^8.2.10",
"@angular/elements": "^8.2.10",
"@angular/forms": "^8.2.10",
"@angular/platform-browser": "^8.2.10",
"@angular/platform-browser-dynamic": "^8.2.10",
Expand Down Expand Up @@ -97,6 +98,7 @@
"peerDependencies": {
"@angular/core": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"@angular/common": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"@angular/elements": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"@angular/forms": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"@angular/router": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"rxjs": "^5.0.0 || ^6.0.0",
Expand Down
12 changes: 11 additions & 1 deletion src/FormioBaseComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
FormioRefreshValue
} from './formio.common';
import { isEmpty, get, assign } from 'lodash';
import { CustomTagsService } from './custom-component/custom-tags.service';

export class FormioBaseComponent implements OnInit, OnChanges, OnDestroy {
@Input() form?: FormioForm;
Expand Down Expand Up @@ -64,6 +65,7 @@ export class FormioBaseComponent implements OnInit, OnChanges, OnDestroy {
constructor(
public loader: FormioLoader,
@Optional() public config: FormioAppConfig,
@Optional() public customTags?: CustomTagsService,
) {
this.formioReady = new Promise((ready) => {
this.formioReadyResolve = ready;
Expand All @@ -75,14 +77,18 @@ export class FormioBaseComponent implements OnInit, OnChanges, OnDestroy {
}

getRendererOptions() {
const extraTags = this.customTags ? this.customTags.tags : [];
return assign({}, {
icons: get(this.config, 'icons', 'fontawesome'),
noAlerts: get(this.options, 'noAlerts', true),
readOnly: this.readOnly,
viewAsHtml: this.viewOnly,
i18n: get(this.options, 'i18n', null),
fileService: get(this.options, 'fileService', null),
hooks: this.hooks
hooks: this.hooks,
sanitizeConfig: {
addTags: extraTags
}
}, this.renderOptions || {});
}

Expand Down Expand Up @@ -146,6 +152,7 @@ export class FormioBaseComponent implements OnInit, OnChanges, OnDestroy {
return;
}

const extraTags = this.customTags ? this.customTags.tags : [];
this.options = Object.assign(
{
errors: {
Expand All @@ -157,6 +164,9 @@ export class FormioBaseComponent implements OnInit, OnChanges, OnDestroy {
disableAlerts: false,
hooks: {
beforeSubmit: null
},
sanitizeConfig: {
addTags: extraTags
}
},
this.options
Expand Down
12 changes: 10 additions & 2 deletions src/components/formbuilder/formbuilder.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { Formio, FormBuilder, Utils } from 'formiojs';
import { assign } from 'lodash';
import { Observable, Subscription } from 'rxjs';
import { CustomTagsService } from '../../custom-component/custom-tags.service';

/* tslint:disable */
@Component({
Expand All @@ -42,7 +43,8 @@ export class FormBuilderComponent implements OnInit, OnChanges, OnDestroy {
@ViewChild('builder', { static: true }) builderElement?: ElementRef<any>;

constructor(
@Optional() private config: FormioAppConfig
@Optional() private config: FormioAppConfig,
@Optional() private customTags?: CustomTagsService
) {
if (this.config) {
Formio.setBaseUrl(this.config.apiUrl);
Expand Down Expand Up @@ -139,10 +141,16 @@ export class FormBuilderComponent implements OnInit, OnChanges, OnDestroy {
});
}
const Builder = this.formbuilder || FormBuilder;
const extraTags = this.customTags ? this.customTags.tags : [];
this.builder = new Builder(
this.builderElement.nativeElement,
form,
assign({icons: 'fontawesome'}, this.options || {})
assign({
icons: 'fontawesome',
sanitizeConfig: {
addTags: extraTags
}
}, this.options || {})
);
return this.builder.ready.then(instance => this.setInstance(instance));
}
Expand Down
4 changes: 3 additions & 1 deletion src/components/formio/formio.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FormioLoader } from '../loader/formio.loader';
import { FormioAppConfig } from '../../formio.config';
import { Formio, Form, Utils } from 'formiojs';
import { FormioBaseComponent } from '../../FormioBaseComponent';
import { CustomTagsService } from '../../custom-component/custom-tags.service';

/* tslint:disable */
@Component({
Expand All @@ -17,8 +18,9 @@ export class FormioComponent extends FormioBaseComponent implements OnInit {
constructor(
public loader: FormioLoader,
@Optional() public config: FormioAppConfig,
@Optional() public customTags?: CustomTagsService,
) {
super(loader, config);
super(loader, config, customTags);
if (this.config) {
Formio.setBaseUrl(this.config.apiUrl);
Formio.setProjectUrl(this.config.appUrl);
Expand Down
153 changes: 153 additions & 0 deletions src/custom-component/create-custom-component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { BuilderInfo, Components, ExtendedComponentSchema, Utils as FormioUtils } from 'formiojs';
import { FormioCustomComponentInfo, FormioCustomElement } from '../formio.common';
import { clone, isNil } from 'lodash';

const BaseInputComponent = Components.components.input;
const TextfieldComponent = Components.components.textfield;

export function createCustomFormioComponent(customComponentOptions: FormioCustomComponentInfo) {
return class CustomComponent extends BaseInputComponent {
static editForm = customComponentOptions.editForm || TextfieldComponent.editForm;
id = FormioUtils.getRandomComponentId();
type = customComponentOptions.type;
_customAngularElement: FormioCustomElement;

static schema() {
return BaseInputComponent.schema({
...customComponentOptions.schema,
type: customComponentOptions.type,
});
}

get defaultSchema() {
return CustomComponent.schema();
}

get emptyValue() {
return customComponentOptions.emptyValue || null;
}

static get builderInfo(): BuilderInfo {
return {
title: customComponentOptions.title,
group: customComponentOptions.group,
icon: customComponentOptions.icon,
weight: customComponentOptions.weight,
documentation: customComponentOptions.documentation,
schema: CustomComponent.schema(),
};
}

constructor(public component: ExtendedComponentSchema, options: any, data: any) {
super(component, {
...options,
sanitizeConfig: {
addTags: [customComponentOptions.selector],
},
}, data);

if (customComponentOptions.extraValidators) {
this.validators = this.validators.concat(customComponentOptions.extraValidators);
}
}

elementInfo() {
const info = super.elementInfo();
info.type = customComponentOptions.selector;
info.changeEvent = customComponentOptions.changeEvent || 'valueChange';
info.attr = {
...info.attr,
class: info.attr.class.replace('form-control', 'form-control-custom-field') // remove the form-control class as the custom angular component may look different
};
return info;
}

get inputInfo() {
const info = {
id: this.key,
...this.elementInfo()
}
return info;
}

renderElement(value: any, index: number) {
const info = this.inputInfo;
return this.renderTemplate(customComponentOptions.template || 'input', {
input: info,
value,
index
});
}

attach(element: HTMLElement) {
let superAttach = super.attach(element);

this._customAngularElement = element.querySelector(customComponentOptions.selector);

// Bind the custom options and the validations to the Angular component's inputs (flattened)
if (this._customAngularElement) {
// To make sure we have working input in IE...
// IE doesn't render it properly if it's not visible on the screen
// due to the whole structure applied via innerHTML to the parent
// so we need to use appendChild
if (!this._customAngularElement.getAttribute('ng-version')) {
this._customAngularElement.removeAttribute('ref');

const newCustomElement = document.createElement(customComponentOptions.selector) as FormioCustomElement;

newCustomElement.setAttribute('ref', 'input');
Object.keys(this.inputInfo.attr).forEach((attr: string) => {
newCustomElement.setAttribute(attr, this.inputInfo.attr[attr]);
});

this._customAngularElement.appendChild(newCustomElement);
this._customAngularElement = newCustomElement;

superAttach = super.attach(element);
}

for (const key in this.component.customOptions) {
if (this.component.customOptions.hasOwnProperty(key)) {
this._customAngularElement[key] = this.component.customOptions[key];
}
}
for (const key in this.component.validate) {
if (this.component.validate.hasOwnProperty(key)) {
this._customAngularElement[key] = this.component.validate[key];
}
}

// Ensure we bind the value (if it isn't a multiple-value component with no wrapper)
if (!this._customAngularElement.value && !this.component.disableMultiValueWrapper) {
this.restoreValue();
}

}
return superAttach;
}

// Add extra option to support multiple value (e.g. datagrid) with single angular component (disableMultiValueWrapper)
useWrapper() {
return this.component.hasOwnProperty('multiple') && this.component.multiple && !this.component.disableMultiValueWrapper;
}

get defaultValue() {
let defaultValue = this.emptyValue;

// handle falsy default value
if (!isNil(this.component.defaultValue)) {
defaultValue = this.component.defaultValue;
}

if (this.component.customDefaultValue && !this.options.preview) {
defaultValue = this.evaluate(
this.component.customDefaultValue,
{ value: '' },
'value'
);
}

return clone(defaultValue);
}
};
}
10 changes: 10 additions & 0 deletions src/custom-component/custom-tags.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class CustomTagsService {
tags: string[] = [];

addCustomTag(tag: string) {
this.tags.push(tag);
}
}
41 changes: 41 additions & 0 deletions src/custom-component/register-custom-component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Injector, Type } from '@angular/core';
import { createCustomElement } from '@angular/elements';
import { Components } from 'formiojs';
import { FormioCustomComponentInfo } from '../formio.common';
import { createCustomFormioComponent } from './create-custom-component';
import { CustomTagsService } from './custom-tags.service';

export function registerCustomTag(tag: string, injector: Injector): void {
injector.get(CustomTagsService).addCustomTag(tag);
}

export function registerCustomTags(tags: string[], injector: Injector): void {
tags.forEach(tag => registerCustomTag(tag, injector));
}

export function registerCustomFormioComponent(
options: FormioCustomComponentInfo,
angularComponent: Type<any>,
injector: Injector,
): void {
registerCustomTag(options.selector, injector);

const complexCustomComponent = createCustomElement(angularComponent, { injector });
customElements.define(options.selector, complexCustomComponent);

Components.setComponent(options.type, createCustomFormioComponent(options));
}

export function registerCustomFormioComponentWithClass(
options: FormioCustomComponentInfo,
angularComponent: Type<any>,
formioClass: any,
injector: Injector,
): void {
registerCustomTag(options.selector, injector);

const complexCustomComponent = createCustomElement(angularComponent, { injector });
customElements.define(options.selector, complexCustomComponent);

Components.setComponent(options.type, formioClass);
}
Loading