Skip to content

ListView item template selector #2820

Closed

Description

@hamorphis

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 😄

Activity

  1. hamorphis commented on Sep 29, 2016

    @hamorphis
    ContributorAuthor

    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>
    
  2. rborn commented on Sep 29, 2016

    @rborn

    This format seems ok to me 😄

  3. hamorphis commented on Sep 29, 2016

    @hamorphis
    ContributorAuthor

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

  4. ginev commented on Sep 29, 2016

    @ginev

    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?

  5. rborn commented on Sep 29, 2016

    @rborn

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

  6. hamorphis commented on Sep 29, 2016

    @hamorphis
    ContributorAuthor

    @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.

  7. hamorphis commented on Sep 29, 2016

    @hamorphis
    ContributorAuthor

    @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();
    }
    
  8. ginev commented on Sep 29, 2016

    @ginev

    @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.

  9. hdeshev commented on Sep 29, 2016

    @hdeshev
    Contributor

    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.

  10. ginev commented on Sep 29, 2016

    @ginev

    @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.

  11. ginev commented on Sep 29, 2016

    @ginev

    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 });
        }
    
  12. added this to the 2.4.0 milestone on Oct 10, 2016
  13. added
    ready for testTSC needs to test this and confirm against live production apps and automated test suites
    and removed on Oct 19, 2016
  14. added and removed
    ready for testTSC needs to test this and confirm against live production apps and automated test suites
    on Oct 26, 2016
  15. removed their assignment
    on Nov 1, 2016
  16. lock commented on Aug 28, 2019

    @lock

    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.

  17. locked and limited conversation to collaborators on Aug 28, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions