ngx-schema-form
ngx-schema-form copied to clipboard
Passing data to widget?
Hi,
i will use your great library to generate forms in an ionic app (ionicframework.com).
My template:
<sf-form
[schema]="schema"
[actions]="schemaActions"
[model]="myModel"
(onChange)="onChange($event)">
</sf-form>
Home component:
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
imageMetaData = {
imageName: '',
imagePath: ''
};
filename: string = 'test.jpeg';
myModel = { };
schema = {
"$schema": "http://json-schema.org/draft-04/schema#",
"properties": {
"title": {
"label": "Title",
"type": "string",
"description": "Title"
},
"description": {
"label": "Description",
"type": "string",
"description": "Description",
"minLength": 6
},
image: {
"label": "Foto",
"type": "string",
"widget": "image",
"buttons": [{
"id": "takeFoto",
"block": true,
"outline": true,
"label": "Take a Foto!"
}]
}
},
"required": ["title", "description", "image"],
"buttons": [{
"id": "submit", // the id of the action callback
"label": "Submit" // the text inside the button
}],
"fieldsets": [{
"title": "General Information",
"fields": ["title", "description"]
}, {
"title": "Foto",
"fields": ["image"]
}
]
};
schemaActions = {
"takeFoto": () => {
this.takeFoto();
},
"submit": (form, options) => {
debugger;
}
}
constructor(public navCtrl: NavController,
private camera: Camera,
private file: File,
private toastCtrl: ToastController) {
}
onChange(event: Event) {
console.log(event);
}
takeFoto() {
//capturing foto on device and sets imagePath and imageName ...
this.imageMetaData.imagePath = ...;
this.imageMetaData.imageName = ...;
}
}
I created a new Widget:
template:
<ion-item>
<ion-label stacked>Name of file</ion-label>
<ion-input type="text"
[(ngModel)]="filename"
[formControl]="control"
[readonly]="true">
</ion-input>
</ion-item>
and the component:
export class IonImageWidget extends ControlWidget implements AfterViewInit {
filename: string;
constructor() {
super();
}
ngAfterViewInit() {
super.ngAfterViewInit();
//Only for testing
this.filename = 'filename.jpg';
}
}
- How can i access the json-model (myModel) if the form is „submitted"?
- How can i pass the file path and file name from the takeFoto() method to the Widget or do I have to put the „takeFoto logic“ into the widget itself?
- How can i update the json model to include the filename/path after capturing the foto?
File inputs are now supported (see https://github.com/makinacorpus/angular2-schema-form/issues/41), it will be part of the next release.
Sorry, i'm a little bit confused...
The file widget loads the content of the given file and sets the json-model with this.formProperty.setValue(this.filedata, false);
So i have to put the "take foto logic" into the widget itself or can i have it in the component with the form?
the model will contain something like:
{
'name': 'myphoto.png',
'type': 'image/png',
'data': 'base64 encoded content'
}
Ok,
the file widget is really great, but i don't want to upload a file.
I have a page (controller + view) and i want to take a foto. The schema is loading on that page and by now the "take foto thing (and save it on the device)" is also handled by that page.
- Can i pass the image path and filename to the widget after the form is rendered or
- Do i have to put the "take foto thing logic" into the widget itself?
Well, you just need to make a custom widget inheriting from FileWidget and change its template to add extra attributes to the input element like this:
<input type="file" capture="camera" accept="image/*" />
It will tell the phone to shoot a picture instead of selecting a file.
Wow, i didn't know that this will work i used cordova instead...
But i get no output in the model of the picture element: I derived a widget: template:
<ion-item>
<ion-label stacked>Name of file</ion-label>
</ion-item>
<input type="file"
accept="image/*"
capture="camera"
[name]="name"
class="text-widget file-widget"
[attr.id]="id"
[formControl]="control"
[attr.disabled]="schema.readOnly?true:null"/>
and my component:
import { AfterViewInit, Component } from '@angular/core';
import { FileWidget } from "../../../../../../../playground/angular2-schema-form/src/defaultwidgets";
@Component({
selector: 'sf-image-widget',
templateUrl: 'image.widget.html'
})
export class IonImageWidget extends FileWidget {}
and my schema (shorten):
...
image: {
"label": "Foto",
"type": "string",
"widget": "image"
}
...
I put the "image" in the required field. I can capture an image and it is displayed as small icon, but it isn't in the model. The form property valid is false and if i inspect the form (_errors) i get this
object {
code: "OBJECT_MISSING_REQUIRED_PROPERTY",
params: ,
message: "Missing required property: image",
path: "#/image",
schemaId: undefined
}
What am I doing wrong?
And do you get the error if image is not required?
The form is valid, but "image" isn't in the model. Only "title" and "description"...
and if you remove the capture="camera" thing then it works?
Hi Eric,
i played a little bit with the code. Here are my results:
Using the file widget:
- The data of the file is appended to the model.
- The form is invalid. Reason:
{
code: "INVALID_TYPE",
params: Array(2),
message: "Expected type string but found type object",
path: "#/image",
schemaId: undefined
}
using my image widget (see above):
- No data is appended to the model
- The form is valid
Well just change "type": "string", to "type": "object", in your model then, no?
No.
Okay, things get mixed up. Let us solve problem by problem.
- Using the original file widget
- the data is appended to the model
{
"title": "Title",
"description": "Welcome!",
"image": {
"name": "test.txt",
"size": 12,
"type": "text/plain",
"data": "data:text/plain;base64,SGVsbG8gd29ybGQh"
}
}
- the form is invalid
Right, the validation expects the file attributes (name, size, type and data) to match with existing properties in the schema. So this is how the schema must look like:
"image": {
"type": "object",
"widget": "file",
"properties": {
"type": {
"type": "string"
},
"name": {
"type": "string"
},
"size": {
"type": "integer"
},
"data": {
"type": "string"
}
},
"description": "Image"
},
and then the form will be valid.
I have fixed the example in the test: https://github.com/makinacorpus/angular2-schema-form/blob/master/tests/src/app/sampleschema.json#L201
Hi Eric,
you are completly right. Hitting my head against the wall...
It seems that everything is now working as expected... Funny... One design question: Should the property reader and filedata be protected to allow access from derived classes?
My derived image widget looks like (need a blob instead of base64):
export class IonImageWidget extends FileWidget {
onFileChange($event) {
const file = $event.target.files[0];
this.filedata.name = file.name;
this.filedata.size = file.size;
this.filedata.type = file.type;
this.reader.readAsBinaryString(file);
}
}
Yes protected would be much better indeed, let me fix that.
I struggled extending a custom widget for a File Object instead of base64 as @big-r81 seems to have accomplished. For anyone else who might be struggling, I had to remove this line from the original FileWidget:
this.filedata.data = btoa(this.reader.result);
Here is my full extended file widget:
import { Component, AfterViewInit } from '@angular/core';
import { FileWidget } from 'ngx-schema-form';
@Component({
selector: 'custom-file-widget',
template: `<div class="widget form-group">
<label [attr.for]="id" class="horizontal control-label">
{{ schema.title }}
</label>
<span *ngIf="schema.description" class="formHelp">{{schema.description}}</span>
<input [name]="name" class="text-widget file-widget" [attr.id]="id"
[formControl]="control" type="file" [attr.disabled]="schema.readOnly?true:null"
(change)="onFileChange($event)">
<input *ngIf="schema.readOnly" [attr.name]="name" type="hidden" [formControl]="control">
</div>`
})
export class CustomFileWidgetComponent extends FileWidget implements AfterViewInit {
ngAfterViewInit() {
// OVERRIDE ControlWidget ngAfterViewInit() as ReactiveForms do not handle
// file inputs
const control = this.control;
this.formProperty.errorsChanges.subscribe((errors) => {
control.setErrors(errors, { emitEvent: true });
});
this.reader.onloadend = () => {
// this.filedata.data = btoa(this.reader.result);
this.formProperty.setValue(this.filedata, false);
};
}
onFileChange($event) {
const file = $event.target.files[0];
this.filedata.data = file
this.reader.readAsBinaryString(file);
}
}
And this was the schema I used:
"file": {
"title": "Add File",
"type": "object",
"widget": "custom-file",
"properties": {
"data": {
"type": "string"
}
}
}
The data property then will house the File Object.
The issue I'm struggling with is being able to show the file if it already exists (a pdf for example or profile image). In this framework what would be the best way to go about doing that? Can you conditionally show buttons? Can you pass in values such as names to the description or file paths (urls)?