Nativescript Vue ListPicker does not update it's items - nativescript

I am trying to load topics (just string values) from a backend and display them in the ListPicker. However the ListPicker won't update it's items which should be displayed.
The code is as follows:
<template>
<Page>
<ActionBar title="Create Challenge" icon="">
<NavigationButton text="Back" android.systemIcon="ic_menu_back" #tap="goBack" />
</ActionBar>
<StackLayout>
<Label text="TOPIC" class="fab lblSubTitle"/>
<ListPicker :items="topics" v-model="selectedItem" />
<Button text="check" #tap="checkIt" />
</StackLayout>
</Page>
</template>
<script>
import {ObservableArray} from 'tns-core-modules/data/observable-array';
import {FirebaseService} from '../../services/firebase-service';
export default {
data() {
return {
selectedItem: 0,
topics: new ObservableArray(["some", "hardcoded", "items"])
};
},
methods: {
goBack() {
this.$navigateBack();
},
checkIt() {
this.topics.push("new item");
}
},
created() {
console.log("Create Challenge - Loading Topics")
// Fetch additional items from the Firebase DB
FirebaseService.fetchTopics().then(result => {
result.forEach(topic => {
this.topics.push(topic);
});
});
}
}
</script>
<style scoped>
.lblSubTitle {
font-size: 15;
margin: 10dp;
color: red;
}
</style>
So the FirebaseService.fetchTopics() returns an array of strings. This works perfektly fine and adds the received values to the ObserveableArray topics.
However the ListPicker only shows the hardcoded values. Not the dynamically added ones. Also the checkIt() method won't update the view.
I have tried to change topics to a conventional array with no effect.
Link to the Playground
NativeScript Version: 6.5.0
Android Device: Pixel 2 - Android 9

ListPicker doesn't listen to changes on ObservableArray. You must use a simple Array and mutate the changes
this.topics = [...this.topics, "new item"];

Related

v-navigation-drawer controlled state from a sub-component

I have a v-navigation-drawer which can be opened by clicking a button in a sub component.
So I changed v-model="drawer" to simply value="drawer" otherwise I get a warning about mutating a prop which makes sense (feels like doing some dirty angular double-way data binding ^^).
Here's the code:
layouts/default.vue:
<template>
<Header :toggleLeftMenu="toggleLeftMenu" />
<LeftMenu :show="showLeftMenu" :toggleLeftMenu="toggleLeftMenu" />
</template>
<script>
export default {
data() {
return {
showLeftMenu: true,
}
},
methods: {
toggleLeftMenu() {
this.showLeftMenu = !this.showLeftMenu;
},
}
}
</script>
components/layout/LeftMenu.vue:
<v-navigation-drawer
:value="show"
width="300"
clipped
fixed
app
>
This issue is that the drawer can be closed by clicking on the backdrop (on small devices). I need to plug the backdrop click to toggleLeftMenu prop, but according to the doc, this doesn't seem to be possible.
How can I achieve full control on the component? Is this #backdropClick event missing or something?
I tried to use #input but it creates an infinite loop which also makes sense.
Thanks
Using vuetify 2.6.1.
I changed v-model="drawer" to simply value="drawer" otherwise I get a warning about mutating a prop
This is not quite the right decision. Of course you should not use drawer as model, but you can create an internalDrawer prop in LeftMenu component, and leave the v-model where it is.
One of the possible ways to resolve your issue is to emit events from both sub-components into its parent.
So let's rewrite your LeftMenu component this way:
<template>
<v-navigation-drawer v-model="internalShow" width="200" clipped fixed app>
some drawer data
</v-navigation-drawer>
</template>
<script>
export default {
props: {
show: Boolean,
},
data() {
return {
internalShow: this.show,
};
},
watch: {
show (val) {
this.internalShow = val;
},
internalShow (val) {
if (val !== this.show) {
this.$emit("change-drawer-state");
}
},
},
};
</script>
In this case, every time when the internalShow state changes, an change-drawer-state event will be emitted.
Your Header component can be rewrited the same way:
<template>
<v-btn #click="$emit('change-drawer-state')">Drawer button</v-btn>
</template>
And this is the code of your parent component:
<template>
<div>
<Header #change-drawer-state="toggleLeftMenu" />
<LeftMenu :show="showLeftMenu" #change-drawer-state="toggleLeftMenu" />
</div>
</template>
<script>
import LeftMenu from "./LeftMenu";
import Header from "./Header";
export default {
components: {
LeftMenu,
Header,
},
data() {
return {
showLeftMenu: false,
};
},
methods: {
toggleLeftMenu() {
this.showLeftMenu = !this.showLeftMenu;
},
},
};
</script>
Both change-drawer-state event handlers are calling the same method - toggleLeftMenu and then the method changes show prop of navigation-drawer.
You can test this solution in a CodeSandbox playground.

How can I change color / backgroundColor of list item in nativescript-vue?

