Trouble binding complex input to variable, efficiently - angularjs

I'm new to Angular and trying to learn. I am trying to find a way to bind some Quantities with their IDs. Here is my template:
"<div class='tableBorder'><label> <input ng-model='" + modelngQTY + "' type='number' name='{{value.DetailID}}' /> {{value.Text | lowercase}} </label> </div>"
The modelngQTY is:
modelngQTY = "selectedOptions[value.CustomID].Details[value.DetailID].QTY";
Unfortunately the object that gets generated with this code is:
{"36":{"Details":{"107":{"QTY":2323232},"108":{"QTY":232323}}}}
I want it to look like this:
{"36":{"Details":[{DetailID: "107", "QTY":2323232},{DetailID: "108","QTY":232323}]}}
Here is a side-by-side visual of the two. I want the one on the left:
The version on the right is terribly hard to loop through with ng-repeat. I have all the data obviously I just cant figure out how to write the model. I have tried {} and [], as well as a lot of different ngmodel versions. The best I could get was the version on the right.
I have already done a lot of googling and there are tons of resources on how to filter and play with ng-options for ng-repeat but I couldn't find hardly anything about INSERTING the data into the model in a good format.
What am I missing here?

A possible solution to this exists at http://plnkr.co/edit/9wMZAD?p=info.
These map functions
vm.endingStructure= _.map(vm.beginningStructure, function(object, value) {
var newStructure = {};
newStructure[value] = {
Details: _.map(object.Details, function(qtyObject, value) {
var arrayStructure = {
DetailID: value,
QTY:qtyObject.QTY
};
return arrayStructure;
})
};
return newStructure;
});
turns { "36": { "Details": { "107": { "QTY": 2323232 }, "108": { "QTY": 232323 } } } } into [ { "36": { "Details": [ { "DetailID": "107", "QTY": 2323232 }, { "DetailID": "108", "QTY": 232323 } ] } } ]

Related

When using db.someDb.update([]), how do you $set an array in Mongodb without creating an object?

I've seen similar questions to update and set, but none seem to tackle what I'm talking about. Let's say you have antsDb with the below docs
{ "name": "ant1", "antId": 1, "colId": "colony1" }
{ "name": "ant2", "antId": 2, "colId": "colony1" }
{ "name": "ant3", "antId": 3, "colId": "colony2" }
and you have colonyDb with
{ "colId": "colony1", "jobsArray": ["findFood", "workHard", "carryFood", "feedQueen"] }
{ "colId": "colony2", "jobsArray": ["childCare", "cutLeaves", "feedQueen"] }
I want to update every ant so that they have the jobsArray for their respective colony. But I don't want to create an object when I set the jobsArray for the ants. I thought I could just put
db.colonyDb.aggregate([]).forEach(function(col) {
db.antsDb.update([
{
"colId": col.colId
},
{
$set: {
"myAntJobs": col.jobsArray
}
}
])
})
and that would set an array called myAntJobs on each matching ant document where it doesn't exist, resulting in
{ "name": "ant1", "antId": 1, "colId": "colony1", "myAntJobs": ["findFood", "workHard", "carryFood", "feedQueen"] }
{ "name": "ant2", "antId": 2, "colId": "colony1", "myAntJobs": ["findFood", "workHard", "carryFood", "feedQueen"] }
{ "name": "ant3", "antId": 3, "colId": "colony2", "myAntJobs": ["childCare", "cutLeaves", "feedQueen"] }
but I get E QUERY [js] Error: need an object instead. I'm guessing it wants me to put something like:
$set: {
"myAntJobs": {
"myJobs": col.jobsArray
}
}
Won't this make all of the arrays on the ants into objects with one field? Or am I thinking of this the wrong way...
The biggest issue is I don't really have a space for testing (although I put so much effort into making this question, I could probably use what I just typed out lol).
Can someone functionally explain how the $set works in this context? Also if you know of a better way to add an array to resultant docs in a forEach more effectively, please tell me. But I'd like to understand $set in this context first and foremost.

How to uncheck a checkbox that gets filtered in ng-repeat

