I Extensiones carpeta, cree archivos y carpetas adicionales. Escribirás una implementación única para plataformas Android e iOS. La estructura final debe ser la siguiente.
Extensions
├── MySliderModule
│ └── controls
│ ├── MySliderPlugin
│ │ ├── android
│ │ │ └── MySlider.ts
│ │ ├── ios
│ │ │ └── MySlider.ts
│ │ └── MySlider.ts
│ └── MySliderExtension.ts
└── tsconfig.json
I MySliderPlugin/android/MySlider.ts
archivo, copie y pegue el siguiente código.
import { Observable } from 'tns-core-modules/data/observable';
import { View } from 'tns-core-modules/ui/core/view';
import { layout } from 'tns-core-modules/ui/core/view';
import { device as Device } from 'tns-core-modules/platform';
/*
This is a way to keep iOS and Android implementation of your extension separate
We will encapsulate the MySlider class definition inside a function called GetMySliderClass
This is so that the class definition won't be executed when you load this javascript
via require function.
The class definition will only be executed when you execute GetMySliderClass
*/
declare var com: any;
declare var android: any;
export function GetMySliderClass() {
/**
* IMPLEMENT THE ANDROID VERSION OF YOUR PLUGIN HERE
* In this sample you have 2 controls a label and a seekbar (slider)
* You extends this control with Observable (View) class so that you can accept listeners
* and notify them when UI interaction is triggered
*/
function getPadding() {
// Return left & right padding in dp
// For tablet you want 24dp, for other type you use 16dp
return Device.deviceType === 'Tablet' ? 24 : 16;
}
class MySlider extends View {
private _androidcontext;
private _label;
private _labelText = "";
private _seekbar;
private _layout;
private _value = 0;
private _min = 0; //Used to track min for API 25 or lower
private updateText() {
this._label.setText(this._labelText + "(" + this._value + ")")
}
public constructor(context: any) {
super();
this._androidcontext = context;
this.createNativeView();
}
/**
* Creates new native controls.
*/
public createNativeView(): Object {
//Create an Android label
this._label = new android.widget.TextView(this._androidcontext);
const labelBottomPaddingInPx = layout.round(layout.toDevicePixels(8)); // For top & bottom padding, always 16dp
this._label.setPadding(0, 0, 0, labelBottomPaddingInPx);
this._label.setLayoutParams(new android.view.ViewGroup.LayoutParams(-1, -2));
//Create an Android seekbar
this._seekbar = new android.widget.SeekBar(this._androidcontext);
this._seekbar.setLayoutParams(new android.view.ViewGroup.LayoutParams(-1, -2));
//Create a LinearLayout container to contain the label and seekbar
this._layout = new android.widget.LinearLayout(this._androidcontext);
this._layout.setOrientation(android.widget.LinearLayout.VERTICAL);
this._layout.setLayoutParams(new android.view.ViewGroup.LayoutParams(-1, -1));
const hortPaddingInPx = layout.round(layout.toDevicePixels(getPadding()));
const vertPaddingInPx = layout.round(layout.toDevicePixels(16)); // For top & bottom padding, always 16dp
this._layout.setPadding(hortPaddingInPx, vertPaddingInPx, hortPaddingInPx, vertPaddingInPx);
this._layout.addView(this._label);
this._layout.addView(this._seekbar);
this.setNativeView(this._layout);
return this._layout;
}
/**
* Initializes properties/listeners of the native view.
*/
initNativeView(): void {
console.log("initNativeView called");
// Attach the owner to nativeView.
// When nativeView is tapped you get the owning JS object through this field.
(<any>this._seekbar).owner = this;
(<any>this._layout).owner = this;
super.initNativeView();
//Attach a listener to be notified whenever the native Seekbar is changed so that you can notify the MDK Extension
this._seekbar.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener({
onStartTrackingTouch(seekBar: any) {
// You do not have any use for this event, so do nothing here
},
//This handler function will be called when user let go of the handle
// This is where you will trigger an event called "OnSliderValueChanged" to the MDK Extension Class
onStopTrackingTouch(seekBar: any) {
var eventData = {
eventName: "OnSliderValueChanged",
object: seekBar.owner,
value: seekBar.owner._value
};
seekBar.owner.notify(eventData);
},
//This handler function will be called whenever the slider's value is changed
// i.e. whenever user drag the slider's handle
onProgressChanged(seekBar: any, progress: number, fromUser: boolean) {
seekBar.owner._value = progress;
seekBar.owner.updateText();
}
}));
}
/**
* Clean up references to the native view and resets nativeView to its original state.
* If you have changed nativeView in some other way except through setNative callbacks
* you have a chance here to revert it back to its original state
* so that it could be reused later.
*/
disposeNativeView(): void {
// Remove reference from native view to this instance.
(<any>this._seekbar).owner = null;
(<any>this._layout).owner = null;
// If you want to recycle nativeView and have modified the nativeView
// without using Property or CssProperty (e.g. outside our property system - 'setNative' callbacks)
// you have to reset it to its initial state here.
}
//Must return the native view of the control for MDK FormCell and Section Extension
public getView(): any {
return this._layout;
}
public setText(newText: string): void {
if (newText != null && newText != undefined) {
this._labelText = newText;
this._label.setText(newText);
}
}
public setValue(newVal: number): void {
if (newVal != null && newVal != undefined) {
this._value = newVal;
this.updateText();
if (this._seekbar.getProgress() < this._min) {
this._seekbar.setProgress(this._min);
}
else {
this._seekbar.setProgress(newVal);
}
}
}
public setMinValue(newMin: number): void {
if (newMin != null && newMin != undefined) {
if (Device.sdkVersion >= 26) { //setMin is only available in set API Level 26 or newer
this._seekbar.setMin(newMin);
}
else {
this._min = newMin;
if (this._seekbar.getProgress() < this._min) {
this._seekbar.setProgress(this._min);
}
}
}
}
public setMaxValue(newMax: number): void {
if (newMax != null && newMax != undefined) {
this._seekbar.setMax(newMax);
}
}
}
return MySlider;
}
En su función de importación, si ve errores relacionados
tns-core-modules
omdk-core
, puede ignorarlos. Actualmente no hay ninguna referencia a dichas bibliotecas en el editor MDK.
Salva el MySliderPlugin/android/MySlider.ts
expediente.
I MySliderPlugin/iOS/MySlider.ts
archivo, copie y pegue el siguiente código.
import { View } from 'tns-core-modules/ui/core/view';
/*
This is a way to keep iOS and Android implementation of your extension separate
You will encapsulate the MySlider class definition inside a function called GetMySliderClass
This is so that the class definition won't be executed when you load this javascript
via require function.
The class definition will only be executed when you execute GetMySliderClass
*/
export function GetMySliderClass() {
/**
* IMPLEMENT THE IOS VERSION OF YOUR PLUGIN HERE
*/
// This is a class that handles the native event callbacks
class SliderHandler extends NSObject {
//This handler function will be called whenever the slider's value is changed
// i.e. whenever user drag the slider's handle
public valueChanged(nativeSlider: UISlider, nativeEvent: _UIEvent) {
nativeSlider.value = Math.round(nativeSlider.value);
const owner: MySlider = (<any>nativeSlider).owner;
if (owner) {
owner.setValue(nativeSlider.value);
}
}
//This handler function will be called when user let go of the handle
// This is where you will trigger an event called "OnSliderValueChanged" to the MDK Extension Class
public afterValueChanged(nativeSlider: UISlider, nativeEvent: _UIEvent) {
nativeSlider.value = Math.round(nativeSlider.value);
const owner: MySlider = (<any>nativeSlider).owner;
if (owner) {
owner.setValue(nativeSlider.value);
var eventData = {
eventName: "OnSliderValueChanged",
object: owner,
value: nativeSlider.value
};
owner.notify(eventData);
}
}
public static ObjCExposedMethods = {
"valueChanged": { returns: interop.types.void, params: [interop.types.id, interop.types.id] },
"afterValueChanged": { returns: interop.types.void, params: [interop.types.id, interop.types.id] }
};
}
const handler = SliderHandler.new();
class MySlider extends View {
private _label;
private _labelText = "";
private _slider;
private _layout;
private _value = 0;
private updateText() {
this._label.text = this._labelText + "(" + this._value + ")";
}
public constructor(context: any) {
super();
this.createNativeView();
}
/**
* Creates new native controls.
*/
public createNativeView(): Object {
//Create the Stack view - this is the main view of this extension
this._layout = UIStackView.new();
//Configuring the paddings around the stack view
this._layout.autoresizingMask = [UIViewAutoresizing.FlexibleHeight, UIViewAutoresizing.FlexibleWidth];
this._layout.layoutMarginsRelativeArrangement = true;
let inset = new NSDirectionalEdgeInsets();
inset.top = 8; inset.leading = 16; inset.bottom = 8; inset.trailing = 16;
this._layout.directionalLayoutMargins = inset;
// Set the layout stacking to be vertical
this._layout.axis = UILayoutConstraintAxis.Vertical;
//Create the label view
this._label = UILabel.new();
this._label.font = this._label.font.fontWithSize(15); //Set font size
this._label.textColor = UIColor.colorWithRedGreenBlueAlpha(106 / 255, 109 / 255, 112 / 255, 1.0); //Set text color
this._layout.setCustomSpacingAfterView(4, this._label); //Set the bottom margin of label
//Create the slider control
this._slider = UISlider.new();
//Assign a handler for whenever value changed i.e. when user is dragging the slider handle
this._slider.addTargetActionForControlEvents(handler, "valueChanged", UIControlEvents.ValueChanged);
//Assign a handler for when user let go of the handle
this._slider.addTargetActionForControlEvents(handler, "afterValueChanged", UIControlEvents.TouchUpInside | UIControlEvents.TouchUpOutside);
//Add the label and slider to the stack view
this._layout.addArrangedSubview(this._label);
this._layout.addArrangedSubview(this._slider);
//store the native view
this.setNativeView(this._layout);
//return the stack view
return this._layout;
}
/**
* Initializes properties/listeners of the native view.
*/
initNativeView(): void {
// Attach the owner to nativeViews.
// When nativeViews are tapped you get the owning JS object through this field.
(<any>this._slider).owner = this;
(<any>this._layout).owner = this;
super.initNativeView();
}
/**
* Clean up references to the native view and resets nativeView to its original state.
* If you have changed nativeView in some other way except through setNative callbacks
* you have a chance here to revert it back to its original state
* so that it could be reused later.
*/
disposeNativeView(): void {
// Remove reference from native view to this instance.
(<any>this._slider).owner = null;
(<any>this._layout).owner = null;
// If you want to recycle nativeView and have modified the nativeView
// without using Property or CssProperty (e.g. outside our property system - 'setNative' callbacks)
// you have to reset it to its initial state here.
}
//Must return the native view of the control for MDK FormCell and Section Extension
public getView(): any {
return this._layout;
}
public setText(newText: string): void {
if (newText != null && newText != undefined) {
this._labelText = newText;
this._label.text = newText;
}
}
public setValue(newVal: number): void {
if (newVal != null && newVal != undefined) {
this._value = newVal;
this.updateText();
this._slider.value = newVal;
}
}
public setMinValue(newMin: number): void {
if (newMin != null && newMin != undefined) {
this._slider.minimumValue = newMin;
}
}
public setMaxValue(newMax: number): void {
if (newMax != null && newMax != undefined) {
this._slider.maximumValue = newMax;
}
}
}
return MySlider;
}
Salva el MySliderPlugin/iOS/MySlider.ts
expediente.
I MySliderPlugin/MySlider.ts
archivo, copie y pegue el siguiente código.
import * as application from 'tns-core-modules/application';
export let MySlider;
let MySliderModule;
/*
This is a sample of how to implement iOS and Android codes separately in a metadata extension.
Because all ts files in metadata Extensions folder will be bundled together using webpack,
if you execute any iOS codes in Android vice versa, it will likely cause issue such as crash.
By splitting the implementation into different files and encapsulate them in a function, it allows
us to load only the required module for the platform at runtime.
*/
if (!MySlider) {
//Here you will check what platform the app is in at runtime.
if (application.ios) {
//if app is in iOS platform, load the MySlider module from ios folder
MySliderModule = require('./ios/MySlider');
} else {
//otherise, assume app is in Android platform, load the MySlider module from android folder
MySliderModule = require('./android/MySlider');
}
// calling GetMySliderClass() will return MySlider class for the correct platform.
// See the MySlider.ts in ios/andrid folder for details
MySlider = MySliderModule.GetMySliderClass();
}
Salva el MySliderPlugin/MySlider.ts
expediente.
I MySliderExtension.ts
archivo, reemplace este código.
import { BaseControl } from 'mdk-core/controls/BaseControl';
import { MySlider } from './MySliderPlugin/MySlider'
export class MySliderClass extends BaseControl {
private _slider: MySlider;
private _minVal: number = 0;
private _maxVal: number = 10000;
public initialize(props) {
super.initialize(props);
//Create the Slider plugin control
this.createSlider();
//Assign the slider's native view as the main view of this extension
this.setView(this._slider.getView());
}
private createSlider() {
//Create MySlider and initialize its native view
this._slider = new MySlider(this.androidContext());
this._slider.initNativeView();
this._slider.setMinValue(this._minVal);
this._slider.setMaxValue(this._maxVal);
//Set the slider's properties if "ExtensionProperties" is defined
let extProps = this.definition().data.ExtensionProperties;
if (extProps) {
//In here you will use ValueResolver to resolve binding/rules for the properties
// This will allow the app to use binding/rules to set the properties' value
// Resolve title's value
this.valueResolver().resolveValue(extProps.Title, this.context, true).then(function (title) {
this._slider.setText(title);
}.bind(this));
// Resolve min value
this.valueResolver().resolveValue(extProps.MinValue, this.context, true).then(function (minVal) {
if (minVal !== null && minVal !== undefined) {
this._minVal = minVal;
this._slider.setMinValue(this._minVal);
}
}.bind(this));
// Resolve max value
this.valueResolver().resolveValue(extProps.MaxValue, this.context, true).then(function (maxVal) {
if (maxVal !== null && maxVal !== undefined) {
this._maxVal = maxVal;
this._slider.setMaxValue(this._maxVal);
}
}.bind(this));
// Resolve value
this.valueResolver().resolveValue(extProps.Value, this.context, true).then(function (value) {
this.setValue(value, false, false);
}.bind(this));
}
//Set up listener for MySlider's OnSliderValueChanged event that will be triggered when user let of the slider's handle
// It's eventData object contain a property 'value' that will contain the value of the slider
this._slider.on("OnSliderValueChanged", function (eventData) {
//We will call the setValue
this.setValue(eventData.value, true, false);
}.bind(this));
}
// Override
protected createObservable() {
let extProps = this.definition().data.ExtensionProperties;
//Pass ExtensionProperties.OnValueChange to BaseControl's OnValueChange
if (extProps && extProps.OnValueChange) {
this.definition().data.OnValueChange = extProps.OnValueChange;
}
return super.createObservable();
}
public setValue(value: any, notify: boolean, isTextValue?: boolean): Promise<any> {
//Check the value
if (value != null && value != undefined && !isNaN(value)) {
if (typeof value == "string" && value.trim() == "") {
return Promise.reject("Error: Value is not a number");
}
let val = Number.parseInt(value);
//Don't let value go lower than permitted minimum or higher than permitted maximum
val = val < this._minVal ? this._minVal : val;
val = val > this._maxVal ? this._maxVal : val;
if (this._slider) {
//Set the slider's value
this._slider.setValue(val);
}
//Store the value. The observable will trigger "OnValueChange" to the MDK app
// MDK app can register to this event in the metadata with property "OnValueChange"
return this.observable().setValue(val, notify, isTextValue);
} else if (isNaN(value)) {
return Promise.reject("Error: Value is not a number");
}
return Promise.resolve();
}
public viewIsNative() {
return true;
}
}
Salva el MySliderExtension.ts
expediente.
I tsconfig.json
archivo, copie y pegue el siguiente código.
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"removeComments": true,
"inlineSourceMap": false,
"sourceMap": false,
"noEmitOnError": false,
"noEmitHelpers": true,
"declaration": true,
"lib": [
"es6",
"dom"
],
"baseUrl": ".",
"paths": {
"tns-core-modules/*": [
"./node_modules/tns-core-modules/*"
],
"mdk-sap": [
"./node_modules/mdk-sap"
],
"toolbar-plugin": [
"./node_modules/toolbar-plugin"
],
"zip-plugin": [
"./node_modules/zip-plugin"
]
}
},
"exclude": [
"node_modules",
"platforms",
"modules",
"plugins",
"build.definitions"
]
}
Este archivo se utiliza para utilizar tipos definidos en MDK SDK o NativeScript. Puedes encontrar más detalles al respecto en documentación útil.
Salva el tsconfig.json
expediente.