Suppose I have a object like as shown below:
var ob = [
{name: "root",id: 1},
{name: "root2",id: 2}
];
And I want to append children object dynamically to it. For example:
Suppose if I click on id 1 then children object should be appended to ob object.
var ob = [
{name: "root",id: 1, children: [
{name: 'sub1', id:'5'},
{name: 'sub2', id:'6'},
]
},
{name: "root2",id: 2}
];
Now if I click again on id 6 again children should be added to id 6.
var ob = [
{name: "root",id: 1, children: [
{name: 'sub1', id:'5'},
{name: 'sub2', id:'6', children: [
{name: 'subsub1', id:'8'},
{name: 'subsub2', id:'9'},
]
},
]
},
{name: "root2",id: 2}
];
I am trying to write a recursive function for it but no success. On click of any term I have reference only to the clicked term. I don't know about the parent term.
EDIT:
Below is my code:
<div *ngFor = "let term of terms">
<div class="row tr">
<a (click) = "showTerms($event)">{{term.id}}</a>
</div>
<div class="col-xs-6">{{term.desc}}</div>
<app-icd-codes *ngIf = "term.children" [terms] = "term.children"></app-icd-codes>
</div>
Here on click of a tag I am adding children's. So I need to create a dynamic object and update that object as shown above.
The most easy way is pass as argument the index of "terms". Put two buttons, one to AddTerms and another one to hideTerms/showTerms.
<div *ngFor = "let term of terms;let i=index">
<!--see the way to get the index of the array -->
<div class="row tr">
{{term.id}}
<!--you make a link, I use a button-->
<!--the button "add" is visible when there're NOT "children"-->
<button *ngIf="!term.terms" (click)="addTerms(i)">Add</button>
<!--the button to Show/hide is visible when there ARE "children"-->
<button *ngIf="term.terms" (click)="term.show=!term.show">
<span *ngIf="term.show">^</span>
<span *ngIf="!term.show">v</span>
</button>
</div>
<ng-container *ngIf ="term.terms && term.show">
<app-icd-codes [terms]="term.terms"></app-icd-codes>
</ng-container>
</div>
Then you must put your function addTerms. A simple function can be like
//see that you received the "index" of children
addTerms(index: number) {
this.terms[index].show = true; //<--to show the children
this.terms[index].terms = [{ id: 3 }, { id: 4 }]; //a "easy" way to add
}
Ok, really the function must be like
addTerms(index: number) {
let id=this.terms[index].id; //we get the "id"
//call a httpClient and subscribe
this.httpClient.get("url/"+id).subscribe(res=>
//in the subscription
{
this.terms[index].show = true; //<--to show the children
this.terms[index].terms = res
})
}
NOTE: Can result "strange" add new properties to an Object (in this case "children" and "show"). If we feel more confortable, we can add the properies when we create the object with a null value
Related
I wrote a working example of a real problem that I'm trying to solve
I created an object simulating the json return I have from the database
I need:
list contracts and contract batchs
when entering the function, mark the last batch of the contract as selected in the drop-down list
when entering the function, display the invoices for the last batch only of the contract selected in the ul-invoices element.
load and display the respective invoices when changing the batch
Problems:
I cannot list invoices for the last batch of the selected contract
Although there is a function for onchange="getInvoices", I always getInvoices is not defined
Note:
When entering the page, I already have the information of the selected contract, in the case of the example, I left the contract with ID 1.
In the example I am using the in-attendance class to define the selected contract
I am using Revealing Pattern and I want to keep this pattern
<html>
<label id="contracts"></label>
<ul id="ul-invoices"></ul>
<script>
let lblContract = document.querySelector('#contracts');
let UlInvoices = document.querySelector('#ul-invoices');
let contractInAttendance = 1;
const objectContract = {
id: 1,
nome: 'wagner',
contracts: [{
id: 1,
contract: '123456',
batches: [ {
id: 1,
contract_id: 1,
batch: '1',
invoices: [ {
value: 10,
batch_id: 1,
}]
},
{
id: 2,
contract_id: 1,
batch: '2',
invoices: [{
value: 10,
batch_id: 2,
}]
}]
},
{
id: 2,
contract: '246789',
batches: [ {
id: 3,
contract_id: 2,
batch: '1',
invoices: [ {
value: 20,
batch_id: 3,
}]
}]
}]
}
const revelling = (function() {
function privateInit() {
const contracts = objectContract.contracts;
let contractFilteredById = contracts.filter(contract => contract.id === contractInAttendance);
for (const contract of contracts) {
const selectedContract = contract.id === contractFilteredById[0].id ? 'in-attendance' : '';
//let batchFilteredById = contract.batches.filter(batch => batch.id === batchInAttendance);
let htmlForBatchsOptions = contract.batches.map(batch => `<option value=${batch.id}>${batch.batch}</option>`).join('');
lblContract.innerHTML +=
`<div class="contract-${selectedContract}" style="display: flex;">
<div id="contract-${contract.contract}" data-contract="${contract.id}" class="loren">
<span>${contract.contract}</span>
</div>
<div class="ipsulum" style="margin-left: 5px;">
<select class="sel-batch" onchange="getInvoices(this)">
${htmlForBatchsOptions}
</select>
</div>
</div>`;
const batchOption = document.querySelector('select.sel-batch');
batchOption.value = 2;
/!* create method for load invoices */
}
}
/!* Method fix for load invoices onchange sel-batch */
function getInvoices(selectObject) {
console.log('populate invoices element #ul-invoices');
}
return {
init: privateInit()
}
})();
revelling.init;
</script>
</html>
The IIFE function in this pattern makes that method getInvoices private, so you can't add a handler this way, because it is trying to find a global method that doesn't exist.
You need to assign the event handler this way:
lblContract.innerHTML +=
`<div class="contract-${selectedContract}" style="display: flex;">
<div id="contract-${contract.contract}" data-contract="${contract.id}" class="loren">
<span>${contract.contract}</span>
</div>
<div class="ipsulum" style="margin-left: 5px;">
<select class="sel-batch">
${htmlForBatchsOptions}
</select>
</div>
</div>`;
const batchOption = lblContract.querySelector('select.sel-batch');
// The event listener holds a reference to the inner function
batchOption.addEventListener("change", getInvoices);
I trying to add sub menu by clicking the button but it doesn't work. My data looks like:
$scope.menuItems = [
{ name: 'Menu1',
children: [
{ name: 'Sub1' },
{ name: 'Sub2'} ]
},
{ name: 'Menu1',
children: [
{ name: 'Sub1' } ]
}
];
$scope.addSubItem = function() {
$scope.menuItems.children.push({
name: 'Test Sub Item'
});
};
http://plnkr.co/edit/2R5kpY2iGhiE6FEy65Ji?p=preview
Plunker Solution here
You need to modify the submenu button markup to pass the reference to the menu item that the button resides in:
<ul class="sub-menu">
<li ng-repeat="menuItem in menuItem.children" ng-include="'sub-tree-renderer.html'"></li>
<button class="btn btn-warning" style="margin-top: 10px;" ng-click="addSubItem(menuItem)">Add Sub Menu Item</button>
</ul>
and then in your addSubItem function operate directly on the item like this:
$scope.addSubItem = function(item) {
item.children.push({
name: 'Sub' + (item.children.length + 1)
});
};
Also make sure that every time you create new item the children array is defined as empty array instead of being undefined:
$scope.addItem = function() {
$scope.menuItems.push({
name: 'Test Menu Item',
children: []
});
};
I would recommend using data value object that you can construct a new item with instead of using hand typed object literals as if you use them in many places it is easy to make mistake and cause bugs which happens only in some places and are time consuming to find.
You need to specify the index of the menuItems array that you wish to add the sub menu to.
This would add a sub menu to the first menu item:
$scope.menuItems[0].children.push({
name: 'Test Sub Item'
});
Also, if this is of n depth and can vary depending on the data that is driving the menu, you could build a controller for the menu item and have it recursively add a child/show in your template based on the node you are clicking on. Then you don't need to explicitly worry about indexes.
firstly you should determine sub menu by it index. here you can use $index for this. when you add new Item just add item name. when you need add children array also.
<ul class="sub-menu">
<li ng-repeat="menuItem in menuItem.children" ng-include="'sub-tree-renderer.html'"></li>
<button class="btn btn-warning" style="margin-top: 10px;" ng-click="addSubItem($index)">Add Sub Menu Item</button>
</ul>
and in controller
$scope.addSubItem = function(index) {
$scope.menuItems[index].children.push({
name: 'Test Sub Item'
});
};
$scope.addItem = function() {
var item = {
name: 'Test Menu Item',
children: []
};
$scope.menuItems.push(item);
};
I have a table with these fields: product, lot, input1, input2. You can clone a line, and you can add a new line.
What I want to do is that for each row you can add a new Lot created by a "number" and by "id" that user write in the input field under the Select lot. And I wanted that the script add the new Lot in the json data and the lot 's option list.
This is the function for add that I tried to do:
$scope.addLot = function() {
var inWhichProduct = row.selectedProduct;
var newArray = {
"number": row.newLot.value,
"id": row.newLot.id
};
for (var i = 0; i < $scope.items.length; i++) {
if ($scope.items[i].selectedProduct === inWhichProduct) {
$scope.items[i].selectedLot.push(newArray);
}
}
};
-->> THIS <<-- is the full code.
Can you help me?
I think your question is a little too broad to answer on Stack Overflow, but here's an attempt:
<div ng-app="myApp" ng-controller="myCtrl">
<table>
<tr ng-repeat="lot in lots">
<td>{{ lot.id }}</td>
<td>{{ lot.name }}</td>
</tr>
</table>
<p>name:</p> <input type="text" ng-model="inputName">
<p>id:</p> <input type="text" ng-model="inputId">
<button ng-click="addLotButton(inputId, inputName)">Add</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-beta.2/angular.min.js"></script>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.lots = [{
name: "test",
id: 1
},
{
name: "test2",
id: 2
}
];
$scope.addLot = function(lotId, lotName) {
var newLotObject = {
name: lotName,
id: lotId
};
$scope.lots.push(newLotObject);
};
$scope.addLotButton = function(id, name) {
$scope.addLot(id, name);
};
$scope.addLot(3, "Another test");
});
</script>
Basically this code just takes some input and adds an object to the scope for that input. The table is created using an ng-repeat of this data. It's not great code at all but it's just a quick example.
The push method adds newArray to selectedLot array. It's not working on the JSON data but on arrays. If you want to have the JSON, you can give a try to :
var myJsonString = JSON.stringify(yourArray);
It will create a JSON string based on the parameter
Maybe you should try to structure your data to make lots as properties of products.
{
products: [
{id: 1, lots: [{id:1}, {id:2}]},
{id: 2, lots: [{id:1}, {id:2}]}
]
}
To add a lot to a product :
product = products[0];
product.lots.push(newArray);
Change the fallowing:
html:
<button ng-click="addLot(row.selectedProduct.id,row.newLot.value,row.newLot.id)">Add</button>
js:
$scope.addLot = function(id,val,lotId) {
// console.log(id);
var inWhichProduct = id;
var newArray = { "value": val, "id": lotId };
//console.log($scope.items)
angular.forEach($scope.items,function(v,i){
if($scope.items[i].id == id )
{
$scope.items[i].lots.push(newArray);
console.log($scope.items[i].lots);
}
});
};
http://plnkr.co/edit/W8eche8eIEUuDBsRpLse?p=preview
I am having the hardest time getting two way binding to work for SELECT elements. I am trying to change the selected element programmably. I've found several Stackoverflow examples for binding the change event for SELECT, but I've not been many going the other way, where your application code changes the selected element.
There have been a few that I've found that use ng-repeat on an OPTION element but I've a) not been able to get it to work, and b) does not seem to be the "Angular Way".
HTML Code:
<div ng-controller="SIController">
<select id="current-command" ng-model="currentCommand"
ng-options="c as c.label for c in availableCommands track by c.id"></select>
<button ng-click="changeSelectedOption()">Select "open"</button>
Controller Code:
var myApp = angular.module('myApp', []);
function SIController($scope) {
$scope.availableCommands = [
{id: 'edit', label: 'Edit'},
{id: 'open', label: 'Open'},
{id: 'close', label: 'Close'}
];
$scope.currentCommand = "close";
$scope.changeSelectedOption = function() {
$scope.currentCommand = 'open';
};
};
I can verify that $scope.currentCommand is changing when the button is clicked, but the OPTION does not seem to be getting selected.
Fiddle here
Is there the working fiddle : http://jsfiddle.net/bzhkkw18/9/
To explain what I've done, there is the name of the function which didn't match on the ng-click and in the controller.
And the main part was the definition of your option. In your ng-options you set the all object. If it's what you really want, you have to do the same in your currentCommand like this :
//Object 2 is close
$scope.currentCommand = $scope.availableCommands[2];
I recently had a similar problem. Take look at my answer. You should modify the way you defined ng-options.
function MyCtrl($scope) {
$scope.values = [
{name : "Daily", id : 1},
{name : "Weekly", id : 2},
{name : "Monthly", id : 3},
{name : "Yearly", id : 4}];
$scope.selectedItem = $scope.values[0].id;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
<div ng-app ng-controller="MyCtrl">
<select ng-model="selectedItem" ng-options="selectedItem.id as selectedItem.name for selectedItem in values"></select>
selectedItem: {{selectedItem}}
</div>
The Knockout mapping plugin documentation has a section entitled "Uniquely identifying objects using “keys”". This describes how to update part of an object and then only update that part of the display rather than completely replacing the display of all properties of a partially-modified object. That all works splendidly in their simple example, which I have slightly modified here to make my question more clear. My modifications were to:
Replace the object with a corrected name after a 2 second delay.
Highlight the unchanging part of the display, so you can see that it is actually not replaced when the update happens.
1. Simple object (jsFiddle)
<h1 data-bind="text: name"></h1>
<ul data-bind="foreach: children">
<li><span class="id" data-bind="text: id"></span> <span data-bind="text: name"></span></li>
</ul>
<script>
var data = {
name: 'Scot',
children: [
{id : 1, name : 'Alicw'}
]
};
var mapping = {
children: {
key: function(data) {
console.log(data);
return ko.utils.unwrapObservable(data.id);
}
}
};
var viewModel = ko.mapping.fromJS(data, mapping);
ko.applyBindings(viewModel);
var range = document.createRange();
range.selectNode(document.getElementsByClassName("id")[0]);
window.getSelection().addRange(range);
setTimeout(function () {
var data = {
name: 'Scott',
children: [
{id : 1, name : 'Alice'}
]
};
ko.mapping.fromJS(data, viewModel);
}, 2000);
</script>
But what isn't clear to me is how I would achieve the same behavior for a more complex nested data structure. In the following example, I took the above code and wrapped the data in a list. I would like this to behave the same as above, but it doesn't. The whole display is redone because of the change in one property. You can see this because, unlike the above example, the highlighting is lost after the data is updated.
2. More complex nested object (jsFiddle)
<!-- ko foreach: parents -->
<h1 data-bind="text: name"></h1>
<ul data-bind="foreach: children">
<li><span class="id" data-bind="text: id"></span> <span data-bind="text: name"></span></li>
</ul>
<!-- /ko -->
<script>
var data = {
parents: [
{
name: 'Scot',
children: [
{id : 1, name : 'Alicw'}
]
}
]
};
var mapping = {
children: {
key: function(data) {
console.log(data);
return ko.utils.unwrapObservable(data.id);
}
}
};
var viewModel = ko.mapping.fromJS(data, mapping);
ko.applyBindings(viewModel);
var range = document.createRange();
range.selectNode(document.getElementsByClassName("id")[0]);
window.getSelection().addRange(range);
setTimeout(function () {
var data = {
parents: [
{
name: 'Scott',
children: [
{id : 1, name : 'Alice'}
]
}
]
};
ko.mapping.fromJS(data, viewModel);
}, 2000);
</script>
So basically what I'm asking is, how can I make the second example work like the first, given the more nested data structure? You can assume that ids are unique for each child (so if I added another parent besides Scott, his children would start with id=2, etc.).
Interesting observation there and nice write-up. It appears to work if you define a key on the parent as well as the child. Try this fiddle:
http://jsfiddle.net/8QJe7/6/
It defines instantiable view model functions for the parents and children, where the parent constructor does its child mappings.