Skip to content

Commit

Permalink
Custom Angular fields
Browse files Browse the repository at this point in the history
  • Loading branch information
Balint Mero committed Oct 3, 2019
1 parent 7d79685 commit acf466f
Show file tree
Hide file tree
Showing 12 changed files with 2,834 additions and 2,724 deletions.
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,257 changes: 2,568 additions & 2,689 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.9",
"@angular/compiler-cli": "^8.2.9",
"@angular/core": "^8.2.9",
"@angular/elements": "^8.2.9",
"@angular/forms": "^8.2.9",
"@angular/platform-browser": "^8.2.9",
"@angular/platform-browser-dynamic": "^8.2.9",
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
10 changes: 9 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 @@ -62,6 +63,7 @@ export class FormioBaseComponent implements OnInit, OnChanges, OnDestroy {
private submitting = false;

constructor(
public customTags: CustomTagsService,
public loader: FormioLoader,
@Optional() public config: FormioAppConfig,
) {
Expand All @@ -82,7 +84,10 @@ export class FormioBaseComponent implements OnInit, OnChanges, OnDestroy {
viewAsHtml: this.viewOnly,
i18n: get(this.options, 'i18n', null),
fileService: get(this.options, 'fileService', null),
hooks: this.hooks
hooks: this.hooks,
sanitizeConfig: {
addTags: this.customTags.tags
}
}, this.renderOptions || {});
}

Expand Down Expand Up @@ -157,6 +162,9 @@ export class FormioBaseComponent implements OnInit, OnChanges, OnDestroy {
disableAlerts: false,
hooks: {
beforeSubmit: null
},
sanitizeConfig: {
addTags: this.customTags.tags
}
},
this.options
Expand Down
11 changes: 9 additions & 2 deletions src/components/formbuilder/formbuilder.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from '../../formio.common';
import { Formio, FormBuilder, Utils } from 'formiojs';
import { assign } from 'lodash';
import { CustomTagsService } from '../../custom-component/custom-tags.service';

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

constructor(
@Optional() private config: FormioAppConfig
@Optional() private config: FormioAppConfig,
private customTags: CustomTagsService
) {
if (this.config) {
Formio.setBaseUrl(this.config.apiUrl);
Expand Down Expand Up @@ -133,7 +135,12 @@ export class FormBuilderComponent implements OnInit, OnChanges, OnDestroy {
this.builder = new Builder(
this.builderElement.nativeElement,
form,
assign({icons: 'fontawesome'}, this.options || {})
assign({
icons: 'fontawesome',
sanitizeConfig: {
addTags: this.customTags.tags
}
}, 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 @@ -15,10 +16,11 @@ import { FormioBaseComponent } from '../../FormioBaseComponent';
export class FormioComponent extends FormioBaseComponent implements OnInit {
@Input() noeval ? = false;
constructor(
public customTags: CustomTagsService,
public loader: FormioLoader,
@Optional() public config: FormioAppConfig,
) {
super(loader, config);
super(customTags, loader, config);
if (this.config) {
Formio.setBaseUrl(this.config.apiUrl);
Formio.setProjectUrl(this.config.appUrl);
Expand Down
152 changes: 152 additions & 0 deletions src/custom-component/create-custom-component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
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 (!this._customAngularElement.value) {
this.restoreValue();
}

}
return superAttach;
}

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

0 comments on commit acf466f

Please sign in to comment.