I want to update selected item style when user taps on items. nextIndex/event.index is updated but style doesn't apply. Thanks for your help.
https://play.nativescript.org/?template=play-vue&id=ihH3iO
export default {
name: "CustomListView",
props: ["page", "title", "items", "selectedIndex"],
data() {
return {
nextIndex: this.selectedIndex ? this.selectedIndex : undefined
};
},
methods: {
onItemTap(event) {
this.nextIndex = event.index;
}
}
};
.selected {
color: white;
background-color: black;
}
<ListView for="(item, index) in items" #itemTap="onItemTap">
<v-template>
<Label :class="['list-item-label', { selected: index == nextIndex }]" :text="item" />
</v-template>
</ListView>
More info about this issue.
This is expected behavior because the ListView's item template is rendered and updated by the list view when scrolling (view recycling) if you need to make sure the list view is updated when you change your property, call refresh on it.
So the solution is
<template>
<Page class="page">
<ActionBar title="Home" class="action-bar" />
<ListView v-for="(item, index) in items" #itemTap="onItemTap" ref="listView">
<v-template>
<Label :class="[{selected: index === nextIndex}, 'list-item-label']"
:text="item" />
</v-template>
</ListView>
</Page>
</template>
<script>
export default {
name: "CustomListView",
data() {
let selectedIndex = 2;
return {
items: ["Bulbasaur", "Parasect", "Venonat", "Venomoth"],
nextIndex: selectedIndex
};
},
methods: {
onItemTap(event) {
this.nextIndex = event.index;
this.$refs.listView.nativeView.refresh();
}
}
};
</script>
You need refresh your listView the code for that is this.$refs.listView.nativeView.refresh();
Don't forget to add the ref on the <ListView>

How i can parse webview content?

I need parse content from webview in my application,
is it possible? Now i can get only url page.
I tried search content in LoadFinished object, but i not found
WebView Component:
<template>
<Page class="webview-page">
<ScrollView>
<WebView height="100%" src="https://www.fl.ru/login/"
#loadFinished="parsePage" />
</ScrollView>
</Page>
</template>
<script>
export default {
methods: {
parsePage(webargs) {
//I need gets content here
console.log(webargs.url);
}
},
data() {
return {};
}
};
</script>
I expect get all html content from pageview
Try this,
<template>
<Page class="page">
<ActionBar title="Home" class="action-bar" />
<GridLayout>
<WebView src="https://www.nativescript.org/" #loadFinished="onLoadFinished"
:visibility="visibility"></WebView>
<ScrollView v-if="html">
<Label class="h3" :text="html" textWrap="true"></Label>
</ScrollView>
</GridLayout>
</Page>
</template>
<script>
import {
device
} from "platform";
export default {
data() {
return {
html: ""
};
},
computed: {
visibility: function() {
return this.html ? "hidden" : "visible";
}
},
methods: {
onLoadFinished: function(args) {
const that = this,
webView = args.object,
jsStr = "document.body.innerHTML";
if (webView.ios) {
webView.ios.evaluateJavaScriptCompletionHandler(jsStr,
function(
result,
error
) {
if (error) {
console.log("error...");
} else if (result) {
that.html = result;
}
});
} else if (webView.android) {
// Works only on Android 19 and above
webView.android.evaluateJavascript(
jsStr,
new android.webkit.ValueCallback({
onReceiveValue: function(html) {
that.html = html;
}
})
);
}
}
}
};
</script>
If you like support for older Android versions (API level 17 & 18), the implementation gets bit difficult. You will have to implement a #JavascriptInterface interface which can be written only in Java. There is already an issue reported on enabling the ability to access Java Annotation form JavaScript. You may have to write an Android library project where you implement the #JavascriptInterface and utilise it to track content as explained here.
Playground Sample

Using ngFor in NativeScript + Angula2 application

I'm trying to show a list of labels with ngFor.
BELTS is a simple data structure. each item in it have a subject.
I'm getting an error:
JS: Error: Error in ./SilabusComponent class SilabusComponent - inline template:2:64 caused by:
Cannot read property 'subject' of undefined.
seems that it doesn't know item. any idea?
following is the component decoretor:
#Component({
selector: "kbm-silabus",
template: `
<StackLayout>
<Label ngFor="let item of silabusList; let i=index" [text]="item.subject"></Label>
</StackLayout>
`
})
export class SilabusComponent {
private sub: any;
silabusList: Array<Silabus>;
ngOnInit() {
this.page.actionBar.title = "KBM School";
this.sub = this.route.params.subscribe(params => {
this.id = params['id'];
this.silabusList = [];
this.silabusSubjects = [];
BELTS[this.id].silabus.forEach((item, index) => {
this.silabusList.push(new Silabus(item.subject, item.content));
console.log(item.subject);
})
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
You have missed the asterisk sign in front of ngFor, which is very important as otherwise, you can only apply ngFor for templates with its full syntax. In NativeScript projects use the *ngFor syntax.
e.g.
<StackLayout *ngFor="let item of silabusList">
<Label [text]="item.subject"></Label>
</StackLayout>
Basically, this is how to achive the same output with the different syntax rules:
<!-- Examples (A) and (B) are the same -->
<!-- (A) *ngFor -->
<Label *ngFor="let hero of heroes" [text]="hero"></Label >
<!-- (B) ngFor with template -->
<template ngFor let-hero [ngForOf]="heroes">
<Label [text]="hero"></Label >
</template>

TextChange in repeater nativescript

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 }

Resources