How i can parse webview content? - nativescript

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

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.

Image file path in data property not showing until I open up the component in vue chrome dev tools

I'm trying to build an image preview system for an avatar:
<template>
<div>
<div class="level">
<img :src="avatar" width="50" height="50" class="mr-1">
</div>
<form method="POST" enctype="multipart/form-data">
<input type="file" accept="image/*" #change="onChange">
</form>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
avatar: null,
setAuthHeader: axios.defaults.headers.common['Authorization'] = 'Bearer ' + this.$store.state.token,
};
},
computed: {
user() {
return this.$store.state.user
},
avatar_path() {
this.avatar = this.user.avatar_path
},
},
methods: {
onChange(e) {
if (! e.target.files.length) return;
let file = e.target.files[0];
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = e => {
this.avatar = e.target.result;
this.persist(file);
};
},
persist(avatar) {
let data = new FormData();
data.append('avatar', avatar);
this.setAuthHeader;
axios.post(`/settings/avatar`, data)
.then(() => flash('Avatar uploaded!'));
}
}
}
</script>
In my code the user computed property returns a JSON object from vuex which gets the object from local stroage.
What keeps happening is when I refresh the page that changes the profile image the default image doesn't show up. The element looks like this in chrome devtools:
<img width="50" height="50" class="mr-1">
When I open up vue dev tools, click on the component this functionality is in, the image src gets added:
<img width="50" height="50" class="mr-1" src="http://127.0.0.1:8000/storage/avatars/default.png">
You're using the avatar_path computed property incorrectly:
You shouldn't be modifying state in a computed property (you're assigning to this.avatar).
Nothing is accessing avatar_path for it to be called. It's when you open Vue dev tools that the dev tools code accesses that property so it can display it in the component data UI.
The best fix is to change avatar into a computed property like this:
computed: {
avatar() {
if (this.user) {
return this.user.avatar_path;
} else {
// Use a placeholder image URL
return '/path/to/placeholder.png';
}
}
}
<template>
<div>
<div class="level">
<img :src="avatar" width="50" height="50" class="mr-1">
</div>
<form method="POST" enctype="multipart/form-data">
<input type="file" accept="image/*" #change="onChange">
</form>
</div>
<script>
import axios from 'axios'
export default {
data() {
return {
setAuthHeader: axios.defaults.headers.common['Authorization'] = 'Bearer ' + this.$store.state.token,
};
},
computed: {
user() {
return this.$store.state.user
},
avatar() {
return this.user.avatar_path;
},
},
methods: {
}
}

Nativescript Vue ListPicker does not update it's items

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"];

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>

Component methods do not fire in Vue

Using Laravel 5.3, I have the following in /resources/assets/js/app.js:
require('./bootstrap');
require('./components/test-component'); // custom
const app = new Vue({
el: '#app'
});
The component test-component.js is simply
Vue.component('test-component', {
data() {
return {
someData: '' // does work!
};
},
mounted() {
console.log('ready'); // does not fire...
},
methods: {
testMethod() {
console.log('here'); // does not fire...
}
}
});
Then in a Blade template,
<test-component>
<form>
<!-- a bunch of HTML markup here... -->
<button type="button" #click="testMethod">Fire a test method...</button>
</form>
</test-component>
When a button is clicked, nothing shows in the console. Moreover, mounted does not fire when the form is loaded. At the same time, someData model binding works perfectly!
Question
What I am doing wrong here?
You need to add template to component options:
Vue.component('test-component', {
template: ` <form>
<!-- a bunch of HTML markup here... -->
<button type="button" #click="testMethod">Fire a test method...</button>
</form>`,
//..
});
You should add a template:
Vue.component('test-component', {
data() {
return {
someData: '' // does work!
};
},
template: '<div></div>', // add your template here
mounted() {
console.log('ready'); // does not fire...
},
methods: {
testMethod() {
console.log('here'); // does not fire...
}
}
});

Resources