I am trying to get specific values from an observablearray. In this example I'm trying to get the number of two values I have created in a array.
I have set the code up like this:
HTML
<div id="test" style="width: 100%">
<table style=" width: 50%; display: block; float: left; margin: 0; padding: 0; border: none">
<tr>
<td>Item 1</td>
<td><span data-bind="text: someArray()[0].number"></span></td>
</tr>
<tr>
<td>Item 2</td>
<td><span data-bind="text: someArray()[1].number"></span></td>
</tr>
</table>
</div>
JavaScript
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js" type="text/javascript"></script>
<script src="https://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js" type="text/javascript"></script>
<script type="text/javascript">
var viewModel = {
someArray: ko.observableArray([]) //
}
ko.applyBindings(viewModel);
$(function () {
getData();
});
var getData = function () {
viewModel.someArray([
{
number: 123,
text: "abc"
},
{
number: 456,
text: "def",
}
]);
}
</script>
I have also uploaded it to JSfiddle.
How can I access specific positions in the array?
The following works:
var viewModel = {
someArray: ko.observableArray([]) //
}
var getData = function () {
viewModel.someArray.push(
{number: 123,text: "abc"},
{number: 456,text: "def"},
{number: 789,text: "ghi"}
);
}
getData();
ko.applyBindings(viewModel);
You were pushing an array into viewModel.someArray rather than 3 separate objects. You also have to apply the bindings after inserting the array objects or else you will get a cannot parse binding exception when there are no rows in someArray.
You might consider using the foreach binding to better fit your needs Knockout Foreach Binding. Then you can call applyBindings anytime. If you're going to have other rows in the array that you don't want, create a computed observable array out of the indices that you do want and build a foreach binding around that.
Your fiddle doesn't work for a few reasons, the biggest being that you're trying to use JQuery, but it's not loaded into the page.
Here's the modified javascript that works:
var viewModel = {
someArray: ko.observableArray([]) //
}
var getData = function () {
viewModel.someArray.push(
{
number: 123,
text: "abc"
});
viewModel.someArray.push(
{
number: 456,
text: "def",
});
viewModel.someArray.push(
{
number: 789,
text: "ghi",
});
}
getData();
ko.applyBindings(viewModel);
Related
I want to access the data object from the methods and modify the values inside the data object, the name of the object I want to access comes from the function parameter
template
<tbody>
<td>B</td>
<td>High Kick Fwd</td>
<td>{{kal.totalPoints}}</td>
<template v-for="elements in kal">
<td
v-for="item in elements"
:key="elements[item]"
#click="universalfunction(kal)" // passing the object
:class="{red:item.mistake,green:!item.mistake}"
class="workingElemments"
>{{item.Marks}}</td>
</template>
<td>{{parseFloat(kal.markObtain).toFixed(2)}}</td>
</tbody>
data: function() {
return {
kal: {
elements: {
below90: { mistake: false, Marks: 0.4 },
bodyPosition: { mistake: false, Marks: 0.2 },
toeFlex: { mistake: false, Marks: 0.1 },
Hezitation: { mistake: false, Marks: 0.1 }
},
totalPoints: 1,
markObtain: 1.0
}
}
universalfunction: function(pObject) {
this.pObject.elements.mistake = true; //error: cannot access the elements of undefined
}
what I want is the function should access the data object according to the parameter name and modify the original object
pObject isn't a property on the this object - it's been passed as a parameter to the universalfunction function.
universalfunction: function(pObject) {
this.pObject.elements.mistake = true;
}
I've made a few assumptions about what you're trying to achieve but I think you're looking for something like this:
new Vue({
el: '#app',
data: {
kal: {
elements: {
below90: {
mistake: false,
Marks: 0.4
},
bodyPosition: {
mistake: false,
Marks: 0.2
},
toeFlex: {
mistake: false,
Marks: 0.1
},
Hezitation: {
mistake: false,
Marks: 0.1
}
},
totalPoints: 1,
markObtain: 1.0
}
},
methods: {
universalFunction (item) {
item.mistake = !item.mistake;
}
}
});
td {
border: 1px solid black;
padding: 10px;
}
.red {
color: red;
}
.green {
color: green;
}
.workingElements {
background-color: #eee;
cursor: pointer;
}
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<table>
<tbody>
<tr>
<td>B</td>
<td>High Kick Fwd</td>
<td>{{ kal.totalPoints }}</td>
<td
v-for="(item, propertyName) in kal.elements"
:key="propertyName"
:class="item.mistake ? 'red': 'green'"
class="workingElements"
#click="universalFunction(item)"
>
{{item.Marks}}
</td>
<td>{{ kal.markObtain.toFixed(2) }}</td>
</tr>
</tbody>
</table>
</div>
I've removed one of the loops as it didn't seem to make sense with the data you had. Looping over the outer object was causing two unnecessary empty cells to be created in the table. Instead I'm just looping over the elements and passing the relevant item to the method when it's clicked.
An alternative would be to pass the name using #click="universalFunction(propertyName)" (where I've declared propertyName in my v-for) and then have the method as:
universalFunction (name) {
this.kal.elements[name].mistake = true;
}
Personally I would recommend using an array rather than an object to store the elements.
I'm trying to make a RuneScape hiscores checker in Meteor.js. I'm now successfully parsing data from their API and saving it as object (you can see app in action here: http://rs-hiscores.meteor.com/). Here's my server code:
var cheerio = Meteor.npmRequire('cheerio');
var BASE_SKILLS = [
'overall', 'attack', 'defence', 'strength', 'hitpoints', 'ranged',
'prayer', 'magic', 'cooking', 'woodcutting', 'fletching', 'fishing',
'firemaking', 'crafting', 'smithing', 'mining', 'herblore', 'agility',
'thieving', 'slayer', 'farming', 'runecrafting', 'hunter', 'construction'
];
var skills = {
'osrs': BASE_SKILLS,
'rs3': BASE_SKILLS.concat('summoning', 'dungeoneering', 'divination')
};
var urls = {
'osrs': 'http://services.runescape.com/m=hiscore_oldschool/index_lite.ws?player=',
'rs3': 'http://hiscore.runescape.com/index_lite.ws?player='
};
Meteor.methods({
lookup: function(player, game) {
if (!urls.hasOwnProperty(game)) {
game = 'rs3';
}
var url = urls[game].concat(encodeURIComponent(player));
result = Meteor.http.get(url);
$ = cheerio.load(result.content);
body = $.html();
parsed = Meteor.call('parseStats', body, skills[game]);
return parsed;
},
parseStats: function(raw, skillsList) {
var stats = raw.split('\n').slice(0, skillsList.length);
var statsObj = { };
stats.forEach(function(stat, index) {
var chunk = stat.split(',');
statsObj[skillsList[index]] = {
rank: +chunk[0],
level: +chunk[1],
xp: +chunk[2]
};
});
return statsObj;
}
});
In my client code, I'm testing lookup method manually by providing player name inside function as an argument:
Meteor.call('lookup', 'zezima', 'rs3', function(error, result) {
if (error) {
console.error(error);
}
console.log(result);
Session.set("data", result);
});
Template.rawdata.helpers({
getData: function() {
return Session.get("data");
}
});
Result in console:
{
agility: {
level: 99,
rank: 174,
xp: 100234567
},
attack: {
level: 99,
rank: 601,
xp: 127370193
},
construction: {
level: 99,
rank: 119,
xp: 141170001
},
and so on...
}
Obviously, when I call getData inside my template in html and render it, it shows: [object Object]. So I want to somehow iterate through objects and insert them into table, that will look like this one:
<table id="rs-hiscores">
<thead>
<tr>
<th>Skill name</th>
<th>Rank</th>
<th>Level</th>
<th>Xp</th>
</tr>
</thead>
<tbody id="rs-hiscore-data">
<tr>
<td>Agility</td>
<td>99</td>
<td>174</td>
<td>109317063</td>
</tr>
<tr>
<td>Attack</td>
<td>99</td>
<td>601</td>
<td>127370193</td>
</tr>
<tr>
<td>Construction</td>
<td>99</td>
<td>119</td>
<td>141170001</td>
</tr>
...and so on ...
</tbody>
</table>
How can I do that? And also - whould it be possible to sort results in the same order as skills are inside BASE_SKILLS? Thank for help in advance.
Okay so you're going to need the data in a decent array, in the order of BASE_SKILLS, using Underscore to map the skills from your data to a new array, your getData helper should look like this:
var data = Session.get('data');
var array = _.map( BASE_SKILLS, function(skillName){
var skill = data[skillName]
return {
name: skillName,
rank: skill.rank,
level: skill.level,
xp: skill.xp
};
});
return array;
This makes a neat array that looks like this:
[
{
name: "overall",
rank: 99,
level: 174,
xp: 109317063
},
{
name: "attack",
rank: 23,
level: 201,
xp: 256311563
},
...etc
]
Then you'll need to get those values into the html in the way you want.
<table id="rs-hiscores">
<thead>
<tr>
<th>Skill name</th>
<th>Rank</th>
<th>Level</th>
<th>Xp</th>
</tr>
</thead>
<tbody id="rs-hiscore-data">
{{#each getData}}
<tr>
<td>{{name}}</td>
<td>{{rank}}</td>
<td>{{level}}</td>
<td>{{xp}}</td>
</tr>
{{/each}}
</tbody>
</table>
You will have the lowercase keys in your table though, overall instead of "Overall" but to get nice names you can just make a dictionary helper function that swaps the one for the other like so: {{getNiceName name}}.
I am trying to create a method that receives an Id from the table, when a checkbox has been checked, and should update the $scope.selected in the angular controller.
And when the checkbox is uncheked, then it should remove it from the $scope.selected again.
So far it looks like this:
$scope.updateselection = function(id) {
if ($scope.selected(sort_id) != id) { //what is the correct syntax here??
$scope.selected.push({ sort_id: id });
} else {
$scope.selected.remove({ sort_id: id });
}
};
For interaction with ckeckboxes in Angular I've recommend to use the following directive: http://vitalets.github.io/checklist-model/
If you don't want to use this directive, you can create watcher on your checkboxes table.
For example:
HTML:
<table>
<tr>
<td ng-repeat="item in items">
<input type="checkbox" ng-model="item.value">
</td>
</tr>
<table>
JS:
$scope.items = [
{
name: "name1",
value: true
},
{
name: "name2",
value: false
}
];
$scope.selectedItems = [];
$scope.$watch('items', function(newValues){
$scope.selectedItems.length = 0;
angular.forEach(newValues, function(item) {
if (item.value == true) {
$scope.selectedItems.push(item.name);
}
});
console.log($scope.selectedItems);
}, true);
This way will allow you to always have the actual information about the selected checkbox.
I've created JSFiddle with working example for you.
I'm looking for a way to add rows to a table. My data structure looks like that:
rows = [
{ name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
{ name : 'row2' }
];
I want to create a table which looks like that:
table
row1
row1.1
row1.2
row2
Is that possible with angular js ng-repeat? If not, what would be a "angular" way of doing that?
Edit:
Flatten the array would be a bad solution because if i can iterate over the sub elements i could use different html tags inside the cells, other css classes, etc.
More than one year later but found a workaround, at least for two levels (fathers->sons).
Just repeat tbody's:
<table>
<tbody ng-repeat="row in rows">
<tr>
<th>{{row.name}}</th>
</tr>
<tr ng-repeat="sub in row.subrows">
<td>{{sub.name}}</td>
</tr>
</tbody>
</table>
As far as I know all browsers support multiple tbody elements inside a table.
More than 3 years later, I have been facing the same issue, and before writing down a directive I tried this out, and it worked well for me :
<table>
<tbody>
<tr ng-repeat-start="row in rows">
<td>
{{ row.name }}
</td>
</tr>
<tr ng-repeat-end ng-repeat="subrow in row.subrows">
<td>
{{ subrow.name }}
</td>
</tr>
</tbody>
</table>
You won't be able to do this with ng-repeat. You can do it with a directive, however.
<my-table rows='rows'></my-table>
Fiddle.
myApp.directive('myTable', function () {
return {
restrict: 'E',
link: function (scope, element, attrs) {
var html = '<table>';
angular.forEach(scope[attrs.rows], function (row, index) {
html += '<tr><td>' + row.name + '</td></tr>';
if ('subrows' in row) {
angular.forEach(row.subrows, function (subrow, index) {
html += '<tr><td>' + subrow.name + '</td></tr>';
});
}
});
html += '</table>';
element.replaceWith(html)
}
}
});
I'm a bit surprised that so many are advocating custom directives and creating proxy variables being updated by $watch.
Problems like this are the reason that AngularJS filters were made!
From the docs:
A filter formats the value of an expression for display to the user.
We aren't looking to manipulate the data, just format it for display in a different way. So let's make a filter that takes in our rows array, flattens it, and returns the flattened rows.
.filter('flattenRows', function(){
return function(rows) {
var flatten = [];
angular.forEach(rows, function(row){
subrows = row.subrows;
flatten.push(row);
if(subrows){
angular.forEach(subrows, function(subrow){
flatten.push( angular.extend(subrow, {subrow: true}) );
});
}
});
return flatten;
}
})
Now all we need is to add the filter to ngRepeat:
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th>Rows with filter</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows | flattenRows">
<td>{{row.name}}</td>
</tr>
</tbody>
</table>
You are now free to combine your table with other filters if desired, like a search.
While the multiple tbody approach is handy, and valid, it will mess up any css that relies on the order or index of child rows, such as a "striped" table and also makes the assumption that you haven't styled your tbody in a way that you don't want repeated.
Here's a plunk: http://embed.plnkr.co/otjeQv7z0rifPusneJ0F/preview
Edit:I added a subrow value and used it in the table to show which rows are subrows, as you indicated a concern for being able to do that.
Yes, it's possible:
Controller:
app.controller('AppController',
[
'$scope',
function($scope) {
$scope.rows = [
{ name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
{ name : 'row2' }
];
}
]
);
HTML:
<table>
<tr ng-repeat="row in rows">
<td>
{{row.name}}
<table ng-show="row.subrows">
<tr ng-repeat="subrow in row.subrows">
<td>{{subrow.name}}</td>
</tr>
</table>
</td>
</tr>
</table>
Plunker
In case you don't want sub-tables, flatten the rows (while annotating subrows, to be able to differentiate):
Controller:
function($scope) {
$scope.rows = [
{ name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
{ name : 'row2' }
];
$scope.flatten = [];
var subrows;
$scope.$watch('rows', function(rows){
var flatten = [];
angular.forEach(rows, function(row){
subrows = row.subrows;
delete row.subrows;
flatten.push(row);
if(subrows){
angular.forEach(subrows, function(subrow){
flatten.push( angular.extend(subrow, {subrow: true}) );
});
}
});
$scope.flatten = flatten;
});
}
HTML:
<table>
<tr ng-repeat="row in flatten">
<td>
{{row.name}}
</td>
</tr>
</table>
Plunker
Here is an example. This code prints all names of all the people within the peopeByCity array.
TS:
export class AppComponent {
peopleByCity = [
{
city: 'Miami',
people: [
{
name: 'John', age: 12
}, {
name: 'Angel', age: 22
}
]
}, {
city: 'Sao Paulo',
people: [
{
name: 'Anderson', age: 35
}, {
name: 'Felipe', age: 36
}
]
}
]
}
HTML:
<div *ngFor="let personsByCity of peopleByCity">
<div *ngFor="let person of personsByCity.people">
{{ person.name }}
</div>
</div>
I am having a viewmodel and an associated template as below.
var AilmentItem = function () {
this.SelectedAilment = ko.observable();
}
function AilmentsViewModel() {
this.Ailments = ko.observableArray([new AilmentItem()]);
this.AilmentsType = ko.observableArray([{ Name: 'Diabetes' }, { Name: 'Arthritis' }, { Name: 'High BP'}]);
}
HTML script
<script type="text/javascript">
$(function () {
var AilmentsVM = new AilmentsViewModel();
ko.applyBindings(AilmentsVM, $('#Ailments')[0]);
});
</script>
<div id="Ailments">
<div>
<table>
<tbody data-bind='template: { name: "ailmentRowTemplate", foreach: Ailments }'>
</tbody>
</table>
</div>
</div>
<script type="text/html" id="ailmentRowTemplate">
<tr>
<td><select data-bind="options: AilmentsVM.AilmentsType(), optionsText: 'Name', value: SelectedAilment"></select></td>
</tr>
</script>
In the HTML template I need to bind AilmentsType to one of the columns as drop down. Can someone guide me how to achieve it? Thanks.
Your AilmentsVM does not have global scope, because it is being created in your jQuery ready block, so you can't access it directly in a data-bind.
If you are using 1.3 beta, then you can use either the $root or $parent special variables that Knockout provides. In this case, they would be the same, as you are only one level in from the top-level scope. So, just do: $root.AilmentsType.
If you are using an earlier version, then you can use the templateOptions functionality to pass options to a jQuery template. It would look like this:
<tbody data-bind='template: { name: "ailmentRowTemplate", foreach: Ailments, templateOptions: { types: AilmentsType } }'>
</tbody>
Then, access it like:
<select data-bind="options: $item.types, optionsText: 'Name', value: SelectedAilment"></select>