RadDataForm: Custom Editors

If you followed the editors article, you now know how to setup the editors that will be used to edit an object's properties with DataForm for NativeScript. This article will show you what to do if the editor you would like to use is not on the list with available editors. For example, if we wanted to have a Button to change the value of the property age in this example, we could use android.widget.Button in Android and UIButton in iOS.

Figure 1. RadDataForm with custom editor

NativeScriptUI-DataForm-Custom-Editors-Android NativeScriptUI-DataForm-Custom-Editors-iOS

Creating a custom property editor

In this article you will learn how to create a custom editor that uses native buttons to change its value as in the screenshot above. First, you will need to set an instance of CustomPropertyEditor as the editor of the entity property that is associated with the property that will be edited (in our example this is the age property):

Here's the flow for the usage of the custom editor step-by-step:

  1. The data form loads and it needs a view that will be used for the custom editor - the editorNeedsView event occurs;
  2. The property value has to be loaded in the editor - the editorHasToApplyValue event occurs;
  3. The user interacts with the provided editor view which changes the value of the editor - you have to call editor's notifyValueChanged method;
  4. The data form needs the current value of the editor - the editorNeedsValue event occurs when we have to update the value of the property depending on the current editor value;

All aforementioned events are fired with arguments of DataFormCustomPropertyEditorEventData type.

Here's what we are expected to do in the handlers of each of the mentioned events:

  • The editorNeedsView event occurs when a view has to be placed inside our custom editor, so in our event handler we will create a native view depending on the current platform and set the result as value of the view property of the event data (In Android the event data will contain a context property with context that we can use to create our view).
  • The editorHasToApplyValue event occurs when the value of the property has to be used as initial value of our editor. Here, we will just take the value of the value property and apply it as formatted text for the view provided with the view property.
  • The editorNeedsValue event occurs when we have to update the property value. This means that we will use again the view and value properties of the passed event data, but this time we will set the value depending on the value of our editor.

The next code snippets show the implementation of the mentioned event handlers, the declaration of the HTML and some basic CSS styling:

<RadDataForm tkExampleTitle tkToggleNavButton [source]="person">
    <TKEntityProperty tkDataFormProperty name="name"></TKEntityProperty>
    <TKEntityProperty tkDataFormProperty name="age">
        <TKCustomPropertyEditor tkEntityPropertyEditor (editorNeedsView)="editorNeedsView($event)" (editorHasToApplyValue)="editorHasToApplyValue($event)"
         (editorNeedsValue)="editorNeedsValue($event)"></TKCustomPropertyEditor>
    </TKEntityProperty>
    <TKEntityProperty tkDataFormProperty name="birthDate">
        <TKPropertyEditor tkEntityPropertyEditor type="DatePicker"></TKPropertyEditor>
    </TKEntityProperty>
</RadDataForm>
import { Component, OnInit, ViewChild, AfterViewInit } from "@angular/core";
import { PersonBase } from "../../data-services/person";
import { ButtonEditorHelper } from "../../data-services/helper";
import { FontStyles, PropertyEditor, RadDataForm } from "nativescript-telerik-ui-pro/dataform";
import { RadDataFormComponent } from "nativescript-telerik-ui-pro/dataform/angular";
import { android as androidApplication } from "tns-core-modules/application";

@Component({
    moduleId: module.id,
    selector: "tk-dataform-custom-editors",
    templateUrl: "dataform-custom-editors.component.html",
    styleUrls: ['dataform-custom-editors.component.css']
})
export class DataformCustomEditorsComponent implements OnInit, AfterViewInit {
    private _person: PersonBase;
    private _nameEditor: PropertyEditor;
    private _ageEditor: PropertyEditor;
    private _birthDateEditor: PropertyEditor;
    private _buttonValue: any;
    private _buttonEditorHelper;

    constructor() {
    }

    @ViewChild("myDataForm") dataFormComponent: RadDataFormComponent;

    ngOnInit() {
        this._person = new PersonBase("John", 23, "1993-05-16");
    }

    ngAfterViewInit() {

    }

    get person(): PersonBase {
        return this._person;
    }

    public editorNeedsView(args) {
        if (androidApplication) {
            this._buttonEditorHelper = new ButtonEditorHelper();
            this._buttonEditorHelper.editor = args.object;
            var androidEditorView: android.widget.Button = new android.widget.Button(args.context);
            var that = this;
            androidEditorView.setOnClickListener(new android.view.View.OnClickListener({
                onClick(view: android.view.View) {
                    that.handleTap(view, args.object);
                }
            }));
            args.view = androidEditorView;
            this.updateEditorValue(androidEditorView, this._person.age);
        } else {
            this._buttonEditorHelper = new ButtonEditorHelper();
            this._buttonEditorHelper.editor = args.object;
            var iosEditorView = UIButton.buttonWithType(UIButtonType.System);
            iosEditorView.contentHorizontalAlignment = UIControlContentHorizontalAlignment.Left;
            iosEditorView.addTargetActionForControlEvents(this._buttonEditorHelper, "handleTap:", UIControlEvents.TouchUpInside);
            args.view = iosEditorView;
        }
    }

    public editorHasToApplyValue(args) {
        this._buttonEditorHelper.updateEditorValue(args.view, args.value);
    }

    public editorNeedsValue(args) {
        args.value = this._buttonEditorHelper.buttonValue;
    }

    public updateEditorValue(editorView, value) {
        this._buttonEditorHelper.buttonValue = value;
        editorView.setText(this._buttonEditorHelper.buttonValue + " (tap to increase)");
    }

    public handleTap(editorView, editor) {
        var newValue = this._buttonEditorHelper.buttonValue + 1;
        this.updateEditorValue(editorView, newValue);
        editor.notifyValueChanged();
    }
}
button {
    font-size: 15;
    horizontal-align: center;
}

Notice that we called the notifyValueChanged method of the CustomPropertyEditor. This is necessary since we decide when to update the value and in this example, this happens when the button that we manually created earlier is pressed.

We have created a helper class which exposes a handleTap method that will be executed when the native button is tapped. This is explained in more details here. The rest is quite similar to the code we have in the Android implementation - editorNeedsView to create the native view; editorHasToApplyValue - to update the editor with the provided value and editorNeedsValue - to provide the value that the editor currently holds. The next code snippets show the implementation of the mentioned native handlers for iOS and Android respectively:

import { CustomPropertyEditor } from "nativescript-telerik-ui-pro/dataform";

export class ButtonEditorHelper extends NSObject 
{    
    public buttonValue: number;
    public editor: CustomPropertyEditor;

    public updateEditorValue(editorView, newValue) {
        this.buttonValue = newValue;
        editorView.setTitleForState(this.buttonValue + " (tap to increase)", UIControlState.Normal);
    }

    public "handleTap:"(sender) {
        var newValue = this.buttonValue + 1;
        this.updateEditorValue(sender, newValue);
        this.editor.notifyValueChanged();
    }

    public static ObjCExposedMethods = {
        "handleTap:": { returns: interop.types.void, params: [ UIView.class() ] }
    };
}
import { CustomPropertyEditor } from "nativescript-telerik-ui-pro/dataform";

export class ButtonEditorHelper {    
    public buttonValue: number;
    public editor: CustomPropertyEditor;

    public updateEditorValue(editorView, newValue) {
        this.buttonValue = newValue;
    }
}

References

Want to see this scenario in action?
Check our SDK examples repo on GitHub. You will find this and many other practical examples with NativeScript UI.

Related articles you might find useful: