Nativescript: ListView : change one item's backgroundColor apply on several

Created on 16 Dec 2015  路  11Comments  路  Source: NativeScript/NativeScript

Hi,

When i tap on an item of a ListView, i want to change its background color.
Unfortunately, when i set the backgroundColor property, the color applies also on several other items.

I think the problem occurs when the ListView is scrollable or when a CSS class is applied inside the itemTemplate.
If you remove the CSS class on the GridLayout inside the itemTemplate, the ListView is fully rendering in the screen and the issue doesn't appear.

  • Windows 10
  • TNS 1.5.1
  • Android SDK 23.1.0

Regards,
Guillaume

question

Most helpful comment

Hey, @guillaume-roy! I looked more deeply in the project and I found a better solution. It is more correct to work with the data and not with the UI. Thus I made the items in the ListView Observable and added one new property isSelected. Every time the function onSelectItem(args) is called the isSelected will be switch the the opposite value. This property is bound to the value of the CSS class in the XML. Depending on whether the item is selected or not, there will be a suitable CSS class to be applied. Check out the code below and tell me if it works for you

main-page.xml

<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded">
  <StackLayout orientation="vertical">
    <Label text="Tap on an item"/>
    <ListView items="{{items}}" itemTap="onSelectItem">
        <ListView.itemTemplate >
            <GridLayout >
                <Label text="{{name}}"  class="{{isSelected ? 'selected' : 'list-view-item'}}"/>
            </GridLayout>
        </ListView.itemTemplate>
    </ListView>
  </StackLayout>
</Page>

main-page.js

var vmModule = require("./main-view-model");
var colorModule = require("color");

function pageLoaded(args) {
    var page = args.object;
    page.bindingContext = vmModule.mainViewModel;
}
exports.pageLoaded = pageLoaded;

function onSelectItem(args) {
    var gridLayout = args.view;
    var bindingCtx = gridLayout.bindingContext;

    bindingCtx.isSelected =  !bindingCtx.isSelected;
}
exports.onSelectItem = onSelectItem;

main-view-model.js

var observable = require("data/observable");

var HelloWorldModel = (function (_super) {
    __extends(HelloWorldModel, _super);
    function HelloWorldModel() {
        _super.call(this);

        this.items = [];
        for(var i = 0; i < 20; i++) {
            this.items.push(new observable.Observable({
                id: i,
                name: "Item " + i,
                isSelected : false
            }));
        }
    }
    return HelloWorldModel;
})(observable.Observable);
exports.HelloWorldModel = HelloWorldModel;
exports.mainViewModel = new HelloWorldModel();

app.css

.list-view-item {
  height: 48;
  font-size: 16;
  color: #212121;
  background-color: #FFFFFF;
  padding-top: 16;
  padding-bottom: 16;
  padding-left: 16;
  padding-right: 16; 
}

.selected {
  height: 48;
  font-size: 16;
  background-color: #DCEDC8;
  padding-top: 16;
  padding-bottom: 16;
  padding-left: 16;
  padding-right: 16;
}

All 11 comments

@guillaume-roy , can you please post the code that you are using to build the ListView.

Hi @N3ll ,

In my previous post you can find the full source code zipped.
But, here is my code :

  • main-page.xml
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded">
  <StackLayout orientation="vertical">
    <Label text="Tap on an item"/>
    <ListView items="{{items}}" itemTap="onSelectItem">
        <ListView.itemTemplate>
            <GridLayout class="list-view-item">
                <Label text="{{name}}"/>
            </GridLayout>
        </ListView.itemTemplate>
    </ListView>
  </StackLayout>
</Page>
  • main-page.js
var vmModule = require("./main-view-model");
var colorModule = require("color");

function pageLoaded(args) {
    var page = args.object;
    page.bindingContext = vmModule.mainViewModel;
}
exports.pageLoaded = pageLoaded;

function onSelectItem(args) {
    var gridLayout = args.view;
    gridLayout.backgroundColor = new colorModule.Color("#DCEDC8");
}
exports.onSelectItem = onSelectItem;
  • main-view-model.js
