Cats Example with nativescript secure storage do not update frontend - nativescript

while playing around with the cats listview example from Raymond Camden
(thanks so much
[1]: https://nativescript.org/blog/client-side-storage-in-nativescript-applications/),
I try to store the data with the nativescript secure storage plugin.
In the cats.vue file I will show my code:
<template>
<Page class="page">
<ActionBar title="cats" class="action-bar" color="#ffbb00" />
<StackLayout class="home-panel">
<ListView for="cat in cats">
<v-template>
<Label :text="cat.name"></Label>
</v-template>
</ListView>
<Button #tap="addCat" text="Add Cat"></Button>
</StackLayout>
</Page>
</template>
<script>
const appSettings = require("application-settings");
var SecureStorage = require("nativescript-secure-storage").SecureStorage;
var secs = new SecureStorage();
var cats= [];
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randomName() {
var initialParts = ["Fluffy","Scruffy","King","Queen","Emperor","Lord","Hairy","Smelly","Most Exalted Knight","Crazy","Silly","Dumb","Brave","Sir","Fatty"];
var lastParts = ["Saam","Smoe","Elvira","Jacob","Lynn","Fufflepants the III","Squarehead","Redshirt","Titan","Kitten Zombie","Dumpster Fire","Butterfly Wings","Unicorn Rider"];
return initialParts[getRandomInt(0, initialParts.length - 1)] + ' ' + lastParts[getRandomInt(0, lastParts.length - 1)];
}
function getCats() {
return secs.getSync({
key: "foo"
});
}
export default {
data() {
return {
cats: []
}
},
created() {
this.cats = getCats();
},
methods: {
addCat() {
cats = [{ "name":randomName()}, ...cats];
var success = secs.setSync({
key: "foo",
value: cats
});
this.cats = getCats();
}
}
};
</script>
<style scoped>
</style>
The problem is, I do not see any cats on the app screen, when I add a cat.
When I use the original code with the CouchDB, it updates the cats.
What should I change?
Also when the app starts, there are no visible cats which should be stored in another
start of the app. How may I load the data from the securestorage to the screen when the app starts?
May you please explain the changes, so that I can understand it?
Do you have good link with an explanation of nativescript-vue update the data synchronization to the screen?
Best regards and thank you very much for your help
Juergen

This is wrong.
You should not use var as it is a global.
Remove the var cats= [], leave it only in data, not outside the export.
Further more move your other two functions to the methods within the exported object.

Thanks Robertino,
with following code there are 2 problems less:
<script>
const appSettings = require("application-settings");
var SecureStorage = require("nativescript-secure-storage").SecureStorage;
var secs = new SecureStorage();
export default {
data() {
return {
cats: []
}
},
created() {
this.cats = this.getCats();
},
methods: {
addCat() {
this.cats = [{ "name":this.randomName()},...this.cats ];
//console.log("xxxxxxxxxxxxxxxxxxx:" + cats);
var success = secs.setSync({
key: "foo",
value: this.cats
});
this.cats = this.getCats();
console.log(`after get:${this.cats}`);
},
getCats() {
return secs.getSync({
key: "foo"
});
},
getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
},
randomName() {
var initialParts = ["Fluffy","Scruffy","King","Queen","Emperor","Lord","Hairy","Smelly","Most Exalted Knight","Crazy","Silly","Dumb","Brave","Sir","Fatty"];
var lastParts = ["Saam","Smoe","Elvira","Jacob","Lynn","Fufflepants the III","Squarehead","Redshirt","Titan","Kitten Zombie","Dumpster Fire","Butterfly Wings","Unicorn Rider"];
return initialParts[this.getRandomInt(0, initialParts.length - 1)] + ' ' + lastParts[this.getRandomInt(0, lastParts.length - 1)];
}
}
};
I see a nice console.log in the method addcat() after the getcat method.
The cat data will be in the array after an add. And, when I restart, the cats stayed in the database.
The only problem now is, no cats where listed in the frontend.
Neither when adding nor when starting the app.
The data will not be shown in the frontend.
Any ideas?
Regards Juergen

Related

How to let vue component wait until the data is ready for render?

