Saturday 8 August 2015

Making a replacement checkbox/radiobutton in nativescript

Continuing the implementation of my nativescript program and given I saw people were asking for checkbox and radio buttons on Github, I thought I would post my checkbox/radio button control.

You can see it being used here in the search screen.



You can make your own widgets using dependencyObservable properties.

My checkbox/radio button widget extends the image control. It displays a different image if it checked/unchecked and also different images if it is a radio/checkbox widget.

When it is displaying a radio, it prevents unchecking if you tap the checked widget.

Logic for having only one selected radio button needs to be done in your attached model.

I figured out how to do this by looking at how they did it in the source code for the switch widget as well in the documentation. Being open source code it really helps in the understanding.

You can declare widgets at the top of your page like this.

<Page xmlns="http://www.nativescript.org/tns.xsd" xmlns:check="helpers/checkboximage">

You can then reference them in XML layout.

<check:CheckboxImageXml row="2" col="1" single="true" checked="{{imageAlignLeft}}"/>

Code is as per below.

import image = require("ui/image");
import gestures = require("ui/gestures");
import observable = require("data/observable");
import cssClassHelper = require("./cssClassHelper");
import dependencyObservable = require("ui/core/dependency-observable");
import proxy = require("ui/core/proxy");

function onCheckedPropertyChanged(data: dependencyObservable.PropertyChangeData) {
    var checkBoxOImageXml = <CheckboxImageXml> data.object;
    checkBoxOImageXml.setFields();
}

function onSinglePropertyChanged(data: dependencyObservable.PropertyChangeData) {
    var checkBoxOImageXml = <CheckboxImageXml> data.object;
    checkBoxOImageXml.setFields();
}

export class CheckboxImageXml extends image.Image {

    public static singleProperty = new dependencyObservable.Property(
        "single",
        "CheckboxImageXml",
        new proxy.PropertyMetadata(
            undefined,
            dependencyObservable.PropertyMetadataSettings.None,
            onSinglePropertyChanged
            )
        );

    public static checkedProperty = new dependencyObservable.Property(
        "checked",
        "CheckboxImageXml",
        new proxy.PropertyMetadata(
            undefined,
            dependencyObservable.PropertyMetadataSettings.None,
            onCheckedPropertyChanged
            )
        );

    constructor() {
        super();

        this.cssClass = cssClassHelper.iconblack;
        this.setFields();
        this.style.margin = "2";

        this.observe(gestures.GestureTypes.tap,(args: gestures.GestureEventData) => {
            if (!this.single && this.checked) {
                return;
            }
            this.checked = !this.checked;
        });
    }


    public set checked(value: boolean) {
        this._setValue(CheckboxImageXml.checkedProperty, value);
    }

    public get checked() {
        return this._getValue(CheckboxImageXml.checkedProperty);
    }

    public set single(value: boolean) {
        this._setValue(CheckboxImageXml.singleProperty, value);
    }

    public get single() {
        return this._getValue(CheckboxImageXml.singleProperty);
    }

    public setFields() {
        var imageUrl = "~/res/icons/black/" + (this.single ? "checkbox" : "radio") + (this.checked ? "on" : "off") + "_black.png";
        this.src = imageUrl;
    }

}
Javascript code added on request on 25/10/2015.

/* tslint:disable:use-strict triple-equals max-line-length one-line */
var __extends = this.__extends || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var image = require("ui/image");
var gestures = require("ui/gestures");
var observable = require("data/observable");
var cssClassHelper = require("./cssClassHelper");
var dependencyObservable = require("ui/core/dependency-observable");
var proxy = require("ui/core/proxy");
function onCheckedPropertyChanged(data) {
    var checkBoxOImageXml = data.object;
    checkBoxOImageXml.setFields();
}
function onSinglePropertyChanged(data) {
    var checkBoxOImageXml = data.object;
    checkBoxOImageXml.setFields();
}
var CheckboxImageXml = (function (_super) {
    __extends(CheckboxImageXml, _super);
    function CheckboxImageXml() {
        var _this = this;
        _super.call(this);
        this.cssClass = cssClassHelper.iconblack;
        this.setFields();
        this.style.margin = "2";
        this.observe(gestures.GestureTypes.tap, function (args) {
            if (!_this.single && _this.checked) {
                return;
            }
            _this.checked = !_this.checked;
        });
    }
    Object.defineProperty(CheckboxImageXml.prototype, "checked", {
        get: function () {
            return this._getValue(CheckboxImageXml.checkedProperty);
        },
        set: function (value) {
            this._setValue(CheckboxImageXml.checkedProperty, value);
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(CheckboxImageXml.prototype, "single", {
        get: function () {
            return this._getValue(CheckboxImageXml.singleProperty);
        },
        set: function (value) {
            this._setValue(CheckboxImageXml.singleProperty, value);
        },
        enumerable: true,
        configurable: true
    });
    CheckboxImageXml.prototype.setFields = function () {
        var imageUrl = "~/res/icons/black/" + (this.single ? "checkbox" : "radio") + (this.checked ? "on" : "off") + "_black.png";
        this.src = imageUrl;
    };
    CheckboxImageXml.singleProperty = new dependencyObservable.Property("single", "CheckboxImageXml", new proxy.PropertyMetadata(undefined, dependencyObservable.PropertyMetadataSettings.None, onSinglePropertyChanged));
    CheckboxImageXml.checkedProperty = new dependencyObservable.Property("checked", "CheckboxImageXml", new proxy.PropertyMetadata(undefined, dependencyObservable.PropertyMetadataSettings.None, onCheckedPropertyChanged));
    return CheckboxImageXml;
})(image.Image);
exports.CheckboxImageXml = CheckboxImageXml;
var CheckboxImage = (function (_super) {
    __extends(CheckboxImage, _super);
    function CheckboxImage() {
        _super.apply(this, arguments);
    }
    CheckboxImage.prototype.initialize = function (single, field, model) {
        var _this = this;
        this.cssClass = cssClassHelper.iconblack;
        this._field = field;
        this._single = single;
        this._model = model;
        this.setFields();
        this.style.margin = "2";
        this.observe(gestures.GestureTypes.tap, function (args) {
            if (!_this._single && _this._model.get(field)) {
                return;
            }
            _this._model.set(field, !_this._model.get(field));
        });
        this._model.addEventListener(observable.Observable.propertyChangeEvent, function (data) {
            if (data.propertyName === _this._field) {
                _this.setFields();
            }
        });
    };
    Object.defineProperty(CheckboxImage.prototype, "model", {
        get: function () {
            return this._model;
        },
        set: function (value) {
            this._model = value;
            this.initialize(this._single, this._field, this._model);
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(CheckboxImage.prototype, "field", {
        get: function () {
            return this._field;
        },
        set: function (value) {
            this._field = value;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(CheckboxImage.prototype, "single", {
        get: function () {
            return this._single;
        },
        set: function (value) {
            this._single = value;
        },
        enumerable: true,
        configurable: true
    });
    CheckboxImage.prototype.setFields = function () {
        var imageUrl = "~/res/icons/black/" + (this._single ? "checkbox" : "radio") + (this._model.get(this._field) ? "on" : "off") + "_black.png";
        this.src = imageUrl;
    };
    return CheckboxImage;
})(image.Image);
exports.CheckboxImage = CheckboxImage;

2 comments:

  1. thanks for your nice article.
    What is "cssClassHelper" ?

    ReplyDelete
    Replies
    1. It is just a simple class I use to make sure that all the css class references I make in code are valid. You can ignore it/take it out in the example.

      Delete