The Problem
When events, particularly onClick, are included on views that are childen of a model bound view the event doesn't fire and causes an error.
Test Case
<Alloy>
<TableView dataCollection='testModel'>
<TableViewRow>
<View class="artist_row" onClick="test">
<Label class="location" text="Location" />
</View>
</TableViewRow>
</TableView>
</Alloy>
This compiles with Alloy to:
function Controller() {
function test(e) {}
require("alloy/controllers/BaseController").apply(this, Array.prototype.slice.call(arguments));
$model = arguments[0] ? arguments[0].$model : null;
var $ = this, exports = {}, __defers = {};
$.__views.TableView = Ti.UI.createTableView({
id: "TableView"
});
var __alloyId7 = function(e) {
var models = Alloy.Collections.testModel.models, len = models.length, rows = [];
for (var i = 0; i < len; i++) {
var __alloyId1 = models[i];
__alloyId1.__transform = {};
var __alloyId2 = Ti.UI.createTableViewRow({
id: "__alloyId0"
});
rows.push(__alloyId2);
var __alloyId4 = Ti.UI.createView({
id: "__alloyId3"
});
__alloyId2.add(__alloyId4);
test ? $.__views.__alloyId3.addEventListener("click", test) : __defers["$.__views.__alloyId3!click!test"] = !0;
var __alloyId6 = Ti.UI.createLabel({
text: "Location",
id: "__alloyId5"
});
__alloyId4.add(__alloyId6);
}
$.__views.TableView.setData(rows);
};
Alloy.Collections.testModel.on("fetch destroy change add remove reset", __alloyId7);
$.addTopLevelView($.__views.TableView);
exports.destroy = function() {
Alloy.Collections.testModel.off("fetch destroy change add remove reset", __alloyId7);
};
_.extend($, $.__views);
__defers["$.__views.__alloyId3!click!test"] && $.__views.__alloyId3.addEventListener("click", test);
_.extend($, exports);
}
var Alloy = require("alloy"), Backbone = Alloy.Backbone, _ = Alloy._, $model;
module.exports = Controller;
The problem is that the addEventListener has been added to __views.__alloyId3 rather than __alloyId4. When run you get an error due to attempting to add a listener to an undefined object.
The problem emanates from default.js. For collection bound items the state is set to local and the children of the bound view iterated over to generate the code. With the state set to local the .parse function creates a new local id for every object created but this local id is not passed back to .generateNode where any event handlers are added. It looks like the parser can change the object symbol by returning an updated args.symbol but .parse in default.js doesn't do this. If .parse is modified by adding
args: {symbol: args.symbol}
to the end of the return array then the compiler does add the event handler to the locally generated view and the application works.
This problem is solved if you move the repeated component, in this case the TableViewRow, into its own controller. I will look into how to fix this for inline components.
While a solution is worked on for this issue, a simple workaround is to put the repeated view in its own controller. When the repeated view is created this way, the event handlers will be applied appropriately.
This was actually resolved by a commit earlier in the development of Alloy 1.2.0. PR: https://github.com/appcelerator/alloy/pull/160 test app: https://github.com/appcelerator/alloy/tree/master/test/apps/testing/ALOY-604 Functional testing can pass by running the app on all supported platforms and confirming that the "test" message is logged to the console whenever a row is clicked.
Verified as fixed. Titanium SDK 3.1.2.v20130710144553 Appcelerator Studio 3.1.2.201307101037 Alloy 1.2.0 Android 4.2.2 and iOS 5. Closing.