Access Array via Index - javascript

I tried figuring this out by reading both how the Tags are handled in the Todo-List Example and how the RSVPS are handled in the Parties example, but I could not figure out a general way to achieve my Goal.
I have a Plan Collection which has a name ownerid and excerciselist
Plans.insert({name: names[i], ownerId: 1, excerciselist: excercises});
Now in this Excerciselist, I want to add an undefined Amout of Excercises by ID.
I have an Excercisecollection with the following:
Excercises.insert({name: names[i], muscle: muscles[i], device: devices[i], description: descriptions[i]});
Now all these Excercises have a unique _id element by default.
Adding things to the excerciselist no Problem I can do that by Push.
But what I can not figure out is, how I can access only certain ID's in the excerciselist via it's Index.
I can only access the whole Stringarray and output in in HTML via
{{#each planarray}}
{{excerciselist}}
{{/each}}
But there is no possiblity to do smoething like
{{ excerciselist }}
I have also tried returning only excerciselist to the planarray, but the problem is that because it is only indexed and not mapped it can not be accessed by the LiveHTML.
Does anyone have an Idea how this problem could be solved?

Why don't you add a field for the unique id to the Excersies insert?
Excercises.insert({ uniqueID: [i], name: names[i], muscle: muscles[i], device: devices[i], description: descriptions[i]});
This way you can get just the excercise you want based on the uniqueID-field.
Oh and you should probably call "uniqueID" something that makes more sense.

I found a little Workaround which is not exactly what I had in mind but it gets the job done.
Template.editplan.excercises = function() {
var names = [];
var add = [];
var adder = null;
for(var i = 0; i < this.excerciselist.length; i++)
{
add[i] = [];
adder = Excercises.find({_id: this.excerciselist[i]});
add[i]['_id'] = this.excerciselist[i];
add[i]['location'] = i;
adder.forEach(function (obj) {
add[i]['name'] = obj.name;
});
names.push(add[i]);
}
return names;
};
Basically I made a new Array in which i put the Data I want to have so I can read it in the LiveHTML see example below
{{#each planarray}}
<h1>{{name}}</h1>
{{#each excercises}}
{{name}}
{{/each}}
<select name="selectedexcercise{{_id}}" id="selectedexcercise{{_id}}">
{{> excerciseoption}}
</select>
<input type="button" class="addtoplan" value="Eine Übung hinzfügen">
{{/each}}
But there must be a more efficient or nice way.... At least I hope so!

Related

angularjs: resolve value from key pair

Is there any easy way to find a key in array and return its value instead of the key with angularjs (maybe by using expressions)?
right now, i do something like this:
vm.topics = [{id:1,value:"a"} , {id:2,value:"b"} , {id:3,value:"c"}];
vm.tickets= [{topic:2,summary:"summary 1"} , {topic:1,summary:"summary 2"}];
vm.getTopicName = function (id) {
for (int t=0 ; t<vm.topics.length ; t++)
if (vm.topics[t].id == id)
return vm.topics[t].value;
return id;
};
and in html part:
<tr data-ng-repeat="item in vm.tickets">
<td>{{vm.getTopicName(item.topic)}}</td>
<td>{{item.summary}}</td>
</tr>
Is there anything like
<td>{{item.topic | id as value in vm.topics}}</td>
Funny example, but i think it shows the point.
--- UPDATE ---
as #jantimon mentioned in comments, one way would be to change list to an object of real key pairs and simplify everything:
vm.topics1 = {};
for (var i=0; i < vm.topics.length; i++) {
var t = vm.topics[i];
vm.topics1[t.id] = t.value;
}
and HTML simply changes to:
<td>{{vm.topics1(item.topic)}}</td>
Actually you use two different arrays and from your comment (ajax call) I would create new array in Service that merges topics and tickets, something like:
[... ,
{
topic:2,
summary:"summary 1",
ticket_value: "b"
},
... ]
So controller should only draw it (without additional logic). This way will reduce watchers from your ng-repeat loop and you don't need to call (rebuild) getTopicName()
This is a right way I think,
To simplify your example, if you use Underscorejs library, you can write:
<td>{{topics[_.findIndex(topics, {id: item.topic})].value}}</td>
Demo in Fiddle
HTML
<div data-ng-repeat="item in tickets">
<td>{{topics[_.findIndex(topics, {id: item.topic})].value}} </td>
</div>
JS
$scope._ = _;
$scope.topics = [{id:1,value:"a"} , {id:2,value:"b"} , {id:3,value:"c"}];
$scope.tickets = [{topic:2,summary:"summary 1"} , {topic:1,summary:"summary 2"}];
**As a side note, try to avoid calling methods from HTML like:
<td>{{vm.getTopicName(item.topic)}}</td>

Push data into object AngularJS

I have several checkboxes that you can click on. For every checkbox you click on, an ID, that is connected to the checkbox, is pushed into an array:
HTML:
<tr ng-repeat="info in test">
<td>{{info.stracka}}</td><td>{{info.tid}}</td>
<td><input type="checkbox" id="{{info.id}}" class="checkboxfisk" ng-click="testa(info.id)">
</tr>
Controller:
$scope.testa = function(id) { //När vi klickar i de olika checkboxarna, så fyllas arrayen med dessa
$scope.checkboxes.push(id);
console.log($scope.checkboxes);
};
After this, I'm supposed to send this array containing the ID's to my backend. However, I want to send this Id's in an Object, instead of an array. How can I "push" data, like I do above, into and Object?
You can't push data into object. Consider object as an collection of key value pair. For every value you want to store into object, you need a key. That's why I was asking about your server side object.
So, in your case, before storing data into an object, i need a key. So, I have to do this like this
var testa = function(id){
checkbox[id]=id;
}
This code would definitely store your keys in the object, but look at this.
Object {5: 5, 6: 6}
This is really how the values are stored.
If you use array instead, your working would be simplified a lot on server side.
I am not sure why you need that but one possible solution is
Add name to your checkbox
<input type="checkbox" id="{{info.id}}" class="checkboxfisk" ng-click="testa(info.id, 'UNIQUE_NAME')">
init check boxes to object
$scope.checkboxes = {};
then modify you function
$scope.testa = function(id, name) {
$scope.checkboxes[name] = id;
console.log($scope.checkboxes);
};
While not exactly answering your question about "pushing" - since you cannot push to an object, you could convert your array to object just before sending it using this.
function toObject(arr) {
var rv = {};
for (var i = 0; i < arr.length; ++i)
if (arr[i] !== undefined) rv[i] = arr[i];
return rv;
}
In your example, you would do something like
$scope.checkboxesObject = toObject($scope.checkboxes);
and then send this new $scope.checkboxesObject.
However, make sure you really are "supposed" to send this to backend as an object.

AngularJS: ng-repeat for a specific "ID"?

first of all, I'm quite new to angularJS, so be gentle!
Second, I know there are no "ID"s, "class"es etc. in angularJS so the title might be somewhat misleading. However, I failed to describe the issue any better.
Here's the deal: I have an array of elements, and each has its details. Now, I ng-repeat the first set of elements to show the basic info. And when user clicks "details", the corresponding details are loaded. Yet, in my case, each item gets the details of a selected element.
The case in practice: http://jsbin.com/zilayija/2/edit
Is there any way to append the corresponding details to the ones that match Id ONLY?
The real issue here is that you're binding the details to a single array on your $scope, namely details. As this reference is used in each iteration of the ng-repeat, you'll end up showing it everywhere.
Edit: As mentioned in the comments, the original question did not reflect what was actually asked. What was requested was that the details needed to be loaded in asynchronously and only when they were requested (clicked). I therefore updated the code example).
What changed?
HTML:
<li ng-repeat="item in items">
{{item.name}}
show details
<p ng-show="item.detailsVisible" ng-repeat="detail in getDetails(item.id)">
{{detail.belongsToItem}}
</p>
</li>
JS:
var Item = function(id, name){
var promiseDetailsAreLoaded;
var self = this;
this.id = id;
this.name = name;
this.detailsVisible = false;
this.details = [];
this.showDetails = function(){
if(!promiseDetailsAreLoaded){
promiseDetailsAreLoaded = $http.get("").then(function(){
// mock adding details
for(var i = 0; i < itemDetails.length; i++){
if(itemDetails[i].belongsToItem === self.id){
self.details.push(itemDetails[i]);
}
}
});
}
promiseDetailsAreLoaded.then(function(){
self.detailsVisible = !self.detailsVisible;
});
}
};
$scope.items = [
new Item(1, "Item 1"),
new Item(2, "Item 2"),
new Item(3, "Item 3")
];
I guess you want to archive a master/detail view. You need to place the displaying of the details outside the ng-repeat. You also do not need an array for the details. Just use an object on the scope and assign it in getDetails.
http://jsbin.com/heyaqexa

Display json object dynamically

I have a json object in collection which I need to show it on the page.
Here is what I did:
I first call the helpers template then in that I fetch the json object from the collection:
I am using coffeescirpt and jade-handlebars, here goes my code in coffeescript:
Template.test.helpers
test: ->
test = Question.find().fetch();
test
In the console when I do Question.find().fetch() the following thing occurs:
QuestionData: Object
question1: "How many kids do I have ?"
question2: "when will i die ?"
question3: "how many wife do i have ?"
question4: "test"
__proto__: Object
_id: "w9mGrv7LYNJpQyCgL"
userid: "ntBgqed5MWDQWY4xt"
specialNote: "Rohan ale"
Now in the jade when I call the template by:
template(name="hello")
.test {{QuestionData}}
I can see only the [object] [object]. To see the question1,question2 I have to do the following:
template(name="hello")
.test {{QuestionData.question1}}, {{QuestionData.question2}}
How can I dynamically show all the questions without doing {{QuestionData.question1}} ...
Thank You in advance !!!
You can dynamically compose field names in a loop.
b = { q1: 'a1', q2: 'a2', q3: 'a3' };
for (x=1;x<=3;x++) { console.log( b[ 'q' + x ] ) }
That being said, there's a lot here that seems a misunderstanding to me. I'd step back and say that you should look into storing one question per mongo document. This gives you the easiest data for meteor to play with. Or, storing multiple questions in an array:
test = {
questions : [
"How many kids do I have ?"
"when will i die ?"
"how many wife do i have ?"
"test" ] ,
userid: "ntBgqed5MWDQWY4xt",
specialNote: "Rohan ale"
}
The problems come when you think how you store the answers, sort the questions, etc. Probably a collection called questions, with a field maybe called sortOrder, a field called tag, etc.
How did you pick up calling templates this way, rather than having them as html files that a router manages for you?
instead of just returning your json object with Questions.find().fetch() you could add another step to put your data into an array like:
test = function() {
var temp = [];
for (item in Questions.find().fetch()) {
temp.push(item);
};
return temp;
};
return test;
(sorry for not writing in coffee script, i'm not aware of the language abstraction)
To answer your question on how to do it, you can do something like this(in JS, sorry, not a coffeeScripter):
Template.Questionnaire.questions = function () {
var questions = [];
_.each(Object.keys(this), function (field) {
if(/^question.+/.test(field)) {
questions.push({ label: field, question: this[field]});
}
});
return questions;
};
And then in a template:
<template name="Questionnaire">
{{#each questions}}
<label>{{label}}</label>
<span>{{question}}</span>
{{/each}}
</template>
Something like that. But I agree with Jim Mack and that you should probably be storing this in an array.
Like as JIm Mack Posted, save your collection in an array
first of all insert your question in an array by doing these in your coffeescript:
x = document.getElementById('question-form')
length = x.length
i = 0
question = []
while i< length
aaa = x.elements[i]
question.push
questions: aaa
i++
then since you are using Jade-handlebars you need register helpers
in your jade file do these
{{#each arrayify myObject}}
{{#each this.name}}
p {{questions}}
{{/each}}
{{/each}}
The arrayify and myObject are the handlebars helpers. Then in your coffeescript
Handlebars.registerHelper "arrayify", (obj) ->
result = []
for temp in obj
userQuestion = temp.question
result.push
name: userQuestion
return result
Template.templatename.myObject = ->
temp = []
for item in Question.find().fetch()
temp.push item
return temp
Hope these will work.

How can you resolve references to db records in other collections before rendering in meteor?

I am using meteor on a page that displays a list of transactions. Transaction documents look like so:
{ payer: 'username1', payee: 'username2', amount: 100, date: someDate }
To display a list of transactions like this:
<ul class="recent-transactions">
{{#each transactions}}
<li>{{this.amount}} to {{this.payee}} from {{this.payer}}</li>
{{/each}}
</ul>
In the helper, or right before rendering the template somehow, I would like to replace the payee and payer property with the actual user documents I can get with Meteor.users.findOne({ username: someUsername}) so that the template can look like this:
<ul class="recent-transactions">
{{#each transactions}}
<li>{{this.amount}} to {{this.payee.profile.FirstName}} from {{this.payer.profile.FirstName}}</li>
{{/each}}
</ul>
Is there a way to do that?
You can also use a transform function (see http://docs.meteor.com/#collections under transform)
Template.hello.transactions = function(){
return Transactions.find({}, {transform: function(doc) {
var payee = Meteor.users.findOne({username: doc.payee});
var payer = Meteor.users.findOne({username: doc.payer});
doc.payer_FirstName = payeer.profile.FirstName;
doc.payee_FirstName = payee.profile.FirstName;
return doc;
}
});
}
The transform function can let you change a document before it is returned, all the existing fields stay and both payee_FirstName & payer_FirstName are now added as if they were part of the collection.
In the above example I have used Meteor.users. Make sure all the user's details are published in a secure fashion. By default I think meteor hides all but the logged in user.
So you can resolve it to a different collection. In this case the payer/payee record is matched up to the username in Meteor.users. So you can pull out the payee/payer's details without having to store all of the details in Transactions.
I would make transactions into a function that returns the correct data. The point of handlebars is that it is supposed to be 'logic-less' so you should abstract away any processing from the actual template.
I would do it by creating a function for the helper like so:
Template.transactionTemplate.transactions = function(){
recentTransactions = [];
Transcationscollection.find({/* your query */}).forEach(function(transaction){
recentTransactions.push({
"amount" : transaction.amount,
"payeeFirstName" : payee.findOne(/* do something with transaction.payee */).profile.FirstName,
"payerFirstName" : payer.findOne(/* do something wtih transaction.payer */).profile.FirstName
});
});
/* Personally I convert it to json to display in handlebars... not sure if you have to its just how I do it. */
return EJSON.stringify(recentTransactions);
};

Categories