vue component won't wait for data from controller using axios get, it prompt error:
index.vue?d4c7:200 Uncaught (in promise) TypeError: Cannot read property 'ftth' of undefined
my code are below:
<template>
<div class="dashboard-editor-container">
<el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
<line-chart :chart-data="lineChartData"/>
</el-row>
</div>
</template>
<script>
import LineChart from './components/LineChart';
import axios from 'axios';
const lineChartData = {
all: {
FTTHData: [],
VDSLData: [],
ADSLData: [],
},
};
export default {
name: 'Dashboard',
components: {
LineChart,
},
data() {
return {
lineChartData: lineChartData.all,
};
},
created() {
this.getData();
},
methods: {
handleSetLineChartData(type) {
this.lineChartData = lineChartData[type];
},
async getData() {
axios
.get('/api/data_graphs')
.then(response => {
console.log(response.data);
var data = response.data;
var i = 0;
for (i = Object.keys(data).length - 1; i >= 0; i--) {
lineChartData.all.FTTHData.push(data[i]['ftth']);
lineChartData.all.VDSLData.push(data[i]['vdsl']);
lineChartData.all.ADSLData.push(data[i]['adsl']);
}
});
},
},
};
</script>
Do I have to use watch method?
First, because you have such a nested data structure you'll want a computed property to return whether the data is loaded or not. Normally, you could do this check in the template.
computed: {
isDataLoaded() {
const nestedLoaded = Object.keys(this.lineChartData).map(key => this.lineChartData[key].length !== 0)
return this.lineChartData && nestedLoaded.length !== 0
}
}
You can use v-if="isDataLoaded" to hide the element until the data has been loaded.
It is not exactly clear how response.data looks like, but because you're using Object.keys I'm assuming it's an object.
If you need to loop over the keys then when using numeric indexes you most likely won't get an object. So you need to get the key and index i and use that value to access the object. Change this:
for (i = Object.keys(data).length - 1; i >= 0; i--) {
lineChartData.all.FTTHData.push(data[i]['ftth']);
lineChartData.all.VDSLData.push(data[i]['vdsl']);
lineChartData.all.ADSLData.push(data[i]['adsl']);
}
to this:
const keys = Object.keys(data)
for (i = keys.length - 1; i >= 0; i--) {
lineChartData.all.FTTHData.push(data[keys[i]]['ftth']);
lineChartData.all.VDSLData.push(data[keys[i]]['vdsl']);
lineChartData.all.ADSLData.push(data[keys[i]]['adsl']);
}
But for looping over object's keys is easier to use this:
for (let key in data) {
lineChartData.all.FTTHData.push(data[key]['ftth']);
lineChartData.all.VDSLData.push(data[key]['vdsl']);
lineChartData.all.ADSLData.push(data[key]['adsl']);
}
The alternative syntax will feed you keys and in my opinion is easier to read.
For the mean time:
Use set Axios Timeout 5000ms
axios
.get('/api/data_graphs', { timeout: 5000 })
.then(response => {
console.log(response.data);
var data = response.data;
var i = 0;
for (i = Object.keys(data).length - 1; i >= 0; i--) {
lineChartData.all.FTTHData.push(data[i]['ftth']);
lineChartData.all.VDSLData.push(data[i]['vdsl']);
lineChartData.all.ADSLData.push(data[i]['adsl']);
}
this.lineChartIsLoaded = true;
});
Use v-if in vue component
<line-chart v-if="lineChartIsLoaded" :chart-data="lineChartData" :date-data="dateData" />
Set lineChartIsLoaded to false at default
const lineChartIsLoaded = false;
You can write dummy data in your data properties before real ones are loading
all: {
FTTHData: ["Loading..."],
VDSLData: ["Loading..."],
ADSLData: ["Loading..."],
},

Nativescript imagepicker not working in iOS :: not picking up image path?

