I have a column chooser widget that contains list of names with unique IDs. There is a backend service that returns an Object with the data.
output = {
id: 1, name: "john", title: "developer",
id: 2, name: "mark", title: "designer",
id: 3, name: "sally", title: "HR"
...
}
I need to create a table in html using AngularJS that dynamically adds/removes rows as I select the specific ID from my column choser.
<div ng-app="MyApp" ng-controller="MyController">
<table border = "1">
<tr>
<th>ID</th>
<th>Name</th>
<th>Title</th>
</tr>
<tbody ng-repeat="m in output">
<tr>
<td>{{m.id}}</td>
<td>{{m.name}}</td>
<td>{{m.title}}</td>
</tr>
</tbody>
</table>
</div>
What should I add in my controller to get the desired result? Is there any way other than using $scope?
Your output object should be an array of objects:
Example Blow:
$sope.output = [
{ id: 1, name: "john", title: "developer"} ,
{ id: 2, name: "mark", title: "designer"} ,
{ id: 3, name: "sally", title: "HR"}
];
Backend service should return an Object with the data in below format to iterate that in the HTML template.
[{id: 1, name: "john", title: "developer"},
{id: 2, name: "mark", title: "designer"},
{id: 3, name: "sally", title: "HR"}]
Demo :
var module = angular.module('myApp',[]);
module.controller("myController", function($scope) {
$scope.output = [{id: 1, name: "john", title: "developer"},
{id: 2, name: "mark", title: "designer"},
{id: 3, name: "sally", title: "HR"}];
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myController">
<table border = "1">
<tr>
<th>ID</th>
<th>Name</th>
<th>Title</th>
</tr>
<tbody ng-repeat="m in output">
<tr>
<td>{{m.id}}</td>
<td>{{m.name}}</td>
<td>{{m.title}}</td>
</tr>
</tbody>
</table>
</div>
I think you want something like this, try it and let me know
Select the multiple column names to be displayed by holding ctrl key
angular.module("MyApp", [])
.controller("MyController", ($scope) => {
$scope.columns = []
$scope.output = [{
id: 1,
name: "john",
title: "developer"
},
{
id: 2,
name: "mark",
title: "designer"
},
{
id: 3,
name: "sally",
title: "HR"
}
]
$scope.getColumns = () => {
return Object.keys($scope.ouput).filter(name => $scope.columns.indexOf(name) !== -1)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="MyApp" ng-controller="MyController">
<select ng-model="columns" multiple>
<option value="id">ID</option>
<option value="name">Name</option>
<option value="title">Title</option>
</select>
<table border="1">
<tr>
<th ng-repeat="column in columns">{{column}}</th>
</tr>
<tbody>
<tr ng-repeat="m in output">
<td ng-repeat="column in columns">{{m[column]}}</td>
</tr>
</tbody>
</table>
</div>
Related
I have an object that look like this:
groupedContacts:
{
4: [
{name: 'foo'},
{name: 'bar'}
],
6: [
{name: 'foo bar'},
]
}
Then I have an other array:
companyList.models:
models: [
{id:4, name: 'company A'},
{id:6, name: 'company B'},
]
So the Id's in my companies resemble the keys in my groupedContacts, in 1 array I have the company names and in the other I have the contacts of the companies.
Now I want to show them in multiple tables ofcourse like this
Table 1
Company A (id4)
- foo
- bar
Table2
Company B (id6)
- foo bar
Here's my code, unfortunately I get my 2 tables of my 2 companies but no contacts. And I get no errors whatsoever:
<div
v-for="(company, key) in companyList.models"
:key="key"
class="table table--hover mt-4"
>
<h4 class="mb-4" v-html="company.name" />
<table>
<thead>
<tr>
<th class="text-left" v-html="__t('name')" />
</tr>
</thead>
<tbody
v-for="(groupContacts, key) in groupedContacts"
:key="key"
v-if="company.id === key"
>
<tr
v-for="(contact, contactKey) in groupContacts"
:key="contactKey"
>
<td v-html="contact.name" />
</tr>
</tbody>
</table>
</div>
This is my result in my browser:
I recommend to use a computed property called companies as follows :
computed: {
companies() {
return this.models.map(c => {
c.groups = this.groupContacts[c.id];
return c;
})
}
}
then loop through it like :
<div v-for="(company, key) in companies" :key="key" class="table table--hover mt-4">
<h4 class="mb-4">{{company.name}}</h4>
<table>
<thead>
<tr>
<th class="text-left">Name</th>
</tr>
</thead>
<tbody>
<tr v-for="(contact, contactKey) in company.groups" :key="contactKey">
<td> {{contact.name}}</td>
</tr>
</tbody>
</table>
</div>
new Vue({
el: '#app',
data() {
return {
groupContacts:
{
4: [{
name: 'foo'
},
{
name: 'bar'
}
],
6: [{
name: 'foo bar'
}, ]
},
models: [{
id: 4,
name: 'company A'
},
{
id: 6,
name: 'company B'
},
]
}
},
computed: {
companies() {
return this.models.map(c => {
c.groups = this.groupContacts[c.id];
return c;
})
}
}
})
<body>
<div id="app">
<div v-for="(company, key) in companies" :key="key" class="table table--hover mt-4">
<h4 class="mb-4">{{company.name}}</h4>
<table>
<thead>
<tr>
<th class="text-left">Name</th>
</tr>
</thead>
<tbody>
<tr v-for="(contact, contactKey) in company.groups" :key="contactKey">
<td> {{contact.name}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
mixing v-if with v-for should be avoided (as stated in the Vue.js docs).
Following the recommendation from the docs, you could use a computed property for this case:
<template>
<div>
<div v-for="(company, i) in companyContacts" :key="`company_${i}`" class="table table--hover mt-4">
<h4 class="mb-4" v-html="company.name" />
<table>
<thead>
<tr>
<th class="text-left" v-html="__t('name')" />
</tr>
</thead>
<tbody>
<tr v-for="(contact, j) in company.contacts" :key="`company_${i}_contact_${j}`">
<td v-html="contact.name" />
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
computed: {
companyContacts() {
return this.companyList.models.map(model => {
model.contacts = this.groupContacts[model.id]
return model
})
}
},
data: () => {
groupedContacts: {
4: [{
name: 'foo'
},
{
name: 'bar'
}
],
6: [{
name: 'foo bar'
}, ]
},
companyList: {
models: [{
id: 4,
name: 'company A'
},
{
id: 6,
name: 'company B'
},
]
}
}
}
</script>
Hope this helps!
Let us assume we have following data structure:
var data = [
{
name: "item name",
nestedData: [{
name: "nested name",
quantity: 1
},
{
name: "nested name 2",
quantity: 2
}
]
},
{
name: "item name 2",
nestedData: [{
name: "nested name 3",
quantity: 3
}
]
}
];
Standard behavior of ng-repeat directive will iterate over high level elements. If we run ng-repeat="item in data" it will produce two items.
Is it possible - without using custom directive - to iterate over first item ("item name") twice (multiply it by a length of nestedData array)?
The output I'd like to achieve is:
<table>
<thead>
<th>Name</th>
<th>Nested name</th>
<th>Nested quantity</th>
</thead>
<tbody>
<tr>
<td rowspan="2">item name</td>
<td>nested name</td>
<td>1</td>
</tr>
<tr>
<td ng-hide="true">item name</td>
<td>nested name 2</td>
<td>2</td>
</tr>
<tr>
<td>item name 2</td>
<td>nested name 3</td>
<td>3</td>
</tr>
</tbody>
</table>
Nested ng-repeat is not suitable in this situation because there's a need to iterate over <tr>'s.
You can use a nested ng-repeat to get your desired result as it's valid HTML to have multiple tbody elements.
Here is a JSFiddle for a working example
<table>
<thead>
<th>Name</th>
<th>Nested name</th>
<th>Nested quantity</th>
</thead>
<tbody ng-repeat="item in data">
<tr ng-repeat="nestedItem in item.nestedData">
<td rowspan="{{item.nestedData.length}}" ng-hide="$index == 1">{{item.name}}</td>
<td>{{nestedItem.name}}</td>
<td>{{nestedItem.quantity}}</td>
</tr>
</tbody>
</table>
It's a different approach to achieve expected output.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
var data = [
{
name: "item name",
nestedData: [
{
name: "nested name",
quantity: 1
},
{
name: "nested name 2",
quantity: 2
},
{
name: "nested name 3",
quantity: 3
}
]
},
{
name: "item name 2",
nestedData: [{
name: "nested name 3",
quantity: 3
}
]
}
];
var nestedData = [];
angular.forEach(data, function(item){
if(item.nestedData.length > 1){
angular.forEach(item.nestedData, function(nestedItem){
nestedData.push({
name : item.name,
nestedName: nestedItem.name,
nestedQty: nestedItem.quantity,
colspan: item.nestedData.length
});
});
} else {
nestedData.push({
name : item.name,
nestedName: item.nestedData[0].name,
nestedQty: item.nestedData[0].quantity
});
}
});
$scope.data = nestedData;
});
tr.multiple > td:first-child {
display: none;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div class="container" ng-app="myApp" ng-controller="myCtrl">
<table class="table table-stripped">
<thead>
<th>Name</th>
<th>Nested name</th>
<th>Nested quantity</th>
</thead>
<tbody>
<tr ng-repeat="item in data" ng-class="{'multiple': item.colspan > '1' && !$first}">
<td rowspan="{{item.colspan ? item.colspan : '1'}}">{{item.name}}</td>
<td>{{item.nestedName}}</td>
<td>{{item.nestedQty}}</td>
</tr>
</tbody>
</table>
</div>
I am trying to create a drop-down list using angular-Js.
The element that is coming should be currently selected in drop-down list is coming from some databse.(Initialized as $scope.choice = "two", in sample app).
With the below code, last element in the list is the sleected element alwyas.
Also, in the associated text box, I want the description to change based on the selected element, but it always defaults to the description of the first item in list box.
JS File
var app = angular.module('app',[]);
app.controller('Test',function($scope)
{
$scope.choice = "two";
$scope.items = [{name: 'one', age: 30, description: 'Thirty' },{ name: 'two', age: 27, description: 'Twenty Seven' },{ name: 'three', age: 50, description: 'Fifty' }];
});
HTML
<html ng-app="app">
<body>
<div ng-controller="Test">
<table class="table table-striped">
<tbody>
<tr>
<td><select
ng-model="selectedItem"
ng-options="item.name for item in items track by item.name" ></select></td>
<td><input type="text" name="field" ng-model=" selectedItem.description"
class="form-control"ng-pattern="/{{ selectedItem.description}}/" required /></td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
When I change the drop-down element in list, I get the following error:
Error: [$parse:syntax] http://errors.angularjs.org/1.3.2/$parse/syntax?p0=undefined&p1=not%20a%20primary%20expression&p2=null&p3=%2F%2F&p4=%2F%2F
at Error (native)
at http://ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js:6:416
at eb.throwError (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js:190:268)
at eb.primary (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js:189:450)
at eb.unary (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js:196:183)
at eb.multiplicative (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js:195:486)
at eb.additive (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js:195:280)
at eb.relational (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js:195:144)
at eb.equality (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js:195:6)
at eb.logicalAND (http://ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js:194:387) <input type="text" name="field" ng-model=" selectedItem.description" class="form-control ng-pristine ng-untouched ng-valid" ng-pattern="/{{ selectedItem.description}}/" required=""> angular.js:11383(anonymous function) angular.js:11383(anonymous function)
Any help with this would be appreciated.
Thanks
That bit is reason of your error
ng-pattern="/{{ selectedItem.description}}/"
if you want choose default option you can do that by:
$scope.items = [{name: 'one', age: 30, description: 'Thirty' },{ name: 'two', age: 27, description: 'Twenty Seven' },{ name: 'three', age: 50, description: 'Fifty' }];
//set default option
$scope.selectedItem = $scope.items[1]
var app = angular.module('app', []);
app.controller('homeCtrl', function($scope) {
$scope.items = [{
name: 'one',
age: 30,
description: 'Thirty'
}, {
name: 'two',
age: 27,
description: 'Twenty Seven'
}, {
name: 'three',
age: 50,
description: 'Fifty'
}];
$scope.selectedItem = $scope.items[1]
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="homeCtrl">
<table class="table table-striped">
<tbody>
<tr>
<td>
<select ng-model="selectedItem" ng-options="item.name for item in items track by item.name"></select>
</td>
<td>
<input type="text" name="field" ng-model=" selectedItem.description" class="form-control" ng-pattern="" required />
</td>
</tr>
</tbody>
</table>
</div>
</div>
I'm trying to get the item that just changed in this small example, is there some kind of context I should use? I hoped it would be as simple as just referring to $this, but that doesn't seem to work.
<html ng-app>
<head>
<script src="https://code.angularjs.org/1.2.9/angular.min.js"></script>
<script>
function myController($scope) {
$scope.items = [
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
{ id: 3, name: "Third" }
];
$scope.test = function() {
// How do I get the object in the list related to the change i did?
// I.e. "{ id: 2, name: "Second" }" for the second item.
};
}
</script>
</head>
<body>
<div ng-controller="myController">
<table>
<tbody ng-repeat="item in items">
<tr>
<td>{{ item.id }}</td>
<td><input type="text" ng-model="item.name" ng-change="test()"/></td>
</tr>
</tbody>
</table>
</div>
</body>
Take a look at this
Working Demo
html
<div ng-app='myApp' ng-controller="myController">
<table>
<tbody ng-repeat="item in items">
<tr>
<td>{{ item.id }}</td>
<td><input type="text" ng-model="item.name" ng-change="test(item.name)"/></td>
</tr>
</tbody>
</table>
</div>
script
var app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.items = [
{ id: 1, name: "First" },
{ id: 2, name: "Second" },
{ id: 3, name: "Third" }
];
$scope.test = function(item) {
alert(item);
};
});
I'm having a hard time getting a filter working. I'm roughly following the example at http://www.knockmeout.net/2011/06/10-things-to-know-about-knockoutjs-on.html, but I just can't seem to make it work. Any help is greatly appreciated.
HTML:
<h1><b>Medical Product Survey</b></h1>
<div class='facilities available'>
<div class='bar'>
<h2>Hospitals / Facilities</h2>
<a class='button add'>+</a>
</div>
<table>
<thead>
<tr>
<th>
<input data-bind="value: filter, valueUpdate: 'afterkeydown'" />
</th>
<th>Modified</th>
<th>Status</th>
</tr>
</thead>
<tbody data-bind="template: { name: 'facilitiesAvailableRow', foreach: filteredFacilties }"></tbody>
</table>
<!-- Row template for available facilities -->
<script type="text/html" id="facilitiesAvailableRow">
<tr>
<td>${name}</td>
<td>${modified}</td>
<td>${status}</td>
</tr>
</script>
</div>
<div class='facilities available'>
<div class='bar'>
<h2>Archived</h2>
</div>
<table>
<thead>
<tr>
<th><input type='search' id='facility_search' /></th>
<th>Archive Date</th>
<th>Size</th>
<th></th>
</tr>
</thead>
<tbody data-bind="template: { name: 'facilitiesArchivedRow', foreach: facilitiesArchived }"></tbody>
</table>
<!-- Row template for available facilities -->
<script type="text/html" id="facilitiesArchivedRow">
<tr>
<td>${name}</td>
<td>${modified}</td>
<td>${size}mb</td>
<td><input type='button' value='restore' /></td>
</tr>
</script>
</div>
Javascript:
//Model for facilities interaction on the front page.
var viewModel = {
facilitiesAvailable: ko.observableArray([
{ name: "Test 1", modified: "1/20/1986", status: "Good to go" },
{ name: "Test 2", modified: "1/21/1987", status: "Good to go2" },
{ name: "Test 3", modified: "1/22/1988", status: "Good to go3" },
{ name: "Test 4", modified: "1/23/1989", status: "Good to go4" },
{ name: "Test 5", modified: "1/24/1990", status: "Good to go5" }
]),
facilitiesArchived: ko.observableArray([
{ name: "Archive 1", modified: "1/20/1982", size: 123 },
{ name: "Archive 2", modified: "1/21/1983", size: 198 },
{ name: "Archive 3", modified: "1/22/1984", size: 340 }
]),
filter: ko.observable(""),
};
//Filtering.
viewModel.filteredFacilities = ko.dependentObservable(function() {
var filter = this.filter().toLowerCase();
if(!filter) {
return this.facilitiesAvailable();
} else {
return ko.utils.arrayFilter(this.facilitiesAvailable(), function(item) {
if(item.name.toLowerCase().search(filter) != -1) {
return true;
}
});
}
}, viewModel);
ko.applyBindings(viewModel);
The error I'm getting is:
Uncaught Error: Unable to parse binding attribute.
Message: ReferenceError: filteredFacilties is not defined;
Attribute value: template: { name: 'facilitiesAvailableRow', foreach: filteredFacilties }
You misspelled filteredFacilties. It should be filteredFacilities in your template binding.
Change:
data-bind="template: { name: 'facilitiesAvailableRow', foreach: filteredFacilties }"
to:
data-bind="template: { name: 'facilitiesAvailableRow', foreach: filteredFacilities }"