Search code examples
javascripttypescriptnativescriptnativescript-plugin

How to implement on-demand sorting of data in listview via nativescript-drop-down plugin?


I am creating a simple Nativescript app with a listview of items based off an array. I am having trouble grasping the Observable module/MVVM pattern. I am using nativescript-drop-down to provide a sort-by list in order to sort the data in the listview. The data is properly sorted when the page first renders, however does not update when the index is changed.

How do I ensure the "selectedIndex" value of the dropdown gets properly passed to the viewmodel and the listview updated accordingly?

I have tried sorting based off the selectedIndex in both the model as well as the "dropDownSelectedIndexChanged" function.

main-page.ts

const mainPageModel = new MainPageModel();

export function navigatingTo(args: EventData) {
    const page = <Page>args.object;
    page.bindingContext = mainPageModel;
}

I have tried setting this function in the main-page but it will not update the list unless navigating away then back to the page:

export function dropDownSelectedIndexChanged(args: SelectedIndexChangedEventData) {
    if (args.newIndex == 0) {
        mainPageModel.dataItems.sort(function (a, b) {
            return a.name.localeCompare(b.name);
        });
    } else if (args.newIndex == 1) {
        mainPageModel.dataItems.sort(function (a, b) {
            return b.name.localeCompare(a.name);
        });
    }
}

main-view-model.ts

export class MainPageModel extends Observable {
    // Dropdown populating
    @ObservableProperty() dropdownItems: ObservableArray<any>;
    @ObservableProperty() selectedIndex: number;

    // Recipe templating
    @ObservableProperty() isBusy: boolean = true;
    @ObservableProperty() dataItems: ObservableArray<any>;

    constructor() {
        super();
        this.isBusy = true;
        this.dataItems = new ObservableArray<any>();

        this.dropdownItems = new ObservableArray<any>();
        this.selectedIndex = 0;

        // Populate sort by dropdown
        const items = ["Name (A-Z)", "Name (Z-A)"];
        this.dropdownItems.push(items);

        // Populate recipes stored locally
        let storedRecipes = [];
        try {
            storedRecipes = appSettings.getAllKeys();
            storedRecipes.forEach(element => {
           this.dataItems.push(JSON.parse(appSettings.getString(element)))
            });
        } catch (e) {
            console.log("No stored recipes")
        }

        // Populate recipes from placeholder data
        getData().then((recipeData) => {
            this.dataItems.push(recipeData);
            this.isBusy = false;

            // Sort recipes by index
            // Done as an alternative to "dropDownSelectedIndexChanged"
            if (this.selectedIndex == 0) {
                this.dataItems.sort(function (a, b) {
                    return a.name.localeCompare(b.name);
                });
            } else if (this.selectedIndex == 1) {
                this.dataItems.sort(function (a, b) {
                    return b.name.localeCompare(a.name);
                });
            }
        });
    }
}

main-page.xml

    <GridLayout columns="*,auto,auto" rows="auto,5*" >
        <!-- Add &#xf2e7; back to button -->
        <Button horizontalAlignment="left" row="0" col="0" text="Add New Recipe" tap="addRecipe" class="add-button" />
        <Label horizontalAlignment="right" verticalAlignment="center" text="Sort by:" col="1" row="0" padding="10" fontWeight="bold" fontSize="18" />
        <dd:DropDown items="{{ dropdownItems }}" selectedIndex="{{ selectedIndex }}" 
                    opened="dropDownOpened" closed="dropDownClosed" 
                    selectedIndexChanged="dropDownSelectedIndexChanged"
                    row="0" col="2" itemsPadding="8" verticalAlignment="center" itemsTextAlignment="center" horizontalAlignment="right" />

        <ListView row="1" colSpan="3" class="list-group" items="{{ dataItems }}" separatorColor="transparent">
            <ListView.itemTemplate>
                <GridLayout recipeData="{{ $value }}" tap="recipeDetail" route="recipe-detail/recipe-detail" rows="*,auto,auto" columns="5*, *" class="list-group-item">
                    <Label row="0" col="0" text="{{ name }}" class="h2" />
                    <Image horizontalAlignment="center" row="0" rowSpan="2" col="2" src="{{ image }}" />
                </GridLayout>
            </ListView.itemTemplate>
        </ListView>
        <ActivityIndicator row="1" colSpan="3" busy="{{ isBusy }}" class="activity-indicator" />
    </GridLayout>

While sorting works on the initial page load if the logic is in the viewmodel, it does not change if I select another index. Alternatively, if I try to change the "selectedIndex" value via the "dropDownSelectedIndexChanged" function, the listview does not sort immediately and only updates after navigating away and back to the main page.


Solution

  • sort(...) method doesn't notify the component about the change unlike other methods (slice, pop, push). Try refreshing the ListView after sort,

    export function dropDownSelectedIndexChanged(args: SelectedIndexChangedEventData) {
        if (args.newIndex == 0) {
            mainPageModel.dataItems.sort(function (a, b) {
                return a.name.localeCompare(b.name);
            });
        } else if (args.newIndex == 1) {
            mainPageModel.dataItems.sort(function (a, b) {
                return b.name.localeCompare(a.name);
            });
        }
       (<any>event.object).page.getViewById('YourListViewId').refresh();
    }