I am using Nativescript with Angular and have a page where I photograph a receipt or add from gallery and add a couple of text inputs and send to server.
The Add from gallery is working fine in Android but not in iOS.
Here is the template code:
<Image *ngIf="imageSrc" [src]="imageSrc" [width]="previewSize" [height]="previewSize" stretch="aspectFit"></Image>
<Button text="Pick from Gallery" (tap)="onSelectGalleryTap()" class="btn-outline btn-photo"> </Button>
and the component:
public onSelectGalleryTap() {
let context = imagepicker.create({
mode: "single"
});
let that = this;
context
.authorize()
.then(() => {
that.imageAssets = [];
that.imageSrc = null;
return context.present();
})
.then((selection) => {
alert("Selection done: " + JSON.stringify(selection));
that.imageSrc = selection.length > 0 ? selection[0] : null;
// convert ImageAsset to ImageSource
fromAsset(that.imageSrc).then(res => {
var myImageSource = res;
var base64 = myImageSource.toBase64String("jpeg", 20);
this.expense.receipt_data=base64;
})
that.cameraImage=null;
that.imageAssets = selection;
that.galleryProvided=true;
// set the images to be loaded from the assets with optimal sizes (optimize memory usage)
selection.forEach(function (element) {
element.options.width = that.previewSize;
element.options.height = that.previewSize;
});
}).catch(function (e) {
console.log(e);
});
}
I have posted below the Android and iOS screenshots of the line:
alert("Selection done: " + JSON.stringify(selection));
In Android there is a path to the location of the image in the file system but in iOS there are just empty curly brackets where I'd expect to see the path and then when submitted the message back is "Unable to save image" although the image preview is displaying on the page in Image.
Here are the screenshots:
Android:
iOS:
Any ideas why it is failing in iOS?
Thanks
==========
UPDATE
I am now saving the image to a temporary location and it is still not working in iOS. It works in Android.
Here is my code now.
import { ImageAsset } from 'tns-core-modules/image-asset';
import { ImageSource, fromAsset, fromFile } from 'tns-core-modules/image-source';
import * as fileSystem from "tns-core-modules/file-system";
...
...
public onSelectGalleryTap() {
alert("in onSelectGalleryTap");
var milliseconds=(new Date).getTime();
let context = imagepicker.create({
mode: "single"
});
let that = this;
context
.authorize()
.then(() => {
that.imageAssets = [];
that.previewSrc = null;
that.imageSrc = null;
return context.present();
})
.then((selection) => {
that.imageSrc = selection.length > 0 ? selection[0] : null;
// convert ImageAsset to ImageSource
fromAsset(that.imageSrc)
.then(res => {
var myImageSource = res;
let folder=fileSystem.knownFolders.documents();
var path=fileSystem.path.join(folder.path, milliseconds+".jpg");
var saved=myImageSource.saveToFile(path, "jpg");
that.previewSrc=path;
const imageFromLocalFile: ImageSource = <ImageSource> fromFile(path);
var base64 = imageFromLocalFile.toBase64String("jpeg", 20);
this.expense.receipt_data=base64;
})
that.cameraImage=null;
that.imageAssets = selection;
that.galleryProvided=true;
// set the images to be loaded from the assets with optimal sizes (optimize memory usage)
selection.forEach(function (element) {
element.options.width = that.previewSize;
element.options.height = that.previewSize;
});
}).catch(function (e) {
console.log(e);
});
}
Any ideas? Thanks.
It is an already communicated issue, several of us subscribed for, check here issue #321
for updates.

(Vue) Impact on performance of local scope variables in computed properties

Does defining variables inside of a computed property have any impact on the perfomance of Vue components?
Background: I built a table component which generates a HTML table generically from the passed data and has different filters per column, filter for the whole table, sort keys, etc., so I'm defining a lot of local variables inside the computed property.
Imagine having an array of objects:
let data = [
{ id: "y", a: 1, b: 2, c: 3 },
{ id: "z", a: 11, b: 22, c: 33 }
]
..which is used by a Vue component to display the data:
<template>
<div>
<input type="text" v-model="filterKey" />
</div>
<table>
<thead>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
</thead>
<tbody>
<tr v-for="item in filteredData" :key="item.id">
<td v-for="(value, key) in item" :key="key">
{{ value }}
</td>
</tr>
</tbody>
</table>
</template>
The data gets filtered via input:
<script>
export default {
props: {
passedData: Array,
},
data() {
return {
filterKey: null,
};
},
computed: {
filteredData() {
// defining local scope variables
let data = this.passedData;
let filterKey = this.filterKey;
data = data.filter((e) => {
// filter by filterKey or this.filterKey
});
return data;
},
},
};
</script>
My question refers to let data = .. and let filterKey = .. as filteredData() gets triggered from any change of the filterKey (defined in data()) so the local variable gets updated too, although they're not "reactive" in a Vue way.
Is there any impact on the performance when defining local variables inside a computed property? Should you use the reactive variables from data() (e. g. this.filterKey) directly inside of the computed property?
The best way to test if something affects performance, is to actually test it.
According to my tests below, it is consistency more than 1000% slower to use this.passedData instead of adding a variable on top of the function. (869ms vs 29ms)
Make sure you run your benchmarks on the target browsers you write your application for the best results.
function time(name, cb) {
var t0 = performance.now();
const res = cb();
if(res !== 20000000) {
throw new Error('wrong result: ' + res);
}
var t1 = performance.now();
document.write("Call to "+name+" took " + (t1 - t0) + " milliseconds.<br>")
}
function withoutLocalVar() {
const vue = new Vue({
computed: {
hi() {
return 1;
},
hi2() {
return 1;
},
test() {
let sum = 0;
for(let i = 0; i < 10000000; i++) { // 10 000 000
sum += this.hi + this.hi2;
}
return sum;
},
}
})
return vue.test;
}
function withLocalVar() {
const vue = new Vue({
computed: {
hi() {
return 1;
},
hi2() {
return 1;
},
test() {
let sum = 0;
const hi = this.hi;
const hi2 = this.hi2;
for(let i = 0; i < 10000000; i++) { // 10 000 000
sum += hi + hi2;
}
return sum;
},
}
})
return vue.test;
}
function benchmark() {
const vue = new Vue({
computed: {
hi() {
return 1;
},
hi2() {
return 1;
},
test() {
let sum = 0;
const hi = 1;
const hi2 = 1;
for(let i = 0; i < 10000000; i++) { // 10 000 000
sum += hi + hi2;
}
return sum;
},
}
})
return vue.test;
}
time('withoutLocalVar - init', withoutLocalVar);
time('withLocalVar - init', withLocalVar);
time('benchmark - init', benchmark);
time('withoutLocalVar - run1', withoutLocalVar);
time('withLocalVar - run1', withLocalVar);
time('benchmark - run1', benchmark);
time('withoutLocalVar - run2', withoutLocalVar);
time('withLocalVar - run2', withLocalVar);
time('benchmark - run2', benchmark);
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>

convert select to vue-select with dynamic data (Laravel & Vuejs)

