Titanium Alloy ListView Nested Model Array of Tags

我怕爱的太早我们不能终老 提交于 2019-12-03 09:11:01

I think I know what you need, in this case since you want to generate each item dynamically (for example, a scenario where you open your window with your ListView empty first and make an API call to get remote data and fill the ListView with said data) you will need to use ItemTemplates declared in their own controllers.

You just create a new controller like normal and in the view xml you put your ItemTemplate:

<Alloy>
    <ItemTemplate name="template" id="template">
         <View class="item-container">               
             <ImageView bindId="pic" class="pic"/>
             <Label bindId="username" class="username"/>
         </View>
    </ItemTemplate>
</Alloy>

In your tss you put all of the styles referred to each element in your template, since you didn't provide a tss example I can't tell what are your style properties, but in the tss you need to define the style of the template, for example lets say something like:

"#template": // this is the id of your template in your xml
{
    width : Ti.UI.FILL,
    height : '44dp',
    backgroundColor : '#FFFFFF'
}

To fill your ListView with ListItems dynamically, you will need to do something like this in your callback from your API:

function displayListItems(items)
{
    var itemCollection = [];
    for(var i=0; i < items.length; i++)
    {
        var tmp = {
            pic : {
                image : items[i].image
            },
            username : {
                text : items[i].text
            },
            template : 'template' // here goes the name of the template in your xml, **do not confuse name with id, both are different and using one doesn't replace the other**
        };
        itemCollection.push(tmp);
    }
    $.ListView.sections[0].items = itemCollection;
}

And voila, you get your ListView filled dynamically. Now there are some extra steps you can do.

In your template controller you can leave it blank since the ListView can manage the itemclick event, but if you want different actions to take place when a certain element in the Listitem to trigger, you need to specify the functions to be called in your controller for each element.

For example lets say you passed a property called dataInfo to your ImageView and your Label in your template like this:

function displayListItems(items)
{
    var itemCollection = [];
    for(var i=0; i < items.length; i++)
    {
        var tmp = {
            pic : {
                image : items[i].image
                dataInfo : items[i].fooA //lets pass to the ImageView the object fooA
            },
            username : {
                text : items[i].text,
                dataInfo : items[i].fooB //lets pass to the Label the object fooB
            },
            template : 'template' // here goes the name of the template in your xml, **do not confuse name with id, both are different and using one doesn't replace the other**
        };
        itemCollection.push(tmp);
    }
    $.ListView.sections[0].items = itemCollection;
}

And you want the ImageView and the Label to call different functions, you will need to change your xml like this:

<Alloy>
    <ItemTemplate name="template" id="template">
         <View class="item-container">               
             <ImageView bindId="pic" class="pic" onClick="imageFunction"/> <!-- added onClick event -->
             <Label bindId="username" class="username" onClick="labelFunction"/> <!-- added onClick event -->
         </View>
    </ItemTemplate>
</Alloy>

In your controller you will declare each function:

function imageFunction(e)
{
    var dataInfo;
    if(Ti.Platform.osname === 'android')
    {
        var item = e.section.items[e.itemIndex];
        var bindObject = item[e.bindId];
        dataInfo = bindObject.fooA;
    }
    else
    {
        dataInfo = e.source.fooA;
    }

}

function labelFunction(e)
{
    var dataInfo;
    if(Ti.Platform.osname === 'android')
    {
        var item = e.section.items[e.itemIndex];
        var bindObject = item[e.bindId];
        dataInfo = bindObject.fooB;
    }
    else
    {
        dataInfo = e.source.fooB;
    }
}

Now you might ask, why do check for the operative system name, well that is because Android and iOS receive different e objects even if you use the same function. In iOS whatever property you pass to the source of the event can be accessed directly with e.source.propertyName while in Android you need to access to the item in e.section using e.itemIndex, after that you retrieve the view inside the item with the e.bindId associated to it.

One of the biggest restrictions on ListItems is updating the views inside a ListItem, to do this you need to update the whole item you want to change visually and assign it a different template, but the speed at which this is done you won't be able to notice any lag, seriously ListView's performance is something else, unlike ScrollView and let's not talk about the horrible and buggy TableView.

A warning, as of Titanium SDK 3.2.0.GA there's a bug in ItemTemplates that causes for views inside child views in the template to change their zIndex in Android with no way to control it, there are two known instances for this: If you use a don't set the layout in a child view: this could cause for a view that should be displayed beneath another view to come on top of it.

If you use a vertical layout in a child view: this could cause for the positions of each view to be scrambled, this is because zIndex alters the order of display in a vertical layout.

