This will be a noob question. I am working on my first NativeScript app, a simple GPS tracker. It's only one view. I got the Location plugin to work and it outputs location data to the console nicely. It also seems to update the View Model. But when I try to bind it to a Label element's text, it only displays [object Object], regardless what I do.
The view model:
var observableModule = require("data/observable");
function viewModel() {
return new observableModule.fromObject({
"locations": [],
"lastLocation": {
"latitude": "0",
"longitude": "0",
"altitude": 0,
"horizontalAccuracy": 0,
"verticalAccuracy": 0,
"speed": "0",
"direction": "0",
"timestamp": new Date().toISOString()
},
"lastUpload": "Unknown"
});
}
module.exports = viewModel;
The view XML: (just the important part)
<Label text="Current location:"/>
<Label text="{{ lastLocation?.latitude }} ; {{ lastLocation?.longitude }}" />
<Label text="Last location update:"/>
<Label text="{{ lastLocation?.timestamp }}" />
<Label text="Last successful upload:"/>
<Label text="{{ lastUpload }}" />
The code:
var frameModule = require("ui/frame");
var viewModel = require("./home-view-model");
var geolocation = require("nativescript-geolocation");
var dialog = require("ui/dialogs");
var viewModel = new viewModel();
function onLoaded(args) {
page = args.object;
page.bindingContext = viewModel;
var enableLocation = new Promise((resolve, reject) => {
geolocation.enableLocationRequest(true)
.then(
success => resolve(),
error => reject(error)
)
})
.then(
() => {
watchId = geolocation.watchLocation(
function (loc) {
if (loc) {
viewModel.lastLocation = loc;
console.log(viewModel.lastLocation.latitude + ' ; ' + viewModel.lastLocation.longitude);
}
},
function (e) {
dialogsModule.alert('Location error: ' + e.message);
},
{
desiredAccuracy: 3,
updateDistance: 0,
minimumUpdateTime: 10000
})
},
error => {
dialogsModule.alert('Location error: ' + e.message);
}
)
}
exports.onLoaded = onLoaded;
The console.log row properly displays the latitude and longitude values. The view displays [object Object], but it displays the lastUpload value right. What am I doing wrong in the view?
Oh yes, it was a noob question. The solution is that I shouldn't use question marks in the template.
<Label text="Current location:"/>
<Label text="{{ lastLocation.latitude }} ; {{ lastLocation.longitude }}" />
<Label text="Last location update:"/>
<Label text="{{ lastLocation.timestamp }}" />
Now it works.
Related
I'm using react-number-format library if numeric values is set to TextField it will not show any error after reset the form.
t should accept the numeric values also like other string values.
Here is my code you are reproduce error from this.
Formik Form
<Formik
enableReinitialize
initialValues={{
area: "",
password: "",
}}
validate={(values) => {
const errors: Partial<Values> = {};
if (!values.area) {
errors.area = "Required area";
}
return errors;
}}
onSubmit={(values, { setSubmitting, resetForm }) => {
setTimeout(() => {
setSubmitting(false);
console.log(JSON.stringify(values, null, 2));
resetForm();
}, 500);
}}
>
{({ submitForm, isSubmitting }) => (
<Form>
<Field
component={TextField}
name="area"
label="area"
InputProps={{
inputComponent: NumberField,
}}
/>
<br />
<Field
component={TextField}
type="password"
label="Password"
name="password"
/>
{isSubmitting && <LinearProgress />}
<br />
<Button
variant="contained"
color="primary"
disabled={isSubmitting}
onClick={submitForm}
>
Submit
</Button>
</Form>
)}
</Formik>
NumberField
import NumberFormat from "react-number-format";
interface NumberFieldProps {
name: string;
inputRef: (instance: NumberFormat<any> | null) => void;
onChange: (event: {
target: { name: string; value: number | undefined };
}) => void;
}
export default function NumberField(props: NumberFieldProps) {
const { inputRef, onChange, ...other } = props;
return (
<NumberFormat
{...other}
getInputRef={inputRef}
onValueChange={(values) => {
onChange({
target: {
name: other.name,
value: values.floatValue,
},
});
}}
/>
);
}
Click on submit button and you will see Required area error, Set value in area field and submit form, it submit the form and reset it after that click again on submit button you will see it will not give you Required area error. I don't know why this happen.
Now change value: values.floatValue to value: values.value in NumberField file and everything work well. I don't know why it is not working when I set floatValue I need numeric values not string.
I'm currently trying to do a very simple sort on an array of items within a Nativescript Vue app, however it is pulling back some very weird results. I have a playground link that recreates the issue.
Basically, when my page loads, the items are ordered based on a data property for sort direction. This is ordered correctly. However, if I then try to change this direction (ascending or descending) then it just orders really strange.
On load, my items are ordered ascending like this:
When I then try to sort them descending, the order changes to this:
When I then try to sort ascending again, the order changes to this:
Here is basically the code from my playground entry - I know some of it is over-engineered but this is just a result of me testing numerous things:
<template>
<Page>
<ActionBar title="Sort Test" icon="">
</ActionBar>
<StackLayout>
<StackLayout>
<Label :text="item.title" textWrap="true"
v-for="item in orderedItems" :key="item.id" />
</StackLayout>
<Label :text="`Sort Ascending: ${sortAscending}`"
textWrap="true" />
<Label :text="`Sort Descending: ${sortDescending}`"
textWrap="true" />
<Button text="Sort Ascending" #tap="sortAsc" />
<Button text="Sort Descending" #tap="sortDesc" />
</StackLayout>
</Page>
</template>
<script>
export default {
methods: {
sortAsc() {
this.sortAscending = true;
this.sortDescending = false;
},
sortDesc() {
this.sortAscending = false;
this.sortDescending = true;
}
},
computed: {
orderedItems() {
//return (b.date > a.date) ? 1 : (b.date < a.date) ? -1 : 0;
return this.items.sort((a, b) => {
if (this.sortAscending) {
// return (a.id > b.id) ? 1 : (a.id < b.id) ? -1 : 0;
return a.id - b.id;
} else {
// return (b.id > a.id) ? 1 : (b.id < a.id) ? -1 : 0;
return b.id - a.id;
}
});
}
},
data() {
return {
sortAscending: true,
sortDescending: false,
items: [{
id: 1,
title: "Label 1"
},
{
id: 2,
title: "Label 2"
},
{
id: 4,
title: "Label 4"
},
{
id: 5,
title: "Label 5"
},
{
id: 3,
title: "Label 3"
}
]
};
}
};
</script>
<style lang="postcss" scoped></style>
Now, I've found a similar issue that seems to match my exact use case, however changing the behaviour of my orderedItems property doesn't seem to make a difference.
Is there something I'm doing wrong? Is my v-for in the wrong place, or is it a side-effect of using a computed property to display it - should I be using a watch on the sort directions instead to re-order the items data property?
Any help would be appreciated - I haven't tried this on iOS yet but this behaviour is happening in Android.
OK, for anyone interested, the above sort solution works fine - I don't know enough to be sure but the problem seems to be when Nativescript is rendering out the items.
The solution I have in place is to add a loading state in between the sorts. So, when someone clicks the sort ascending / descending button, a loading message appears, the sort happens, then the loading state disappears. Below is a pretty rudimentary version:
<template>
<Page>
<Label v-if="loadingItems">Loading Items</Label>
<StackLayout v-else>
<Label v-for="item in items" :key="item.id" :text="item.title" />
<Button #tap="sort('asc')" text="Sort Ascending" />
<Button #tap="sort('desc')" text="Sort Descending" />
</StackLayout>
</template>
<script>
export default {
data() {
return {
loadingItems: false,
items: [
{
id: 1,
title: "Item 1"
},
{
id: 3,
title: "Item 3"
},
{
id: 3,
title: "Item 3"
},
{
id: 4,
title: "Item 4"
},
{
id: 5,
title: "Item 5"
}
]
},
methods: {
sort(dir) {
this.items = this.items.sort((a,b) => {
return dir == 'asc' ? a.id - b.id : b.id - a.id
});
}
}
}
</script>
Hope this helps anyone with the same issue
I'm looking for an open source NS7 replacement for the nativescript-drop-down menu. Does anyone have suggestions. I'm porting my app to NS7 and having trouble finding a replacement for what has gone to a paid version of the plugin.
My workaround was to open a modal that has the dropdown items and then receiving the chosen item in the closeCallback function, like so:
dropdown-modal.xml
<Page ios:class="bg-light" xmlns="http://schemas.nativescript.org/tns.xsd" shownModally="onShownModally">
<StackLayout class="modal-view">
<Label android:class="p-l-10 font-weight-normal font-size-larger" ios:class="h2 font-weight-bold p-l-15 p-t-10" text="{{ title }}"></Label>
<Label class="hr"></Label>
<ScrollView class="page" height="40%">
<ListView class="list-group" itemTap="{{ onItemTap }}" items="{{ data }}">
<ListView.itemTemplate>
<Label android:class="h4 text-center font-weight-bold p-b-10" ios:class="h3 text-center font-weight-bold p-y-10" width="100%" text="{{ value }}"
textWrap="true"></Label>
</ListView.itemTemplate>
</ListView>
</ScrollView>
</StackLayout>
</Page>
Notice the difference between Android and iOS classes. This makes the dropdown more system-like.
dropdown-modal.js
import { DropDownViewModel } from "./dropdown-view-model";
export const onShownModally = function(args) {
const dropDownViewModel = new DropDownViewModel(args.context.title, args.context.list, args.closeCallback);
const page = args.object;
page.bindingContext = dropDownViewModel;
};
dropdown-view-model.js
import { Observable, ObservableArray } from "#nativescript/core";
export class DropDownViewModel extends Observable {
constructor(title, items, closeCallback) {
super();
this.title = title;
this.data = new ObservableArray(items);
this.selectedItem = '';
this.closeCallback = closeCallback;
}
onItemTap(args) {
this.selectedItem = args.view.bindingContext;
this.closeCallback(this.selectedItem);
}
}
And this is how it is called in another page.
sample-page.xml
<!--Roles-->
<StackLayout class="m-y-10 m-x-2" row="2" col="1">
<Label class="far h3 p-l-15 text-black m-b-1" text=" Role" textWrap="true" />
<TextField editable="false" tap="{{ toggleDropdown }}" dataListId="roles" dataName="role" dataTitle="Role" class="h4 fal text-black input-border-rounded-lg" text="{{ role.value }}" />
</StackLayout>
Notice how dataListId, dataName, and dataTitle were passed.
sample-page.js
import { SamplePageViewModel } from "./sample-page-view-model";
const samplePageViewModel = new SamplePageViewModel();
export const onNavigatingTo = async function(args) {
await samplePageViewModel.populateRolesList();
};
export const onNavigatedTo = async function(args) {
const page = args.object;
page.bindingContext = samplePageViewModel;
};
sample-page-view-model.js
import { Frame, Observable } from "#nativescript/core";
import { LookupService } from "~/services/lookupService";
import { AppSettingsUtility } from "~/utilities/appSettingsUtility";
export class SamplePageViewModel extends Observable {
constructor() {
super();
this.lookupService = new LookupService();
this.appSettingsUtility = new AppSettingsUtility();
this.routes = this.appSettingsUtility.getRoutes();
this.roles = [];
this.role = { code: 0, value: '' };
}
async populateRolesList() {
this.set('roles', await this.lookupService.getRoles()); // assume roles list
}
toggleDropdown(args) {
const page = args.object.page;
const that = this;
const options = {
context: { title: args.object.dataTitle, list: that.get(args.object.dataListId) },
closeCallback: (selectedItem) => {
if(selectedItem)
that.set(args.object.dataName, selectedItem);
}
};
page.showModal(this.routes.dropdown, options);
}
}
I have 2 of the same components in my laravel blade view, but they are conflicting.
What i'm trying to accomplish:
I've made a vue component that uploads a file to firebase and stores it in my database. In my blade view i have 2 places where i want to use this component. I configure the component with props so the component knows where to store the file.
What going wrong:
Every time i try to upload a file with the second component, i fire the function in the first component. How do i fix that the components can't conflict?
My laravel balde view:
component 1
<uploadfile
:key="comp100"
:user_data="{{ Auth::user()->toJson() }}"
store_path="/users/{{ Auth::user()->username }}/settings/email_backgrounds"
:store_route="'settings.project_email'"
:size="1000"
fillmode="cover"
></uploadfile>
component 2
<uploadfile
:key="comp200"
:user_data="{{ Auth::user()->toJson() }}"
store_path="/users/{{ Auth::user()->username }}/settings/email_backgrounds"
:store_route="'settings.project_email'"
:size="1000"
fillmode="cover"
></uploadfile>
The Vue component:
<template>
<div class="vue-wrapper">
<FlashMessage position="right top"></FlashMessage>
<div v-if="loading" class="lds-dual-ring"></div>
<div class="field">
<div class="control">
<label class="button main-button action-button m-t-20" for="uploadFiles"><span style="background-image: url('/images/icons/upload.svg')"></span>Kies bestand</label>
<input type="file" name="uploadFiles" id="uploadFiles" class="dropinput" #change="selectFile">
</div>
</div>
</div>
</template>
<script>
import { fb } from '../../firebase.js';
export default {
data() {
return {
fileObject: {
filePath: null,
url: null,
file: null,
resizedPath: null
},
loading: false
};
},
mounted() {
console.log(this.size)
console.log(this.fillmode)
},
props: [
'user_data',
'store_path',
'store_route',
'size',
'fillmode'
],
methods: {
selectFile(event)
{
var file = event.target.files[0];
this.fileObject.file = file
this.fileObject.filePath = this.store_path + '/' + file.name
this.fileObject.resizedPath = this.store_path + '/resized-' + file.name
if(file.type == 'image/png' || file.type == 'image/jpeg')
{
this.uploadFile(this.fileObject)
} else {
this.flashMessage.success({
title: 'Oeps!',
message: 'De afbeelding moet een png of een jpeg zijn!',
blockClass: 'success-message'
});
}
},
uploadFile(fileObject)
{
var vm = this
console.log(fileObject)
var storageRef = fb.storage().ref(fileObject.filePath)
var uploadTask = storageRef.put(fileObject.file)
this.loading = true
uploadTask.on('state_changed', function(snapshot){
var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
},function(error) {
}, function() {
var resizeImage = fb.functions().httpsCallable('resizeImage')
resizeImage({filePath: fileObject.filePath, contentType: fileObject.file.type, watermark: false, size: vm.size, fit: vm.fillmode}).then(function(result){
var downloadRef = fb.storage().ref(fileObject.resizedPath);
downloadRef.getDownloadURL().then(function(url){
fileObject.url = url
vm.loading = false
vm.storeImage(fileObject)
}).catch(function(error){
console.log(error)
})
}).catch(function(error){
});
});
},
storeImage(file)
{
axios.post('/api/app/store_file', {
api_token: this.user_data.api_token,
user: this.user_data,
file: file,
storeRoute: this.store_route
}).then((res) => {
location.reload()
}).catch((e) => {
});
}
}
}
</script>
Does someone know how to fix this?
i trying do to textchange in repeater and research this link.
Basic blur event in a Telerik Nativescript Mobile App
It work in single textfield but no work in repeater. Isn't set wrong anything?
XML:
<Repeater id="lstSelectedItemsSingle" items="{{itemsSingle}}">
<Repeater.itemTemplate>
<GridLayout columns="auto,*,auto,*,auto" rows="auto,auto,1" padding="6" id = "{{ matchId + dataType + 'GridSingle'}}">
<GridLayout columns="*,*,*" rows="40" col="3" borderRadius="6" borderWidth="1" borderColor="#DBDBDB" >
<button backgroundImage="res://reduce_enable" style="background-repeat:no-repeat;background-position: 50% 50%" backgroundColor="#BFBFBF" />
<TextField col="1" backgroundColor="#ffffff" col="1" text="{{stake}}" style="text-align:center" keyboardType="number" />
<button backgroundImage="res://add_icon_enable" col="2" style="background-repeat:no-repeat;background-position: 50% 50%" backgroundColor="#BFBFBF" col="2"/>
</GridLayout>
</GridLayout>
</Repeater.itemTemplate>
</Repeater>
Model:
exports.onPageLoaded = function(args){
page = args.object;
viewM.set("stake", "2");
viewM.addEventListener(observable.Observable.propertyChangeEvent, function (event) {
console.log(event.propertyName);
}
});
}
It's probably because repeaters are bound to a list of items - usually observables. If you bind inside the repeater using "{{ }}", NativeScript is going to look for that method on that specific object in the repeater. So your code should be structured something like this (TypeScript) ...
import { Observable, EventData } from 'data/observable';
import { Page } from 'ui/page';
class Item extends Observable({
text: string = '';
constructor(text: string) {
this.text = text;
this.todos.on(ObservableArray.changeEvent, (args: any) => {
// handle text change
});
}
});
class ViewModel extends Observable({
items: ObservableArray<Items>
constructor() {
this.items = new ObservableArray<Items>({
new Item('Thing 1'),
new Item('Thing 2')
});
}
});
let loaded = (args: EventData) => {
let page = <Page>args.object;
page.bindingContext = new ViewModel();
}
export { loaded }