I have dynamic products list to create an invoice. Now I want to search the product from select->option list. I found a possible solution like Vue-select in vuejs but I could not understand how to convert my existing code to get benefit from Vue-select. Would someone help me please, how should I write code in 'select' such that I can search product at a time from the list?
My existing code is -
<td>
<select id="orderproductId" ref="selectOrderProduct" class="form-control input-sm" #change="setOrderProducts($event)">
<option>Choose Product ...</option>
<option :value="product.id + '_' + product.product_name" v-for="product in invProducts">#{{ product.product_name }}</option>
</select>
</td>
And I want to convert it something like -
<v-select :options="options"></v-select>
So that, I can search products also if I have many products. And My script file is -
<script>
Vue.component('v-select', VueSelect.VueSelect);
var app = new Vue({
el: '#poOrder',
data: {
orderEntry: {
id: 1,
product_name: '',
quantity: 1,
price: 0,
total: 0,
},
orderDetail: [],
grandTotal: 0,
invProducts: [],
invProducts: [
#foreach ($productRecords as $invProduct)
{
id:{{ $invProduct['id'] }},
product_name:'{{ $invProduct['product_name'] }}',
},
#endforeach
],
},
methods: {
setOrderProducts: function(event) {
//alert('fired');
var self = this;
var valueArr = event.target.value.split('_');
var selectProductId = valueArr[0];
var selectProductName = valueArr[1];
self.orderEntry.id = selectProductId;
self.orderEntry.product_name = selectProductName;
$('#invQuantity').select();
},
addMoreOrderFields:function(orderEntry) {
var self = this;
if(orderEntry.product_name && orderEntry.quantity && orderEntry.price > 0) {
self.orderDetail.push({
id: orderEntry.id,
product_name: orderEntry.product_name,
quantity: orderEntry.quantity,
price: orderEntry.price,
total: orderEntry.total,
});
self.orderEntry = {
id: 1,
product_name:'',
productId: 0,
quantity: 1,
price: 0,
total: 0,
}
$('#orderproductId').focus();
self.calculateGrandTotal();
} else {
$('#warningModal').modal();
}
this.$refs.selectOrderProduct.focus();
},
removeOrderField:function(removeOrderDetail) {
var self = this;
var index = self.orderDetail.indexOf(removeOrderDetail);
self.orderDetail.splice(index, 1);
self.calculateGrandTotal();
},
calculateGrandTotal:function() {
var self = this;
self.grandTotal = 0;
self.totalPrice = 0;
self.totalQuantity = 0;
self.orderDetail.map(function(order){
self.totalQuantity += parseInt(order.quantity);
self.totalPrice += parseInt(order.price);
self.grandTotal += parseInt(order.total);
});
},
setTotalPrice:function(event){
var self = this;
//self.netTotalPrice();
self.netTotalPrice;
}
},
computed: {
netTotalPrice: function(){
var self = this;
var netTotalPriceValue = self.orderEntry.quantity * self.orderEntry.price;
var netTotalPriceInDecimal = netTotalPriceValue.toFixed(2);
self.orderEntry.total = netTotalPriceInDecimal;
return netTotalPriceInDecimal;
}
}
});
Assuming that invProducts is an array of product objects and each product object has a product_name property, try this snippet.
<v-select #input="selectChange()" :label="product_name" :options="invProducts" v-model="selectedProduct">
</v-select>
Create a new data property called selectedProduct and bind it to the vue-select component. So, whenever the selection in the vue-select changes, the value of selectedProduct also changes. In addition to this, #input event can be used to trigger a method in your component. You can get the selected product in that method and do further actions within that event listener.
methods: {
selectChange : function(){
console.log(this.selectedProduct);
//do futher processing
}

dropdown list with an extend option

I'm using the DropdownList component from react-widget. In my code, there are a couple of dropdowns that get their values from an Ajax call. Some of them, like a list of languages, are too big and it's very slow to get the list from Ajax and render it (takes 4 to 5 seconds!). I would like to provide to the dropdwon a small list of languages and an 'Extend' or 'Load Full List' option; if clicking on Extend the dropdown would be refreshed with the full list of languages.
Here is my solution: the code of the parent component:
const languages = ajaxCalls.getLanguages();
const langs = {"languages": [["eng", "English"], ["swe", "Swedish"], ["deu", "German"], ["...", "Load Full List"]]};
const common_langs = langs.languages.map(([id, name]) => ({id, name}));
<SelectBig data={common_langs} extend={languages} onSelect={x=>this.setValue(schema, path, x)} value={this.getValue(path)} />;
And here is the code for SelectBig component:
import React from 'react/lib/ReactWithAddons';
import { DropdownList } from 'react-widgets';
const PT = React.PropTypes;
export const SelectBig = React.createClass({
propTypes: {
data: PT.array,
value: PT.string,
onSelect: PT.func.isRequired,
},
maxResults: 50,
shouldComponentUpdate(nextProps, nextState) {
console.log("nextProps = " , nextProps, " , nextState = ", nextState);
const len = x => (x && x.length !== undefined) ? x.length : 0;
// fast check, not exact, but should work for our use case
return nextProps.value !== this.props.value
|| len(nextProps.data) !== len(this.props.data);
},
getInitialState(){
return {
lastSearch: '',
results: 0,
dataList: [],
};
},
select(val) {
if(val.id === "..."){
this.setState({dataList: this.props.extend})
}
this.props.onSelect(val.id);
},
filter(item, search) { .... },
renderField(item) { .... },
render() {
const busy = !this.props.data;
let data;
if(!this.props.extend){
data = this.props.data || [];
} else {
data = this.state.dataList;
}
return (
<DropdownList
data={data}
valueField='id'
textField={this.renderField}
value={this.props.value}
onChange={this.select}
filter={this.filter}
caseSensitive={false}
minLength={2}
busy={busy} />
);
}
});
But it doesn't look good: When the user chooses 'Load Full List', the dropdown list will be closed and user need to click again to see the updated list. Does anyone have a better solution or a suggestion to improve my code?
The picture shows how it looks like right now!
https://www.npmjs.com/package/react-select-2
Better go with this link, it will work

Resources