javascriptloopsruntime-errorappceleratortitanium-alloy

Appcelerator + Alloy + Eval to Loop through Objects - $ breaks it


I have 384 objects on screen that I need to iterate through in a specific order, changing their properties according to a changing variable. Here's what the objects look like in the .xml file:

    <View formFactor="tablet" id="res1_1" class="resBit" left="1.5%" bottom="25.0%" />
    <View formFactor="tablet" id="res1_2" class="resBit" left="1.5%" bottom="27.3%" />
    <View formFactor="tablet" id="res1_3" class="resBit" left="1.5%" bottom="29.6%" />
    <View formFactor="tablet" id="res1_4" class="resBit" left="1.5%" bottom="31.9%" />
    [...]
    <View formFactor="tablet" id="res16_22" class="resBit" left="93.0%" bottom="73.3%" />
    <View formFactor="tablet" id="res16_23" class="resBit" left="93.0%" bottom="75.6%" />
    <View formFactor="tablet" id="res16_24" class="resBit" left="93.0%" bottom="77.9%" />

This is what the javascript looks like:

// theValues rotates between a set of 100 or so such combinations
theValues = "2,2,3,4,5,5,4,3,2,2,3,4,5,5,4,3".split(",");

// Start on the left and move right
for (i=1; i<17; i++) {
    // Start at the bottom and move up
    for (ii=1; ii<25; ii++) {
        if (ii < (theValues[i-1]) - 1) {
            // Make first row solid
            if (i == 1) { eval('$.res' + i + '_' + ii + '.setOpacity(1);'); }
            // Paint reds
            eval('$.res' + i + '_' + ii + '.setBackgroundColor("red");');
        }
    }
}

And the error I'm getting is:

[ERROR] :  TiExceptionHandler: (main) [10567,152803] ----- Titanium Javascript Runtime Error -----
[ERROR] :  TiExceptionHandler: (main) [0,152803] - In undefined:1,1
[ERROR] :  TiExceptionHandler: (main) [0,152803] - Message: Uncaught ReferenceError: $ is not defined
[ERROR] :  TiExceptionHandler: (main) [1,152804] - Source: $.res1_1.setOpacity(1);

If I just write $.res1_1.setOpacity(1); directly in the code, it works. It's the eval that breaks it. Thoughts? Thanks.


Solution

  • There are many things that you need to take care about other than that error.

    First thing is to overhaul your UI structure. Currently it's not recommended to set left/bottom/top/right properties manually when you can really use layout types to create your views properly.

    From the looks of your XML code, it seems you are creating a grid-view of 16 columns x 25 rows with each view having a left padding of 1.5% & top padding of 2.3% (here you have given it as bottom) The main issue with how you are currently designing it is using the left/bottom properties manually AND of course Why on earth someone would create 384 views manually :) .. It would take a great patience to do so & at least I do not have it :)

    Now the recommended way to create such UI is something like this:

    template.xml

    <Alloy>
        <View class="resBit" left="1.5%" top="2.3%" />
    </Alloy>
    

    your_view.xml

    <View id='parentContainer' formFactor="tablet" layout='horizontal' horizontalWrap='true' right='1.5%'>
        ..... here comes your those resBit views created dynamically
        // formFactor property is only set to parent container which also applies to its child container, so no need to set it on all 384 child views as you did
    
        // horizontalWrap property ensures that child-views which are going out of bound, will be created in
        // next row & thus it creates a grid-view automatically while using proper sizes so the last or
        // 16th view fits to the end of parent view.
    </View>
    

    your_view.js

    // create child-views dynamically like this
    for (var i=1; i<=384; i++) {
        // you can pass any required arguments here needed to create child-views
        var templateView = Alloy.createController('template', {
            position : i
        }).getView();
    
        $.parentContainer.add(templateView);
    }
    

    template.js

    var pos = $.args.position;
    
    // if it's the last or 16th child-view in each row, then set its width to fill the parent width, which will make sure that your next child-view will move to a new row.
    // it's necessary because sometimes while using %age dimensions, there might be a few pixels calculation error which can create the 16th child-view to next row
    
    if ((pos % 16) == 0) {
        $.template.width = Ti.UI.FILL;
    }
    

    Finally, since you do not have ids as res12_14, etc. you can still access all your child-views like this:

    your_view.js

    // theValues rotates between a set of 100 or so such combinations
    theValues = "2,2,3,4,5,5,4,3,2,2,3,4,5,5,4,3".split(",");
    
    // gives you all child-views in an array
    var child_views = $.parentContainer.children;
    
    // Start on the left and move right
    for (i=1; i<17; i++) {
    
        // Start at the bottom and move up
        for (ii=1; ii<25; ii++) {
            if (ii < (theValues[i-1]) - 1) {
                var currentChildPos = i * ii - 1;       // since we started loop from 1 & array index starts from 0
                // Make first row solid
                if (i == 1) {
                    child_views[currentChildPos].setOpacity(1);
                }
    
                // Paint reds
                child_views[currentChildPos].setBackgroundColor("red");
            }
        }
    }
    

    This is how you should truly use the Alloy MVC framework, write less-code, write more maintainable code. If you need to make any change in those resBit views, then you can make it now in template.xml file instead of at 384 places in your previous code.