I've been scratching my head on this one for hours worth of troubleshooting and I can't seem to figure it out so was wondering if any of you could help.
I have an array of objects in a json file, and I'm making a filtering menu based on different properties in the file that one can check/uncheck in view to filter the results. The issue I have is to be able to uncheck any items in the menu that hide as a result of not being available in the current results being displayed.
I have a plunker example here: https://plnkr.co/edit/KZmMiSisA1gKyahG5rHF
Sample from plunker:
$scope.list = [
{ parent : 'fruit', type : 'orange' },
{ parent: 'fruit', type : 'apple' },
{ parent : 'fruit', type : 'kiwi' },
{ parent : 'vegetable', type : 'kale' },
{ parent : 'vegetable', type : 'cabbage' }
];
$scope.filtered = $scope.list;
$scope.selectedType = [];
$scope.selectedParent = [];
$scope.$watch(function () {
return {
selectedType: $scope.selectedType,
selectedParent: $scope.selectedParent,
}
}, function (value) {
var filterType = {
parent : $scope.selectedParent,
type : $scope.selectedType,
};
var startFilter = $scope.list;
for (var i in filterType) {
startFilter = filter(startFilter, filterType[i], i);
}
$scope.filtered = startFilter;
}, true);
Basically, if someone selects "fruit" and then "orange", but then unchecks "fruit", I would want "orange" to uncheck as well.
I just checked your plunker. The code on the bottom is very complicated, but I might be able to help you with these snippets.
Add a ng-change attribute to the parents:
<input type="checkbox"
checklist-model="selectedParent"
checklist-value="key"
data="{{::key}}"
ng-change="checkParent(key, checked)"/>
Now you can detect the changes in your controller:
$scope.checkParent = function(parent, checked) {
if (!checked) {
$scope.list.filter(function(fruit) {
return fruit.parent === parent;
}).forEach(function(fruit) {
$scope.selectedType = $scope.selectedType.filter(function(_selectedType) {
return _selectedType != fruit.type;
});
});
}
};
Plunkr
Beware, that this is inefficient, as it filters Selected type for every fruit to be unselected, it can be refactored with some nice functional tools.
But in general I'd change the controller if possible, and create a map with this structure:
{
parent: {
name: "fruit"
selected: false,
children: [{
type: "organge"
selected: false
}]
...
}
This way you can make your controller code much more readable.
Edit:
I was checking the two filter what you wrote. I couldn't come up with a better code as I still think that you should change the data structure. Iterating over and over lists is an expensive process, and both of your filters has two nested for loops. I cannot think of an easy way of getting rid of them with your data structure.
I spent some time on refactoring your code, getting rid of the watches and utilizing lodash. Check the updated Plunk, I hope it helps.
I added this function to your plunker:
$scope.uncheck = function(key){
$scope.selectedType.splice(key)
}
And this to the parent:
<input type="checkbox" checklist-model="selectedParent" checklist-value="key" data="{{::key}}" ng-change="uncheck(key)" />
It works for me if this is in fact what you are trying to accomplish.

Querying Firebase based on time

I am trying to query my Firebase based on time limits. I am following this blog post with this attached jsFiddle.
The issue is that I am getting a blank firebaseArray back.
var currentTime = (new Date).getTime();
var twentyFoursHoursAgo = currentTime - 86400000;
//scoresRef is defined as new Firebase(http://myfirebase.firebaseio.com/scores)
scoresRef.on('value', function (dataSnapshot) {
var summaryScores = $firebaseArray(scoresRef.orderByChild('timestamp').startAt(twentyFoursHoursAgo).endAt(currentTime));
$scope.summaryScores = summaryScores;
console.log(summaryScores);
}
The idea is that as users add more scores, the array will change. Then I can do different data manipulation on it (like average, etc). That way, there can be a running 24 hour average displayed on the app.
This is what the data looks like in Firebase:
What am I doing wrong? I know the data is in there.
Not sure if this answer your question, but it seems the best I can do is show you something that works.
Querying for a range of timestamps
I added this data structure:
{
"-Jy5pXbn5RpiK1-5z07O": {
"timestamp": 1441076226561
},
"-Jy5pZJsYvsmv_dMtCtn": {
"timestamp": 1441076173543
},
"-Jy5paWbkU6F8C6CEGpj": {
"timestamp": 1441076181550
},
"-Jy5pbc0pJ1I5azenAi5": {
"timestamp": 1441076247056
},
"-Jy5pfnMDKExW2oPf-D-": {
"timestamp": 1441076204166
},
"-Jy5pgk55ypuG9_xICq-": {
"timestamp": 1441076268053
},
"-Jy5phVgU2hDE_izcR8p": {
"timestamp": 1441076271163
},
"-Jy5pilBteGhu05eMWQI": {
"timestamp": 1441076215315
}
}
And then query with this code:
var ref = new Firebase('https://stackoverflow.firebaseio.com/32321406');
var startAt = 1441076265715;
var endAt = startAt + 15000;
var query = ref.orderByChild('timestamp')
.startAt(startAt)
.endAt(endAt);
query.on('value', function(snapshot) {
console.log(snapshot.val());
});
Which outputs:
{
-Jy5pgk55ypuG9_xICq-: {
timestamp: 1441076268053
},
-Jy5phVgU2hDE_izcR8p: {
timestamp: 1441076271163
}
}
And a warning that I should add an indexing rule for timestamp.
jsbin: http://jsbin.com/qaxosisuha/edit?js,console
Binding a collection of data from Firebase to AngularJS
If you're trying to bind the query results to an AngularJS view, you do this by:
$scope.items = $firebaseArray(query);
When using AngularFire don't try to use console.log to monitor what is going in. Instead add this to your HTML:
<pre>{{ items | json }}</pre>
This will print the items and automatically update as the data is asynchronously loaded and updated.
Note that this may be a good time to go through Firebase's AngularFire programming guide, which explains this last bit and many more topics in a pretty easy to follow manner.

displaying nested attributes in backgrid

I have a json object in the following format:
{
properties:{
url:"http://..."
}
}
And I want to display the url in a Backgrid grid. However, I can't figure out how to change the name attribute of the column such that it accesses the nested url. I have tried the following examples to no avail:
{
name: "properties.url",
label: "URL",
cell: "uri"
}
And
{
name: "properties[url]",
label: "URL",
cell: "uri"
}
It seems like a simple enough thing to do but I can't find an answer.
Take a look at Backbone's Wiki.
There are at least 4 choices:
backbone-deep-model
backbone-nested
backbone-nestify
backbone-dotattr
This is the entireity of "backbone-dotattr"
(function(_, Backbone) {
_.extend(Backbone.Model.prototype, {
get: function(key) {
return _.reduce(key.split('.'), function(attr, key) {
if (attr instanceof Backbone.Model)
return attr.attributes[key];
return attr[key];
}, this.attributes);
}
});
})(window._, window.Backbone);
with this, i can specify
name: "child.childAttribute"
works perfectly in the "columns" part for Backgrid. hope it helps.

Wrap items in backbone collection?

I keep running into some confusing solutions and unclear ways to wrap items that match into a div using backbone.
I am just building a simple example for myself, and would like to nest all models in a collection that have the same attribute team, using a comparator works well in organizing the list, but for the life of me I can't find a clear solution to wrapping each so that I have more control over the list of players inside the team.
There has to be a clear easy solution for a beginner like me. I really just want to keep things as clean and simple as possible. My desired html result looks like below.
<div class="pacers">
<li>Paul</li>
<li>Roy</li>
</div>
<div class="bulls">
<li>Kirk</li>
<li>Taj</li>
</div>
Based on a backbone friendly json array like below.
[
{
"name": "Paul",
"team": "pacers"
},
{
"name": "Kirk",
"team": "bulls"
},
{
"firstname": "George",
"team": "pacers"
},
{
"name": "Taj",
"team": "bulls"
}
]
So using a comparator is awesome I just write this comparator : 'team' and it handles the list order for me, cool, but I dont have much control I would like to wrap the list in a more hierarchical system.
Another approach:
If you are using underscore's templates this could be one way of doing it. You can use underscore's groupBy function to group the list based on teams.
var teams = [
{
"name": "Paul",
"team": "pacers"
},
{
"name": "Kirk",
"team": "bulls"
},
{
"firstname": "George",
"team": "pacers"
},
{
"name": "Taj",
"team": "bulls"
}
];
var groupedList = _.groupBy(list, function(l){
return l.team;
});
console.log(JSON.stringify(groupedList));
This is how it would be grouped.
{
"pacers": [
{
"name": "Paul",
"team": "pacers"
},
{
"firstname": "George",
"team": "pacers"
}
],
"bulls": [
{
"name": "Kirk",
"team": "bulls"
},
{
"name": "Taj",
"team": "bulls"
}
]
}
You can then use for each loop and in template and generate HTML in following way. The groupedList is passed as teams to below template.
<%
_.each(teams, function(team, teamName){
%>
<div class="<%=teamName%>">
<%
_.each(team, function(player){
%>
<li><%=player.name%></li>
<%
});
%>
</div>
<%
});
%>
This would generate the HTML the way you expected.
NOTE:
The code snippets are given considering underscore templating, you might have to make changes based on what you use. Hope it helps.
Correct me if I am wrong the problem being described relates more to controlling the contents of each item in relation to it's model as well as how to simply render them in groups.
1) Niranjan has covered grouping out the data into separate lists but remember that this list returned is not a Backbone construct.
2) As per the manual the '_.groupBy' method should be available to you via the collection i.e.:
myCollection.groupBy(etc);
3) I would personally consider mapping the results of the groupBy back into models and pass each and every model into a separate view and render them from within the main list view.
var CollectionView = Backbone.View.extend({
initialize : function () {
// Note: I am pretending that you have a real collection.
this.collection.fetch().then(
this.addAll(true);
);
}
addOne : function (model) {
// call .render individual template items here for each model.
var view = new ItemView(model);
this.$el.append(view.render();
},
addAll : function (groupOpts) {
var col = this.collection;
if(groupOpts === true) {
// Do grouping (or do it in the model). Maybe put back into new collection?
}
_.each(col, function(model) {
this.addOne(model);
}, this);
},
render : function () {
// Render your template here.
}
});
var ItemView = Backbone.View.extend({
render : function () {
}
});
Not a complete example but that's the general pattern I would follow when attempting the same thing. Having an individual view/model for each item, in my opinion, gives you more control.
This could be handled in a pretty crazy view template (depends on your template language)... or you could use a simpler template/view and just make some more crazy collection queries (first using a pluck to get the team, de-dupping that array, then running some where's for each of the teams... but you can see how this gets crazy)
I'd vote for the view and view template should handle this... what are you using? Jade? Mustache?
Something like this - logical psuedo code here since I don't know your template language:
var team;
forEach player in players
if(!team) {
set team = player.team
print open holder and then the first row
} (team !== player.team {
set team = player.team
print close of previous holder, then open holder and then the first row of new team
} else {
print just the player row
}
Even so, you can see how this is a bit dirty in and of itself... but what you are describing is a view/presentation concern, and you can do it here with no new additional loops and maps and wheres (like you'd have to do if you did it in the data layer before calling the views)

Resources