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 11, 2019
1 parent d95cdb7 commit 03795f7
Show file tree
Hide file tree
Showing 12 changed files with 2,841 additions and 2,697 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,233 changes: 2,571 additions & 2,662 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

0 comments on commit 03795f7

Please sign in to comment.