Adding views using for loop to scrollable view in appcelerator - arrays

Can anyone please tell me how to add an array of views using a for loop to a scrollable view in android. The answers that I surfed online are not giving me a clear idea and are way too confusing. Any suggestions?

You have (at least) two options. If you have a lot of work going on for each list item (e.g. showing a thumbnail for a picture) then you may want to use the second approach. Otherwise, you may just use this simple approach (where I show statistics for a number of species - layout controlled in a separate controller). stats is my list:
function showSpecies(stats) {
_.each(stats, function(record){
$.form.add(Alloy.createController('viewStatsRow', {record:record}).getView());
});
}
In this example I have more work going on for "building" every item. So to avoid locking the thread I use a list as a "queue" and just handles the first item - and then call the function with the remaining list until it is empty:
var work = [];
function showNextItem(work,first){
if(work && work.length > 0){
// Progressively show list....
if($.boastList && $.boastList.sections[0]){
var list = [];
list.push(buildOneItem(work.shift())); // Take first element
if(first){
$.boastList.sections[0].items = list; // Replace list
}else{
$.boastList.sections[0].appendItems(list); // append item
}
// Free queue to allow other actions
setTimeout(function(){
showNextItem(work); // Call recursively...
},30);
}
}else{
// All boasts shown...
}
}
function showBoastlist(){
work = [];
DataFactory.boasts.find({}, {$sort:{sortTime:-1}}, function(result){
result.forEach(function(record) {
work.push(record);
});
});
showNextItem(work,true);
}
The buildOneItem function just returns an item ready to be added to the view.
Not sure if this was what you asked for - but hope you can use it ;-)
Happy coding!
/John

Related

Mutating array within an array (Polymer iron-list)