var observable = require("data/observable");
var HelloWorldModel = (function (_super) {
    __extends(HelloWorldModel, _super);
    function HelloWorldModel() {
        _super.call(this);
        this.items = [];
        for(var i = 0; i < 20; i++) {
            this.items.push({
                id: i,
                name: "Item " + i
            });
        }
    }
    return HelloWorldModel;
})(observable.Observable);
exports.HelloWorldModel = HelloWorldModel;
exports.mainViewModel = new HelloWorldModel();
  • app.css
.list-view-item {
  height: 48;
  font-size: 16;
  color: #212121;
  padding-top: 16;
  padding-bottom: 16;
  padding-left: 16;
  padding-right: 16; }

Regards,
Guillaume

Not certain but isn't this because the listview is reusing the actual elements it creates? So if you have 20 items on screen and start scrolling and adding in more, its still approximately the 20 elements you had initially. Just what I thought was happening from my understanding, pretty certain that's why on web based apps when scrolling long lists you use 'virtual scrolling' which reuses DOM elements because painting new elements when scrolling is a memory nightmare waiting to happen.

Ive also seen this before in a listview with infinite scrolling.

@guillaume-roy , sorry for making you paste the code! I guess, I just got lost in the french :) @bradmartin is absolutely right about the reusing of the cells.
What you can do is to attach an itemLoadingEvent to the ListView and check whether the item, which is loading, should be selected - thus colored or not - thus using the default color.

@N3ll thanks for your reply.
Your solution is fine when i load the view and when there is items already selected.
But at first, how can i change the background when the user tap on item ?

Hey, @guillaume-roy! I looked more deeply in the project and I found a better solution. It is more correct to work with the data and not with the UI. Thus I made the items in the ListView Observable and added one new property isSelected. Every time the function onSelectItem(args) is called the isSelected will be switch the the opposite value. This property is bound to the value of the CSS class in the XML. Depending on whether the item is selected or not, there will be a suitable CSS class to be applied. Check out the code below and tell me if it works for you

main-page.xml

<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded">
  <StackLayout orientation="vertical">
    <Label text="Tap on an item"/>
    <ListView items="{{items}}" itemTap="onSelectItem">
        <ListView.itemTemplate >
            <GridLayout >
                <Label text="{{name}}"  class="{{isSelected ? 'selected' : 'list-view-item'}}"/>
            </GridLayout>
        </ListView.itemTemplate>
    </ListView>
  </StackLayout>
</Page>

main-page.js

var vmModule = require("./main-view-model");
var colorModule = require("color");

function pageLoaded(args) {
    var page = args.object;
    page.bindingContext = vmModule.mainViewModel;
}
exports.pageLoaded = pageLoaded;

function onSelectItem(args) {
    var gridLayout = args.view;
    var bindingCtx = gridLayout.bindingContext;

    bindingCtx.isSelected =  !bindingCtx.isSelected;
}
exports.onSelectItem = onSelectItem;

main-view-model.js

var observable = require("data/observable");

var HelloWorldModel = (function (_super) {
    __extends(HelloWorldModel, _super);
    function HelloWorldModel() {
        _super.call(this);

        this.items = [];
        for(var i = 0; i < 20; i++) {
            this.items.push(new observable.Observable({
                id: i,
                name: "Item " + i,
                isSelected : false
            }));
        }
    }
    return HelloWorldModel;
})(observable.Observable);
exports.HelloWorldModel = HelloWorldModel;
exports.mainViewModel = new HelloWorldModel();

app.css

.list-view-item {
  height: 48;
  font-size: 16;
  color: #212121;
  background-color: #FFFFFF;
  padding-top: 16;
  padding-bottom: 16;
  padding-left: 16;
  padding-right: 16; 
}

.selected {
  height: 48;
  font-size: 16;
  background-color: #DCEDC8;
  padding-top: 16;
  padding-bottom: 16;
  padding-left: 16;
  padding-right: 16;
}

@N3ll Thank you, this solution is working for me. :+1:

Array with Observable items doesn't show the items in RadListView. In 2.5 it works but in 4.2 it doesn't. You can reproduce the bug using the above code. Also tried with 5.0 and items appear to be blank. Any idea @N3ll?

@guillaume-roy have you tried this in the latest Nativescript version?

@francorobles No sorry, I don't maintain my app anymore.

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings