Search code examples
javascripttitaniumappcelerator

Appcelerator: Adding custom (dynamically) views to a ScrollView on Button Click to create card


I'm stuck trying to get a very simple task to work on my app. Essentially, I want to be able to add view to a ScrollView when users click a button. Let's call this first view added a "Card". Inside this card, I also have another scrollview which is supposed to then dynamically receive and display "categories" views.Heres an example/screenshot of the working prototype

For the most part everything is working fine: when the "Add" button at the top is clicked, the "card" is created and I am able to see all of the information, HOWEVER, the problem is that each time I "Add" a card, I end up not only adding "categories" views to the current created card by a factor of 2, but also the same happens to the previously added cards. Like the following: Here's another screenshot.  You cant see, but each card now has 8 categories now.  So if I were to add another card, each would end up having 8!

I hope this makes sense.

Here's my code (my button event listener):

addViewButton.addEventListener('click', function(e) {

    var url = "https://my.apilink.com";
    var xhr = Titanium.Network.createHTTPClient();
    xhr.open('GET', url);

    xhr.onload = function() {
        var response = JSON.parse(this.responseText);
        var t = response.data.categories;

        var bons = [];
        for (var item in t) {
            bons.push(t[item]);
        }

        Ti.API.info("Data received" + bons);

        var resp = {
            response : bons
        };
        createCard(resp);

    };

    xhr.send();
}

And here's where it all happens (creating the views (cards)):

 var catScrollView = Titanium.UI.createScrollView({
  top : 130,
  left : 0,
  right : 0,
  contentWidth : '100%',
  showHorizontalScrollIndicator : false,
  showVerticalScrollIndicator : true,
 });


var scrollView = Ti.UI.createScrollView({
    top : 130,
    left : 0,
    right : 0,
    backgroundColor : 'white',
    contentWidth : '100%',
    showHorizontalScrollIndicator : false,
    showVerticalScrollIndicator : true,
});

var topPosition = 20;
var leftPosition = 20;
var topPositionCat = 30;

var i = 0;
function createCard(_args) {

    var response = _args.response;

    Ti.API.info("Response" + response);

    var colorArr = ["red", "orange", "blue", "green", "pink", "yellow"];
    var fakeArray = ["card 0", "card 1", "card 2", "card 3"];
    var ranIndex = getRandom(colorArr.length);

    i++;

    for (var d = 0; d < response.length; d++) {

        var panelImage = Ti.UI.createView({
            backgroundColor : colorArr[ranIndex],
            top : topPosition + (i * 60),
            borderRadius : 5,
            borderColor : 'white',
            borderWidth : 2,
            id : i,
            bit : false,
            active : false,
            height : 350,
            width : 290,
        });

        //add a few attributes to the card
        var cardTitle = Ti.UI.createLabel({
            text : "I am card # " + i,
            top : 10,
            left : 0,
            color : 'black'
        });
        panelImage.add(cardTitle);
        //Add the EventListener for the view.
        panelImage.addEventListener('singletap', cardButtonHandler);

        //Add scrollview and a card here

        var leftPosition = 20;
        if (d % 2 == 0) {
            leftPosition = 20;
        } else {
            leftPosition = 180;
        }

        var panelImageCat = Ti.UI.createView({
            backgroundImage : '/images/row_bg.png',
            top : topPositionCat,
            left : leftPosition,
            height : 100,
            width : 100,
        });


        var catImageButton = Ti.UI.createImageView({
            image : response[d].icon,
            name : response[d].name,
            id : response[d].id,
            icon : response[d].icon,
            width : 90,
            height : 90,
            top : 4
        });
        panelImageCat.add(catImageButton);

        var catName = Ti.UI.createLabel({
            text : response[d].name,
            textAlign : 'center',
            color : 'black',
            top : 3,
            font : {
                fontWeight : 'bold'
            }
        });

        panelImageCat.add(catName);

        if (leftPosition == 180) {
            topPositionCat += panelImageCat.height + 10;
        }

        // add the view in scroll view
        catScrollView.add(panelImageCat);

        panelImage.add(catScrollView);


        // Add the EventListener  for the view.
        catImageButton.addEventListener('click', function(e) {
            alert(e.source.name);
        });
    }// END FOR LOOP
    catScrollView.contentHeight = topPositionCat + 20;

    // add the view in scroll view
    scrollView.add(panelImage);
}

Of course there's more code for when users tap on each "card" etc. But that's irrelevant.

This is probably very simple to solve. I suspect it has to do with how I am setting up the loops to create each card and its corresponded categories views etc. Any help, guidance is highly appreciated.

Thank you!


Solution

  • I guess that the main problem of your code is the way you are adding cards to the row. You shouldn't add a card for each category, but only create a card, add each category to that card, and then, add the card to the parent scrollView.

    Nevertheless, in order to improve the readability of your code (and thus, your capability to maintain it), you should consider doing two things :

    • Move all the code that concern styling inside a separated file.
    • Create "builders" to instantiate views components so that, your main code only contains relevant information.

    Here is an example on TiFiddle, and right below, explanation about it.

    Move styling in a separated file

    Just make a really easy-to-use commonJS-like module. Titanium supports them, do not be shy.

    styles.js

    /* ------ Top Level Components ------ */
    exports.mainWindow = {
        backgroundColor: "#ffffff",
        layout: "vertical"
    };
    
    exports.topLevelScrollView = {
        width: Ti.UI.FILL,
        height: Ti.UI.FILL,
        contentWidth : '100%',
        showHorizontalScrollIndicator : false,
        showVerticalScrollIndicator : true
    };
    
    exports.addButton = {
        width: Ti.UI.FILL,
        backgroundColor: "#999999",
        color: "#ffffff",
        font: { fontSize: 14 },
        height: 50
    };
    
    
    
    /* ------ Card ----*/
    exports.cardContainer = {
        borderRadius : 5,
        borderColor : '#ffffff',
        borderWidth : 2,
        top: 0,
        height : 350,
        width : Ti.UI.FILL,
    };
    
    exports.cardTitle = {
        top : 10,
        left : 10,
        height: Ti.UI.SIZE,
        width: Ti.UI.FILL,
        color : '#141414'
    };
    
    exports.cardScrollView = {
        layout: "horizontal",
        contentWidth : '100%',
        showHorizontalScrollIndicator : false,
        showVerticalScrollIndicator : true,
        top: 50,
        height: Ti.UI.SIZE,
        width: Ti.UI.SIZE
    };
    
    
    
    
    /* ------ Category ------*/
    exports.categoryContainer = {
        backgroundColor: "#cccccc",
        opacity: "0.8",
        left: 30,
        right: 30,
        top: 10,
        bottom: 10,
        height : 100,
        width : 100,
    };
    
    exports.categoryImage = {
        width : 90,
        height : 90,
        top : 4
    };
    
    exports.categoryTitle = {
        textAlign : 'center',
        color : 'black',
        top : 3,
        font : {
            fontWeight : 'bold'
        }
    };
    
    
    
    
    /* TIPS: Always use fancy colors for your tests <3 */
    exports.colors = [
        "#c0392b",
        "#e67e22",
        "#3498db",
        "#2ecc71",
        "#9b59b6",
        "#f1c40f"
    ];
    

    Create also some utils

    The way you are creating card and categories has nothing to go alongside the other function. So, let's create another module.

    ui_utils.js

    exports.pickColor = function (colors) {
        var randomIndex = Math.floor(Math.random() * colors.length);
        return colors[randomIndex];
    };
    
    /* Create and return a cardContainer that may hold several categoryContainers */
    exports.createCardView = function (id, title, color, styles, listener) {
        var cardContainer = Ti.UI.createView(styles.cardContainer),
            cardTitle = Ti.UI.createLabel(styles.cardTitle),
            cardScrollView = Ti.UI.createScrollView(styles.cardScrollView);
    
        cardContainer.id = id;
        cardContainer.bit = false;
        cardContainer.active = false;
        cardContainer.backgroundColor = color;
        cardContainer.cardScrollView = cardScrollView;
        cardTitle.text = title;
    
        cardContainer.addEventListener(listener.eventName, listener.listener);
    
        cardContainer.add(cardTitle);
        cardContainer.add(cardScrollView);
    
        return cardContainer;
    };
    
    /* Create and return a categoryContainer */
    exports.createCategoryView = function (category, styles, listener) {
        var categoryContainer = Ti.UI.createView(styles.categoryContainer),
            categoryTitle = Ti.UI.createLabel(styles.categoryTitle),
            categoryImage = Ti.UI.createImageView(styles.categoryImage);
    
        categoryTitle.text = category.name;
        categoryImage.id = category.id;
        categoryImage.name = category.name;
        categoryImage.image = category.icon;
    
        categoryImage.addEventListener(listener.eventName, listener.listener);
    
        categoryContainer.add(categoryTitle);
        categoryContainer.add(categoryImage);
    
        return categoryContainer;
    };
    

    Rewrite your main file

    And finally, the main function with just the minimal amount of code.

    app.js

    /* Start by requiring defined modules, and creating different views */
    var styles = require('styles'),
        uiUtils = require('ui_utils'),
        mainWindow = Ti.UI.createWindow(styles.mainWindow),
        topLevelScrollView = Ti.UI.createScrollView(styles.topLevelScrollView),
        addButton = Ti.UI.createButton(styles.addButton);
    
    /* The famous one */
    function createCard(_args) {
        var response = _args.response;
        Ti.API.info("Response :" + response);
    
        /* Create the new card */
        var id = topLevelScrollView.children.length, /* Ugly .. find another way */
            title = "I am card #" + id,
            color = uiUtils.pickColor(styles.colors),
            listener = {
                eventName: "singleTap",
                listener: function () { } /* Just supply a listener */
            },
            cardContainer = uiUtils.createCardView(id, title, color, styles, listener);
            cardContainer.top = 50 * id;
    
        /* Iterate over each category that we have in response */
        for (var i = 0, category; category = response[i]; i++) {
            /* Create the category view */
            var listener =  {
                    eventName: "click",
                    listener: function(e) { alert(e.source.name); }
                },
                categoryContainer = uiUtils.createCategoryView(category, styles, listener);
    
            /* Add it to the card scrollView */
            cardContainer.cardScrollView.add(categoryContainer);
        }
    
        /* Dont forget to add the card to the topLevelScrollView */
        topLevelScrollView.add(cardContainer);
    }
    
    
    /* Initialize the different views */
    addButton.title = "ADD";
    mainWindow.add(addButton);
    mainWindow.add(topLevelScrollView);
    mainWindow.open();
    

    Enjoy!