I currently have an iron-list within another iron-list. The parent's data comes from a firebase-query element, and the child's data is computed from each parent item. The db structure and code looks a bit like this:
DB: [
category1: [
itemId1: {
price: 10,
title: "title"
}
]
]
<iron-list id="categoryList" items="{{categories}}" multi-selection as="category">
<template>
<div class="category-holder">
<iron-list id="{{category.$key}}" items="{{_removeExtraIndex(category)}}" as="item" selection-enabled multi-selection selected-items="{{selectedItems}}" grid>
<template>
<div class$="{{_computeItemClass(selected)}}">
<p>[[item.title]]</p>
<p>[[item.price]]</p>
</div>
</template>
</iron-list>
</div>
</template>
</iron-list>
After selecting any number of items, the user can tap on a fab to batch edit the price. This is where I'm having issues. I can't figure out how to access the correct child iron-list in order to call list.set...I'm currently trying the following very nasty method:
var categories = this.$.categoryList;
var categoryItems = categories.items;
(this.selectedItems).forEach(function(item) {
var index = item.itemId;
categoryItems.forEach(function(itemList, categoryIndex) {
if (itemList[index]) {
categories.set('item.' + categoryIndex + '.price', 10);
}
}, this);
}, this);
I'm iterating over the selected items in order to extract the item index and then iterating over the parent iron-list data (categoryItems) in order to check if the given item exists in that subset of data. If so, then I use the category index and attempt to call set on the parent iron-list using the given path to access the actual item I want to edit. As expected, this fails. Hopefully I've made myself clear enough, any help would be appreciated!
EDIT #1:
After much experimenting, I finally figured out how to correctly mutate the child iron-list:
(this.selectedItems).forEach(function(item) {
var list = this.$.categoryList.querySelector('#' + item.category);
var index = list.items.indexOf(item);
list.set(["items", index, "price"], 30);
}, this);
A couple of things worth noting. I'm using querySelector instead of the recommended this.$$(selector) because I keep running into a "function DNE" error. But now I have another problem...after calling the function, the value gets updated correctly but I get the following error:
Uncaught TypeError: inst.dispatchEvent is not a function
Here's a picture of the full error message:
I see the light, hopefully someone can help me out!
OK, I'll take a shot at this. I think the following happens, and I guess this based on how dom-repeat works:
var categories = this.$.categoryList;
var categoryItems = categories.items;
You take the variable that the iron-list is based on, but setting one array to another just creates a reference in javascript. As soon as you update categoryItems, you also update this.$.categoryList.items. When you later sets the new value, iron-list will do a dirty check and compare all subproperties, and because they are equal (because ... reference), the iron-list wont update the dom.
What you should do is to make sure it's a totally new copy and the way of doing that is to use JSON.parse(JSON.stringify(myArray)).
Further on, one major flaw I see in your code is that you're using querySelector to select an element, and then manipulate that. What you should do is to use this.categories and only that variable.
So your method should look something like:
// Get a freshly new array to manipulate
var category = JSON.parse(JSON.stringify(this.categories);
// Loop through it
category.forEach(category) {
// update your categoryList variable
}
// Update the iron list by notifying Polymer that categories has changed.
this.set('categories', category);

Check if the new element has been added to the table in Protractor

In my Angular app I have a table (ng-grid to be precise) and I have a procedure to add the a new element to it. I spare you the details, but the procedure is multi-step, involves talking to the server and results in a new item in the ng-grid table. I know the name of the new item and I want to check in my protractor test that this item has indeed be added. This is how I'm trying to do it:
var nameTestItem="mySuperItem";
//... some actions to add the element, and going back to the page with the table
browser.get('#/resTable');
//checking if there is new item with this title
var element_added=false;
//picking the cell of the table with css selector and searching for the text
element.all(by.css('div.ui-grid-cell-contents')).then(function(elements) {
_.forEach(elements, function(el) {
el.getText().then(function(text){
console.log(text);
element_added= element_added || (nameTestItem==text);
});
});
});
browser.sleep(1000);
browser.pause();
expect(element_added).toBeTruthy();
apparently my problem is that I'm dealing with a lot of promises. How would you tackle this problem? I really don't want to rely on count() because I don't want collisions when several people execute the test.
I'd use filter() instead:
var results = element.all(by.css('div.ui-grid-cell-contents')).filter(function(elm) {
return elm.getText().then(function(text) {
return nameTestItem === text;
});
});
expect(results.count()).toEqual(1);
And, if you are using jasmine-matchers, a bit more readable way:
expect(results).toBeNonEmptyArray();

Using Protractor, how do I get a random select option other that the currently selected one?

I want to handle a test scenario for a select menu where spec would pick a random option other than the one currently selected. I can't seem to find a working locator or method that would let me do it.
Strategy 1: get the current index of option[selected] and select random off other indices.
Strategy 2: get options not selected by.css('option:not([selected])') - get the length of the array and pick random one. This selector seems to ignore the :not part and returns total number of options.
As I am fresh to protractor, I don't really see a way to do that looking at the API. Any hint, please?
First, let's filter non-selected options using .filter():
var nonSelectedOptions = $$("option").filter(function (option) {
return option.isSelected().then(function (isSelected) {
return !isSelected;
});;
});
Now, we need a list of indexes to choose a random one from, let's use .map():
var indexes = nonSelectedOptions.map(function (option, index) {
return index;
});
Now, we need to resolve the indexes promise to get the array of indexes and use the "picking a random item from an array" solution from this answer. We are going to use .get() to get a specific option for the randomly chosen index:
indexes.then(function (indexes) {
var randomIndex = indexes[Math.floor(Math.random()*indexes.length)];
var randomOption = nonSelectedOptions.get(randomIndex);
// now select the randomOption
});
If you would use the wrapper suggested here, the option selection code would be:
randomOption.getAttribute("value").then(function (optionValue) {
mySelect.selectByValue(optionValue);
});
Not tested.

AngularFire - manage child_added and other events on $asArray

Hello guys !
I wanted to do something pretty simple : display the last 10 posts with the newer at the top, and when someone posts a new one, display a bar "click here to see x new posts" that when clicked displays the new ones.
My problem : when a new posts enters Firebase, it immediately displays on screen (using the ng-repeat on the array of the scope linked to the array from Firebase), and takes the older of the 10 elements out.
$firebase(ref.limitToLast(10)).$asArray().$loaded().then(function(messagesData) { ... }
I can detect the change using
messagesData.$watch(function(data) {
console.log("data changed!", data, messagesData);
if(data.event == "child_added") {
// work here
}
});
But I can't figure out how to do what I'm trying to, nor did I find it in the doc. Thanks for any help !
Okay, so, there is a solution. It's possible not to have everything in sync while still enjoying the use of AngularFire for the current elements. Here is how.
Start by getting the messages you want to display at the beginning :
$firebase(ref.orderByKey().limitToLast(nbMessages)).$asArray().$loaded().then(function(messagesData) { ......
Then link them to another array one by one using foreach :
angular.forEach(messagesData, function(value, key) {
messagesDataReturn[key] = value;
// get any other data you need for that message (user name, etc...
});
This will keep every element iterated in sync. So if there is something like a counter of likes, they will be updated live.
Finally add a watcher :
messagesData.$watch(function(data) {
if(data.event == "child_added") {
var newLine = messagesData.filter(function ( obj ) {
return obj.$id === data.key;
})[0];
// add any needed data for this new line
$rootScope.$broadcast('list:updated', newLine);
}
});
In your controller you just have to listen to the broadcast :
$scope.newData = [];
$scope.$on('list:updated', function (event, data) {
$scope.newData.push(data);
});
You can then use $scope.newData to display the way to show the new messages and onclick merge this array with your main one on the scope, so the new messages appears.
Hope this helps someone !

Find a marker in a leaflet markercluster group

I want to find a marker by it's marker.title property in a leaflet markercluster group. I'm using the angular-leaflet directive (v 0.7.5) with angular (1.2.8). The marker data is provided by a service. Here is the code I'm using:
$timeout(function(){
leafletData.getLayers().then(function(layers) {
$scope.markerClusterGrp = layers.overlays.locations;
var clusters = $scope.markerClusterGrp.getLayers();
for (var i in clusters){
if (marker.title == clusters[i].options.title) {
childMarker = clusters[i];
break;
}
}
});
},1000);
This code works. However after I apply a filter to $scope.markers, the list of options.titles returned by above function is different from the list of marker.titles that's in $scope.markers. I've created a jsfiddle to illustrate this behavior. Please look at the console.log statements to see it in action. I am not sure if this is a bug or some error in my code.
http://jsfiddle.net/mukhtyar/z2Ucr/
My end goal is to find the visible parent of the marker i'm interested in and highlight the parent cluster in a mouseover event. Any help in explaining this behavior or suggestions on a different way to approach this problem are much appreciated! Thank you.
Edit: I got around this by using the clusters latlng instead of title.
//Iterate through all the markers
for (var i in clusters){
if ((marker.lat == clusters[i]._latlng.lat) &&
(marker.lng == clusters[i]._latlng.lng)) {
childMarker = clusters[i];
break;
}
}

Resources