I have a product list in RadListView and each product has a quantity selector. User can press + - buttons to increase or decrease the quantity or can type into the textfield too. All these controls are working and data is being updated in the observable but not on the screen. THe value being shown in the textfield doesn't update on screen. Here's the code:
<StackLayout col="1" orientation="horizontal" class="qtySelect bg-secondary">
<Label class="mdi qtyButtons" text="" pid="{{ id }}" tap="qtyDecrease" />
<TextField text="{{ quantity }}" keyboardType="Number" verticalAlignment="top" class="qtyText" />
<Label class="mdi qtyButtons" text="" pid="{{ id }}" tap="qtyIncrease" />
</StackLayout>
JS
function findById(pid){
var index;
products.forEach (function(elem, i) {
if(elem.id == pid){
index = i;
}
});
return index;
}
exports.qtyDecrease = function(args){
var index = findById(args.object.pid);
console.log(index);
var product = products.getItem(index);
if(product.min_order < product.quantity)
product.quantity = parseInt(product.quantity)-1;
else
Toast.makeText("Minimum quantity of "+product.min_order+" is required for this product.").show();
products.set(index, product);
}
exports.qtyIncrease = function(args){
var index = findById(args.object.pid);
console.log(index);
var product = products.getItem(index);
product.quantity = parseInt(product.quantity)+1;
products.set(index, product);
}
If I scroll the item out of view and back in, I can see the updated values. I tried refresh() method on the list but that makes the list scroll to the top which is undesirable.
So how can I force refresh on just that element?
Edit: Full code
<lv:RadListView row="1" items="{{ products }}" id="productList">
<lv:RadListView.listViewLayout>
<lv:ListViewGridLayout scrollDirection="Vertical" spanCount="2" />
</lv:RadListView.listViewLayout>
<lv:RadListView.itemTemplate>
<GridLayout rows="{{ width+', auto' }}" class="borders">
<fresco:FrescoDrawee row="0" width="{{ width }}" height="{{ width }}" decodeWidth="{{ width }}" decodeHeight="{{ width }}" imageUri="{{ image }}" placeholderImageUri="~/images/loading.jpg" failureImageUri="~/images/loading.jpg" progressiveRenderingEnabled="true" id="{{ 'product'+id }}" tap="selectItem" />
<StackLayout row="1" class="card-content bg-white">
<label text="{{ name }}" class="bold" />
<Label text="{{ 'Net Weight: '+netWeight+' gms' }}" class="h6 text-black" />
<GridLayout columns="auto,auto">
<Label col="0" text="Qty: " class="h6 text-black" />
<StackLayout col="1" orientation="horizontal" class="qtySelect bg-secondary">
<Label class="mdi qtyButtons" text="" pid="{{ id }}" tap="qtyDecrease" />
<TextField text="{{ quantity }}" keyboardType="Number" verticalAlignment="top" class="qtyText" />
<Label class="mdi qtyButtons" text="" pid="{{ id }}" tap="qtyIncrease" />
</StackLayout>
</GridLayout>
<Button text="{{ addToCartText }}" class="btn trans-white" padding="0" pid="{{ id }}" tap="addToCart" />
</StackLayout>
</GridLayout>
</lv:RadListView.itemTemplate>
</lv:RadListView>
JS
const screenWidth = settings.getNumber("screenWidth");
var boxWidth = screenWidth/2;
var page;
var prodSearchCriteria;
var productRadList;
var productsVM;
var products;
var numProductsView;
function loadProducts(criteria){
http.request({
url: config.apiUrl+"products.php",
method: "POST",
headers: { "Content-Type": "application/json" },
content: JSON.stringify({
auth: config.apiAuth,
criteria: criteria
})
}).then(function(request){
var response = JSON.parse(request.content);
productsVM.set("numProducts", response.data.length);
productsVM.set("minWeight", response.min_weight);
productsVM.set("maxWeight", response.max_weight);
productsVM.set("designs", response.designs);
products.length = 0;
for (var i = 0; i < response.data.length; i++) {
products.push({
id: response.data[i].id,
image: config.siteUrl+"/product_img/"+response.data[i].image,
name: response.data[i].name,
netWeight: response.data[i].net_weight,
width: boxWidth,
min_order: response.data[i].min_order,
quantity: response.data[i].min_order,
addToCartText: "Add to Cart"
});
}
productIdsString = productIdsString.slice(0, -1);
numProductsView.resetNativeView();
}, function(error){
console.log(JSON.stringify(error));
});
}
exports.onLoaded = function(args) {
page = args.object;
prodSearchCriteria = page.navigationContext;
if("filters" in prodSearchCriteria){
} else {
prodSearchCriteria.filters = {};
}
productsVM = new observableModule.fromObject({
userAvatar: settings.getString("userAvatar"),
userEmail: settings.getString("userEmail"),
cartNumProducts: settings.getNumber("cartNumProducts"),
cartNetWeight: settings.getNumber("cartNetWeight"),
cartFineWeight: settings.getNumber("cartFineWeight"),
cartLabor: settings.getNumber("cartLabor"),
numProducts: 0,
minWeight: 0,
maxWeight: 0,
designs: []
});
products = new observableArrayModule.ObservableArray();
page.bindingContext = productsVM;
productsVM.set("products", products);
numProductsView = view.getViewById(page, "numProducts")
productRadList = view.getViewById(page, "productList");
loadProducts(prodSearchCriteria);
}
Try to wrap the quantity change in setTimeout() of 10 milliseconds
Did you try to bind data again manually (in increase -decrease functions and in textfield’s changed event)?
Related
I have built a RadListview with nativescript. But now I want that if I tap on an item then only that item should open and other Expanded items should close.
I followed this Creating a collapsible list with NativeScript and it works, I just need one item expanding at a time
enter image description here
thank you
export class RoadComponent {
public roads: Array<any>;
constructor(private page:Page,private router: Router, private roadService: RoadService,private back:BackendService) {
}
async ngOnInit() {
this.roads = await this.roadService.getRoads();
}
templateSelector(item: any, index: number, items: any): string {
return item.expanded ? "expanded" : "default";
}
onItemTap(event: ListViewEventData) {
var listView = event.object as RadListView,
rowIndex = event.index,
dataItem = event.view.bindingContext;
dataItem.expanded = !dataItem.expanded; listView.androidListView.getAdapter().notifyItemChanged(rowIndex);
}
}
<Page>
<StackLayout>
<RadListView multipleSelection="false" id="abc" height="100%" [items]="roads" [itemTemplateSelector]="templateSelector" class="list-group" (itemTap)="onItemTap($event)">
<ng-template tkListItemTemplate let-item="item">
<StackLayout id="abc" orientation="vertical">
<Label text="{{item.name}}" class="list-group-item">
</Label>
</StackLayout>
</ng-template>
<ng-template tkTemplateKey="expanded" let-item="item">
<GridLayout rows="auto,auto" columns="*,*" class="list-group-item add-dropdown">
<Label row="0" col="0" text="{{item.name}}" class="list-group-item"></Label>
<Button row="1" col="0" text="{{item.id}}" (tap)="navigatetomap(item.name)" [nsRouterLink]="['/accueil', { outlets: { homeoutlet: ['home'] } }]"></Button>
<Button row="1" col="1" text="{{item.name}}"></Button>
</GridLayout>
</ng-template>
</RadListView>
</StackLayout>
</Page>
if anyone had this issue just make expanded property false to all the items
onItemTap(event: ListViewEventData) {
for(let i=0;i<this.roads.length;i++){
this.roads[i].expanded=false;
}
var listView = event.object as RadListView,
rowIndex = event.index,
dataItem = event.view.bindingContext;
dataItem.expanded = !dataItem.expanded;
listView.androidListView.getAdapter().notifyItemChanged(rowIndex);
}
I have a RadListView that gets data from an Observable Array of objects and displays its content as text.
When modify one of the value of the object in the Observable Array, the RadListView does not refresh or update the value, even with radlist.refresh();
On Android :android: platform, this is not an issue, it works and refreshes fine, but on iOS platform it does not.
Here is my XML code
```<lv:RadListView items="{{ groceryList }}" id="marad" row="1" height="100%"
itemSwipeProgressStarted="onSwipeCellStarted" swipeActions="true">
<lv:RadListView.itemTemplate>
<GridLayout class="grocery-list-item">
<Label
id="nameLabel" textWrap="true" class="p-15 radlist" text="{{ 'ناو: ' + name + '\n' + ' بڕ: ' + quantity + '\n' + ' نرخی یەک دانە: ' + sellPrice }}" />
</GridLayout>
</lv:RadListView.itemTemplate>
<lv:RadListView.headerItemTemplate>
<StackLayout>
<Button class="btn btn-primary" text="خوێندنهوه بە باڕکۆد" tap="barcodeSell"></Button>
<Label class="page-placeholder" text="نرخی گشتی" textWrap="true"/>
<Label
text="0"
id="totalprice"
textWrap="true"
class="input input-border"/>
<Button class="btn btn-primary" text="بفرۆشە" tap="tapSell"></Button>
</StackLayout>
</lv:RadListView.headerItemTemplate>
<lv:RadListView.itemSwipeTemplate>
<GridLayout columns="auto, *, auto">
<GridLayout id="increase-view" col="2" tap="onIncrease"
class="increase-view">
<Label
text="" class="fonticon duplic" />
</GridLayout>
<GridLayout id="delete-view" col="0" tap="onDelete"
class="delete-view">
<Label
text="" class="fonticon duplic" />
</GridLayout>
</GridLayout>
</lv:RadListView.itemSwipeTemplate>
</lv:RadListView>```
Here is my 'modify the value' function in JS:
exports.onIncrease = function (args) {
var item = args.view.bindingContext;
var index = groceryList.indexOf(item);
var totalPrice = groceryList.getItem(index).sellPrice;
var element = groceryList.getItem(index);
var quantity = groceryList.getItem(index);
var quantity3 = quantity["quantity"];
dialogs.prompt({
message: "بڕی کاڵاکە دیاریبکە",
okButtonText: "هەڵبژێرە",
cancelButtonText: "داخە",
inputType: dialogs.inputType.number
}).then(function (r) {
if(r.result) {
element["quantity"] = r.text;
groceryList.setItem(index, element);
sumPrice = (sumPrice - (Number(totalPrice)*Number(quantity3)));
sumPrice = sumPrice + (Number(r.text)*Number(totalPrice));
label.text = sumPrice;
radlist.refresh();
}
else {
var rslt = false;
}
});
};
Here is the code which feeds the RadListView:
function onNavigatingTo(args) {
const page = args.object;
label = page.getViewById("totalprice");
const sideDrawer = app.getRootView();
sideDrawer.gesturesEnabled = false;
spanquantity = page.getViewById("quantbtn");
groceryList = new ObservableArray([
]);
pageData = observableModule.fromObject({
groceryList: groceryList,
});
page.bindingContext = pageData;
radlist = page.getViewById("marad");
sumPrice = 0;
}
exports.onNavigatingTo = onNavigatingTo;
And then an item data is pushed to the Observable Array if the input barcode was found the database, but this is not related to this issue, I'm saying these to make the code clear.
One more thing, if I swipe RadListView and tap delete item button, it deletes the item fine, so, in the case of delete button, it updates and refreshes the list, I guess.
So, this issue is related to the increase item quantity process.
I am using NS 2.3 with AppBuilder. Recently I started seeing the following error in my console:
UIDataDetectorTypeLegacyPhoneNumber is incompatible with other types
Here is the markup of th epage where the error occurs:
<Page xmlns="http://schema.nativescript.org/tns.xsd" actionBarHidden="true" loaded="pageLoaded" navigatingTo="onNavigatingTo" xmlns:drawer="nativescript-telerik-ui/sidedrawer" xmlns:sharedDrawers="widgets/drawers">
<drawer:RadSideDrawer id="mainDrawer">
<drawer:RadSideDrawer.mainContent>
<ScrollView id="wrapper" opacity="0">
<DockLayout stretchLastChild="true">
<StackLayout class="bg" dock="top" paddingTop="7">
<GridLayout id="mainHeader" rows="auto" columns="auto, *, auto" dock="top">
<Button class="icomoon-icon" text="" row="0" col="0" horizontalAlignment="center" verticalAlignment="center" tap="openDrawer" fontSize="18"/>
<Label class="title" horizontalAlignment="center" text="Page Title" row="0" col="1" />
<Button class="icomoon-icon" text="" row="0" col="2" horizontalAlignment="center" verticalAlignment="center" tap="close" fontSize="20" />
</GridLayout>
<Image src="~/images/img.png" width="130" />
<Label text="{{ greeting }}" class="greeting" />
</StackLayout>
<StackLayout dock="top" cssClass="content">
<Label class="heading" text="some text..." textWrap="true" />
<Label id="text" text="some more text..." class="text" textWrap="true" />
<HtmlView id="h-privacy" class="h-view" html="{{ privacy }}" tap="toPrivacyPolicy" />
<Label cssClass="button" text="continue" tap="next" />
<HtmlView id="h-notices" class="h-view" html="{{ notices }}" tap="toNotices" marginTop="10" />
</StackLayout>
</DockLayout>
</ScrollView>
</drawer:RadSideDrawer.mainContent>
<drawer:SideDrawer.drawerContent>
<sharedDrawers:mainDrawer />
</drawer:SideDrawer.drawerContent>
</drawer:RadSideDrawer>
</Page>
The page simple displays some text to the user and has a button to move to the next screen. When the page is loaded, the above error shows 4 times in the output window. Note that the app still works without a problem, I was just curious why am I getting this error and how to prevent it from happening.
Here is the loaded event code:
function pageLoaded(args) {
loader.show();
var page = args.object;
var lang = appSettings.getString('languageCode', 'en');
model.set('strings', lang == 'en' ? en : es);
//get the personal information
model.set('customer', JSON.parse(global.user));
//set the greeting
var greeting = '';
var hour = (new Date()).getHours();
if (hour >= 0 && hour < 12) {
greeting = 'Good morning, ' + model.customer.firstName;
}
if (hour >= 12 && hour < 17) {
greeting = 'Good afternoon, ' + model.customer.firstName;
}
if (hour >= 17 && hour <= 23) {
greeting = 'Good evening, ' + model.customer.firstName;
}
model.set('greeting', greeting);
loader.hide();
page.getViewById('wrapper').animate({
opacity: 1,
duration: 1000
});
model.set('privacy', 'some text');
model.set('notices', 'some text');
//set the line height
var lbl = page.getViewById('text');
func.labelLineHeight(lbl);
page.bindingContext = model;
if (page.ios) {
var fontSize = 14;
var fontName = 'HelveticaNeue';
var hp = page.getViewById('h-privacy');
hp.ios.font = UIFont.fontWithNameSize(fontName, fontSize);
hp.marginLeft = -5;
var hn = page.getViewById('h-notices');
hn.ios.font = UIFont.fontWithNameSize(fontName, fontSize);
hn.marginLeft = -5;
}
}
project.json:
{
"nativescript": {
"id": "com.company.appname"
},
"dependencies": {
"moment": "2.14.1",
"moment-timezone": "0.5.5",
"nativescript-background-http": "2.4.0",
"nativescript-email": "1.3.3",
"nativescript-intl": "0.0.4",
"nativescript-iqkeyboardmanager": "1.0.0",
"nativescript-loading-indicator": "2.2.1",
"nativescript-maskedinput": "0.0.3",
"nativescript-phone": "1.2.3",
"nativescript-social-share": "1.3.1",
"nativescript-telerik-ui": "1.5.0",
"nativescript-touchid": "2.1.0",
"tns-core-modules": "2.3.0",
"nativescript-push-notifications": "0.0.19",
"nativescript-pdf-view": "file:nativescript-pdf-view-1.2.0.tgz"
},
"devDependencies": {}
}
Does anyone have a an idea what this is about?
Thank you.
That works and the value of cnt is displayed:
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="loaded">
<Label text="{{ cnt }}" />
</Page>
That does not display the value of cnt:
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="loaded">
<StackLayout>
<Label text="{{ cnt }}" />
</StackLayout>
</Page>
The model is:
var observable = require("data/observable");
var upDownViewModel = new observable.Observable({cnt: 0});
module.exports = upDownViewModel;
And loaded is:
exports.loaded = function(args) {
var page = args.object;
page.bindingContext = model;
}
The problem is that the Label needs a height to work with the StackLayout if a data binding is used.
I'm trying to add a delete button and making it work, i'm splitting up the problems in two.
In my xml file I have this:
<Page loaded="onPageLoaded">
<GridLayout rows="auto, *">
<StackLayout orientation="horizontal" row="0">
<TextField width="200" text="{{ task }}" hint="Enter a task" id="task" />
<Button cssClass="test" text="Add" tap="add"></Button>
<Button cssClass="test" text="Refresh" tap="refresh"></Button>
</StackLayout>
<ListView items="{{ tasks }}" row="1">
<ListView.itemTemplate>
<Label text="{{ name }}" />
<Button cssClass="test" text="X" tap="delbutton"></Button>
</ListView.itemTemplate>
</ListView>
</GridLayout>
</Page>
The first problem is the delbutton, which is the delete button, if i add it like that it will replace my view with a bunch of X's. I cant seem to understand why.
The second problem i'm having trouble with is how to make it work so that it loops through and deletes the item i want to delete, what im cyrrently doing is getting data form a backend server with json that looks like this:
exports.onPageLoaded = function(args) {
page = args.object;
pageData.set("task", "");
pageData.set("tasks", tasks);
page.bindingContext = pageData;
var result;
http.request({
url: "http://192.168.1.68:3000/posts.json",
method: "GET",
headers: { "Content-Type": "application/json" },
}).then(function (response) {
result = response.content.toJSON();
for (var i in result) {
tasks.push({ name: result[i].name });
}
}, function (e) {
console.log("Error occurred " + e);
});
};
exports.delbutton = function() {
console.log("REM")
};
Thanks for your help and time.
The first problem (that only the X is showing) is due to the fact that a ListView item wants exactly one (1) child. You have two (a Label and a Button). Fortunately one item might be a so what you want to do is to enclose your two elements in a StackLayout, like this:
<Page loaded="onPageLoaded">
<GridLayout rows="auto, *">
<StackLayout orientation="horizontal" row="0">
<TextField width="200" text="{{ task }}" hint="Enter a task" id="task" />
<Button cssClass="test" text="Add" tap="add"></Button>
<Button cssClass="test" text="Refresh" tap="refresh"></Button>
</StackLayout>
<ListView items="{{ tasks }}" row="1">
<ListView.itemTemplate>
<StackLayout orientation="horizontal">
<Label text="{{ name }}" />
<Label text="{{ hello }}" />
<Button cssClass="test" text="X" tap="delbutton"></Button>
</StackLayout>
</ListView.itemTemplate>
</ListView>
</GridLayout>
</Page>
As for the second part of removing items from the ListView. I don't know if your pageData is an observable object as the declaration is not part of your pasted code, but I'm guessing it is. Anyways, I've created an example of how to populate data using observables (which is the NativeScript way of building ui:s, see previous link) and how to remove an item from the ListView.
I've added comments in the code to explain what I'm doing.
var Observable = require('data/observable');
var ObservableArray = require('data/observable-array');
/**
* Creating an observable object,
* see documentation: https://docs.nativescript.org/bindings.html
*
* Populate that observable object with an (empty) observable array.
* This way we can modify the array (e.g. remove an item) and
* the UI will reflect those changes (and remove if from the ui
* as well).
*
* Observable objects are one of NativeScripts most fundamental parts
* for building user interfaces as they will allow us to
* change an object and that change gets propagated to the ui
* without us doing anything.
*
*/
var contextArr = new ObservableArray.ObservableArray();
var contextObj = new Observable.Observable({
tasks: contextArr
});
exports.onPageLoaded = function(args) {
var page = args.object;
page.bindingContext = contextObj;
/**
* Simulating adding data to array after http request has returned json.
* Also adding an ID to each item so that we can refer to that when we're
* removing it.
*/
contextArr.push({name: 'First Item', id: contextArr.length});
contextArr.push({name: 'Second Item', id: contextArr.length});
contextArr.push({name: 'Third Item', id: contextArr.length});
};
exports.delbutton = function(args) {
/**
* Getting the "bindingContext" of the tapped item.
* The bindingContext will contain e.g: {name: 'First Item', id: 0}
*/
var btn = args.object;
var tappedItemData = btn.bindingContext;
/**
* Iterate through our array and if the tapped item id
* is the same as the id of the id of the current iteration
* then remove it.
*/
contextArr.some(function (item, index) {
if(item.id === tappedItemData.id) {
contextArr.splice(index, 1);
return false;
}
});
};