This bug is triggered randomly and the Appcelerator team hasn't put much work on it, check the JIRA ticket here TIMOB-16704.

This can be avoided if you use a template with fixed positioned views and making sure no view comes on top of another, also remember no vertical layouts, haven't tested this with horizontal but personally I try to avoid horizontal layouts since there are other bugs related to it when used in scrollviews, normal views, etc.

EDIT

Another thing you might want to do with this is to assign a different look to the items you render, you have to options:

  • To apply the styles when you declare the ListItem.
  • To apply a different layout to each ListItem depending on a series of conditions.

For the first option you need to omit or overwrite the declaration of certain properties in your template:

For example, let's use a different background color where the property fooA exists and another color if it doesn't:

function displayListItems(items)
{
    var itemCollection = [];
    for(var i=0; i < items.length; i++)
    {
        var properties = {};
        if(typeof items[i].fooA !== 'undefined')
        {
            properties = {
               backgroundColor : 'red'
            };
        }
        else
        {
            properties = {
               backgroundColor : 'blue'
            };
        }
        var tmp = {
            properties : properties, // properties for the template
            pic : {
                image : items[i].image
                dataInfo : items[i].fooA //lets pass to the ImageView the object fooA
            },
            username : {
                text : items[i].text,
                dataInfo : items[i].fooB //lets pass to the Label the object fooB
            },
            template : 'template' // here goes the name of the template in your xml, **do not confuse name with id, both are different and using one doesn't replace the other**
        };
        itemCollection.push(tmp);
    }
    $.ListView.sections[0].items = itemCollection;
}

You can change width, height, backgroundColor, layout, etc. according to your needs.

Now if you want each item to have a distinct look (meaning different views to display different content) and perhaps a different behavior, you'll need to use different templates.

This might sound bothersome but it is not, templates are fast to create once you get used to them which doesn't take long, another downer might be that if you want 11 different looks, that might mean you'll need 11 templates but that's a extreme case and you might want to rethink your UI if you're dealing with that many templates.

Although restrictive, item templates offer a wide array of options for you to use, a little of imagination is the only ingredient necessary to bring out all of the possibilities.

EDIT 2

I finally understood what was you problem, if you need to create a template whose content changes according to a x variable, then you should try declaring the template on your ListView controller, but this should be done before opening the window were you will be showing the ListView since the templates property can only be set on creation, you should add something like:

function createTemplate(items)
{
    var template = {};
    for(var i=0; i < items.length; i++)
    {

        template.childTemplates = [];
        for(var j=0; items[i].tags.length; j++)
        {
           var childTemplate = {
                type: 'Ti.UI.Label',
                bindId: 'tag' + j,
                properties : {
                    width : Ti.UI.SIZE, // Here you define how your style
                    height : Ti.UI.SIZE,
                    font : {
                        fontSize : '18dp'
                    },
                    text : items[i].tags[j].text // you can pass the text here or if you want to on the later for
                }
            };
           template.childTemplates.push(childTemplate);
        }
    }
    // After this you should end up with a template with as many childTemplates as tags each item have, so send the template to the controller with your ListView
    Alloy.createController('ListViewWindow', {customTemplate: template});
}

And in your ListView controller you retrieve the template:

var args = arguments[0] || {};
var template = args.customTemplate;

$.ListView.templates = {'customTemplate' : template}; // before any window open call, do this

This should add the template to your ListView, you can also create the ListView in your controller instead of declaring it in your Alloy xml, use the one that fits your needs more.

This should be possible with a ListView if you create the template in the controller dynamically. You would also need to iterate through each "tags" object and generate a Ti.UI.Label "type" for each tag item. However, I'm not certain this method will be more efficient than using a TableView object because essentially every ListItem you create will contain a different template.

To generate a dynamic template it would be similar to this below: Keep in mind you will need to iterate over "tags" and generate x Ti.UI.Label types where x is the length of "tags". Also, the click event should work using Titanium SDK 3.2.1.

var plainTemplate = {
childTemplates: [
    {
        type: 'Ti.UI.Label',
        bindId: 'username'
    },
    {
        type: 'Ti.UI.ImageView',  
        bindId: 'pic'       

    },                    
    {
        type: 'Ti.UI.Label',
        bindId: 'tags', 
        events: { click : handleTagClickEvent }  // Binds a callback to click event
    }
]};


function handleTagClickEvent(e) {
    Ti.API.info('You clicked a tag label: ' + e.type);
}


var listView = Ti.UI.createListView({
    templates: { 'plain': plainTemplate },
    defaultItemTemplate: 'plain'           
});

Hope this helps you in some way!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!