Nativescript: ListView item template selector

Created on 29 Sep 2016  路  12Comments  路  Source: NativeScript/NativeScript

To give more context (based on the slack chat we had):
We need to be able to display sets of data with different "designs" in a ListView without the need of using collapsed views (like a huge template which will be impossible to maintain and cost CPU cycles) or logic inside the view-model for ContentView which would have performance penalties.

A good scenario could be a news app, that needs to display the news with different designs based on the importance of the article:

Thank you 馃槃

done feature high

Most helpful comment

We would like to gather some ideas and feedback about how the public API for this feature might look like. All suggestions are welcome. Currently we are thinking along the lines of something like this:

<Page 
  xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo">
  <ListView items="{{ people }}" itemTemplateSelector="{{ age % 2 === 0 ? '1' : '2' }}">
    <ListView.itemTemplates>
        <template key="1">
          <Label text="{{ name + ' ' + age }}" style.backgroundColor="blue"/>
        </template>
        <template key="2">
          <Label text="{{ name + ' ' + age }}" style.backgroundColor="red"/>
        </template>
    </ListView.itemTemplates>
  </ListView>
</Page>

All 12 comments

We would like to gather some ideas and feedback about how the public API for this feature might look like. All suggestions are welcome. Currently we are thinking along the lines of something like this:

<Page 
  xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo">
  <ListView items="{{ people }}" itemTemplateSelector="{{ age % 2 === 0 ? '1' : '2' }}">
    <ListView.itemTemplates>
        <template key="1">
          <Label text="{{ name + ' ' + age }}" style.backgroundColor="blue"/>
        </template>
        <template key="2">
          <Label text="{{ name + ' ' + age }}" style.backgroundColor="red"/>
        </template>
    </ListView.itemTemplates>
  </ListView>
</Page>

This format seems ok to me 馃槃

Have in mind that we should somehow implement this for Angular too!

Looks good to me too. @hamorphis what do you think about exposing a callback API to allow for returning a View instance to use for a particular item?

@hamorphis you lost me here, I don't know Angular 馃槉

@ginev Sounds very reasonable. Can you please show me how do you imagine the callback API. I want to gather as much concrete dummy code samples as possible.

@ginev Something like this maybe?

<Page 
  xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo">
  <ListView items="{{ people }}" itemTemplateSelector="onSelectItemTemplate">
  </ListView>
</Page>

And in the page code-behind file:

export function onSelectItemTemplate(dataItem: any): View {
   return dataItem.age % 2 === 0 ? new Label() : new Button();
}

@hamorphis something like that. Note that this will affect performance as in the normal case Views are cached and the bindingContext is changed. With this approach a new View will be created for each item. We might want to implement caching for separate templates.

The current Angular ListView directive doesn't use the template parser functionality. You'd ask why... because it has to use Angular templates which have their own bindings.

For the time being we use a somewhat hacky solution which handles the onItemLoading event, where it instantiates an Angular template, pulls the root view from inside, and passes it to the list view item.

It would be great if we use this feature to revisit our base template handling logic (possibly across the entire framework) and come up with a TS/JS API that allows us to programmatically register:

  1. keyed templates: functions that return a View and have an associated key/id.
  2. template selector function that accepts a data item (maybe item index as well) and returns a template key.

The above will allow the Angular implementation to discover all provided TemplateRef's, extract their keys, build template functions and pass those along with a selector function to the base ListView implementation.

On a side note... how does this affect RadListView? I gather we need the same (or a similar) API there.

@hdeshev RadListView suffers from the same illness regarding Angular 2.0 templates. We had to rewrite the logic that parses standard NS templates and binds them. Now, there is a callback that simply returns a ready View for an item. This is something similar to the suggestion done by @hamorphis above but without passing an index or data item as an argument:

  this._listView.itemViewLoader = (viewType) => {
            switch (viewType) {
                case ListViewViewTypes.ItemView:
                    if (component._itemTemplate && this.loader) {
                        var nativeItem = this.loader.createEmbeddedView(component._itemTemplate, new ListItemContext(), 0);
                        var typedView = getSingleViewFromViewRef(nativeItem);
                        typedView["ng_view"] = nativeItem;
                        return typedView;
                    }
                    break;
                case ListViewViewTypes.ItemSwipeView:
                    if (component._itemSwipeTemplate && this.loader) {
                        var nativeItem = this.loader.createEmbeddedView(component._itemSwipeTemplate, new ListItemContext(), 0);
                        var typedView = getSingleViewFromViewRef(nativeItem);
                        typedView["ng_view"] = nativeItem;
                        return typedView;
                    }
                    break;
                case ListViewViewTypes.LoadOnDemandView:
                    if (component._loadOnDemandTemplate && this.loader) {
                        var viewRef = this.loader.createEmbeddedView(component._loadOnDemandTemplate, new ListItemContext(), 0);
                        var nativeView = getSingleViewFromViewRef(viewRef);
                        nativeView["ng_view"] = viewRef;
                        return nativeView;
                    }
                    break;
                case ListViewViewTypes.HeaderView:
                    if (component._headerTemplate && this.loader) {
                        var viewRef = this.loader.createEmbeddedView(component._headerTemplate, new ListItemContext(), 0);
                        var nativeView = getSingleViewFromViewRef(viewRef);
                        nativeView["ng_view"] = viewRef;
                        return nativeView;
                    }
                    break;
                case ListViewViewTypes.FooterView:
                    if (component._footerTemplate && this.loader) {
                        var viewRef = this.loader.createEmbeddedView(component._footerTemplate, new ListItemContext(), 0);
                        var nativeView = getSingleViewFromViewRef(viewRef);
                        nativeView["ng_view"] = viewRef;
                        return nativeView;
                    }
                    break;
            }
        };

We can build on-top of that to implement the Template Selector functionality.

Missed to point out that we're also using the itemLoading event to perform the binding:

  @HostListener("itemLoading", ['$event'])
    public onItemLoading(args) {
        let index = args.itemIndex;
        let currentItem = this.getDataItem(index);
        var ngView = args.view["ng_view"];
        if (ngView) {
            this.setupViewRef(ngView, currentItem, index);
        }
    }

    public setupViewRef(viewRef: EmbeddedViewRef<any>, data: any, index: number): void {
        const context = viewRef.context;
        context.$implicit = data;
        context.item = data;
        context.index = index;
        context.even = (index % 2 == 0);
        context.odd = !context.even;

        this.setupItemView.next({ view: viewRef, data: data, index: index, context: context });
    }

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